2016-10-19 03:11:36 +00:00
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
2013-06-12 15:59:58 +00:00
2012-07-29 20:25:31 +00:00
class Ticket < ApplicationModel
2019-01-28 06:04:05 +00:00
include CanBeImported
2017-05-02 15:21:13 +00:00
include HasActivityStreamLog
include ChecksClientNotification
include ChecksLatestChangeObserved
2018-02-20 04:29:30 +00:00
include CanCsvImport
2019-02-12 07:38:59 +00:00
include ChecksHtmlSanitized
2017-05-02 15:21:13 +00:00
include HasHistory
include HasTags
include HasSearchIndexBackend
2017-05-05 09:16:47 +00:00
include HasOnlineNotifications
include HasKarmaActivityLog
include HasLinks
2017-06-16 20:43:09 +00:00
include Ticket :: ChecksAccess
2019-03-13 23:51:22 +00:00
include HasObjectManagerAttributesValidation
2017-01-31 17:13:45 +00:00
2013-09-29 21:37:49 +00:00
include Ticket :: Escalation
include Ticket :: Subject
include Ticket :: Assets
2014-01-27 22:59:41 +00:00
include Ticket :: SearchIndex
2018-04-26 08:55:53 +00:00
include Ticket :: Search
2013-09-29 21:37:49 +00:00
2017-01-31 17:13:45 +00:00
store :preferences
2017-04-11 11:11:44 +00:00
before_create :check_generate , :check_defaults , :check_title , :set_default_state , :set_default_priority
2018-02-10 10:40:57 +00:00
before_update :check_defaults , :check_title , :reset_pending_time , :check_owner_active
2015-02-01 12:08:11 +00:00
2016-09-09 11:29:33 +00:00
validates :group_id , presence : true
2017-01-31 17:13:45 +00:00
activity_stream_permission 'ticket.agent'
activity_stream_attributes_ignored :organization_id , # organization_id will channge automatically on user update
:create_article_type_id ,
:create_article_sender_id ,
:article_count ,
:first_response_at ,
:first_response_escalation_at ,
:first_response_in_min ,
:first_response_diff_in_min ,
:close_at ,
:close_escalation_at ,
:close_in_min ,
:close_diff_in_min ,
:update_escalation_at ,
:update_in_min ,
:update_diff_in_min ,
:last_contact_at ,
:last_contact_agent_at ,
:last_contact_customer_at ,
2017-08-30 13:50:48 +00:00
:last_owner_update_at ,
2017-01-31 17:13:45 +00:00
:preferences
history_attributes_ignored :create_article_type_id ,
:create_article_sender_id ,
:article_count ,
:preferences
2014-01-27 22:59:41 +00:00
2018-12-31 19:15:17 +00:00
history_relation_object 'Ticket::Article'
2019-02-12 07:38:59 +00:00
sanitized_html :note
2018-04-12 14:57:37 +00:00
belongs_to :group
belongs_to :organization
has_many :articles , class_name : 'Ticket::Article' , after_add : :cache_update , after_remove : :cache_update , dependent : :destroy , inverse_of : :ticket
has_many :ticket_time_accounting , class_name : 'Ticket::TimeAccounting' , dependent : :destroy , inverse_of : :ticket
2018-10-09 06:17:41 +00:00
belongs_to :state , class_name : 'Ticket::State'
belongs_to :priority , class_name : 'Ticket::Priority'
belongs_to :owner , class_name : 'User'
belongs_to :customer , class_name : 'User'
belongs_to :created_by , class_name : 'User'
belongs_to :updated_by , class_name : 'User'
belongs_to :create_article_type , class_name : 'Ticket::Article::Type'
belongs_to :create_article_sender , class_name : 'Ticket::Article::Sender'
2012-04-10 14:06:46 +00:00
2014-09-09 23:42:20 +00:00
self . inheritance_column = nil
2013-03-28 23:13:15 +00:00
attr_accessor :callback_loop
2013-08-17 21:10:36 +00:00
= begin
2014-11-10 07:34:20 +00:00
get user access conditions
2017-06-16 20:43:09 +00:00
conditions = Ticket . access_condition ( User . find ( 1 ) , 'full' )
2014-11-10 07:34:20 +00:00
returns
result = [ user1 , user2 , ... ]
= end
2017-06-16 20:43:09 +00:00
def self . access_condition ( user , access )
2016-08-12 16:39:09 +00:00
if user . permissions? ( 'ticket.agent' )
2017-06-16 20:43:09 +00:00
[ 'group_id IN (?)' , user . group_ids_access ( access ) ]
elsif ! user . organization || ( ! user . organization . shared || user . organization . shared == false )
[ 'tickets.customer_id = ?' , user . id ]
2014-11-10 07:34:20 +00:00
else
2017-06-16 20:43:09 +00:00
[ '(tickets.customer_id = ? OR tickets.organization_id = ?)' , user . id , user . organization . id ]
2014-11-10 07:34:20 +00:00
end
end
= begin
2015-05-21 14:40:04 +00:00
processes tickets which have reached their pending time and sets next state_id
2016-02-22 23:28:13 +00:00
processed_tickets = Ticket . process_pending
2015-05-21 14:40:04 +00:00
returns
processed_tickets = [ < Ticket > , ... ]
= end
def self . process_pending
2016-02-20 10:12:15 +00:00
result = [ ]
2015-05-21 14:40:04 +00:00
2016-02-20 10:12:15 +00:00
# process pending action tickets
pending_action = Ticket :: StateType . find_by ( name : 'pending action' )
ticket_states_pending_action = Ticket :: State . where ( state_type_id : pending_action )
. where . not ( next_state_id : nil )
2017-08-30 13:50:48 +00:00
if ticket_states_pending_action . present?
2016-02-20 10:12:15 +00:00
next_state_map = { }
2017-10-01 12:25:52 +00:00
ticket_states_pending_action . each do | state |
2016-02-20 10:12:15 +00:00
next_state_map [ state . id ] = state . next_state_id
2017-10-01 12:25:52 +00:00
end
2016-02-20 10:12:15 +00:00
tickets = where ( state_id : next_state_map . keys )
. where ( 'pending_time <= ?' , Time . zone . now )
2017-10-01 12:25:52 +00:00
tickets . each do | ticket |
2016-09-06 05:51:12 +00:00
Transaction . execute do
ticket . state_id = next_state_map [ ticket . state_id ]
ticket . updated_at = Time . zone . now
ticket . updated_by_id = 1
ticket . save!
end
2016-02-20 10:12:15 +00:00
result . push ticket
2017-10-01 12:25:52 +00:00
end
2016-02-20 10:12:15 +00:00
end
2015-05-21 14:40:04 +00:00
2016-02-20 10:12:15 +00:00
# process pending reminder tickets
pending_reminder = Ticket :: StateType . find_by ( name : 'pending reminder' )
ticket_states_pending_reminder = Ticket :: State . where ( state_type_id : pending_reminder )
2015-05-21 14:40:04 +00:00
2017-08-30 13:50:48 +00:00
if ticket_states_pending_reminder . present?
2016-02-20 10:12:15 +00:00
reminder_state_map = { }
2017-10-01 12:25:52 +00:00
ticket_states_pending_reminder . each do | state |
2016-02-20 10:12:15 +00:00
reminder_state_map [ state . id ] = state . next_state_id
2017-10-01 12:25:52 +00:00
end
2015-05-21 14:40:04 +00:00
2016-02-20 10:12:15 +00:00
tickets = where ( state_id : reminder_state_map . keys )
. where ( 'pending_time <= ?' , Time . zone . now )
2015-05-21 14:40:04 +00:00
2017-10-01 12:25:52 +00:00
tickets . each do | ticket |
2015-05-21 14:40:04 +00:00
2016-05-19 08:20:38 +00:00
article_id = nil
article = Ticket :: Article . last_customer_agent_article ( ticket . id )
if article
article_id = article . id
end
2016-02-20 10:12:15 +00:00
# send notification
2016-04-15 21:56:10 +00:00
Transaction :: BackgroundJob . run (
2018-12-19 17:31:51 +00:00
object : 'Ticket' ,
type : 'reminder_reached' ,
object_id : ticket . id ,
2016-05-19 08:20:38 +00:00
article_id : article_id ,
2018-12-19 17:31:51 +00:00
user_id : 1 ,
2016-02-20 10:12:15 +00:00
)
result . push ticket
2017-10-01 12:25:52 +00:00
end
2016-02-20 10:12:15 +00:00
end
2015-05-22 06:58:32 +00:00
2015-05-21 14:40:04 +00:00
result
end
= begin
2016-02-22 23:28:13 +00:00
processes escalated tickets
processed_tickets = Ticket . process_escalation
returns
processed_tickets = [ < Ticket > , ... ]
= end
def self . process_escalation
result = [ ]
# get max warning diff
2016-09-14 07:15:30 +00:00
tickets = where ( 'escalation_at <= ?' , Time . zone . now + 15 . minutes )
2016-02-22 23:28:13 +00:00
2017-10-01 12:25:52 +00:00
tickets . each do | ticket |
2016-02-22 23:28:13 +00:00
# get sla
sla = ticket . escalation_calculation_get_sla
2016-05-19 08:20:38 +00:00
article_id = nil
article = Ticket :: Article . last_customer_agent_article ( ticket . id )
if article
article_id = article . id
end
2016-02-22 23:28:13 +00:00
# send escalation
2016-09-14 07:15:30 +00:00
if ticket . escalation_at < Time . zone . now
2016-04-15 21:56:10 +00:00
Transaction :: BackgroundJob . run (
2018-12-19 17:31:51 +00:00
object : 'Ticket' ,
type : 'escalation' ,
object_id : ticket . id ,
2016-05-19 08:20:38 +00:00
article_id : article_id ,
2018-12-19 17:31:51 +00:00
user_id : 1 ,
2016-02-22 23:28:13 +00:00
)
result . push ticket
next
end
# check if warning need to be sent
2016-04-15 21:56:10 +00:00
Transaction :: BackgroundJob . run (
2018-12-19 17:31:51 +00:00
object : 'Ticket' ,
type : 'escalation_warning' ,
object_id : ticket . id ,
2016-05-19 08:20:38 +00:00
article_id : article_id ,
2018-12-19 17:31:51 +00:00
user_id : 1 ,
2016-02-22 23:28:13 +00:00
)
result . push ticket
2017-10-01 12:25:52 +00:00
end
2016-02-22 23:28:13 +00:00
result
end
= begin
2017-08-30 13:50:48 +00:00
processes tickets which auto unassign time has reached
processed_tickets = Ticket . process_auto_unassign
returns
processed_tickets = [ < Ticket > , ... ]
= end
def self . process_auto_unassign
# process pending action tickets
state_ids = Ticket :: State . by_category ( :work_on ) . pluck ( :id )
return [ ] if state_ids . blank?
2018-10-09 06:17:41 +00:00
2017-08-30 13:50:48 +00:00
result = [ ]
groups = Group . where ( active : true ) . where ( 'assignment_timeout IS NOT NULL AND groups.assignment_timeout != 0' )
return [ ] if groups . blank?
2018-10-09 06:17:41 +00:00
2017-10-01 12:25:52 +00:00
groups . each do | group |
2017-08-30 13:50:48 +00:00
next if group . assignment_timeout . blank?
2018-10-09 06:17:41 +00:00
2017-11-01 14:23:44 +00:00
ticket_ids = Ticket . where ( 'state_id IN (?) AND owner_id != 1 AND group_id = ? AND last_owner_update_at IS NOT NULL' , state_ids , group . id ) . limit ( 600 ) . pluck ( :id )
2017-10-01 12:25:52 +00:00
ticket_ids . each do | ticket_id |
2017-08-30 13:50:48 +00:00
ticket = Ticket . find_by ( id : ticket_id )
next if ! ticket
2018-10-09 06:17:41 +00:00
2017-08-30 13:50:48 +00:00
minutes_since_last_assignment = Time . zone . now - ticket . last_owner_update_at
next if ( minutes_since_last_assignment / 60 ) < = group . assignment_timeout
2018-10-09 06:17:41 +00:00
2017-08-30 13:50:48 +00:00
Transaction . execute do
ticket . owner_id = 1
ticket . updated_at = Time . zone . now
ticket . updated_by_id = 1
ticket . save!
end
result . push ticket
2017-10-01 12:25:52 +00:00
end
end
2017-08-30 13:50:48 +00:00
result
end
= begin
2013-08-16 14:30:51 +00:00
merge tickets
2013-08-17 20:04:57 +00:00
ticket = Ticket . find ( 123 )
result = ticket . merge_to (
2015-09-03 09:14:09 +00:00
ticket_id : 123 ,
user_id : 123 ,
2013-08-16 14:30:51 +00:00
)
returns
result = true | false
= end
2012-07-03 13:24:31 +00:00
def merge_to ( data )
2012-11-07 23:47:05 +00:00
2017-07-19 10:03:17 +00:00
# prevent cross merging tickets
2017-07-25 11:53:01 +00:00
target_ticket = Ticket . find_by ( id : data [ :ticket_id ] )
2017-07-19 10:03:17 +00:00
raise 'no target ticket given' if ! target_ticket
2017-07-25 11:53:01 +00:00
raise Exceptions :: UnprocessableEntity , 'ticket already merged, no merge into merged ticket possible' if target_ticket . state . state_type . name == 'merged'
# check different ticket ids
raise Exceptions :: UnprocessableEntity , 'Can\'t merge ticket with it self!' if id == target_ticket . id
2017-07-19 10:03:17 +00:00
2012-07-03 13:24:31 +00:00
# update articles
2016-09-06 05:51:12 +00:00
Transaction . execute do
Ticket :: Article . where ( ticket_id : id ) . each ( & :touch )
# quiet update of reassign of articles
2017-11-23 08:09:44 +00:00
Ticket :: Article . where ( ticket_id : id ) . update_all ( [ 'ticket_id = ?' , data [ :ticket_id ] ] ) # rubocop:disable Rails/SkipsModelValidations
2016-09-06 05:51:12 +00:00
2019-05-20 09:41:28 +00:00
# mark target ticket as updated
# otherwise the "received_merge" history entry
# will be the same as the last updated_at
# which might be a long time ago
target_ticket . updated_at = Time . zone . now
2019-02-13 09:09:20 +00:00
# add merge event to both ticket's history (Issue #2469 - Add information "Ticket merged" to History)
target_ticket . history_log (
'received_merge' ,
data [ :user_id ] ,
id_to : target_ticket . id ,
id_from : id ,
)
history_log (
'merged_into' ,
data [ :user_id ] ,
id_to : target_ticket . id ,
id_from : id ,
)
2016-09-06 05:51:12 +00:00
# create new merge article
Ticket :: Article . create (
2018-12-19 17:31:51 +00:00
ticket_id : id ,
type_id : Ticket :: Article :: Type . lookup ( name : 'note' ) . id ,
sender_id : Ticket :: Article :: Sender . lookup ( name : 'Agent' ) . id ,
body : 'merged' ,
internal : false ,
2016-09-06 05:51:12 +00:00
created_by_id : data [ :user_id ] ,
updated_by_id : data [ :user_id ] ,
)
2017-05-02 11:37:20 +00:00
# reassign links to the new ticket
2017-11-23 08:09:44 +00:00
# rubocop:disable Rails/SkipsModelValidations
2017-05-02 11:37:20 +00:00
Link . where (
2018-12-19 17:31:51 +00:00
link_object_source_id : Link :: Object . find_by ( name : 'Ticket' ) . id ,
2017-05-02 11:37:20 +00:00
link_object_source_value : id ,
) . update_all ( link_object_source_value : data [ :ticket_id ] )
Link . where (
2018-12-19 17:31:51 +00:00
link_object_target_id : Link :: Object . find_by ( name : 'Ticket' ) . id ,
2017-05-02 11:37:20 +00:00
link_object_target_value : id ,
) . update_all ( link_object_target_value : data [ :ticket_id ] )
2017-11-23 08:09:44 +00:00
# rubocop:enable Rails/SkipsModelValidations
2017-05-02 11:37:20 +00:00
2016-09-06 05:51:12 +00:00
# link tickets
Link . add (
2018-12-19 17:31:51 +00:00
link_type : 'parent' ,
link_object_source : 'Ticket' ,
2016-09-06 05:51:12 +00:00
link_object_source_value : data [ :ticket_id ] ,
2018-12-19 17:31:51 +00:00
link_object_target : 'Ticket' ,
2016-09-06 05:51:12 +00:00
link_object_target_value : id
)
# set state to 'merged'
self . state_id = Ticket :: State . lookup ( name : 'merged' ) . id
# rest owner
2017-04-18 07:52:39 +00:00
self . owner_id = 1
2016-09-06 05:51:12 +00:00
# save ticket
save!
# touch new ticket (to broadcast change)
2017-11-23 08:09:44 +00:00
target_ticket . touch # rubocop:disable Rails/SkipsModelValidations
2016-09-06 05:51:12 +00:00
end
true
2015-04-01 14:33:24 +00:00
end
= begin
2018-12-21 16:33:45 +00:00
check if online notification should be shown in general as already seen with current state
2015-04-01 14:33:24 +00:00
ticket = Ticket . find ( 1 )
2015-09-03 09:14:09 +00:00
seen = ticket . online_notification_seen_state ( user_id_check )
returns
result = true # or false
2015-04-01 14:33:24 +00:00
= end
2015-04-01 11:47:10 +00:00
2015-09-03 09:14:09 +00:00
def online_notification_seen_state ( user_id_check = nil )
2016-02-20 12:36:59 +00:00
state = Ticket :: State . lookup ( id : state_id )
state_type = Ticket :: StateType . lookup ( id : state . state_type_id )
2015-07-26 21:21:16 +00:00
2017-07-26 18:46:31 +00:00
# always to set unseen for ticket owner and users which did not the update
2016-02-20 13:09:56 +00:00
if state_type . name != 'merged'
if user_id_check
return false if user_id_check == owner_id && user_id_check != updated_by_id
end
end
2015-07-26 21:21:16 +00:00
# set all to seen if pending action state is a closed or merged state
if state_type . name == 'pending action' && state . next_state_id
2016-02-20 12:36:59 +00:00
state = Ticket :: State . lookup ( id : state . next_state_id )
state_type = Ticket :: StateType . lookup ( id : state . state_type_id )
2015-07-26 21:21:16 +00:00
end
2015-08-31 09:13:40 +00:00
# set all to seen if new state is pending reminder state
2015-09-03 09:14:09 +00:00
if state_type . name == 'pending reminder'
if user_id_check
return false if owner_id == 1
return false if updated_by_id != owner_id && user_id_check == owner_id
2018-10-09 06:17:41 +00:00
2015-09-03 09:14:09 +00:00
return true
end
return true
end
2015-08-31 09:13:40 +00:00
2015-07-26 21:21:16 +00:00
# set all to seen if new state is a closed or merged state
2015-04-01 14:33:24 +00:00
return true if state_type . name == 'closed'
return true if state_type . name == 'merged'
2018-10-09 06:17:41 +00:00
2015-04-01 14:33:24 +00:00
false
2012-07-03 13:24:31 +00:00
end
2015-09-23 14:25:06 +00:00
= begin
get count of tickets and tickets which match on selector
2019-03-26 00:17:17 +00:00
ticket_count , tickets = Ticket . selectors ( params [ :condition ] , limit : limit , current_user : current_user , access : 'full' )
2015-09-23 14:25:06 +00:00
= end
2019-03-26 00:17:17 +00:00
def self . selectors ( selectors , options )
limit = options [ :limit ] || 10
current_user = options [ :current_user ]
access = options [ :access ] || 'full'
2016-03-01 14:26:46 +00:00
raise 'no selectors given' if ! selectors
2018-10-09 06:17:41 +00:00
2019-03-26 00:17:17 +00:00
query , bind_params , tables = selector2sql ( selectors , current_user : current_user )
2015-09-17 18:39:51 +00:00
return [ ] if ! query
2015-10-12 13:44:34 +00:00
2017-08-13 09:54:57 +00:00
ActiveRecord :: Base . transaction ( requires_new : true ) do
2017-04-12 13:41:54 +00:00
2019-06-27 18:26:28 +00:00
if ! current_user
ticket_count = Ticket . distinct . where ( query , * bind_params ) . joins ( tables ) . count
tickets = Ticket . distinct . where ( query , * bind_params ) . joins ( tables ) . limit ( limit )
2017-08-13 09:54:57 +00:00
return [ ticket_count , tickets ]
end
2019-06-27 18:26:28 +00:00
access_condition = Ticket . access_condition ( current_user , access )
ticket_count = Ticket . distinct . where ( access_condition ) . where ( query , * bind_params ) . joins ( tables ) . count
tickets = Ticket . distinct . where ( access_condition ) . where ( query , * bind_params ) . joins ( tables ) . limit ( limit )
return [ ticket_count , tickets ]
rescue ActiveRecord :: StatementInvalid = > e
Rails . logger . error e
raise ActiveRecord :: Rollback
2017-08-13 09:54:57 +00:00
end
[ ]
2015-09-17 01:04:16 +00:00
end
2015-09-23 14:25:06 +00:00
= begin
generate condition query to search for tickets based on condition
2019-03-26 00:17:17 +00:00
query_condition , bind_condition , tables = selector2sql ( params [ :condition ] , current_user : current_user )
2015-09-23 14:25:06 +00:00
condition example
{
2016-09-11 08:46:01 +00:00
'ticket.title' = > {
operator : 'contains' , # contains not
value : 'some value' ,
} ,
2015-09-23 14:25:06 +00:00
'ticket.state_id' = > {
operator : 'is' ,
value : [ 1 , 2 , 5 ]
2015-10-12 13:44:34 +00:00
} ,
'ticket.created_at' = > {
operator : 'after (absolute)' , # after,before
value : '2015-10-17T06:00:00.000Z' ,
} ,
'ticket.created_at' = > {
2018-08-15 15:16:41 +00:00
operator : 'within next (relative)' , # within next, within last, after, before
2015-10-12 13:44:34 +00:00
range : 'day' , # minute|hour|day|month|year
value : '25' ,
} ,
2015-10-14 14:33:26 +00:00
'ticket.owner_id' = > {
operator : 'is' , # is not
pre_condition : 'current_user.id' ,
} ,
'ticket.owner_id' = > {
operator : 'is' , # is not
pre_condition : 'specific' ,
value : 4711 ,
} ,
2016-09-14 07:15:30 +00:00
'ticket.escalation_at' = > {
2016-02-26 12:19:57 +00:00
operator : 'is not' , # not
value : nil ,
2017-04-12 13:41:54 +00:00
} ,
'ticket.tags' = > {
operator : 'contains all' , # contains all|contains one|contains all not|contains one not
value : 'tag1, tag2' ,
} ,
2015-09-23 14:25:06 +00:00
}
= end
2019-03-26 00:17:17 +00:00
def self . selector2sql ( selectors , options = { } )
current_user = options [ :current_user ]
2015-10-14 14:33:26 +00:00
current_user_id = UserInfo . current_user_id
if current_user
current_user_id = current_user . id
end
2015-09-17 01:04:16 +00:00
return if ! selectors
2015-10-14 14:33:26 +00:00
# remember query and bind params
2015-09-17 01:04:16 +00:00
query = ''
bind_params = [ ]
2016-01-19 22:30:23 +00:00
like = Rails . application . config . db_like
2015-09-17 01:04:16 +00:00
2017-11-23 08:09:44 +00:00
if selectors . respond_to? ( :permit! )
selectors = selectors . permit! . to_h
end
2015-10-14 14:33:26 +00:00
# get tables to join
2015-10-12 13:44:34 +00:00
tables = ''
2017-11-23 08:09:44 +00:00
selectors . each_key do | attribute |
2015-09-17 18:39:51 +00:00
selector = attribute . split ( / \ . / )
next if ! selector [ 1 ]
next if selector [ 0 ] == 'ticket'
next if tables . include? ( selector [ 0 ] )
2018-10-09 06:17:41 +00:00
2015-10-12 13:44:34 +00:00
if query != ''
query += ' AND '
end
if selector [ 0 ] == 'customer'
tables += ', users customers'
query += 'tickets.customer_id = customers.id'
elsif selector [ 0 ] == 'organization'
tables += ', organizations'
query += 'tickets.organization_id = organizations.id'
elsif selector [ 0 ] == 'owner'
tables += ', users owners'
query += 'tickets.owner_id = owners.id'
2016-05-03 00:36:44 +00:00
elsif selector [ 0 ] == 'article'
tables += ', ticket_articles articles'
query += 'tickets.id = articles.ticket_id'
2018-01-15 11:54:26 +00:00
elsif selector [ 0 ] == 'ticket_state'
tables += ', ticket_states'
query += 'tickets.state_id = ticket_states.id'
2015-10-12 13:44:34 +00:00
else
2016-03-01 14:26:46 +00:00
raise " invalid selector #{ attribute . inspect } -> #{ selector . inspect } "
2015-10-12 13:44:34 +00:00
end
2017-10-01 12:25:52 +00:00
end
2015-09-17 18:39:51 +00:00
2015-10-14 14:33:26 +00:00
# add conditions
2017-10-01 12:25:52 +00:00
selectors . each do | attribute , selector_raw |
2015-10-14 14:33:26 +00:00
# validation
2016-03-01 14:26:46 +00:00
raise " Invalid selector #{ selector_raw . inspect } " if ! selector_raw
raise " Invalid selector #{ selector_raw . inspect } " if ! selector_raw . respond_to? ( :key? )
2018-10-09 06:17:41 +00:00
2015-09-23 14:25:06 +00:00
selector = selector_raw . stringify_keys
2016-03-01 14:26:46 +00:00
raise " Invalid selector, operator missing #{ selector . inspect } " if ! selector [ 'operator' ]
2018-08-15 15:16:41 +00:00
raise " Invalid selector, operator #{ selector [ 'operator' ] } is invalid #{ selector . inspect } " if selector [ 'operator' ] !~ / ^(is|is \ snot|contains|contains \ s(not|all|one|all \ snot|one \ snot)|(after|before) \ s \ (absolute \ )|(within \ snext|within \ slast|after|before) \ s \ (relative \ ))$ /
2015-10-14 14:33:26 +00:00
2017-10-24 20:34:52 +00:00
# validate value / allow blank but only if pre_condition exists and is not specific
2018-08-15 15:16:41 +00:00
if ! selector . key? ( 'value' ) ||
( selector [ 'value' ] . class == Array && selector [ 'value' ] . respond_to? ( :blank? ) && selector [ 'value' ] . blank? ) ||
( selector [ 'operator' ] =~ / ^contains / && selector [ 'value' ] . respond_to? ( :blank? ) && selector [ 'value' ] . blank? )
2016-11-10 15:30:36 +00:00
return nil if selector [ 'pre_condition' ] . nil?
2017-10-24 20:34:52 +00:00
return nil if selector [ 'pre_condition' ] . respond_to? ( :blank? ) && selector [ 'pre_condition' ] . blank?
2016-11-10 15:30:36 +00:00
return nil if selector [ 'pre_condition' ] == 'specific'
2015-10-14 14:33:26 +00:00
end
# validate pre_condition values
2016-04-14 07:17:13 +00:00
return nil if selector [ 'pre_condition' ] && selector [ 'pre_condition' ] !~ / ^(not_set|current_user \ .|specific) /
2015-10-14 14:33:26 +00:00
# get attributes
2015-09-17 18:39:51 +00:00
attributes = attribute . split ( / \ . / )
2018-06-20 12:13:35 +00:00
attribute = " #{ ActiveRecord :: Base . connection . quote_table_name ( " #{ attributes [ 0 ] } s " ) } . #{ ActiveRecord :: Base . connection . quote_column_name ( attributes [ 1 ] ) } "
2017-09-11 00:50:05 +00:00
# magic selectors
if attributes [ 0 ] == 'ticket' && attributes [ 1 ] == 'out_of_office_replacement_id'
2018-06-20 12:13:35 +00:00
attribute = " #{ ActiveRecord :: Base . connection . quote_table_name ( " #{ attributes [ 0 ] } s " ) } . #{ ActiveRecord :: Base . connection . quote_column_name ( 'owner_id' ) } "
2017-09-11 00:50:05 +00:00
end
2017-04-12 13:41:54 +00:00
if attributes [ 0 ] == 'ticket' && attributes [ 1 ] == 'tags'
selector [ 'value' ] = selector [ 'value' ] . split ( / , / ) . collect ( & :strip )
end
2015-10-14 14:33:26 +00:00
if query != ''
query += ' AND '
end
2015-09-17 01:04:16 +00:00
if selector [ 'operator' ] == 'is'
2016-04-14 07:17:13 +00:00
if selector [ 'pre_condition' ] == 'not_set'
2017-11-23 08:09:44 +00:00
if attributes [ 1 ] . match? ( / ^(created_by|updated_by|owner|customer|user)_id / )
2018-08-06 17:36:27 +00:00
query += " ( #{ attribute } IS NULL OR #{ attribute } IN (?)) "
2015-10-14 14:33:26 +00:00
bind_params . push 1
else
2017-07-06 13:58:10 +00:00
query += " #{ attribute } IS NULL "
2015-10-14 14:33:26 +00:00
end
elsif selector [ 'pre_condition' ] == 'current_user.id'
2016-03-01 14:26:46 +00:00
raise " Use current_user.id in selector, but no current_user is set #{ selector . inspect } " if ! current_user_id
2018-10-09 06:17:41 +00:00
2017-11-23 08:09:44 +00:00
query += " #{ attribute } IN (?) "
2017-09-11 00:50:05 +00:00
if attributes [ 1 ] == 'out_of_office_replacement_id'
bind_params . push User . find ( current_user_id ) . out_of_office_agent_of . pluck ( :id )
else
bind_params . push current_user_id
end
2015-10-14 14:33:26 +00:00
elsif selector [ 'pre_condition' ] == 'current_user.organization_id'
2016-03-01 14:26:46 +00:00
raise " Use current_user.id in selector, but no current_user is set #{ selector . inspect } " if ! current_user_id
2018-10-09 06:17:41 +00:00
2015-10-14 14:33:26 +00:00
query += " #{ attribute } IN (?) "
2018-06-05 10:37:52 +00:00
user = User . find_by ( id : current_user_id )
2015-10-14 14:33:26 +00:00
bind_params . push user . organization_id
else
2016-02-26 12:19:57 +00:00
# rubocop:disable Style/IfInsideElse
if selector [ 'value' ] . nil?
2017-07-06 13:58:10 +00:00
query += " #{ attribute } IS NULL "
2016-02-26 12:19:57 +00:00
else
2017-09-11 00:50:05 +00:00
if attributes [ 1 ] == 'out_of_office_replacement_id'
2018-08-15 15:16:41 +00:00
query += " #{ attribute } IN (?) "
2017-09-11 00:50:05 +00:00
bind_params . push User . find ( selector [ 'value' ] ) . out_of_office_agent_of . pluck ( :id )
else
2018-08-15 15:16:41 +00:00
if selector [ 'value' ] . class != Array
selector [ 'value' ] = [ selector [ 'value' ] ]
end
query += if selector [ 'value' ] . include? ( '' )
" ( #{ attribute } IN (?) OR #{ attribute } IS NULL) "
else
" #{ attribute } IN (?) "
end
2017-09-11 00:50:05 +00:00
bind_params . push selector [ 'value' ]
end
2016-02-26 12:19:57 +00:00
end
# rubocop:enable Style/IfInsideElse
2015-10-14 14:33:26 +00:00
end
2015-09-17 01:04:16 +00:00
elsif selector [ 'operator' ] == 'is not'
2016-04-14 07:17:13 +00:00
if selector [ 'pre_condition' ] == 'not_set'
2017-11-23 08:09:44 +00:00
if attributes [ 1 ] . match? ( / ^(created_by|updated_by|owner|customer|user)_id / )
2018-08-06 17:36:27 +00:00
query += " ( #{ attribute } IS NOT NULL AND #{ attribute } NOT IN (?)) "
2015-10-14 14:33:26 +00:00
bind_params . push 1
else
2017-07-06 13:58:10 +00:00
query += " #{ attribute } IS NOT NULL "
2015-10-14 14:33:26 +00:00
end
elsif selector [ 'pre_condition' ] == 'current_user.id'
2018-07-04 10:07:25 +00:00
query += " ( #{ attribute } IS NULL OR #{ attribute } NOT IN (?)) "
2017-09-11 00:50:05 +00:00
if attributes [ 1 ] == 'out_of_office_replacement_id'
bind_params . push User . find ( current_user_id ) . out_of_office_agent_of . pluck ( :id )
else
bind_params . push current_user_id
end
2015-10-14 14:33:26 +00:00
elsif selector [ 'pre_condition' ] == 'current_user.organization_id'
2018-07-04 10:07:25 +00:00
query += " ( #{ attribute } IS NULL OR #{ attribute } NOT IN (?)) "
2018-06-05 10:37:52 +00:00
user = User . find_by ( id : current_user_id )
2015-10-14 14:33:26 +00:00
bind_params . push user . organization_id
else
2016-02-26 12:19:57 +00:00
# rubocop:disable Style/IfInsideElse
if selector [ 'value' ] . nil?
query += " #{ attribute } IS NOT NULL "
else
2017-09-11 00:50:05 +00:00
if attributes [ 1 ] == 'out_of_office_replacement_id'
bind_params . push User . find ( selector [ 'value' ] ) . out_of_office_agent_of . pluck ( :id )
2018-08-15 15:16:41 +00:00
query += " ( #{ attribute } IS NULL OR #{ attribute } NOT IN (?)) "
2017-09-11 00:50:05 +00:00
else
2018-08-15 15:16:41 +00:00
if selector [ 'value' ] . class != Array
selector [ 'value' ] = [ selector [ 'value' ] ]
end
query += if selector [ 'value' ] . include? ( '' )
" ( #{ attribute } IS NOT NULL AND #{ attribute } NOT IN (?)) "
else
" ( #{ attribute } IS NULL OR #{ attribute } NOT IN (?)) "
end
2017-09-11 00:50:05 +00:00
bind_params . push selector [ 'value' ]
end
2016-02-26 12:19:57 +00:00
end
# rubocop:enable Style/IfInsideElse
2015-10-14 14:33:26 +00:00
end
2015-09-17 01:04:16 +00:00
elsif selector [ 'operator' ] == 'contains'
2016-01-19 22:30:23 +00:00
query += " #{ attribute } #{ like } (?) "
2015-09-17 01:04:16 +00:00
value = " % #{ selector [ 'value' ] } % "
bind_params . push value
elsif selector [ 'operator' ] == 'contains not'
2016-01-19 22:30:23 +00:00
query += " #{ attribute } NOT #{ like } (?) "
2015-09-17 01:04:16 +00:00
value = " % #{ selector [ 'value' ] } % "
bind_params . push value
2017-04-12 13:41:54 +00:00
elsif selector [ 'operator' ] == 'contains all' && attributes [ 0 ] == 'ticket' && attributes [ 1 ] == 'tags'
2017-04-12 14:27:00 +00:00
query += " ? = (
2017-04-12 13:41:54 +00:00
SELECT
COUNT ( * )
FROM
tag_objects ,
tag_items ,
tags
WHERE
tickets . id = tags . o_id AND
tag_objects . id = tags . tag_object_id AND
tag_objects . name = 'Ticket' AND
tag_items . id = tags . tag_item_id AND
tag_items . name IN ( ?)
) "
2017-04-12 14:27:00 +00:00
bind_params . push selector [ 'value' ] . count
2017-04-12 13:41:54 +00:00
bind_params . push selector [ 'value' ]
elsif selector [ 'operator' ] == 'contains one' && attributes [ 0 ] == 'ticket' && attributes [ 1 ] == 'tags'
query += " 1 <= (
SELECT
COUNT ( * )
FROM
tag_objects ,
tag_items ,
tags
WHERE
tickets . id = tags . o_id AND
tag_objects . id = tags . tag_object_id AND
tag_objects . name = 'Ticket' AND
tag_items . id = tags . tag_item_id AND
tag_items . name IN ( ?)
) "
bind_params . push selector [ 'value' ]
elsif selector [ 'operator' ] == 'contains all not' && attributes [ 0 ] == 'ticket' && attributes [ 1 ] == 'tags'
query += " 0 = (
SELECT
COUNT ( * )
FROM
tag_objects ,
tag_items ,
tags
WHERE
tickets . id = tags . o_id AND
tag_objects . id = tags . tag_object_id AND
tag_objects . name = 'Ticket' AND
tag_items . id = tags . tag_item_id AND
tag_items . name IN ( ?)
) "
bind_params . push selector [ 'value' ]
elsif selector [ 'operator' ] == 'contains one not' && attributes [ 0 ] == 'ticket' && attributes [ 1 ] == 'tags'
query += " (
SELECT
COUNT ( * )
FROM
tag_objects ,
tag_items ,
tags
WHERE
tickets . id = tags . o_id AND
tag_objects . id = tags . tag_object_id AND
tag_objects . name = 'Ticket' AND
tag_items . id = tags . tag_item_id AND
tag_items . name IN ( ?)
2018-01-15 16:49:46 +00:00
) BETWEEN 0 AND 0 "
2017-04-12 14:28:12 +00:00
bind_params . push selector [ 'value' ]
2015-09-17 18:39:51 +00:00
elsif selector [ 'operator' ] == 'before (absolute)'
query += " #{ attribute } <= ? "
bind_params . push selector [ 'value' ]
elsif selector [ 'operator' ] == 'after (absolute)'
query += " #{ attribute } >= ? "
2015-09-17 01:04:16 +00:00
bind_params . push selector [ 'value' ]
2015-10-12 13:44:34 +00:00
elsif selector [ 'operator' ] == 'within last (relative)'
query += " #{ attribute } >= ? "
time = nil
if selector [ 'range' ] == 'minute'
time = Time . zone . now - selector [ 'value' ] . to_i . minutes
elsif selector [ 'range' ] == 'hour'
time = Time . zone . now - selector [ 'value' ] . to_i . hours
elsif selector [ 'range' ] == 'day'
time = Time . zone . now - selector [ 'value' ] . to_i . days
elsif selector [ 'range' ] == 'month'
time = Time . zone . now - selector [ 'value' ] . to_i . months
elsif selector [ 'range' ] == 'year'
time = Time . zone . now - selector [ 'value' ] . to_i . years
else
2016-03-01 14:26:46 +00:00
raise " Unknown selector attributes ' #{ selector . inspect } ' "
2015-10-12 13:44:34 +00:00
end
bind_params . push time
elsif selector [ 'operator' ] == 'within next (relative)'
2015-10-12 21:31:33 +00:00
query += " #{ attribute } <= ? "
2015-10-12 13:44:34 +00:00
time = nil
if selector [ 'range' ] == 'minute'
time = Time . zone . now + selector [ 'value' ] . to_i . minutes
elsif selector [ 'range' ] == 'hour'
time = Time . zone . now + selector [ 'value' ] . to_i . hours
elsif selector [ 'range' ] == 'day'
time = Time . zone . now + selector [ 'value' ] . to_i . days
elsif selector [ 'range' ] == 'month'
time = Time . zone . now + selector [ 'value' ] . to_i . months
elsif selector [ 'range' ] == 'year'
time = Time . zone . now + selector [ 'value' ] . to_i . years
else
2016-03-01 14:26:46 +00:00
raise " Unknown selector attributes ' #{ selector . inspect } ' "
2015-10-12 13:44:34 +00:00
end
bind_params . push time
2015-09-17 18:39:51 +00:00
elsif selector [ 'operator' ] == 'before (relative)'
query += " #{ attribute } <= ? "
2015-10-12 13:44:34 +00:00
time = nil
if selector [ 'range' ] == 'minute'
time = Time . zone . now - selector [ 'value' ] . to_i . minutes
elsif selector [ 'range' ] == 'hour'
time = Time . zone . now - selector [ 'value' ] . to_i . hours
elsif selector [ 'range' ] == 'day'
time = Time . zone . now - selector [ 'value' ] . to_i . days
elsif selector [ 'range' ] == 'month'
time = Time . zone . now - selector [ 'value' ] . to_i . months
elsif selector [ 'range' ] == 'year'
time = Time . zone . now - selector [ 'value' ] . to_i . years
else
2016-03-01 14:26:46 +00:00
raise " Unknown selector attributes ' #{ selector . inspect } ' "
2015-10-12 13:44:34 +00:00
end
bind_params . push time
2015-09-17 18:39:51 +00:00
elsif selector [ 'operator' ] == 'after (relative)'
query += " #{ attribute } >= ? "
2015-10-12 13:44:34 +00:00
time = nil
if selector [ 'range' ] == 'minute'
time = Time . zone . now + selector [ 'value' ] . to_i . minutes
elsif selector [ 'range' ] == 'hour'
time = Time . zone . now + selector [ 'value' ] . to_i . hours
elsif selector [ 'range' ] == 'day'
time = Time . zone . now + selector [ 'value' ] . to_i . days
elsif selector [ 'range' ] == 'month'
time = Time . zone . now + selector [ 'value' ] . to_i . months
elsif selector [ 'range' ] == 'year'
time = Time . zone . now + selector [ 'value' ] . to_i . years
else
2016-03-01 14:26:46 +00:00
raise " Unknown selector attributes ' #{ selector . inspect } ' "
2015-10-12 13:44:34 +00:00
end
bind_params . push time
2015-09-17 01:04:16 +00:00
else
2016-03-01 14:26:46 +00:00
raise " Invalid operator ' #{ selector [ 'operator' ] } ' for ' #{ selector [ 'value' ] . inspect } ' "
2015-09-17 01:04:16 +00:00
end
2017-10-01 12:25:52 +00:00
end
2017-04-12 13:41:54 +00:00
2015-09-17 18:39:51 +00:00
[ query , bind_params , tables ]
2015-09-17 01:04:16 +00:00
end
2016-02-20 07:14:51 +00:00
= begin
2016-05-03 00:36:44 +00:00
perform changes on ticket
2017-03-03 08:28:45 +00:00
ticket . perform_changes ( { } , 'trigger' , item , current_user_id )
2016-05-03 00:36:44 +00:00
= end
2017-03-03 08:28:45 +00:00
def perform_changes ( perform , perform_origin , item = nil , current_user_id = nil )
2018-03-20 17:47:49 +00:00
logger . debug { " Perform #{ perform_origin } #{ perform . inspect } on Ticket.find( #{ id } ) " }
2017-05-05 09:16:47 +00:00
2018-05-23 10:25:11 +00:00
article = begin
2018-06-05 10:37:52 +00:00
Ticket :: Article . find_by ( id : item . try ( :dig , :article_id ) )
2018-05-23 10:25:11 +00:00
rescue ArgumentError
nil
end
2017-05-05 09:16:47 +00:00
# if the configuration contains the deletion of the ticket then
# we skip all other ticket changes because they does not matter
if perform [ 'ticket.action' ] . present? && perform [ 'ticket.action' ] [ 'value' ] == 'delete'
2017-11-23 08:09:44 +00:00
perform . each_key do | key |
2017-05-05 09:16:47 +00:00
( object_name , attribute ) = key . split ( '.' , 2 )
next if object_name != 'ticket'
next if attribute == 'action'
perform . delete ( key )
end
end
2018-07-13 17:50:10 +00:00
perform_notification = { }
2019-01-29 14:04:47 +00:00
perform_article = { }
2016-05-03 00:36:44 +00:00
changed = false
perform . each do | key , value |
( object_name , attribute ) = key . split ( '.' , 2 )
2019-01-29 14:04:47 +00:00
raise " Unable to update object #{ object_name } . #{ attribute } , only can update tickets, send notifications and create articles! " if object_name != 'ticket' && object_name != 'article' && object_name != 'notification'
2016-05-03 00:36:44 +00:00
2019-01-29 14:04:47 +00:00
# send notification/create article (after changes are done)
if object_name == 'article'
perform_article [ key ] = value
next
end
2016-05-03 00:36:44 +00:00
if object_name == 'notification'
2018-07-13 17:50:10 +00:00
perform_notification [ key ] = value
2016-05-03 00:36:44 +00:00
next
end
# update tags
if key == 'ticket.tags'
2017-04-12 07:34:49 +00:00
next if value [ 'value' ] . blank?
2018-10-09 06:17:41 +00:00
2016-05-03 00:36:44 +00:00
tags = value [ 'value' ] . split ( / , / )
if value [ 'operator' ] == 'add'
2017-10-01 12:25:52 +00:00
tags . each do | tag |
2018-05-29 15:42:14 +00:00
tag_add ( tag , current_user_id || 1 )
2017-10-01 12:25:52 +00:00
end
2016-05-03 00:36:44 +00:00
elsif value [ 'operator' ] == 'remove'
2017-10-01 12:25:52 +00:00
tags . each do | tag |
2018-05-29 15:42:14 +00:00
tag_remove ( tag , current_user_id || 1 )
2017-10-01 12:25:52 +00:00
end
2016-05-03 00:36:44 +00:00
else
logger . error " Unknown #{ attribute } operator #{ value [ 'operator' ] } "
end
next
end
2017-05-05 09:16:47 +00:00
# delete ticket
if key == 'ticket.action'
next if value [ 'value' ] . blank?
next if value [ 'value' ] != 'delete'
2018-10-09 06:17:41 +00:00
2018-12-31 19:15:17 +00:00
logger . info { " Deleted ticket from #{ perform_origin } #{ perform . inspect } Ticket.find( #{ id } ) " }
2017-11-21 14:25:04 +00:00
destroy!
2017-05-05 09:16:47 +00:00
next
end
2017-03-03 08:28:45 +00:00
# lookup pre_condition
if value [ 'pre_condition' ]
2017-11-23 08:09:44 +00:00
if value [ 'pre_condition' ] . match? ( / ^not_set / )
2017-03-03 08:28:45 +00:00
value [ 'value' ] = 1
2017-11-23 08:09:44 +00:00
elsif value [ 'pre_condition' ] . match? ( / ^current_user \ . / )
2017-03-03 08:28:45 +00:00
raise 'Unable to use current_user, got no current_user_id for ticket.perform_changes' if ! current_user_id
2018-10-09 06:17:41 +00:00
2017-03-03 08:28:45 +00:00
value [ 'value' ] = current_user_id
end
end
2016-05-03 00:36:44 +00:00
# update ticket
next if self [ attribute ] . to_s == value [ 'value' ] . to_s
2018-10-09 06:17:41 +00:00
2016-05-03 00:36:44 +00:00
changed = true
self [ attribute ] = value [ 'value' ]
2018-07-13 17:50:10 +00:00
logger . debug { " set #{ object_name } . #{ attribute } = #{ value [ 'value' ] . inspect } for ticket_id #{ id } " }
end
if changed
save!
end
2019-01-29 14:04:47 +00:00
perform_article . each do | key , value |
raise 'Unable to create article, we only support article.note' if key != 'article.note'
Ticket :: Article . create! (
ticket_id : id ,
subject : value [ :subject ] ,
content_type : 'text/html' ,
body : value [ :body ] ,
internal : value [ :internal ] ,
sender : Ticket :: Article :: Sender . find_by ( name : 'System' ) ,
type : Ticket :: Article :: Type . find_by ( name : 'note' ) ,
preferences : {
perform_origin : perform_origin ,
} ,
updated_by_id : 1 ,
created_by_id : 1 ,
)
end
2018-07-13 17:50:10 +00:00
perform_notification . each do | key , value |
2018-10-16 08:45:15 +00:00
# send notification
case key
when 'notification.sms'
send_sms_notification ( value , article , perform_origin )
2018-07-13 17:50:10 +00:00
next
2018-10-16 08:45:15 +00:00
when 'notification.email'
send_email_notification ( value , article , perform_origin )
2018-07-13 17:50:10 +00:00
end
end
2018-05-29 15:42:14 +00:00
true
end
= begin
perform active triggers on ticket
Ticket . perform_triggers ( ticket , article , item , options )
= end
def self . perform_triggers ( ticket , article , item , options = { } )
recursive = Setting . get ( 'ticket_trigger_recursive' )
type = options [ :type ] || item [ :type ]
local_options = options . clone
local_options [ :type ] = type
local_options [ :reset_user_id ] = true
local_options [ :disable ] = [ 'Transaction::Notification' ]
local_options [ :trigger_ids ] || = { }
local_options [ :trigger_ids ] [ ticket . id ] || = [ ]
local_options [ :loop_count ] || = 0
local_options [ :loop_count ] += 1
ticket_trigger_recursive_max_loop = Setting . get ( 'ticket_trigger_recursive_max_loop' ) & . to_i || 10
if local_options [ :loop_count ] > ticket_trigger_recursive_max_loop
message = " Stopped perform_triggers for this object (Ticket/ #{ ticket . id } ), because loop count was #{ local_options [ :loop_count ] } ! "
logger . info { message }
return [ false , message ]
end
triggers = if Rails . configuration . db_case_sensitive
:: Trigger . where ( active : true ) . order ( 'LOWER(name)' )
else
:: Trigger . where ( active : true ) . order ( :name )
end
return [ true , 'No triggers active' ] if triggers . blank?
# check if notification should be send because of customer emails
send_notification = true
if local_options [ :send_notification ] == false
send_notification = false
elsif item [ :article_id ]
article = Ticket :: Article . lookup ( id : item [ :article_id ] )
if article & . preferences && article . preferences [ 'send-auto-response' ] == false
send_notification = false
end
end
Transaction . execute ( local_options ) do
triggers . each do | trigger |
2018-07-13 17:50:10 +00:00
logger . debug { " Probe trigger ( #{ trigger . name } / #{ trigger . id } ) for this object (Ticket: #{ ticket . id } /Loop: #{ local_options [ :loop_count ] } ) " }
2018-05-29 15:42:14 +00:00
condition = trigger . condition
# check if one article attribute is used
one_has_changed_done = false
article_selector = false
trigger . condition . each_key do | key |
( object_name , attribute ) = key . split ( '.' , 2 )
next if object_name != 'article'
next if attribute == 'id'
2018-10-09 06:17:41 +00:00
2018-05-29 15:42:14 +00:00
article_selector = true
end
if article && article_selector
one_has_changed_done = true
end
if article && type == 'update'
one_has_changed_done = true
end
# check ticket "has changed" options
has_changed_done = true
condition . each do | key , value |
next if value . blank?
next if value [ 'operator' ] . blank?
next if ! value [ 'operator' ] [ 'has changed' ]
# remove condition item, because it has changed
( object_name , attribute ) = key . split ( '.' , 2 )
next if object_name != 'ticket'
next if item [ :changes ] . blank?
next if ! item [ :changes ] . key? ( attribute )
2018-10-09 06:17:41 +00:00
2018-05-29 15:42:14 +00:00
condition . delete ( key )
one_has_changed_done = true
end
# check if we have not matching "has changed" attributes
condition . each_value do | value |
next if value . blank?
next if value [ 'operator' ] . blank?
next if ! value [ 'operator' ] [ 'has changed' ]
2018-10-09 06:17:41 +00:00
2018-05-29 15:42:14 +00:00
has_changed_done = false
break
end
# check ticket action
if condition [ 'ticket.action' ]
next if condition [ 'ticket.action' ] [ 'operator' ] == 'is' && condition [ 'ticket.action' ] [ 'value' ] != type
next if condition [ 'ticket.action' ] [ 'operator' ] != 'is' && condition [ 'ticket.action' ] [ 'value' ] == type
2018-10-09 06:17:41 +00:00
2018-05-29 15:42:14 +00:00
condition . delete ( 'ticket.action' )
end
next if ! has_changed_done
# check in min one attribute of condition has changed on update
one_has_changed_condition = false
if type == 'update'
# verify if ticket condition exists
condition . each_key do | key |
( object_name , attribute ) = key . split ( '.' , 2 )
next if object_name != 'ticket'
2018-10-09 06:17:41 +00:00
2018-05-29 15:42:14 +00:00
one_has_changed_condition = true
next if item [ :changes ] . blank?
next if ! item [ :changes ] . key? ( attribute )
2018-10-09 06:17:41 +00:00
2018-05-29 15:42:14 +00:00
one_has_changed_done = true
break
end
next if one_has_changed_condition && ! one_has_changed_done
end
# check if ticket selector is matching
condition [ 'ticket.id' ] = {
operator : 'is' ,
2018-12-19 17:31:51 +00:00
value : ticket . id ,
2018-05-29 15:42:14 +00:00
}
next if article_selector && ! article
# check if article selector is matching
if article_selector
condition [ 'article.id' ] = {
operator : 'is' ,
2018-12-19 17:31:51 +00:00
value : article . id ,
2018-05-29 15:42:14 +00:00
}
end
# verify is condition is matching
2019-03-26 00:17:17 +00:00
ticket_count , tickets = Ticket . selectors ( condition , limit : 1 )
2018-05-29 15:42:14 +00:00
next if ticket_count . blank?
next if ticket_count . zero?
next if tickets . first . id != ticket . id
2018-10-09 06:17:41 +00:00
2018-05-29 15:42:14 +00:00
user_id = ticket . updated_by_id
if article
user_id = article . updated_by_id
end
if recursive == false && local_options [ :loop_count ] > 1
message = " Do not execute recursive triggers per default until Zammad 3.0. With Zammad 3.0 and higher the following trigger is executed ' #{ trigger . name } ' on Ticket: #{ ticket . id } . Please review your current triggers and change them if needed. "
logger . info { message }
return [ true , message ]
end
local_send_notification = true
if article && send_notification == false && trigger . perform [ 'notification.email' ] && trigger . perform [ 'notification.email' ] [ 'recipient' ]
recipient = trigger . perform [ 'notification.email' ] [ 'recipient' ]
local_send_notification = false
local_options [ :send_notification ] = false
if recipient . include? ( 'ticket_customer' ) || recipient . include? ( 'article_last_sender' )
logger . info { " Skip trigger ( #{ trigger . name } / #{ trigger . id } ) because sender do not want to get auto responder for object (Ticket/ #{ ticket . id } /Article/ #{ article . id } ) " }
next
end
end
if local_options [ :trigger_ids ] [ ticket . id ] . include? ( trigger . id )
logger . info { " Skip trigger ( #{ trigger . name } / #{ trigger . id } ) because was already executed for this object (Ticket: #{ ticket . id } /Loop: #{ local_options [ :loop_count ] } ) " }
next
end
local_options [ :trigger_ids ] [ ticket . id ] . push trigger . id
logger . info { " Execute trigger ( #{ trigger . name } / #{ trigger . id } ) for this object (Ticket: #{ ticket . id } /Loop: #{ local_options [ :loop_count ] } ) " }
ticket . perform_changes ( trigger . perform , 'trigger' , item , user_id )
if recursive == true
Observer :: Transaction . commit ( local_options )
end
end
end
[ true , ticket , local_options ]
2016-05-03 00:36:44 +00:00
end
= begin
2016-02-20 07:14:51 +00:00
get all email references headers of a ticket , to exclude some , parse it as array into method
references = ticket . get_references
result
[ 'message-id-1234' , 'message-id-5678' ]
ignore references header ( s )
references = ticket . get_references ( [ 'message-id-5678' ] )
result
[ 'message-id-1234' ]
= end
def get_references ( ignore = [ ] )
references = [ ]
2017-10-01 12:25:52 +00:00
Ticket :: Article . select ( 'in_reply_to, message_id' ) . where ( ticket_id : id ) . each do | article |
2017-11-23 08:09:44 +00:00
if article . in_reply_to . present?
2016-02-20 07:14:51 +00:00
references . push article . in_reply_to
end
2017-11-23 08:09:44 +00:00
next if article . message_id . blank?
2018-10-09 06:17:41 +00:00
2016-02-20 07:14:51 +00:00
references . push article . message_id
2017-10-01 12:25:52 +00:00
end
ignore . each do | item |
2016-02-20 07:14:51 +00:00
references . delete ( item )
2017-10-01 12:25:52 +00:00
end
2016-02-20 07:14:51 +00:00
references
end
2016-11-18 07:25:07 +00:00
= begin
get all articles of a ticket in correct order ( overwrite active record default method )
artilces = ticket . articles
result
[ article1 , articl2 ]
= end
def articles
Ticket :: Article . where ( ticket_id : id ) . order ( :created_at , :id )
end
2012-11-28 10:03:17 +00:00
private
2012-11-28 09:46:26 +00:00
2013-08-15 22:16:38 +00:00
def check_generate
2017-06-16 22:53:20 +00:00
return true if number
2018-10-09 06:17:41 +00:00
2013-08-15 22:16:38 +00:00
self . number = Ticket :: Number . generate
2017-06-16 22:53:20 +00:00
true
2013-06-12 15:59:58 +00:00
end
2013-08-15 22:16:38 +00:00
2015-01-07 21:28:15 +00:00
def check_title
2017-06-16 22:53:20 +00:00
return true if ! title
2018-10-09 06:17:41 +00:00
2015-05-07 12:10:38 +00:00
title . gsub! ( / \ s| \ t| \ r / , ' ' )
2017-06-16 22:53:20 +00:00
true
2015-01-07 21:28:15 +00:00
end
2013-06-12 15:59:58 +00:00
def check_defaults
2015-05-07 12:10:38 +00:00
if ! owner_id
2013-06-12 15:59:58 +00:00
self . owner_id = 1
end
2017-06-16 22:53:20 +00:00
return true if ! customer_id
2018-10-09 06:17:41 +00:00
2016-12-20 23:07:47 +00:00
customer = User . find_by ( id : customer_id )
2017-06-16 22:53:20 +00:00
return true if ! customer
return true if organization_id == customer . organization_id
2018-10-09 06:17:41 +00:00
2015-04-30 15:25:04 +00:00
self . organization_id = customer . organization_id
2017-06-16 22:53:20 +00:00
true
2013-06-12 15:59:58 +00:00
end
2013-06-12 14:57:29 +00:00
2015-02-11 22:05:31 +00:00
def reset_pending_time
# ignore if no state has changed
2017-09-23 06:25:55 +00:00
return true if ! changes_to_save [ 'state_id' ]
2015-02-11 22:05:31 +00:00
2017-06-28 08:25:25 +00:00
# ignore if new state is blank and
# let handle ActiveRecord the error
return if state_id . blank?
2015-02-11 22:05:31 +00:00
# check if new state isn't pending*
2016-03-18 02:04:49 +00:00
current_state = Ticket :: State . lookup ( id : state_id )
current_state_type = Ticket :: StateType . lookup ( id : current_state . state_type_id )
2015-02-11 22:05:31 +00:00
# in case, set pending_time to nil
2017-11-23 08:09:44 +00:00
return true if current_state_type . name . match? ( / ^pending /i )
2018-10-09 06:17:41 +00:00
2015-04-30 15:25:04 +00:00
self . pending_time = nil
2017-06-16 22:53:20 +00:00
true
2015-02-11 22:05:31 +00:00
end
2017-02-12 17:21:03 +00:00
def set_default_state
2017-06-16 22:53:20 +00:00
return true if state_id
2018-10-09 06:17:41 +00:00
2017-02-12 17:21:03 +00:00
default_ticket_state = Ticket :: State . find_by ( default_create : true )
2017-06-16 22:53:20 +00:00
return true if ! default_ticket_state
2018-10-09 06:17:41 +00:00
2017-02-12 17:21:03 +00:00
self . state_id = default_ticket_state . id
2017-06-16 22:53:20 +00:00
true
2017-02-12 17:21:03 +00:00
end
def set_default_priority
2017-06-16 22:53:20 +00:00
return true if priority_id
2018-10-09 06:17:41 +00:00
2017-02-12 17:21:03 +00:00
default_ticket_priority = Ticket :: Priority . find_by ( default_create : true )
2017-06-16 22:53:20 +00:00
return true if ! default_ticket_priority
2018-10-09 06:17:41 +00:00
2017-02-12 17:21:03 +00:00
self . priority_id = default_ticket_priority . id
2017-06-16 22:53:20 +00:00
true
2017-02-12 17:21:03 +00:00
end
2018-02-10 10:40:57 +00:00
def check_owner_active
2018-02-19 10:15:47 +00:00
return true if Setting . get ( 'import_mode' )
2018-02-16 11:56:51 +00:00
# return when ticket is unassigned
return true if owner_id . blank?
return true if owner_id == 1
# return if owner is active, is agent and has access to group of ticket
return true if owner . active? && owner . permissions? ( 'ticket.agent' ) && owner . group_access? ( group_id , 'full' )
# else set the owner of the ticket to the default user as unassigned
self . owner_id = 1
2018-02-10 10:40:57 +00:00
true
end
2018-10-16 08:45:15 +00:00
# articles.last breaks (returns the wrong article)
# if another email notification trigger preceded this one
# (see https://github.com/zammad/zammad/issues/1543)
def build_notification_template_objects ( article )
{
2018-12-19 17:31:51 +00:00
ticket : self ,
2018-10-16 08:45:15 +00:00
article : article || articles . last
}
end
def send_email_notification ( value , article , perform_origin )
# value['recipient'] was a string in the past (single-select) so we convert it to array if needed
value_recipient = Array ( value [ 'recipient' ] )
recipients_raw = [ ]
value_recipient . each do | recipient |
if recipient == 'article_last_sender'
if article . present?
if article . reply_to . present?
recipients_raw . push ( article . reply_to )
elsif article . from . present?
recipients_raw . push ( article . from )
elsif article . origin_by_id
email = User . find_by ( id : article . origin_by_id ) . email
recipients_raw . push ( email )
elsif article . created_by_id
email = User . find_by ( id : article . created_by_id ) . email
recipients_raw . push ( email )
end
end
elsif recipient == 'ticket_customer'
email = User . find_by ( id : customer_id ) . email
recipients_raw . push ( email )
elsif recipient == 'ticket_owner'
email = User . find_by ( id : owner_id ) . email
recipients_raw . push ( email )
elsif recipient == 'ticket_agents'
User . group_access ( group_id , 'full' ) . sort_by ( & :login ) . each do | user |
recipients_raw . push ( user . email )
end
else
logger . error " Unknown email notification recipient ' #{ recipient } ' "
next
end
end
recipients_checked = [ ]
recipients_raw . each do | recipient_email |
skip_user = false
users = User . where ( email : recipient_email )
users . each do | user |
next if user . preferences [ :mail_delivery_failed ] != true
next if ! user . preferences [ :mail_delivery_failed_data ]
till_blocked = ( ( user . preferences [ :mail_delivery_failed_data ] - Time . zone . now - 60 . days ) / 60 / 60 / 24 ) . round
next if till_blocked . positive?
logger . info " Send no trigger based notification to #{ recipient_email } because email is marked as mail_delivery_failed for #{ till_blocked } days "
skip_user = true
break
end
next if skip_user
# send notifications only to email adresses
next if recipient_email . blank?
next if recipient_email !~ / @ /
# check if address is valid
begin
Mail :: AddressList . new ( recipient_email ) . addresses . each do | address |
recipient_email = address . address
break if recipient_email . present? && recipient_email =~ / @ / && ! recipient_email . match? ( / \ s / )
end
rescue
if recipient_email . present?
if recipient_email !~ / ^(.+?)<(.+?)@(.+?)>$ /
next # no usable format found
end
recipient_email = " #{ $2 } @ #{ $3 } "
end
next if recipient_email . blank?
next if recipient_email !~ / @ /
next if recipient_email . match? ( / \ s / )
end
# do not sent notifications to this recipients
send_no_auto_response_reg_exp = Setting . get ( 'send_no_auto_response_reg_exp' )
begin
next if recipient_email . match? ( / #{ send_no_auto_response_reg_exp } /i )
rescue = > e
logger . error " ERROR: Invalid regex ' #{ send_no_auto_response_reg_exp } ' in setting send_no_auto_response_reg_exp "
logger . error 'ERROR: ' + e . inspect
next if recipient_email . match? ( / (mailer-daemon|postmaster|abuse|root|noreply|noreply.+?|no-reply|no-reply.+?)@.+? /i )
end
# check if notification should be send because of customer emails
if article . present? && article . preferences . fetch ( 'is-auto-response' , false ) == true && article . from && article . from =~ / #{ Regexp . quote ( recipient_email ) } /i
logger . info " Send no trigger based notification to #{ recipient_email } because of auto response tagged incoming email "
next
end
# loop protection / check if maximal count of trigger mail has reached
map = {
2018-12-19 17:31:51 +00:00
10 = > 10 ,
30 = > 15 ,
60 = > 25 ,
2018-10-16 08:45:15 +00:00
180 = > 50 ,
600 = > 100 ,
}
skip = false
map . each do | minutes , count |
already_sent = Ticket :: Article . where (
ticket_id : id ,
2018-12-19 17:31:51 +00:00
sender : Ticket :: Article :: Sender . find_by ( name : 'System' ) ,
type : Ticket :: Article :: Type . find_by ( name : 'email' ) ,
2018-10-16 08:45:15 +00:00
) . where ( 'ticket_articles.created_at > ? AND ticket_articles.to LIKE ?' , Time . zone . now - minutes . minutes , " % #{ recipient_email . strip } % " ) . count
next if already_sent < count
logger . info " Send no trigger based notification to #{ recipient_email } because already sent #{ count } for this ticket within last #{ minutes } minutes (loop protection) "
skip = true
break
end
next if skip
map = {
2018-12-19 17:31:51 +00:00
10 = > 30 ,
30 = > 60 ,
60 = > 120 ,
2018-10-16 08:45:15 +00:00
180 = > 240 ,
600 = > 360 ,
}
skip = false
map . each do | minutes , count |
already_sent = Ticket :: Article . where (
sender : Ticket :: Article :: Sender . find_by ( name : 'System' ) ,
2018-12-19 17:31:51 +00:00
type : Ticket :: Article :: Type . find_by ( name : 'email' ) ,
2018-10-16 08:45:15 +00:00
) . where ( 'ticket_articles.created_at > ? AND ticket_articles.to LIKE ?' , Time . zone . now - minutes . minutes , " % #{ recipient_email . strip } % " ) . count
next if already_sent < count
logger . info " Send no trigger based notification to #{ recipient_email } because already sent #{ count } in total within last #{ minutes } minutes (loop protection) "
skip = true
break
end
next if skip
email = recipient_email . downcase . strip
next if recipients_checked . include? ( email )
recipients_checked . push ( email )
end
return if recipients_checked . blank?
recipient_string = recipients_checked . join ( ', ' )
group_id = self . group_id
return if ! group_id
email_address = Group . find ( group_id ) . email_address
if ! email_address
logger . info " Unable to send trigger based notification to #{ recipient_string } because no email address is set for group ' #{ group . name } ' "
return
end
if ! email_address . channel_id
logger . info " Unable to send trigger based notification to #{ recipient_string } because no channel is set for email address ' #{ email_address . email } ' (id: #{ email_address . id } ) "
return
end
objects = build_notification_template_objects ( article )
# get subject
subject = NotificationFactory :: Mailer . template (
templateInline : value [ 'subject' ] ,
2018-12-19 17:31:51 +00:00
locale : 'en-en' ,
objects : objects ,
quote : false ,
2018-10-16 08:45:15 +00:00
)
subject = subject_build ( subject )
body = NotificationFactory :: Mailer . template (
templateInline : value [ 'body' ] ,
2018-12-19 17:31:51 +00:00
locale : 'en-en' ,
objects : objects ,
quote : true ,
2018-10-16 08:45:15 +00:00
)
( body , attachments_inline ) = HtmlSanitizer . replace_inline_images ( body , id )
message = Ticket :: Article . create (
2018-12-19 17:31:51 +00:00
ticket_id : id ,
to : recipient_string ,
subject : subject ,
content_type : 'text/html' ,
body : body ,
internal : false ,
sender : Ticket :: Article :: Sender . find_by ( name : 'System' ) ,
type : Ticket :: Article :: Type . find_by ( name : 'email' ) ,
preferences : {
2018-10-16 08:45:15 +00:00
perform_origin : perform_origin ,
} ,
updated_by_id : 1 ,
created_by_id : 1 ,
)
attachments_inline . each do | attachment |
Store . add (
2018-12-19 17:31:51 +00:00
object : 'Ticket::Article' ,
o_id : message . id ,
data : attachment [ :data ] ,
filename : attachment [ :filename ] ,
2018-10-16 08:45:15 +00:00
preferences : attachment [ :preferences ] ,
)
end
2019-02-10 08:40:55 +00:00
original_article = objects [ :article ]
if original_article & . should_clone_inline_attachments? # rubocop:disable Style/GuardClause
original_article . clone_attachments ( 'Ticket::Article' , message . id , only_inline_attachments : true )
original_article . should_clone_inline_attachments = false # cancel the temporary flag after cloning
end
2018-10-16 08:45:15 +00:00
end
def sms_recipients_by_type ( recipient_type , article )
case recipient_type
when 'article_last_sender'
return nil if article . blank?
if article . origin_by_id
article . origin_by_id
elsif article . created_by_id
article . created_by_id
end
when 'ticket_customer'
customer_id
when 'ticket_owner'
owner_id
when 'ticket_agents'
User . group_access ( group_id , 'full' ) . sort_by ( & :login )
else
logger . error " Unknown sms notification recipient ' #{ recipient } ' "
nil
end
end
def build_sms_recipients_list ( value , article )
Array ( value [ 'recipient' ] )
. each_with_object ( [ ] ) { | recipient_type , sum | sum . concat ( Array ( sms_recipients_by_type ( recipient_type , article ) ) ) }
. map { | user_or_id | User . lookup ( id : user_or_id ) }
. select { | user | user . mobile . present? }
end
def send_sms_notification ( value , article , perform_origin )
sms_recipients = build_sms_recipients_list ( value , article )
if sms_recipients . blank?
logger . debug " No SMS recipients found for Ticket # #{ number } "
return
end
sms_recipients_to = sms_recipients
. map { | recipient | " #{ recipient . fullname } ( #{ recipient . mobile } ) " }
. join ( ', ' )
channel = Channel . find_by ( area : 'Sms::Notification' )
if ! channel . active?
# write info message since we have an active trigger
logger . info " Found possible SMS recipient(s) ( #{ sms_recipients_to } ) for Ticket # #{ number } but SMS channel is not active. "
return
end
objects = build_notification_template_objects ( article )
2019-02-10 11:01:38 +00:00
body = NotificationFactory :: Renderer . new (
objects : objects ,
locale : 'en-en' ,
timezone : Setting . get ( 'timezone_default' ) ,
template : value [ 'body' ] ,
escape : false
) . render . html2text . tr ( ' ' , ' ' ) # convert non-breaking space to simple space
2018-10-16 08:45:15 +00:00
# attributes content_type is not needed for SMS
article = Ticket :: Article . create (
2018-12-19 17:31:51 +00:00
ticket_id : id ,
subject : 'SMS notification' ,
to : sms_recipients_to ,
body : body ,
internal : true ,
sender : Ticket :: Article :: Sender . find_by ( name : 'System' ) ,
type : Ticket :: Article :: Type . find_by ( name : 'sms' ) ,
preferences : {
2018-10-16 08:45:15 +00:00
perform_origin : perform_origin ,
sms_recipients : sms_recipients . map ( & :mobile ) ,
2018-12-19 17:31:51 +00:00
channel_id : channel . id ,
2018-10-16 08:45:15 +00:00
} ,
updated_by_id : 1 ,
created_by_id : 1 ,
)
end
2015-04-27 14:15:29 +00:00
end