diff --git a/Gemfile.lock b/Gemfile.lock
index f568c61ef..7141ed216 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -45,7 +45,7 @@ GEM
ast (2.2.0)
autoprefixer-rails (6.3.6)
execjs
- biz (1.5.1)
+ biz (1.5.2)
clavius (~> 1.0)
tzinfo
browser (2.0.3)
@@ -129,7 +129,7 @@ GEM
inflection (1.0.0)
json (1.8.3)
jwt (1.5.1)
- koala (2.2.0)
+ koala (2.3.0)
addressable
faraday
multi_json
@@ -199,7 +199,7 @@ GEM
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
- puma (3.3.0)
+ puma (3.4.0)
rack (1.6.4)
rack-livereload (0.3.16)
rack
@@ -269,8 +269,8 @@ GEM
simplecov (>= 0.4.1)
slack-notifier (1.5.1)
slop (3.6.0)
- spring (1.6.4)
- sprockets (3.5.2)
+ spring (1.7.1)
+ sprockets (3.6.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.0.4)
diff --git a/app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee b/app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee
new file mode 100644
index 000000000..7baecf237
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee
@@ -0,0 +1,156 @@
+class Index extends App.ControllerIntegrationBase
+ featureIntegration: 'sipgate_integration'
+ featureName: 'sipgate.io'
+ featureConfig: 'sipgate_config'
+ description: [
+ ['This service shows you contacts of incoming calls and a caller list in realtime.']
+ ['Also caller id of outbound calls can be changed.']
+ ]
+
+ render: =>
+ super
+ new Form(
+ el: @$('.js-form')
+ )
+
+ new App.HttpLog(
+ el: @$('.js-log')
+ facility: 'sipgate.io'
+ )
+
+class Form extends App.Controller
+ events:
+ 'submit form': 'update'
+ 'click .js-inboundBlockCallerId .js-add': 'addInboundBlockCallerId'
+ 'click .js-outboundRouting .js-add': 'addOutboundRouting'
+ 'click .js-inboundBlockCallerId .js-remove': 'removeInboundBlockCallerId'
+ 'click .js-outboundRouting .js-remove': 'removeOutboundRouting'
+
+ constructor: ->
+ super
+
+ # check authentication
+ return if !@authenticate()
+
+ @subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false)
+
+ currentConfig: ->
+ config = App.Setting.get('sipgate_config')
+ if !config.outbound
+ config.outbound = {}
+ if !config.outbound.routing_table
+ config.outbound.routing_table = []
+ if !config.inbound
+ config.inbound = {}
+ if !config.inbound.block_caller_ids
+ config.inbound.block_caller_ids = []
+ config
+
+ setConfig: (value) ->
+ App.Setting.set('sipgate_config', value)
+
+ render: =>
+ @config = @currentConfig()
+
+ @html App.view('integration/sipgate')(
+ config: @config
+ )
+
+ updateCurrentConfig: =>
+ config = @config
+ cleanupInput = @cleanupInput
+
+ # default caller_id
+ default_caller_id = @$('input[name=default_caller_id]').val()
+ config.outbound.default_caller_id = cleanupInput(default_caller_id)
+
+ # routing table
+ config.outbound.routing_table = []
+ @$('.js-outboundRouting .js-row').each(->
+ dest = cleanupInput($(@).find('input[name="dest"]').val())
+ caller_id = cleanupInput($(@).find('input[name="caller_id"]').val())
+ note = $(@).find('input[name="note"]').val()
+ config.outbound.routing_table.push {
+ dest: dest
+ caller_id: caller_id
+ note: note
+ }
+ )
+
+ # blocked caller ids
+ config.inbound.block_caller_ids = []
+ @$('.js-inboundBlockCallerId .js-row').each(->
+ caller_id = $(@).find('input[name="caller_id"]').val()
+ note = $(@).find('input[name="note"]').val()
+ config.inbound.block_caller_ids.push {
+ caller_id: cleanupInput(caller_id)
+ note: note
+ }
+ )
+
+ @config = config
+
+ update: (e) =>
+ e.preventDefault()
+ @updateCurrentConfig()
+ @setConfig(@config)
+
+ cleanupInput: (value) ->
+ return value if !value
+ value.replace(/\s/g, '').trim()
+
+ addInboundBlockCallerId: (e) =>
+ e.preventDefault()
+ @updateCurrentConfig()
+ element = $(e.currentTarget).closest('tr')
+ caller_id = element.find('input[name="caller_id"]').val()
+ note = element.find('input[name="note"]').val()
+ @config.inbound.block_caller_ids.push {
+ caller_id: @cleanupInput(caller_id)
+ note: note
+ }
+ @setConfig(@config)
+ @render()
+
+ addOutboundRouting: (e) =>
+ e.preventDefault()
+ @updateCurrentConfig()
+ element = $(e.currentTarget).closest('tr')
+ dest = @cleanupInput(element.find('input[name="dest"]').val())
+ caller_id = @cleanupInput(element.find('input[name="caller_id"]').val())
+ note = element.find('input[name="note"]').val()
+ @config.outbound.routing_table.push {
+ dest: dest
+ caller_id: caller_id
+ note: note
+ }
+ @setConfig(@config)
+ @render()
+
+ removeInboundBlockCallerId: (e) =>
+ e.preventDefault()
+ @updateCurrentConfig()
+ element = $(e.currentTarget).closest('tr')
+ element.remove()
+
+ removeOutboundRouting: (e) =>
+ e.preventDefault()
+ @updateCurrentConfig()
+ element = $(e.currentTarget).closest('tr')
+ element.remove()
+
+class State
+ @current: ->
+ App.Setting.get('sipgate_integration')
+
+App.Config.set(
+ 'IntegrationSipgate'
+ {
+ name: 'sipgate.io'
+ target: '#system/integration/sipgate'
+ description: 'VoIP services provide.'
+ controller: Index
+ state: State
+ }
+ 'NavBarIntegrations'
+)
diff --git a/app/assets/javascripts/app/controllers/cti.coffee b/app/assets/javascripts/app/controllers/cti.coffee
new file mode 100644
index 000000000..08b09ec27
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/cti.coffee
@@ -0,0 +1,114 @@
+class App.CTI extends App.Controller
+ constructor: ->
+ super
+
+ @meta =
+ active: false
+
+ preferences = @Session.get('preferences') || {}
+ @meta.active = preferences.cti || false
+
+ @load()
+
+ App.Event.bind(
+ 'cti_event'
+ (data) =>
+ console.log('cti_event', data)
+ if data.state is 'newCall'
+ console.log('notify')
+ @notify(data)
+ 'cti_event'
+ )
+ App.Event.bind(
+ 'cti_list_push'
+ (data) =>
+ @list = data
+ @render()
+ 'cti_list_push'
+ )
+
+ # fetch data, render view
+ load: ->
+ @ajax(
+ id: 'cti_log'
+ type: 'GET'
+ url: "#{@apiPath}/cti/log"
+ success: (data) =>
+ @list = data
+ @render()
+ )
+
+ notify: (data) ->
+ console.log(data)
+ #return if !
+ if data.state is 'newCall' && data.direction is 'in'
+ App.Event.trigger 'notify', {
+ type: 'notice'
+ msg: App.i18n.translateContent('Call from %s for %s', data.from, data.to)
+ timeout: 2500
+ }
+
+ featureActive: =>
+ return true
+ if @Config.get('sipgate_integration')
+ return true
+ false
+
+ render: ->
+ if !@isRole('CTI')
+ @renderScreenUnauthorized(objectName: 'CTI')
+ return
+
+ @html App.view('cti/index')(
+ list: @list
+ )
+
+ @updateNavMenu()
+
+ show: (params) =>
+ @title 'CTI', true
+ @navupdate '#cti'
+
+ counter: ->
+ counter = 0
+
+ switch: (state = undefined) =>
+
+ # read state
+ if state is undefined
+ return @meta.active
+
+ @meta.active = state
+
+ # update user preferences
+ @ajax(
+ id: 'preferences'
+ type: 'PUT'
+ url: "#{@apiPath}/users/preferences"
+ data: JSON.stringify(user: {cti: state})
+ processData: true
+ )
+
+ updateNavMenu: =>
+ delay = ->
+ App.Event.trigger('menu:render')
+ @delay(delay, 200, 'updateNavMenu')
+
+class CTIRouter extends App.ControllerPermanent
+ constructor: (params) ->
+ super
+
+ # check authentication
+ return if !@authenticate(false, 'CTI')
+
+ App.TaskManager.execute(
+ key: 'CTI'
+ controller: 'CTI'
+ params: {}
+ show: true
+ persistent: true
+ )
+
+App.Config.set('cti', CTIRouter, 'Routes')
+App.Config.set('CTI', { controller: 'CTI', authentication: true }, 'permanentTask')
+App.Config.set('CTI', { prio: 1300, parent: '', name: 'Phone', target: '#cti', key: 'CTI', shown: false, role: ['CTI'], class: 'phone' }, 'NavBar')
diff --git a/app/assets/javascripts/app/controllers/widget/http_log.coffee b/app/assets/javascripts/app/controllers/widget/http_log.coffee
index 01951edad..4a625b3bd 100644
--- a/app/assets/javascripts/app/controllers/widget/http_log.coffee
+++ b/app/assets/javascripts/app/controllers/widget/http_log.coffee
@@ -9,22 +9,23 @@ class App.HttpLog extends App.Controller
fetch: =>
@ajax(
- id: 'http_logs'
- type: 'GET'
- url: "#{@apiPath}/http_logs/#{@facility}"
+ id: 'http_logs'
+ type: 'GET'
+ url: "#{@apiPath}/http_logs/#{@facility}"
data:
limit: @limit || 50
processData: true
success: (data) =>
- @records = data
- @render()
+ if !@records[0] || (data[0] && @records[0] && data[0].updated_at isnt @records[0].updated_at)
+ @records = data
+ @render()
+ @delay(@fetch, 20000)
)
render: =>
@html App.view('widget/http_log')(
records: @records
)
- #@delay(message, 2000)
show: (e) =>
e.preventDefault()
diff --git a/app/assets/javascripts/app/views/cti/index.jst.eco b/app/assets/javascripts/app/views/cti/index.jst.eco
new file mode 100644
index 000000000..7aba99b57
--- /dev/null
+++ b/app/assets/javascripts/app/views/cti/index.jst.eco
@@ -0,0 +1,28 @@
+
+
<%- @T('Caller log') %>
+
+
+
+
+
+ <%- @T('From') %> |
+ <%- @T('To') %> |
+ <%- @T('State') %> |
+ <%- @T('Comment') %> |
+ <%- @T('Time') %> |
+
+
+
+ <% for item in @list: %>
+
+ <%= item.from %><% if item.from_comment: %> (<%= item.from_comment %>)<% end %> |
+ <%= item.to %><% if item.to_comment: %> (<%= item.to_comment %>)<% end %> |
+ <%= item.state %> |
+ <%= item.comment %> |
+ <%- @humanTime(item.created_at) %> |
+
+ <% end %>
+
+
+
+
diff --git a/app/assets/javascripts/app/views/integration/sipgate.jst.eco b/app/assets/javascripts/app/views/integration/sipgate.jst.eco
new file mode 100644
index 000000000..a21d2c380
--- /dev/null
+++ b/app/assets/javascripts/app/views/integration/sipgate.jst.eco
@@ -0,0 +1,78 @@
+
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/widget/http_log.jst.eco b/app/assets/javascripts/app/views/widget/http_log.jst.eco
index 4f3790b12..349bab12e 100644
--- a/app/assets/javascripts/app/views/widget/http_log.jst.eco
+++ b/app/assets/javascripts/app/views/widget/http_log.jst.eco
@@ -1,6 +1,6 @@
-<%- @T('Recent logs') %>
+ <%- @T('Recent logs') %>
diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss
index d2e0eaacd..508f0fb68 100644
--- a/app/assets/stylesheets/zammad.scss
+++ b/app/assets/stylesheets/zammad.scss
@@ -3734,7 +3734,6 @@ footer {
}
.popover-content {
- flex: 1;
padding-left: 0;
padding-right: 0;
margin-bottom: 0;
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 51c705347..37a12b9d3 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
helper_method :current_user,
:authentication_check,
:config_frontend,
+ :http_log_config,
:role?,
:model_create_render,
:model_update_render,
@@ -18,7 +19,7 @@ class ApplicationController < ActionController::Base
before_action :cors_preflight_check
after_action :user_device_update, :set_access_control_headers
- after_action :trigger_events
+ after_action :trigger_events, :http_log
# For all responses in this controller, return the CORS access control headers.
def set_access_control_headers
@@ -47,6 +48,10 @@ class ApplicationController < ActionController::Base
false
end
+ def http_log_config(config)
+ @http_log_support = config
+ end
+
private
# execute events
@@ -98,6 +103,60 @@ class ApplicationController < ActionController::Base
session[:user_agent] = request.env['HTTP_USER_AGENT']
end
+ # log http access
+ def http_log
+ return if !@http_log_support
+
+ # request
+ request_data = {
+ content: '',
+ content_type: request.headers['Content-Type'],
+ content_encoding: request.headers['Content-Encoding'],
+ source: request.headers['User-Agent'] || request.headers['Server'],
+ }
+ request.headers.each {|key, value|
+ next if key[0, 5] != 'HTTP_'
+ request_data[:content] += if key == 'HTTP_COOKIE'
+ "#{key}: xxxxx\n"
+ else
+ "#{key}: #{value}\n"
+ end
+ }
+ body = request.body.read
+ if body
+ request_data[:content] += "\n" + body
+ end
+ request_data[:content] = request_data[:content].slice(0, 8000)
+
+ # response
+ response_data = {
+ code: response.status = response.code,
+ content: '',
+ content_type: nil,
+ content_encoding: nil,
+ source: nil,
+ }
+ response.headers.each {|key, value|
+ response_data[:content] += "#{key}: #{value}\n"
+ }
+ body = response.body
+ if body
+ response_data[:content] += "\n" + body
+ end
+ response_data[:content] = response_data[:content].slice(0, 8000)
+ record = {
+ direction: 'in',
+ facility: @http_log_support[:facility],
+ url: url_for(only_path: false, overwrite_params: {}),
+ status: response.status,
+ ip: request.remote_ip,
+ request: request_data,
+ response: response_data,
+ method: request.method,
+ }
+ HttpLog.create(record)
+ end
+
# user device recent action update
def user_device_update
@@ -228,12 +287,11 @@ class ApplicationController < ActionController::Base
}
end
- def authentication_check(auth_param = {} )
+ def authentication_check(auth_param = {})
result = authentication_check_only(auth_param)
# check if basic_auth fallback is possible
if auth_param[:basic_auth_promt] && result[:auth] == false
-
return request_http_basic_authentication
end
diff --git a/app/controllers/integration/sipgate_controller.rb b/app/controllers/integration/sipgate_controller.rb
new file mode 100644
index 000000000..0cbf6f518
--- /dev/null
+++ b/app/controllers/integration/sipgate_controller.rb
@@ -0,0 +1,178 @@
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+
+require 'builder'
+
+class Integration::SipgateController < ApplicationController
+
+ # list current caller log
+ def index
+ return if !authentication_check
+ return if deny_if_not_role('CTI')
+ list = Cti::Log.order('created_at DESC').limit(60)
+ render json: list
+ end
+
+ # notify about inbound call / block inbound call
+ def in
+ http_log_config facility: 'sipgate.io'
+ return if !configured?
+
+ if params['event'] == 'newCall'
+
+ config = Setting.get('sipgate_config')
+ config_inbound = config[:inbound] || {}
+ block_caller_ids = config_inbound[:block_caller_ids] || []
+
+ # check if call need to be blocked
+ block_caller_ids.each {|item|
+ next unless item[:caller_id] == params['from']
+ xml = Builder::XmlMarkup.new(indent: 2)
+ xml.instruct!
+ content = xml.Response(onHangup: url, onAnswer: url) do
+ xml.Reject('reason' => 'busy')
+ end
+
+ send_data content, type: 'application/xml; charset=UTF-8;'
+
+ #params['Reject'] = 'busy'
+ params['comment'] = 'reject, busy'
+ if params['user']
+ params['comment'] = "#{params['user']} -> reject, busy"
+ end
+ update_log(params)
+ return true
+ }
+ end
+
+ update_log(params)
+
+ xml = Builder::XmlMarkup.new(indent: 2)
+ xml.instruct!
+ content = xml.Response(onHangup: url, onAnswer: url)
+ send_data content, type: 'application/xml; charset=UTF-8;'
+ end
+
+ # set caller id of outbound call
+ def out
+ http_log_config facility: 'sipgate.io'
+ return if !configured?
+
+ config = Setting.get('sipgate_config')
+ config_outbound = config[:outbound][:routing_table]
+ default_caller_id = config[:outbound][:default_caller_id]
+
+ xml = Builder::XmlMarkup.new(indent: 2)
+ xml.instruct!
+
+ # set callerId
+ content = nil
+ to = params[:to]
+ from = nil
+ if to
+ config_outbound.each {|row|
+ dest = row[:dest].gsub(/\*/, '.+?')
+ next if to !~ /^#{dest}$/
+ from = row[:caller_id]
+ content = xml.Response(onHangup: url, onAnswer: url) do
+ xml.Dial(callerId: from) { xml.Number(params[:to]) }
+ end
+ break
+ }
+ if !content && default_caller_id
+ from = default_caller_id
+ content = xml.Response(onHangup: url, onAnswer: url) do
+ xml.Dial(callerId: default_caller_id) { xml.Number(params[:to]) }
+ end
+ end
+ else
+ content = xml.Response(onHangup: url, onAnswer: url)
+ end
+
+ send_data content, type: 'application/xml; charset=UTF-8;'
+ if from
+ params['from'] = from
+ end
+ update_log(params)
+ end
+
+ private
+
+ def configured?
+ if !Setting.get('sipgate_integration')
+ xml_error('Feature is disable, please contact your admin to enable it!')
+ return false
+ end
+ config = Setting.get('sipgate_config')
+ if !config || !config[:inbound] || !config[:outbound]
+ xml_error('Feature not configured, please contact your admin!')
+ return false
+ end
+ true
+ end
+
+ def update_log(params)
+
+ user = params['user']
+ if params['user'] && params['user'].class == Array
+ user = params['user'].join(', ')
+ end
+ from_comment = nil
+ to_comment = nil
+ if params['direction'] == 'in'
+ to_comment = user
+ else
+ from_comment = user
+ end
+ comment = nil
+ if params['cause']
+ comment = params['cause']
+ end
+
+ if params['event'] == 'newCall'
+ Cti::Log.create(
+ direction: params['direction'],
+ from: params['from'],
+ from_comment: from_comment,
+ to: params['to'],
+ to_comment: to_comment,
+ call_id: params['callId'],
+ comment: comment,
+ state: params['event'],
+ )
+ elsif params['event'] == 'answer'
+ log = Cti::Log.find_by(call_id: params['callId'])
+ raise "No such call_id #{params['callId']}" if !log
+ log.state = 'answer'
+ log.comment = comment
+ log.save
+ elsif params['event'] == 'hangup'
+ log = Cti::Log.find_by(call_id: params['callId'])
+ raise "No such call_id #{params['callId']}" if !log
+ log.state = 'hangup'
+ log.comment = comment
+ log.save
+ else
+ raise "Unknown event #{params['event']}"
+ end
+
+ end
+
+ def xml_error(error)
+ xml = Builder::XmlMarkup.new(indent: 2)
+ xml.instruct!
+ content = xml.Response() do
+ xml.Error(error)
+ end
+ send_data content, type: 'application/xml; charset=UTF-8;', status: 422
+ end
+
+ def base_url
+ http_type = Setting.get('http_type')
+ fqdn = Setting.get('fqdn')
+ "#{http_type}://#{fqdn}/api/v1/sipgate"
+ end
+
+ def url
+ "#{base_url}/#{params['direction']}"
+ end
+end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index a2c727aae..072fb7e0a 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -85,7 +85,7 @@ class UsersController < ApplicationController
group_ids = []
role_ids = []
if count <= 2
- Role.where(name: [ Z_ROLENAME_ADMIN, 'Agent', 'Chat']).each { |role|
+ Role.where(name: [ Z_ROLENAME_ADMIN, 'Agent', 'Chat', 'CTI']).each { |role|
role_ids.push role.id
}
Group.all().each { |group|
diff --git a/app/models/channel/email_parser.rb b/app/models/channel/email_parser.rb
index 797b06cb7..3ed371242 100644
--- a/app/models/channel/email_parser.rb
+++ b/app/models/channel/email_parser.rb
@@ -490,6 +490,7 @@ retrns
Observer::Transaction.commit
# run postmaster post filter
+ filters = {}
Setting.where(area: 'Postmaster::PostFilter').order(:name).each {|setting|
filters[setting.name] = Kernel.const_get(Setting.get(setting.name))
}
diff --git a/app/models/cti/log.rb b/app/models/cti/log.rb
new file mode 100644
index 000000000..bf97b138a
--- /dev/null
+++ b/app/models/cti/log.rb
@@ -0,0 +1,78 @@
+module Cti
+ class Log < ApplicationModel
+ self.table_name = 'cti_logs'
+
+ after_create :push_event, :push_caller_list
+ after_update :push_event, :push_caller_list
+ after_destroy :push_event, :push_caller_list
+
+=begin
+
+ Cti::Log.create(
+ direction: 'in',
+ from: '007',
+ from_comment: '',
+ to: '008',
+ to_comment: '',
+ call_id: '1',
+ comment: '',
+ state: 'newCall',
+ )
+
+ Cti::Log.create(
+ direction: 'in',
+ from: '007',
+ from_comment: '',
+ to: '008',
+ to_comment: '',
+ call_id: '2',
+ comment: '',
+ state: 'answer',
+ )
+
+ Cti::Log.create(
+ direction: 'in',
+ from: '009',
+ from_comment: '',
+ to: '010',
+ to_comment: '',
+ call_id: '3',
+ comment: '',
+ state: 'hangup',
+ )
+
+=end
+
+ def push_event
+ users = User.of_role('CTI')
+ users.each {|user|
+
+ # send notify about event
+ Sessions.send_to(
+ user.id,
+ {
+ event: 'cti_event',
+ data: self,
+ },
+ )
+ }
+ end
+
+ def push_caller_list
+ list = Cti::Log.order('created_at DESC').limit(60)
+
+ users = User.of_role('CTI')
+ users.each {|user|
+
+ # send notify on create/update/delete
+ Sessions.send_to(
+ user.id,
+ {
+ event: 'cti_list_push',
+ data: list,
+ },
+ )
+ }
+ end
+ end
+end
diff --git a/app/models/observer/ticket/article/signature_detection.rb b/app/models/observer/ticket/article/signature_detection.rb
deleted file mode 100644
index a3b520215..000000000
--- a/app/models/observer/ticket/article/signature_detection.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
-
-require 'signature_detection'
-
-class Observer::Ticket::Article::SignatureDetection < ActiveRecord::Observer
- observe 'ticket::_article'
-
- def before_create(record)
-
- # return if we run import mode
- return if Setting.get('import_mode')
-
- # if sender is not customer, do not change anything
- sender = Ticket::Article::Sender.lookup(id: record.sender_id)
- return if !sender
- return if sender['name'] != 'Customer'
-
- # set email attributes
- type = Ticket::Article::Type.lookup(id: record.type_id)
- return if type['name'] != 'email'
-
- # add queue job to update current signature of user id
- Delayed::Job.enqueue(Observer::Ticket::Article::SignatureDetection::BackgroundJob.new(record.created_by_id))
-
- # user
- user = User.lookup(id: record.created_by_id)
- return if !user
- return if !user.preferences
- return if !user.preferences[:signature_detection]
-
- record.preferences[:signature_detection] = SignatureDetection.find_signature_line(user.preferences[:signature_detection], record.body)
- end
-end
diff --git a/app/models/observer/ticket/article/signature_detection/background_job.rb b/app/models/observer/ticket/article/signature_detection/background_job.rb
deleted file mode 100644
index 83eecbf23..000000000
--- a/app/models/observer/ticket/article/signature_detection/background_job.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class Observer::Ticket::Article::SignatureDetection::BackgroundJob
- def initialize(id)
- @user_id = id
- end
-
- def perform
- SignatureDetection.rebuild_user(@user_id)
- end
-end
diff --git a/app/models/observer/transaction.rb b/app/models/observer/transaction.rb
index 5644a23c8..8f403d38e 100644
--- a/app/models/observer/transaction.rb
+++ b/app/models/observer/transaction.rb
@@ -1,7 +1,7 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class Observer::Transaction < ActiveRecord::Observer
- observe :ticket, 'ticket::_article'
+ observe :ticket, 'ticket::_article', :user, :organization
def self.commit(params = {})
@@ -28,10 +28,10 @@ class Observer::Transaction < ActiveRecord::Observer
# get uniq objects
list_objects = get_uniq_changes(list)
- list_objects.each {|_id, item|
-
- # send background job
- Delayed::Job.enqueue(Transaction::BackgroundJob.new(item, params))
+ list_objects.each {|_object, objects|
+ objects.each {|_id, item|
+ Delayed::Job.enqueue(Transaction::BackgroundJob.new(item, params))
+ }
}
end
@@ -40,33 +40,37 @@ class Observer::Transaction < ActiveRecord::Observer
result = get_uniq_changes(events)
result = {
- 1 => {
- object: 'Ticket',
- type: 'create',
- ticket_id: 123,
- article_id: 123,
- },
- 9 => {
- object: 'Ticket',
- type: 'update',
- ticket_id: 123,
- changes: {
- attribute1: [before, now],
- attribute2: [before, now],
- }
+ 'Ticket' =>
+ 1 => {
+ object: 'Ticket',
+ type: 'create',
+ object_id: 123,
+ article_id: 123,
+ },
+ 9 => {
+ object: 'Ticket',
+ type: 'update',
+ object_id: 123,
+ changes: {
+ attribute1: [before, now],
+ attribute2: [before, now],
+ },
+ },
},
}
result = {
- 9 => {
- object: 'Ticket',
- type: 'update',
- ticket_id: 123,
- article_id: 123,
- changes: {
- attribute1: [before, now],
- attribute2: [before, now],
- }
+ 'Ticket' =>
+ 9 => {
+ object: 'Ticket',
+ type: 'update',
+ object_id: 123,
+ article_id: 123,
+ changes: {
+ attribute1: [before, now],
+ attribute2: [before, now],
+ },
+ },
},
}
@@ -76,57 +80,59 @@ class Observer::Transaction < ActiveRecord::Observer
list_objects = {}
events.each { |event|
- # get current state of objects
- if event[:name] == 'Ticket::Article'
+ # simulate article create as ticket update
+ article = nil
+ if event[:object] == 'Ticket::Article'
article = Ticket::Article.lookup(id: event[:id])
-
- # next if article is already deleted
next if !article
+ next if event[:type] == 'update'
- ticket = article.ticket
- if !list_objects[ticket.id]
- list_objects[ticket.id] = {}
+ # set new event infos
+ ticket = Ticket.lookup(id: article.ticket_id)
+ event[:object] = 'Ticket'
+ event[:id] = ticket.id
+ event[:type] = 'update'
+ event[:changes] = nil
+ end
+
+ # get current state of objects
+ object = Kernel.const_get(event[:object]).lookup(id: event[:id])
+
+ # next if object is already deleted
+ next if !object
+
+ if !list_objects[event[:object]]
+ list_objects[event[:object]] = {}
+ end
+ if !list_objects[event[:object]][object.id]
+ list_objects[event[:object]][object.id] = {}
+ end
+ store = list_objects[event[:object]][object.id]
+ store[:object] = event[:object]
+ store[:object_id] = object.id
+
+ if !store[:type] || store[:type] == 'update'
+ store[:type] = event[:type]
+ end
+
+ # merge changes
+ if event[:changes]
+ if !store[:changes]
+ store[:changes] = event[:changes]
+ else
+ event[:changes].each {|key, value|
+ if !store[:changes][key]
+ store[:changes][key] = value
+ else
+ store[:changes][key][1] = value[1]
+ end
+ }
end
- list_objects[ticket.id][:object] = 'Ticket'
- list_objects[ticket.id][:article_id] = article.id
- list_objects[ticket.id][:ticket_id] = ticket.id
+ end
- if !list_objects[ticket.id][:type]
- list_objects[ticket.id][:type] = 'update'
- end
-
- elsif event[:name] == 'Ticket'
- ticket = Ticket.lookup(id: event[:id])
-
- # next if ticket is already deleted
- next if !ticket
-
- if !list_objects[ticket.id]
- list_objects[ticket.id] = {}
- end
- list_objects[ticket.id][:object] = 'Ticket'
- list_objects[ticket.id][:ticket_id] = ticket.id
-
- if !list_objects[ticket.id][:type] || list_objects[ticket.id][:type] == 'update'
- list_objects[ticket.id][:type] = event[:type]
- end
-
- # merge changes
- if event[:changes]
- if !list_objects[ticket.id][:changes]
- list_objects[ticket.id][:changes] = event[:changes]
- else
- event[:changes].each {|key, value|
- if !list_objects[ticket.id][:changes][key]
- list_objects[ticket.id][:changes][key] = value
- else
- list_objects[ticket.id][:changes][key][1] = value[1]
- end
- }
- end
- end
- else
- raise "unknown object for integration #{event[:name]}"
+ # remember article id if exists
+ if article
+ store[:article_id] = article.id
end
}
list_objects
@@ -138,7 +144,7 @@ class Observer::Transaction < ActiveRecord::Observer
return if Setting.get('import_mode')
e = {
- name: record.class.name,
+ object: record.class.name,
type: 'create',
data: record,
id: record.id,
@@ -151,9 +157,6 @@ class Observer::Transaction < ActiveRecord::Observer
# return if we run import mode
return if Setting.get('import_mode')
- # ignore updates on articles / we just want send integrations on ticket updates
- return if record.class.name == 'Ticket::Article'
-
# ignore certain attributes
real_changes = {}
record.changes.each {|key, value|
@@ -173,7 +176,7 @@ class Observer::Transaction < ActiveRecord::Observer
return if real_changes.empty?
e = {
- name: record.class.name,
+ object: record.class.name,
type: 'update',
data: record,
changes: real_changes,
diff --git a/app/models/ticket.rb b/app/models/ticket.rb
index 05530bdf8..30a38888c 100644
--- a/app/models/ticket.rb
+++ b/app/models/ticket.rb
@@ -183,7 +183,7 @@ returns
Transaction::BackgroundJob.run(
object: 'Ticket',
type: 'reminder_reached',
- ticket_id: ticket.id,
+ object_id: ticket.id,
article_id: ticket.articles.last.id,
)
@@ -223,7 +223,7 @@ returns
Transaction::BackgroundJob.run(
object: 'Ticket',
type: 'escalation',
- ticket_id: ticket.id,
+ object_id: ticket.id,
article_id: ticket.articles.last.id,
)
result.push ticket
@@ -234,7 +234,7 @@ returns
Transaction::BackgroundJob.run(
object: 'Ticket',
type: 'escalation_warning',
- ticket_id: ticket.id,
+ object_id: ticket.id,
article_id: ticket.articles.last.id,
)
result.push ticket
diff --git a/app/models/transaction/notification.rb b/app/models/transaction/notification.rb
index b28d06b55..5c9cca837 100644
--- a/app/models/transaction/notification.rb
+++ b/app/models/transaction/notification.rb
@@ -6,7 +6,7 @@ class Transaction::Notification
{
object: 'Ticket',
type: 'update',
- ticket_id: 123,
+ object_id: 123,
via_web: true,
changes: {
'attribute1' => [before, now],
@@ -21,9 +21,15 @@ class Transaction::Notification
end
def perform
+
+ # return if we run import mode
+ return if Setting.get('import_mode')
+
+ return if @item[:object] != 'Ticket'
+
return if @params[:disable_notification]
- ticket = Ticket.find(@item[:ticket_id])
+ ticket = Ticket.find(@item[:object_id])
if @item[:article_id]
article = Ticket::Article.find(@item[:article_id])
end
diff --git a/app/models/transaction/signature_detection.rb b/app/models/transaction/signature_detection.rb
new file mode 100644
index 000000000..3c14b74e5
--- /dev/null
+++ b/app/models/transaction/signature_detection.rb
@@ -0,0 +1,58 @@
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+require 'signature_detection'
+
+class Transaction::SignatureDetection
+
+=begin
+ {
+ object: 'Ticket',
+ type: 'update',
+ object_id: 123,
+ via_web: true,
+ changes: {
+ 'attribute1' => [before, now],
+ 'attribute2' => [before, now],
+ }
+ },
+=end
+
+ def initialize(item, params = {})
+ @item = item
+ @params = params
+ end
+
+ def perform
+
+ # return if we run import mode
+ return if Setting.get('import_mode')
+
+ return if @item[:type] != 'create'
+ return if @item[:object] != 'Ticket'
+
+ ticket = Ticket.lookup(id: @item[:object_id])
+ return if !ticket
+ article = ticket.articles.first
+ return if !article
+
+ # if sender is not customer, do not change anything
+ sender = Ticket::Article::Sender.lookup(id: article.sender_id)
+ return if !sender
+ return if sender['name'] != 'Customer'
+
+ # set email attributes
+ type = Ticket::Article::Type.lookup(id: article.type_id)
+ return if type['name'] != 'email'
+
+ # add queue job to update current signature of user id
+ SignatureDetection.rebuild_user(article.created_by_id)
+
+ # user
+ user = User.lookup(id: article.created_by_id)
+ return if !user
+ return if !user.preferences
+ return if !user.preferences[:signature_detection]
+ article.preferences[:signature_detection] = SignatureDetection.find_signature_line(user.preferences[:signature_detection], article.body)
+ article.save
+ end
+
+end
diff --git a/app/models/transaction/slack.rb b/app/models/transaction/slack.rb
index 23138b83a..a4c331b00 100644
--- a/app/models/transaction/slack.rb
+++ b/app/models/transaction/slack.rb
@@ -6,14 +6,14 @@ class Transaction::Slack
backend = Transaction::Slack.new(
object: 'Ticket',
type: 'create',
- ticket_id: 1,
+ object_id: 1,
)
backend.perform
{
object: 'Ticket',
type: 'update',
- ticket_id: 123,
+ object_id: 123,
via_web: true,
changes: {
'attribute1' => [before, now],
@@ -27,6 +27,10 @@ backend.perform
end
def perform
+
+ # return if we run import mode
+ return if Setting.get('import_mode')
+
return if @item[:object] != 'Ticket'
return if !Setting.get('slack_integration')
@@ -34,7 +38,7 @@ backend.perform
return if !config
return if !config['items']
- ticket = Ticket.find(@item[:ticket_id])
+ ticket = Ticket.find(@item[:object_id])
if @item[:article_id]
article = Ticket::Article.find(@item[:article_id])
end
diff --git a/config/application.rb b/config/application.rb
index 4228be027..e783fae9a 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -30,7 +30,6 @@ module Zammad
'observer::_ticket::_article::_communicate_email',
'observer::_ticket::_article::_communicate_facebook',
'observer::_ticket::_article::_communicate_twitter',
- 'observer::_ticket::_article::_signature_detection',
'observer::_ticket::_reset_new_state',
'observer::_ticket::_escalation_calculation',
'observer::_ticket::_ref_object_touch',
diff --git a/config/routes/http_log.rb b/config/routes/http_log.rb
index 2dad26384..0258934e0 100644
--- a/config/routes/http_log.rb
+++ b/config/routes/http_log.rb
@@ -2,7 +2,7 @@ Zammad::Application.routes.draw do
api_path = Rails.configuration.api_path
match api_path + '/http_logs', to: 'http_logs#index', via: :get
- match api_path + '/http_logs/:facility', to: 'http_logs#index', via: :get
+ match api_path + '/http_logs/:facility', to: 'http_logs#index', via: :get, constraints: { facility: /.*/ }
match api_path + '/http_logs', to: 'http_logs#create', via: :post
end
diff --git a/config/routes/integration_sipgate.rb b/config/routes/integration_sipgate.rb
new file mode 100644
index 000000000..d569898d2
--- /dev/null
+++ b/config/routes/integration_sipgate.rb
@@ -0,0 +1,7 @@
+Zammad::Application.routes.draw do
+
+ match '/api/v1/cti/log', to: 'integration/sipgate#index', via: :get
+ match '/api/v1/sipgate/in', to: 'integration/sipgate#in', via: :post
+ match '/api/v1/sipgate/out', to: 'integration/sipgate#out', via: :post
+
+end
diff --git a/db/migrate/20160421000001_add_sipgate_integration.rb b/db/migrate/20160421000001_add_sipgate_integration.rb
new file mode 100644
index 000000000..2a046670a
--- /dev/null
+++ b/db/migrate/20160421000001_add_sipgate_integration.rb
@@ -0,0 +1,37 @@
+class AddSipgateIntegration < ActiveRecord::Migration
+ def up
+ Setting.create_if_not_exists(
+ title: 'sipgate.io integration',
+ name: 'sipgate_integration',
+ area: 'Integration::Switch',
+ description: 'Define if sipgate.io (http://www.sipgate.io) is enabled or not.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: true,
+ name: 'sipgate_integration',
+ tag: 'boolean',
+ options: {
+ true => 'yes',
+ false => 'no',
+ },
+ },
+ ],
+ },
+ state: false,
+ preferences: { prio: 1 },
+ frontend: false
+ )
+ Setting.create_if_not_exists(
+ title: 'sipgate.io config',
+ name: 'sipgate_config',
+ area: 'Integration::Sipgate',
+ description: 'Define the sipgate.io config.',
+ options: {},
+ state: {},
+ frontend: false,
+ preferences: { prio: 2 },
+ )
+ end
+end
diff --git a/db/migrate/20160422000001_update_signature_detection.rb b/db/migrate/20160422000001_update_signature_detection.rb
new file mode 100644
index 000000000..33ba97d08
--- /dev/null
+++ b/db/migrate/20160422000001_update_signature_detection.rb
@@ -0,0 +1,13 @@
+class UpdateSignatureDetection < ActiveRecord::Migration
+ def up
+ Setting.create_if_not_exists(
+ title: 'Define transaction backend.',
+ name: '1000_signature_detection',
+ area: 'Transaction::Backend',
+ description: 'Define the transaction backend to detect customers signature in email.',
+ options: {},
+ state: 'Transaction::SignatureDetection',
+ frontend: false
+ )
+ end
+end
diff --git a/db/migrate/20160422000003_create_cti_log.rb b/db/migrate/20160422000003_create_cti_log.rb
new file mode 100644
index 000000000..4f145cd32
--- /dev/null
+++ b/db/migrate/20160422000003_create_cti_log.rb
@@ -0,0 +1,29 @@
+class CreateCtiLog < ActiveRecord::Migration
+ def up
+ create_table :cti_logs do |t|
+ t.string :direction, limit: 20, null: false
+ t.string :state, limit: 20, null: false
+ t.string :from, limit: 100, null: false
+ t.string :from_comment, limit: 250, null: true
+ t.string :to, limit: 100, null: false
+ t.string :to_comment, limit: 250, null: true
+ t.string :call_id, limit: 250, null: false
+ t.string :comment, limit: 500, null: true
+ t.timestamps null: false
+ end
+ add_index :cti_logs, [:call_id], unique: true
+ add_index :cti_logs, [:direction]
+ add_index :cti_logs, [:from]
+
+ # return if it's a new setup
+ return if !Setting.find_by(name: 'system_init_done')
+
+ Role.create_if_not_exists(
+ name: 'CTI',
+ note: 'Access to CTI feature.',
+ updated_by_id: 1,
+ created_by_id: 1
+ )
+
+ end
+end
diff --git a/db/seeds.rb b/db/seeds.rb
index b0b2556e9..00a85194e 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -1778,6 +1778,15 @@ Setting.create_if_not_exists(
state: 'Transaction::Notification',
frontend: false
)
+Setting.create_if_not_exists(
+ title: 'Define transaction backend.',
+ name: '1000_signature_detection',
+ area: 'Transaction::Backend',
+ description: 'Define the transaction backend to detect customers signature in email.',
+ options: {},
+ state: 'Transaction::SignatureDetection',
+ frontend: false
+)
Setting.create_if_not_exists(
title: 'Define transaction backend.',
name: '6000_slack_webhook',
@@ -1822,6 +1831,39 @@ Setting.create_if_not_exists(
frontend: false,
preferences: { prio: 2 },
)
+Setting.create_if_not_exists(
+ title: 'sipgate.io integration',
+ name: 'sipgate_integration',
+ area: 'Integration::Switch',
+ description: 'Define if sipgate.io (http://www.sipgate.io) is enabled or not.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: true,
+ name: 'sipgate_integration',
+ tag: 'boolean',
+ options: {
+ true => 'yes',
+ false => 'no',
+ },
+ },
+ ],
+ },
+ state: false,
+ preferences: { prio: 1 },
+ frontend: false
+)
+Setting.create_if_not_exists(
+ title: 'sipgate.io config',
+ name: 'sipgate_config',
+ area: 'Integration::Sipgate',
+ description: 'Define the sipgate.io config.',
+ options: {},
+ state: {},
+ frontend: false,
+ preferences: { prio: 2 },
+)
signature = Signature.create_if_not_exists(
id: 1,
@@ -1873,6 +1915,13 @@ Role.create_if_not_exists(
updated_by_id: 1,
created_by_id: 1
)
+Role.create_if_not_exists(
+ id: 6,
+ name: 'CTI',
+ note: 'Access to CTI feature.',
+ updated_by_id: 1,
+ created_by_id: 1
+)
Group.create_if_not_exists(
id: 1,
diff --git a/lib/signature_detection.rb b/lib/signature_detection.rb
index 2a21dda43..9b4187a9c 100644
--- a/lib/signature_detection.rb
+++ b/lib/signature_detection.rb
@@ -131,7 +131,6 @@ returns
=end
def self.by_user_id(user_id)
-
type = Ticket::Article::Type.lookup(name: 'email')
sender = Ticket::Article::Sender.lookup(name: 'Customer')
article_bodies = []
@@ -142,7 +141,7 @@ returns
article_bodies.push article.body
}
- find_signature( article_bodies )
+ find_signature(article_bodies)
end
=begin
@@ -167,7 +166,7 @@ returns
=begin
-rebuild signature for user
+rebuild signature detection for user
SignatureDetection.rebuild_user(user_id)
diff --git a/test/integration/sipgate_controller_test.rb b/test/integration/sipgate_controller_test.rb
new file mode 100644
index 000000000..98fb66ecc
--- /dev/null
+++ b/test/integration/sipgate_controller_test.rb
@@ -0,0 +1,333 @@
+# encoding: utf-8
+require 'test_helper'
+require 'rexml/document'
+
+class SipgateControllerTest < ActionDispatch::IntegrationTest
+ setup do
+
+ Cti::Log.destroy_all
+
+ Setting.create_or_update(
+ title: 'sipgate.io integration',
+ name: 'sipgate_integration',
+ area: 'Integration::Switch',
+ description: 'Define if sipgate.io (http://www.sipgate.io) is enabled or not.',
+ options: {
+ form: [
+ {
+ display: '',
+ null: true,
+ name: 'sipgate_integration',
+ tag: 'boolean',
+ options: {
+ true => 'yes',
+ false => 'no',
+ },
+ },
+ ],
+ },
+ state: true,
+ preferences: { prio: 1 },
+ frontend: false
+ )
+ Setting.create_or_update(
+ title: 'sipgate.io config',
+ name: 'sipgate_config',
+ area: 'Integration::Sipgate',
+ description: 'Define the sipgate.io config.',
+ options: {},
+ state: {
+ outbound: {
+ routing_table: [
+ {
+ dest: '41*',
+ caller_id: '41715880339000',
+ },
+ {
+ dest: '491714000000',
+ caller_id: '41715880339000',
+ },
+ ],
+ default_caller_id: '4930777000000',
+ },
+ inbound: {
+ block_caller_ids: [
+ {
+ caller_id: '491715000000',
+ note: 'some note',
+ }
+ ],
+ notify_user_ids: {
+ 2 => true,
+ 4 => false,
+ },
+ }
+ },
+ frontend: false,
+ preferences: { prio: 2 },
+ )
+
+ groups = Group.where(name: 'Users')
+ roles = Role.where(name: 'Agent')
+ agent = User.create_or_update(
+ login: 'cti-agent@example.com',
+ firstname: 'E',
+ lastname: 'S',
+ email: 'cti-agent@example.com',
+ password: 'agentpw',
+ active: true,
+ roles: roles,
+ groups: groups,
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+
+ end
+
+ test 'basic call' do
+
+ # inbound - I
+ params = 'event=newCall&direction=in&from=4912347114711&to=4930600000000&callId=4991155921769858278-1&user%5B%5D=user+1&user%5B%5D=user+2'
+ post '/api/v1/sipgate/in', params
+ assert_response(200)
+ on_hangup = nil
+ on_answer = nil
+ content = @response.body
+ response = REXML::Document.new(content)
+ response.elements.each('Response') do |element|
+ on_hangup = element.attributes['onHangup']
+ on_answer = element.attributes['onAnswer']
+ end
+ assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_hangup)
+ assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_answer)
+
+ # inbound - II - block caller
+ params = 'event=newCall&direction=in&from=491715000000&to=4930600000000&callId=4991155921769858278-2&user%5B%5D=user+1&user%5B%5D=user+2'
+ post '/api/v1/sipgate/in', params
+ assert_response(200)
+ on_hangup = nil
+ on_answer = nil
+ content = @response.body
+ response = REXML::Document.new(content)
+ response.elements.each('Response') do |element|
+ on_hangup = element.attributes['onHangup']
+ on_answer = element.attributes['onAnswer']
+ end
+ assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_hangup)
+ assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_answer)
+ reason = nil
+ response.elements.each('Response/Reject') do |element|
+ reason = element.attributes['reason']
+ end
+ assert_equal('busy', reason)
+
+ # outbound - I - set default_caller_id
+ params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&callId=8621106404543334274-3&user%5B%5D=user+1'
+ post '/api/v1/sipgate/out', params
+ assert_response(200)
+ on_hangup = nil
+ on_answer = nil
+ caller_id = nil
+ number_to_dail = nil
+ content = @response.body
+ response = REXML::Document.new(content)
+ response.elements.each('Response') do |element|
+ on_hangup = element.attributes['onHangup']
+ on_answer = element.attributes['onAnswer']
+ end
+ response.elements.each('Response/Dial') do |element|
+ caller_id = element.attributes['callerId']
+ end
+ response.elements.each('Response/Dial/Number') do |element|
+ number_to_dail = element.text
+ end
+ assert_equal('4930777000000', caller_id)
+ assert_equal('4912347114711', number_to_dail)
+ assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_hangup)
+ assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_answer)
+
+ # outbound - II - set caller_id based on routing_table by explicite number
+ params = 'event=newCall&direction=out&from=4930600000000&to=491714000000&callId=8621106404543334274-4&user%5B%5D=user+1'
+ post '/api/v1/sipgate/out', params
+ assert_response(200)
+ on_hangup = nil
+ on_answer = nil
+ caller_id = nil
+ number_to_dail = nil
+ content = @response.body
+ response = REXML::Document.new(content)
+ response.elements.each('Response') do |element|
+ on_hangup = element.attributes['onHangup']
+ on_answer = element.attributes['onAnswer']
+ end
+ response.elements.each('Response/Dial') do |element|
+ caller_id = element.attributes['callerId']
+ end
+ response.elements.each('Response/Dial/Number') do |element|
+ number_to_dail = element.text
+ end
+ assert_equal('41715880339000', caller_id)
+ assert_equal('491714000000', number_to_dail)
+ assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_hangup)
+ assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_answer)
+
+ # outbound - III - set caller_id based on routing_table by 41*
+ params = 'event=newCall&direction=out&from=4930600000000&to=4147110000000&callId=8621106404543334274-5&user%5B%5D=user+1'
+ post '/api/v1/sipgate/out', params
+ assert_response(200)
+ on_hangup = nil
+ on_answer = nil
+ caller_id = nil
+ number_to_dail = nil
+ content = @response.body
+ response = REXML::Document.new(content)
+ response.elements.each('Response') do |element|
+ on_hangup = element.attributes['onHangup']
+ on_answer = element.attributes['onAnswer']
+ end
+ response.elements.each('Response/Dial') do |element|
+ caller_id = element.attributes['callerId']
+ end
+ response.elements.each('Response/Dial/Number') do |element|
+ number_to_dail = element.text
+ end
+ assert_equal('41715880339000', caller_id)
+ assert_equal('4147110000000', number_to_dail)
+ assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_hangup)
+ assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_answer)
+
+ # no config
+ Setting.set('sipgate_config', {})
+ params = 'event=newCall&direction=in&from=4912347114711&to=4930600000000&callId=4991155921769858278-6&user%5B%5D=user+1&user%5B%5D=user+2'
+ post '/api/v1/sipgate/in', params
+ assert_response(422)
+ error = nil
+ content = @response.body
+ response = REXML::Document.new(content)
+ response.elements.each('Response/Error') do |element|
+ error = element.text
+ end
+ assert_equal('Feature not configured, please contact your admin!', error)
+
+ end
+
+ test 'log call' do
+
+ # outbound - I - new call
+ params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&callId=1234567890-1&user%5B%5D=user+1'
+ post '/api/v1/sipgate/out', params
+ assert_response(200)
+ log = Cti::Log.find_by(call_id: '1234567890-1')
+ assert(log)
+ assert_equal('4930777000000', log.from)
+ assert_equal('4912347114711', log.to)
+ assert_equal('out', log.direction)
+ assert_equal('user 1', log.from_comment)
+ assert_equal(nil, log.comment)
+ assert_equal('newCall', log.state)
+
+ # outbound - I - hangup by agent
+ params = 'event=hangup&direction=out&callId=1234567890-1&cause=cancel'
+ post '/api/v1/sipgate/out', params
+ assert_response(200)
+ log = Cti::Log.find_by(call_id: '1234567890-1')
+ assert(log)
+ assert_equal('4930777000000', log.from)
+ assert_equal('4912347114711', log.to)
+ assert_equal('out', log.direction)
+ assert_equal('user 1', log.from_comment)
+ assert_equal('cancel', log.comment)
+ assert_equal('hangup', log.state)
+
+ # outbound - II - new call
+ params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&callId=1234567890-2&user%5B%5D=user+1'
+ post '/api/v1/sipgate/out', params
+ assert_response(200)
+ log = Cti::Log.find_by(call_id: '1234567890-2')
+ assert(log)
+ assert_equal('4930777000000', log.from)
+ assert_equal('4912347114711', log.to)
+ assert_equal('out', log.direction)
+ assert_equal('user 1', log.from_comment)
+ assert_equal(nil, log.comment)
+ assert_equal('newCall', log.state)
+
+ # outbound - II - answer by customer
+ params = 'event=answer&direction=out&callId=1234567890-2&from=4930600000000&to=4912347114711'
+ post '/api/v1/sipgate/out', params
+ assert_response(200)
+ log = Cti::Log.find_by(call_id: '1234567890-2')
+ assert(log)
+ assert_equal('4930777000000', log.from)
+ assert_equal('4912347114711', log.to)
+ assert_equal('out', log.direction)
+ assert_equal('user 1', log.from_comment)
+ assert_equal(nil, log.comment)
+ assert_equal('answer', log.state)
+
+ # outbound - II - hangup by customer
+ params = 'event=hangup&direction=out&callId=1234567890-2&cause=normalClearing&from=4930600000000&to=4912347114711'
+ post '/api/v1/sipgate/out', params
+ assert_response(200)
+ log = Cti::Log.find_by(call_id: '1234567890-2')
+ assert(log)
+ assert_equal('4930777000000', log.from)
+ assert_equal('4912347114711', log.to)
+ assert_equal('out', log.direction)
+ assert_equal('user 1', log.from_comment)
+ assert_equal('normalClearing', log.comment)
+ assert_equal('hangup', log.state)
+
+ # inbound - I - new call
+ params = 'event=newCall&direction=in&to=4930600000000&from=4912347114711&callId=1234567890-3&user%5B%5D=user+1'
+ post '/api/v1/sipgate/in', params
+ assert_response(200)
+ log = Cti::Log.find_by(call_id: '1234567890-3')
+ assert(log)
+ assert_equal('4930600000000', log.to)
+ assert_equal('4912347114711', log.from)
+ assert_equal('in', log.direction)
+ assert_equal('user 1', log.to_comment)
+ assert_equal(nil, log.comment)
+ assert_equal('newCall', log.state)
+
+ # inbound - I - answer by customer
+ params = 'event=answer&direction=in&callId=1234567890-3&to=4930600000000&from=4912347114711'
+ post '/api/v1/sipgate/in', params
+ assert_response(200)
+ log = Cti::Log.find_by(call_id: '1234567890-3')
+ assert(log)
+ assert_equal('4930600000000', log.to)
+ assert_equal('4912347114711', log.from)
+ assert_equal('in', log.direction)
+ assert_equal('user 1', log.to_comment)
+ assert_equal(nil, log.comment)
+ assert_equal('answer', log.state)
+
+ # inbound - I - hangup by customer
+ params = 'event=hangup&direction=in&callId=1234567890-3&cause=normalClearing&to=4930600000000&from=4912347114711'
+ post '/api/v1/sipgate/in', params
+ assert_response(200)
+ log = Cti::Log.find_by(call_id: '1234567890-3')
+ assert(log)
+ assert_equal('4930600000000', log.to)
+ assert_equal('4912347114711', log.from)
+ assert_equal('in', log.direction)
+ assert_equal('user 1', log.to_comment)
+ assert_equal('normalClearing', log.comment)
+ assert_equal('hangup', log.state)
+
+ get '/api/v1/cti/log'
+ assert_response(401)
+
+ headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }
+ credentials = ActionController::HttpAuthentication::Basic.encode_credentials('cti-agent@example.com', 'agentpw')
+ get '/api/v1/cti/log', {}, headers.merge('Authorization' => credentials)
+ assert_response(200)
+ result = JSON.parse(@response.body)
+ assert_equal(result.class, Array)
+ assert_equal(3, result.count)
+
+ end
+
+end
diff --git a/test/integration/zendesk_import_test.rb b/test/integration/zendesk_import_test.rb
index a68fa24c5..1fab59be1 100644
--- a/test/integration/zendesk_import_test.rb
+++ b/test/integration/zendesk_import_test.rb
@@ -46,7 +46,7 @@ class ZendeskImportTest < ActiveSupport::TestCase
test 'check counts' do
assert_equal( 143, User.count, 'users' )
assert_equal( 3, Group.count, 'groups' )
- assert_equal( 5, Role.count, 'roles' )
+ assert_equal( 6, Role.count, 'roles' )
assert_equal( 2, Organization.count, 'organizations' )
assert_equal( 143, Ticket.count, 'tickets' )
assert_equal( 151, Ticket::Article.count, 'ticket articles' )
diff --git a/test/unit/email_signatur_detection_test.rb b/test/unit/email_signatur_detection_test.rb
index 9d02fa785..c8844760c 100644
--- a/test/unit/email_signatur_detection_test.rb
+++ b/test/unit/email_signatur_detection_test.rb
@@ -73,6 +73,7 @@ class EmailSignaturDetectionTest < ActiveSupport::TestCase
ticket1, article1, user1, mail = Channel::EmailParser.new.process({}, raw_email)
assert(ticket1)
assert(article1)
+ Delayed::Worker.new.work_off
# process email II
file = File.open("#{Rails.root}/test/fixtures/email_signature_detection/client_a_2.txt", 'rb')
@@ -80,8 +81,6 @@ class EmailSignaturDetectionTest < ActiveSupport::TestCase
ticket2, article2, user2, mail = Channel::EmailParser.new.process({}, raw_email)
assert(ticket2)
assert(article2)
-
- # process background jobs (user signature detection & article signature detection)
Delayed::Worker.new.work_off
# check if user2 has a signature_detection value
@@ -94,8 +93,10 @@ class EmailSignaturDetectionTest < ActiveSupport::TestCase
ticket3, article3, user3, mail = Channel::EmailParser.new.process({}, raw_email)
assert(ticket3)
assert(article3)
+ Delayed::Worker.new.work_off
# check if article3 has a signature_detection value
+ article3 = Ticket::Article.find(article3.id)
assert_equal(article3.preferences[:signature_detection], 6)
# relbuild all
diff --git a/test/unit/ticket_notification_test.rb b/test/unit/ticket_notification_test.rb
index 4ae1a26a4..4de9a0d46 100644
--- a/test/unit/ticket_notification_test.rb
+++ b/test/unit/ticket_notification_test.rb
@@ -904,11 +904,11 @@ class TicketNotificationTest < ActiveSupport::TestCase
list = EventBuffer.list('transaction')
list_objects = Observer::Transaction.get_uniq_changes(list)
- assert_equal('some notification event test 1', list_objects[ticket1.id][:changes]['title'][0])
- assert_equal('some notification event test 1 - #2', list_objects[ticket1.id][:changes]['title'][1])
- assert_not(list_objects[ticket1.id][:changes]['priority'])
- assert_equal(2, list_objects[ticket1.id][:changes]['priority_id'][0])
- assert_equal(3, list_objects[ticket1.id][:changes]['priority_id'][1])
+ assert_equal('some notification event test 1', list_objects['Ticket'][ticket1.id][:changes]['title'][0])
+ assert_equal('some notification event test 1 - #2', list_objects['Ticket'][ticket1.id][:changes]['title'][1])
+ assert_not(list_objects['Ticket'][ticket1.id][:changes]['priority'])
+ assert_equal(2, list_objects['Ticket'][ticket1.id][:changes]['priority_id'][0])
+ assert_equal(3, list_objects['Ticket'][ticket1.id][:changes]['priority_id'][1])
# update ticket attributes
ticket1.title = "#{ticket1.title} - #3"
@@ -918,11 +918,11 @@ class TicketNotificationTest < ActiveSupport::TestCase
list = EventBuffer.list('transaction')
list_objects = Observer::Transaction.get_uniq_changes(list)
- assert_equal('some notification event test 1', list_objects[ticket1.id][:changes]['title'][0])
- assert_equal('some notification event test 1 - #2 - #3', list_objects[ticket1.id][:changes]['title'][1])
- assert_not(list_objects[ticket1.id][:changes]['priority'])
- assert_equal(2, list_objects[ticket1.id][:changes]['priority_id'][0])
- assert_equal(1, list_objects[ticket1.id][:changes]['priority_id'][1])
+ assert_equal('some notification event test 1', list_objects['Ticket'][ticket1.id][:changes]['title'][0])
+ assert_equal('some notification event test 1 - #2 - #3', list_objects['Ticket'][ticket1.id][:changes]['title'][1])
+ assert_not(list_objects['Ticket'][ticket1.id][:changes]['priority'])
+ assert_equal(2, list_objects['Ticket'][ticket1.id][:changes]['priority_id'][0])
+ assert_equal(1, list_objects['Ticket'][ticket1.id][:changes]['priority_id'][1])
end