Merge branch 'develop' of github.com:martini/zammad into develop
This commit is contained in:
commit
db863f7b1b
44 changed files with 1992 additions and 407 deletions
|
@ -286,16 +286,24 @@ class App.ControllerTabs extends App.Controller
|
||||||
tabs: @tabs
|
tabs: @tabs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# insert content
|
||||||
for tab in @tabs
|
for tab in @tabs
|
||||||
@el.find('.tab-content').append('<div class="tab-pane" id="' + tab.target + '"></div>')
|
@el.find('.tab-content').append("<div class=\"tab-pane\" id=\"#{tab.target}\"></div>")
|
||||||
if tab.controller
|
if tab.controller
|
||||||
params = tab.params || {}
|
params = tab.params || {}
|
||||||
params.el = @el.find( '#' + tab.target )
|
params.name = tab.name
|
||||||
|
params.target = tab.target
|
||||||
|
params.el = @el.find( "##{tab.target}" )
|
||||||
new tab.controller( params )
|
new tab.controller( params )
|
||||||
|
|
||||||
|
# check if tabs need to be hidden
|
||||||
|
if @tabs.length <= 1
|
||||||
|
@el.find('.nav-tabs').addClass('hide')
|
||||||
|
|
||||||
|
# set last or first tab to active
|
||||||
@lastActiveTab = @Config.get('lastTab')
|
@lastActiveTab = @Config.get('lastTab')
|
||||||
if @lastActiveTab && @el.find('.nav-tabs li a[href="' + @lastActiveTab + '"]')[0]
|
if @lastActiveTab && @el.find(".nav-tabs li a[href=#{@lastActiveTab}]")[0]
|
||||||
@el.find('.nav-tabs li a[href="' + @lastActiveTab + '"]').tab('show')
|
@el.find(".nav-tabs li a[href=#{@lastActiveTab}]").tab('show')
|
||||||
else
|
else
|
||||||
@el.find('.nav-tabs li:first a').tab('show')
|
@el.find('.nav-tabs li:first a').tab('show')
|
||||||
|
|
||||||
|
|
|
@ -26,21 +26,30 @@ class App.SettingsArea extends App.Controller
|
||||||
area: @area
|
area: @area
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# filter online service settings
|
||||||
|
if App.Config.get('system_online_service')
|
||||||
|
settings = _.filter(settings, (setting) ->
|
||||||
|
return if setting.online_service
|
||||||
|
return if setting.preferences && setting.preferences.online_service_disable
|
||||||
|
setting
|
||||||
|
)
|
||||||
|
return if _.isEmpty(settings)
|
||||||
|
|
||||||
# sort by prio
|
# sort by prio
|
||||||
settings = _.sortBy( settings, (setting) ->
|
settings = _.sortBy( settings, (setting) ->
|
||||||
return if !setting.preferences
|
return if !setting.preferences
|
||||||
setting.preferences.prio
|
setting.preferences.prio
|
||||||
)
|
)
|
||||||
|
|
||||||
html = $('<div></div>')
|
elements = []
|
||||||
for setting in settings
|
for setting in settings
|
||||||
if setting.name is 'product_logo'
|
if setting.name is 'product_logo'
|
||||||
item = new App.SettingsAreaLogo( setting: setting )
|
item = new App.SettingsAreaLogo( setting: setting )
|
||||||
else
|
else
|
||||||
item = new App.SettingsAreaItem( setting: setting )
|
item = new App.SettingsAreaItem( setting: setting )
|
||||||
html.append( item.el )
|
elements.push item.el
|
||||||
|
|
||||||
@html html
|
@html elements
|
||||||
|
|
||||||
class App.SettingsAreaItem extends App.Controller
|
class App.SettingsAreaItem extends App.Controller
|
||||||
events:
|
events:
|
||||||
|
@ -67,13 +76,13 @@ class App.SettingsAreaItem extends App.Controller
|
||||||
|
|
||||||
# item
|
# item
|
||||||
@html App.view('settings/item')(
|
@html App.view('settings/item')(
|
||||||
setting: @setting,
|
setting: @setting
|
||||||
)
|
)
|
||||||
|
|
||||||
new App.ControllerForm(
|
new App.ControllerForm(
|
||||||
el: @el.find('.form-item'),
|
el: @el.find('.form-item'),
|
||||||
model: { configure_attributes: @configure_attributes, className: '' },
|
model: { configure_attributes: @configure_attributes, className: '' }
|
||||||
autofocus: false,
|
autofocus: false
|
||||||
)
|
)
|
||||||
|
|
||||||
update: (e) =>
|
update: (e) =>
|
||||||
|
@ -103,7 +112,6 @@ class App.SettingsAreaItem extends App.Controller
|
||||||
@setting.save(
|
@setting.save(
|
||||||
done: =>
|
done: =>
|
||||||
ui.formEnable(e)
|
ui.formEnable(e)
|
||||||
|
|
||||||
App.Event.trigger 'notify', {
|
App.Event.trigger 'notify', {
|
||||||
type: 'success'
|
type: 'success'
|
||||||
msg: App.i18n.translateContent('Update successful!')
|
msg: App.i18n.translateContent('Update successful!')
|
||||||
|
@ -112,7 +120,6 @@ class App.SettingsAreaItem extends App.Controller
|
||||||
|
|
||||||
# rerender ui || get new collections and session data
|
# rerender ui || get new collections and session data
|
||||||
if @setting.preferences
|
if @setting.preferences
|
||||||
|
|
||||||
if @setting.preferences.render
|
if @setting.preferences.render
|
||||||
ui.render()
|
ui.render()
|
||||||
App.Event.trigger( 'ui:rerender' )
|
App.Event.trigger( 'ui:rerender' )
|
||||||
|
@ -121,6 +128,11 @@ class App.SettingsAreaItem extends App.Controller
|
||||||
App.Auth.loginCheck()
|
App.Auth.loginCheck()
|
||||||
fail: =>
|
fail: =>
|
||||||
ui.formEnable(e)
|
ui.formEnable(e)
|
||||||
|
App.Event.trigger 'notify', {
|
||||||
|
type: 'error'
|
||||||
|
msg: App.i18n.translateContent('Can\'t update item!')
|
||||||
|
timeout: 2000
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
class App.SettingsAreaLogo extends App.Controller
|
class App.SettingsAreaLogo extends App.Controller
|
||||||
|
|
|
@ -5,7 +5,7 @@ class Branding extends App.ControllerTabs
|
||||||
return if !@authenticate()
|
return if !@authenticate()
|
||||||
@title 'Branding', true
|
@title 'Branding', true
|
||||||
@tabs = [
|
@tabs = [
|
||||||
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'System::Branding' } },
|
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'System::Branding' } }
|
||||||
]
|
]
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
|
@ -15,12 +15,13 @@ class System extends App.ControllerTabs
|
||||||
super
|
super
|
||||||
return if !@authenticate()
|
return if !@authenticate()
|
||||||
@title 'System', true
|
@title 'System', true
|
||||||
@tabs = [
|
@tabs = []
|
||||||
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'System::Base' } },
|
if !App.Config.get('system_online_service')
|
||||||
{ name: 'Storage', 'target': 'storage', controller: App.SettingsArea, params: { area: 'System::Storage' } },
|
@tabs.push { name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'System::Base' } }
|
||||||
{ name: 'Geo Services', 'target': 'geo', controller: App.SettingsArea, params: { area: 'System::Geo' } },
|
@tabs.push { name: 'Services', 'target': 'services', controller: App.SettingsArea, params: { area: 'System::Services' } }
|
||||||
{ name: 'Frontend', 'target': 'ui', controller: App.SettingsArea, params: { area: 'System::UI' } },
|
if !App.Config.get('system_online_service')
|
||||||
]
|
@tabs.push { name: 'Storage', 'target': 'storage', controller: App.SettingsArea, params: { area: 'System::Storage' } }
|
||||||
|
@tabs.push { name: 'Frontend', 'target': 'ui', controller: App.SettingsArea, params: { area: 'System::UI' } }
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
class Security extends App.ControllerTabs
|
class Security extends App.ControllerTabs
|
||||||
|
@ -30,11 +31,10 @@ class Security extends App.ControllerTabs
|
||||||
return if !@authenticate()
|
return if !@authenticate()
|
||||||
@title 'Security', true
|
@title 'Security', true
|
||||||
@tabs = [
|
@tabs = [
|
||||||
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Security::Base' } },
|
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Security::Base' } }
|
||||||
# { name: 'Authentication', 'target': 'auth', controller: App.SettingsArea, params: { area: 'Security::Authentication' } },
|
# { name: 'Authentication', 'target': 'auth', controller: App.SettingsArea, params: { area: 'Security::Authentication' } }
|
||||||
{ name: 'Password', 'target': 'password', controller: App.SettingsArea, params: { area: 'Security::Password' } },
|
{ name: 'Password', 'target': 'password', controller: App.SettingsArea, params: { area: 'Security::Password' } }
|
||||||
{ name: 'Third-Party Applications', 'target': 'third_party_auth', controller: App.SettingsArea, params: { area: 'Security::ThirdPartyAuthentication' } },
|
{ name: 'Third-Party Applications', 'target': 'third_party_auth', controller: App.SettingsArea, params: { area: 'Security::ThirdPartyAuthentication' } }
|
||||||
# { name: 'Session', 'target': 'session', controller: '' },
|
|
||||||
]
|
]
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
|
@ -45,8 +45,8 @@ class Import extends App.ControllerTabs
|
||||||
return if !@authenticate()
|
return if !@authenticate()
|
||||||
@title 'Import', true
|
@title 'Import', true
|
||||||
@tabs = [
|
@tabs = [
|
||||||
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Import::Base' } },
|
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Import::Base' } }
|
||||||
{ name: 'OTRS', 'target': 'otrs', controller: App.SettingsArea, params: { area: 'Import::OTRS' } },
|
{ name: 'OTRS', 'target': 'otrs', controller: App.SettingsArea, params: { area: 'Import::OTRS' } }
|
||||||
]
|
]
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
|
@ -57,15 +57,14 @@ class Ticket extends App.ControllerTabs
|
||||||
return if !@authenticate()
|
return if !@authenticate()
|
||||||
@title 'Ticket', true
|
@title 'Ticket', true
|
||||||
@tabs = [
|
@tabs = [
|
||||||
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Ticket::Base' } },
|
{ name: 'Base', 'target': 'base', controller: App.SettingsArea, params: { area: 'Ticket::Base' } }
|
||||||
{ name: 'Number', 'target': 'number', controller: App.SettingsArea, params: { area: 'Ticket::Number' } },
|
{ name: 'Number', 'target': 'number', controller: App.SettingsArea, params: { area: 'Ticket::Number' } }
|
||||||
# { name: 'Sender Format', 'target': 'sender-format', controller: App.SettingsArea, params: { area: 'Ticket::SenderFormat' } },
|
|
||||||
]
|
]
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
App.Config.set( 'SettingBranding', { prio: 1200, parent: '#settings', name: 'Branding', target: '#settings/branding', controller: Branding, role: ['Admin'] }, 'NavBarAdmin' )
|
App.Config.set( 'SettingBranding', { prio: 1200, parent: '#settings', name: 'Branding', target: '#settings/branding', controller: Branding, role: ['Admin'] }, 'NavBarAdmin' )
|
||||||
App.Config.set( 'SettingSystem', { prio: 1400, parent: '#settings', name: 'System', target: '#settings/system', controller: System, role: ['Admin'] }, 'NavBarAdmin' )
|
App.Config.set( 'SettingSystem', { prio: 1400, parent: '#settings', name: 'System', target: '#settings/system', controller: System, role: ['Admin'] }, 'NavBarAdmin' )
|
||||||
App.Config.set( 'SettingSecurity', { prio: 1500, parent: '#settings', name: 'Security', target: '#settings/security', controller: Security, role: ['Admin'] }, 'NavBarAdmin' )
|
App.Config.set( 'SettingSecurity', { prio: 1600, parent: '#settings', name: 'Security', target: '#settings/security', controller: Security, role: ['Admin'] }, 'NavBarAdmin' )
|
||||||
App.Config.set( 'SettingTicket', { prio: 1600, parent: '#settings', name: 'Ticket', target: '#settings/ticket', controller: Ticket, role: ['Admin'] }, 'NavBarAdmin' )
|
App.Config.set( 'SettingTicket', { prio: 1700, parent: '#settings', name: 'Ticket', target: '#settings/ticket', controller: Ticket, role: ['Admin'] }, 'NavBarAdmin' )
|
||||||
App.Config.set( 'SettingImport', { prio: 1700, parent: '#settings', name: 'Import', target: '#settings/import', controller: Import, role: ['Admin'] }, 'NavBarAdmin' )
|
App.Config.set( 'SettingImport', { prio: 1800, parent: '#settings', name: 'Import', target: '#settings/import', controller: Import, role: ['Admin'] }, 'NavBarAdmin' )
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
class FFlt35
|
class FFlt35
|
||||||
constructor: ->
|
constructor: ->
|
||||||
data = App.Browser.detection()
|
data = App.Browser.detection()
|
||||||
if data.browser is 'Firefox' && data.version && data.version < 35
|
if data.browser.name is 'Firefox' && data.browser.major && data.browser.major < 35
|
||||||
|
|
||||||
# for firefox lower 35 we need to set a class to hide own dropdown images
|
# for firefox lower 35 we need to set a class to hide own dropdown images
|
||||||
# whole file can be removed after dropping firefox 34 and lower support
|
# whole file can be removed after dropping firefox 34 and lower support
|
||||||
|
|
|
@ -45,16 +45,15 @@ class _Singleton
|
||||||
# detect color support
|
# detect color support
|
||||||
@colorSupport = false
|
@colorSupport = false
|
||||||
data = App.Browser.detection()
|
data = App.Browser.detection()
|
||||||
if data
|
if data.browser
|
||||||
if data.browser is 'Chrome'
|
if data.browser.name is 'Chrome'
|
||||||
@colorSupport = true
|
@colorSupport = true
|
||||||
else if data.browser is 'Firefox'
|
else if data.browser.anem is 'Firefox'
|
||||||
if data.version >= 31.0
|
if data.browser.major >= 31.0
|
||||||
@colorSupport = true
|
@colorSupport = true
|
||||||
else if data.browser is 'Safari'
|
else if data.browser.name is 'Safari'
|
||||||
@colorSupport = true
|
@colorSupport = true
|
||||||
|
|
||||||
|
|
||||||
configReady: ->
|
configReady: ->
|
||||||
for type, value of @currentConfig
|
for type, value of @currentConfig
|
||||||
if type is 'module' || type is 'content'
|
if type is 'module' || type is 'content'
|
||||||
|
|
|
@ -17,12 +17,14 @@ get used browser
|
||||||
}
|
}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
class App.Browser
|
class App.Browser
|
||||||
@detection: ->
|
@detection: ->
|
||||||
|
parser = new UAParser()
|
||||||
data =
|
data =
|
||||||
browser: @searchString(@dataBrowser) or "An unknown browser"
|
browser: parser.getBrowser()
|
||||||
version: @searchVersion(navigator.userAgent) or @searchVersion(navigator.appVersion) or "an unknown version"
|
device: parser.getDevice()
|
||||||
os: @searchString(@dataOS) or "an unknown os"
|
os: parser.getOS()
|
||||||
|
|
||||||
@check: ->
|
@check: ->
|
||||||
data = @detection()
|
data = @detection()
|
||||||
|
@ -36,114 +38,21 @@ class App.Browser
|
||||||
Opera: 22
|
Opera: 22
|
||||||
|
|
||||||
# disable id older
|
# disable id older
|
||||||
if data.browser && data.version
|
if data.browser
|
||||||
if map[data.browser] && data.version < map[data.browser]
|
if map[data.browser.name] && data.browser.major < map[data.browser.name]
|
||||||
@message(data, data.browser, map[data.browser])
|
@message(data, map[data.browser.name])
|
||||||
console.log('Browser not supported')
|
console.log('Browser not supported')
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# allow browser
|
# allow browser
|
||||||
return true
|
true
|
||||||
|
|
||||||
@message: (data, browser, version) ->
|
@message: (data, version) ->
|
||||||
new App.ControllerModal(
|
new App.ControllerModal(
|
||||||
head: 'Browser too old!'
|
head: 'Browser too old!'
|
||||||
message: "Your Browser is not supported (#{data.browser} #{data.version} #{data.OS}). Please use a newer one (e. g. #{browser} #{version} or higher)."
|
message: "Your Browser is not supported (#{data.browser.name} #{data.browser.major} on #{data.os.name}). Please use a newer one (e. g. #{data.browser.name} #{version} or higher)."
|
||||||
close: false
|
close: false
|
||||||
backdrop: false
|
backdrop: false
|
||||||
keyboard: false
|
keyboard: false
|
||||||
shown: true
|
shown: true
|
||||||
)
|
)
|
||||||
|
|
||||||
@searchString: (data) ->
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
while i < data.length
|
|
||||||
dataString = data[i].string
|
|
||||||
dataProp = data[i].prop
|
|
||||||
@versionSearchString = data[i].versionSearch or data[i].identity
|
|
||||||
if dataString
|
|
||||||
return data[i].identity unless dataString.indexOf(data[i].subString) is -1
|
|
||||||
else return data[i].identity if dataProp
|
|
||||||
i++
|
|
||||||
|
|
||||||
@searchVersion: (dataString) ->
|
|
||||||
index = dataString.indexOf(@versionSearchString)
|
|
||||||
return if index is -1
|
|
||||||
parseFloat dataString.substring(index + @versionSearchString.length + 1)
|
|
||||||
|
|
||||||
@dataBrowser: [
|
|
||||||
string: navigator.userAgent
|
|
||||||
subString: "Chrome"
|
|
||||||
identity: "Chrome"
|
|
||||||
,
|
|
||||||
string: navigator.userAgent
|
|
||||||
subString: "OmniWeb"
|
|
||||||
versionSearch: "OmniWeb/"
|
|
||||||
identity: "OmniWeb"
|
|
||||||
,
|
|
||||||
string: navigator.vendor
|
|
||||||
subString: "Apple"
|
|
||||||
identity: "Safari"
|
|
||||||
versionSearch: "Version"
|
|
||||||
,
|
|
||||||
prop: window.opera
|
|
||||||
identity: "Opera"
|
|
||||||
versionSearch: "Version"
|
|
||||||
,
|
|
||||||
string: navigator.vendor
|
|
||||||
subString: "iCab"
|
|
||||||
identity: "iCab"
|
|
||||||
,
|
|
||||||
string: navigator.vendor
|
|
||||||
subString: "KDE"
|
|
||||||
identity: "Konqueror"
|
|
||||||
,
|
|
||||||
string: navigator.userAgent
|
|
||||||
subString: "Firefox"
|
|
||||||
identity: "Firefox"
|
|
||||||
,
|
|
||||||
string: navigator.vendor
|
|
||||||
subString: "Camino"
|
|
||||||
identity: "Camino"
|
|
||||||
,
|
|
||||||
# for newer Netscapes (6+)
|
|
||||||
string: navigator.userAgent
|
|
||||||
subString: "Netscape"
|
|
||||||
identity: "Netscape"
|
|
||||||
,
|
|
||||||
string: navigator.userAgent
|
|
||||||
subString: "MSIE"
|
|
||||||
identity: "Explorer"
|
|
||||||
versionSearch: "MSIE"
|
|
||||||
,
|
|
||||||
string: navigator.userAgent
|
|
||||||
subString: "Gecko"
|
|
||||||
identity: "Mozilla"
|
|
||||||
versionSearch: "rv"
|
|
||||||
,
|
|
||||||
# for older Netscapes (4-)
|
|
||||||
string: navigator.userAgent
|
|
||||||
subString: "Mozilla"
|
|
||||||
identity: "Netscape"
|
|
||||||
versionSearch: "Mozilla"
|
|
||||||
]
|
|
||||||
@dataOS: [
|
|
||||||
string: navigator.platform
|
|
||||||
subString: "Win"
|
|
||||||
identity: "Windows"
|
|
||||||
,
|
|
||||||
string: navigator.platform
|
|
||||||
subString: "Mac"
|
|
||||||
identity: "Mac"
|
|
||||||
,
|
|
||||||
string: navigator.userAgent
|
|
||||||
subString: "iPhone"
|
|
||||||
identity: "iPhone/iPod"
|
|
||||||
,
|
|
||||||
string: navigator.platform
|
|
||||||
subString: "Linux"
|
|
||||||
identity: "Linux"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
868
app/assets/javascripts/app/lib/base/ua-parser.js
Normal file
868
app/assets/javascripts/app/lib/base/ua-parser.js
Normal file
|
@ -0,0 +1,868 @@
|
||||||
|
/**
|
||||||
|
* UAParser.js v0.7.8
|
||||||
|
* Lightweight JavaScript-based User-Agent string parser
|
||||||
|
* https://github.com/faisalman/ua-parser-js
|
||||||
|
*
|
||||||
|
* Copyright © 2012-2015 Faisal Salman <fyzlman@gmail.com>
|
||||||
|
* Dual licensed under GPLv2 & MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function (window, undefined) {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// Constants
|
||||||
|
/////////////
|
||||||
|
|
||||||
|
|
||||||
|
var LIBVERSION = '0.7.8',
|
||||||
|
EMPTY = '',
|
||||||
|
UNKNOWN = '?',
|
||||||
|
FUNC_TYPE = 'function',
|
||||||
|
UNDEF_TYPE = 'undefined',
|
||||||
|
OBJ_TYPE = 'object',
|
||||||
|
STR_TYPE = 'string',
|
||||||
|
MAJOR = 'major', // deprecated
|
||||||
|
MODEL = 'model',
|
||||||
|
NAME = 'name',
|
||||||
|
TYPE = 'type',
|
||||||
|
VENDOR = 'vendor',
|
||||||
|
VERSION = 'version',
|
||||||
|
ARCHITECTURE= 'architecture',
|
||||||
|
CONSOLE = 'console',
|
||||||
|
MOBILE = 'mobile',
|
||||||
|
TABLET = 'tablet',
|
||||||
|
SMARTTV = 'smarttv',
|
||||||
|
WEARABLE = 'wearable',
|
||||||
|
EMBEDDED = 'embedded';
|
||||||
|
|
||||||
|
|
||||||
|
///////////
|
||||||
|
// Helper
|
||||||
|
//////////
|
||||||
|
|
||||||
|
|
||||||
|
var util = {
|
||||||
|
extend : function (regexes, extensions) {
|
||||||
|
for (var i in extensions) {
|
||||||
|
if ("browser cpu device engine os".indexOf(i) !== -1 && extensions[i].length % 2 === 0) {
|
||||||
|
regexes[i] = extensions[i].concat(regexes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return regexes;
|
||||||
|
},
|
||||||
|
has : function (str1, str2) {
|
||||||
|
if (typeof str1 === "string") {
|
||||||
|
return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lowerize : function (str) {
|
||||||
|
return str.toLowerCase();
|
||||||
|
},
|
||||||
|
major : function (version) {
|
||||||
|
return typeof(version) === STR_TYPE ? version.split(".")[0] : undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// Map helper
|
||||||
|
//////////////
|
||||||
|
|
||||||
|
|
||||||
|
var mapper = {
|
||||||
|
|
||||||
|
rgx : function () {
|
||||||
|
|
||||||
|
var result, i = 0, j, k, p, q, matches, match, args = arguments;
|
||||||
|
|
||||||
|
// loop through all regexes maps
|
||||||
|
while (i < args.length && !matches) {
|
||||||
|
|
||||||
|
var regex = args[i], // even sequence (0,2,4,..)
|
||||||
|
props = args[i + 1]; // odd sequence (1,3,5,..)
|
||||||
|
|
||||||
|
// construct object barebones
|
||||||
|
if (typeof result === UNDEF_TYPE) {
|
||||||
|
result = {};
|
||||||
|
for (p in props) {
|
||||||
|
q = props[p];
|
||||||
|
if (typeof q === OBJ_TYPE) {
|
||||||
|
result[q[0]] = undefined;
|
||||||
|
} else {
|
||||||
|
result[q] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try matching uastring with regexes
|
||||||
|
j = k = 0;
|
||||||
|
while (j < regex.length && !matches) {
|
||||||
|
matches = regex[j++].exec(this.getUA());
|
||||||
|
if (!!matches) {
|
||||||
|
for (p = 0; p < props.length; p++) {
|
||||||
|
match = matches[++k];
|
||||||
|
q = props[p];
|
||||||
|
// check if given property is actually array
|
||||||
|
if (typeof q === OBJ_TYPE && q.length > 0) {
|
||||||
|
if (q.length == 2) {
|
||||||
|
if (typeof q[1] == FUNC_TYPE) {
|
||||||
|
// assign modified match
|
||||||
|
result[q[0]] = q[1].call(this, match);
|
||||||
|
} else {
|
||||||
|
// assign given value, ignore regex match
|
||||||
|
result[q[0]] = q[1];
|
||||||
|
}
|
||||||
|
} else if (q.length == 3) {
|
||||||
|
// check whether function or regex
|
||||||
|
if (typeof q[1] === FUNC_TYPE && !(q[1].exec && q[1].test)) {
|
||||||
|
// call function (usually string mapper)
|
||||||
|
result[q[0]] = match ? q[1].call(this, match, q[2]) : undefined;
|
||||||
|
} else {
|
||||||
|
// sanitize match using given regex
|
||||||
|
result[q[0]] = match ? match.replace(q[1], q[2]) : undefined;
|
||||||
|
}
|
||||||
|
} else if (q.length == 4) {
|
||||||
|
result[q[0]] = match ? q[3].call(this, match.replace(q[1], q[2])) : undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result[q] = match ? match : undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
str : function (str, map) {
|
||||||
|
|
||||||
|
for (var i in map) {
|
||||||
|
// check if array
|
||||||
|
if (typeof map[i] === OBJ_TYPE && map[i].length > 0) {
|
||||||
|
for (var j = 0; j < map[i].length; j++) {
|
||||||
|
if (util.has(map[i][j], str)) {
|
||||||
|
return (i === UNKNOWN) ? undefined : i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (util.has(map[i], str)) {
|
||||||
|
return (i === UNKNOWN) ? undefined : i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// String map
|
||||||
|
//////////////
|
||||||
|
|
||||||
|
|
||||||
|
var maps = {
|
||||||
|
|
||||||
|
browser : {
|
||||||
|
oldsafari : {
|
||||||
|
version : {
|
||||||
|
'1.0' : '/8',
|
||||||
|
'1.2' : '/1',
|
||||||
|
'1.3' : '/3',
|
||||||
|
'2.0' : '/412',
|
||||||
|
'2.0.2' : '/416',
|
||||||
|
'2.0.3' : '/417',
|
||||||
|
'2.0.4' : '/419',
|
||||||
|
'?' : '/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
device : {
|
||||||
|
amazon : {
|
||||||
|
model : {
|
||||||
|
'Fire Phone' : ['SD', 'KF']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sprint : {
|
||||||
|
model : {
|
||||||
|
'Evo Shift 4G' : '7373KT'
|
||||||
|
},
|
||||||
|
vendor : {
|
||||||
|
'HTC' : 'APA',
|
||||||
|
'Sprint' : 'Sprint'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
os : {
|
||||||
|
windows : {
|
||||||
|
version : {
|
||||||
|
'ME' : '4.90',
|
||||||
|
'NT 3.11' : 'NT3.51',
|
||||||
|
'NT 4.0' : 'NT4.0',
|
||||||
|
'2000' : 'NT 5.0',
|
||||||
|
'XP' : ['NT 5.1', 'NT 5.2'],
|
||||||
|
'Vista' : 'NT 6.0',
|
||||||
|
'7' : 'NT 6.1',
|
||||||
|
'8' : 'NT 6.2',
|
||||||
|
'8.1' : 'NT 6.3',
|
||||||
|
'10' : ['NT 6.4', 'NT 10.0'],
|
||||||
|
'RT' : 'ARM'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//////////////
|
||||||
|
// Regex map
|
||||||
|
/////////////
|
||||||
|
|
||||||
|
|
||||||
|
var regexes = {
|
||||||
|
|
||||||
|
browser : [[
|
||||||
|
|
||||||
|
// Presto based
|
||||||
|
/(opera\smini)\/([\w\.-]+)/i, // Opera Mini
|
||||||
|
/(opera\s[mobiletab]+).+version\/([\w\.-]+)/i, // Opera Mobi/Tablet
|
||||||
|
/(opera).+version\/([\w\.]+)/i, // Opera > 9.80
|
||||||
|
/(opera)[\/\s]+([\w\.]+)/i // Opera < 9.80
|
||||||
|
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/\s(opr)\/([\w\.]+)/i // Opera Webkit
|
||||||
|
], [[NAME, 'Opera'], VERSION], [
|
||||||
|
|
||||||
|
// Mixed
|
||||||
|
/(kindle)\/([\w\.]+)/i, // Kindle
|
||||||
|
/(lunascape|maxthon|netfront|jasmine|blazer)[\/\s]?([\w\.]+)*/i,
|
||||||
|
// Lunascape/Maxthon/Netfront/Jasmine/Blazer
|
||||||
|
|
||||||
|
// Trident based
|
||||||
|
/(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?([\w\.]*)/i,
|
||||||
|
// Avant/IEMobile/SlimBrowser/Baidu
|
||||||
|
/(?:ms|\()(ie)\s([\w\.]+)/i, // Internet Explorer
|
||||||
|
|
||||||
|
// Webkit/KHTML based
|
||||||
|
/(rekonq)\/([\w\.]+)*/i, // Rekonq
|
||||||
|
/(chromium|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi)\/([\w\.-]+)/i
|
||||||
|
// Chromium/Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(trident).+rv[:\s]([\w\.]+).+like\sgecko/i // IE11
|
||||||
|
], [[NAME, 'IE'], VERSION], [
|
||||||
|
|
||||||
|
/(edge)\/((\d+)?[\w\.]+)/i // Microsoft Edge
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(yabrowser)\/([\w\.]+)/i // Yandex
|
||||||
|
], [[NAME, 'Yandex'], VERSION], [
|
||||||
|
|
||||||
|
/(comodo_dragon)\/([\w\.]+)/i // Comodo Dragon
|
||||||
|
], [[NAME, /_/g, ' '], VERSION], [
|
||||||
|
|
||||||
|
/(chrome|omniweb|arora|[tizenoka]{5}\s?browser)\/v?([\w\.]+)/i,
|
||||||
|
// Chrome/OmniWeb/Arora/Tizen/Nokia
|
||||||
|
/(uc\s?browser|qqbrowser)[\/\s]?([\w\.]+)/i
|
||||||
|
// UCBrowser/QQBrowser
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(dolfin)\/([\w\.]+)/i // Dolphin
|
||||||
|
], [[NAME, 'Dolphin'], VERSION], [
|
||||||
|
|
||||||
|
/((?:android.+)crmo|crios)\/([\w\.]+)/i // Chrome for Android/iOS
|
||||||
|
], [[NAME, 'Chrome'], VERSION], [
|
||||||
|
|
||||||
|
/XiaoMi\/MiuiBrowser\/([\w\.]+)/i // MIUI Browser
|
||||||
|
], [VERSION, [NAME, 'MIUI Browser']], [
|
||||||
|
|
||||||
|
/android.+version\/([\w\.]+)\s+(?:mobile\s?safari|safari)/i // Android Browser
|
||||||
|
], [VERSION, [NAME, 'Android Browser']], [
|
||||||
|
|
||||||
|
/FBAV\/([\w\.]+);/i // Facebook App for iOS
|
||||||
|
], [VERSION, [NAME, 'Facebook']], [
|
||||||
|
|
||||||
|
/version\/([\w\.]+).+?mobile\/\w+\s(safari)/i // Mobile Safari
|
||||||
|
], [VERSION, [NAME, 'Mobile Safari']], [
|
||||||
|
|
||||||
|
/version\/([\w\.]+).+?(mobile\s?safari|safari)/i // Safari & Safari Mobile
|
||||||
|
], [VERSION, NAME], [
|
||||||
|
|
||||||
|
/webkit.+?(mobile\s?safari|safari)(\/[\w\.]+)/i // Safari < 3.0
|
||||||
|
], [NAME, [VERSION, mapper.str, maps.browser.oldsafari.version]], [
|
||||||
|
|
||||||
|
/(konqueror)\/([\w\.]+)/i, // Konqueror
|
||||||
|
/(webkit|khtml)\/([\w\.]+)/i
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
// Gecko based
|
||||||
|
/(navigator|netscape)\/([\w\.-]+)/i // Netscape
|
||||||
|
], [[NAME, 'Netscape'], VERSION], [
|
||||||
|
/(swiftfox)/i, // Swiftfox
|
||||||
|
/(icedragon|iceweasel|camino|chimera|fennec|maemo\sbrowser|minimo|conkeror)[\/\s]?([\w\.\+]+)/i,
|
||||||
|
// IceDragon/Iceweasel/Camino/Chimera/Fennec/Maemo/Minimo/Conkeror
|
||||||
|
/(firefox|seamonkey|k-meleon|icecat|iceape|firebird|phoenix)\/([\w\.-]+)/i,
|
||||||
|
// Firefox/SeaMonkey/K-Meleon/IceCat/IceApe/Firebird/Phoenix
|
||||||
|
/(mozilla)\/([\w\.]+).+rv\:.+gecko\/\d+/i, // Mozilla
|
||||||
|
|
||||||
|
// Other
|
||||||
|
/(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf)[\/\s]?([\w\.]+)/i,
|
||||||
|
// Polaris/Lynx/Dillo/iCab/Doris/Amaya/w3m/NetSurf
|
||||||
|
/(links)\s\(([\w\.]+)/i, // Links
|
||||||
|
/(gobrowser)\/?([\w\.]+)*/i, // GoBrowser
|
||||||
|
/(ice\s?browser)\/v?([\w\._]+)/i, // ICE Browser
|
||||||
|
/(mosaic)[\/\s]([\w\.]+)/i // Mosaic
|
||||||
|
], [NAME, VERSION]
|
||||||
|
|
||||||
|
/* /////////////////////
|
||||||
|
// Media players BEGIN
|
||||||
|
////////////////////////
|
||||||
|
|
||||||
|
, [
|
||||||
|
|
||||||
|
/(apple(?:coremedia|))\/((\d+)[\w\._]+)/i, // Generic Apple CoreMedia
|
||||||
|
/(coremedia) v((\d+)[\w\._]+)/i
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(aqualung|lyssna|bsplayer)\/((\d+)?[\w\.-]+)/i // Aqualung/Lyssna/BSPlayer
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(ares|ossproxy)\s((\d+)[\w\.-]+)/i // Ares/OSSProxy
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(audacious|audimusicstream|amarok|bass|core|dalvik|gnomemplayer|music on console|nsplayer|psp-internetradioplayer|videos)\/((\d+)[\w\.-]+)/i,
|
||||||
|
// Audacious/AudiMusicStream/Amarok/BASS/OpenCORE/Dalvik/GnomeMplayer/MoC
|
||||||
|
// NSPlayer/PSP-InternetRadioPlayer/Videos
|
||||||
|
/(clementine|music player daemon)\s((\d+)[\w\.-]+)/i, // Clementine/MPD
|
||||||
|
/(lg player|nexplayer)\s((\d+)[\d\.]+)/i,
|
||||||
|
/player\/(nexplayer|lg player)\s((\d+)[\w\.-]+)/i // NexPlayer/LG Player
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
/(nexplayer)\s((\d+)[\w\.-]+)/i // Nexplayer
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(flrp)\/((\d+)[\w\.-]+)/i // Flip Player
|
||||||
|
], [[NAME, 'Flip Player'], VERSION], [
|
||||||
|
|
||||||
|
/(fstream|nativehost|queryseekspider|ia-archiver|facebookexternalhit)/i
|
||||||
|
// FStream/NativeHost/QuerySeekSpider/IA Archiver/facebookexternalhit
|
||||||
|
], [NAME], [
|
||||||
|
|
||||||
|
/(gstreamer) souphttpsrc (?:\([^\)]+\)){0,1} libsoup\/((\d+)[\w\.-]+)/i
|
||||||
|
// Gstreamer
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(htc streaming player)\s[\w_]+\s\/\s((\d+)[\d\.]+)/i, // HTC Streaming Player
|
||||||
|
/(java|python-urllib|python-requests|wget|libcurl)\/((\d+)[\w\.-_]+)/i,
|
||||||
|
// Java/urllib/requests/wget/cURL
|
||||||
|
/(lavf)((\d+)[\d\.]+)/i // Lavf (FFMPEG)
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(htc_one_s)\/((\d+)[\d\.]+)/i // HTC One S
|
||||||
|
], [[NAME, /_/g, ' '], VERSION], [
|
||||||
|
|
||||||
|
/(mplayer)(?:\s|\/)(?:(?:sherpya-){0,1}svn)(?:-|\s)(r\d+(?:-\d+[\w\.-]+){0,1})/i
|
||||||
|
// MPlayer SVN
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(mplayer)(?:\s|\/|[unkow-]+)((\d+)[\w\.-]+)/i // MPlayer
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(mplayer)/i, // MPlayer (no other info)
|
||||||
|
/(yourmuze)/i, // YourMuze
|
||||||
|
/(media player classic|nero showtime)/i // Media Player Classic/Nero ShowTime
|
||||||
|
], [NAME], [
|
||||||
|
|
||||||
|
/(nero (?:home|scout))\/((\d+)[\w\.-]+)/i // Nero Home/Nero Scout
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(nokia\d+)\/((\d+)[\w\.-]+)/i // Nokia
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/\s(songbird)\/((\d+)[\w\.-]+)/i // Songbird/Philips-Songbird
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(winamp)3 version ((\d+)[\w\.-]+)/i, // Winamp
|
||||||
|
/(winamp)\s((\d+)[\w\.-]+)/i,
|
||||||
|
/(winamp)mpeg\/((\d+)[\w\.-]+)/i
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(ocms-bot|tapinradio|tunein radio|unknown|winamp|inlight radio)/i // OCMS-bot/tap in radio/tunein/unknown/winamp (no other info)
|
||||||
|
// inlight radio
|
||||||
|
], [NAME], [
|
||||||
|
|
||||||
|
/(quicktime|rma|radioapp|radioclientapplication|soundtap|totem|stagefright|streamium)\/((\d+)[\w\.-]+)/i
|
||||||
|
// QuickTime/RealMedia/RadioApp/RadioClientApplication/
|
||||||
|
// SoundTap/Totem/Stagefright/Streamium
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(smp)((\d+)[\d\.]+)/i // SMP
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(vlc) media player - version ((\d+)[\w\.]+)/i, // VLC Videolan
|
||||||
|
/(vlc)\/((\d+)[\w\.-]+)/i,
|
||||||
|
/(xbmc|gvfs|xine|xmms|irapp)\/((\d+)[\w\.-]+)/i, // XBMC/gvfs/Xine/XMMS/irapp
|
||||||
|
/(foobar2000)\/((\d+)[\d\.]+)/i, // Foobar2000
|
||||||
|
/(itunes)\/((\d+)[\d\.]+)/i // iTunes
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(wmplayer)\/((\d+)[\w\.-]+)/i, // Windows Media Player
|
||||||
|
/(windows-media-player)\/((\d+)[\w\.-]+)/i
|
||||||
|
], [[NAME, /-/g, ' '], VERSION], [
|
||||||
|
|
||||||
|
/windows\/((\d+)[\w\.-]+) upnp\/[\d\.]+ dlnadoc\/[\d\.]+ (home media server)/i
|
||||||
|
// Windows Media Server
|
||||||
|
], [VERSION, [NAME, 'Windows']], [
|
||||||
|
|
||||||
|
/(com\.riseupradioalarm)\/((\d+)[\d\.]*)/i // RiseUP Radio Alarm
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(rad.io)\s((\d+)[\d\.]+)/i, // Rad.io
|
||||||
|
/(radio.(?:de|at|fr))\s((\d+)[\d\.]+)/i
|
||||||
|
], [[NAME, 'rad.io'], VERSION]
|
||||||
|
|
||||||
|
//////////////////////
|
||||||
|
// Media players END
|
||||||
|
////////////////////*/
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
cpu : [[
|
||||||
|
|
||||||
|
/(?:(amd|x(?:(?:86|64)[_-])?|wow|win)64)[;\)]/i // AMD64
|
||||||
|
], [[ARCHITECTURE, 'amd64']], [
|
||||||
|
|
||||||
|
/(ia32(?=;))/i // IA32 (quicktime)
|
||||||
|
], [[ARCHITECTURE, util.lowerize]], [
|
||||||
|
|
||||||
|
/((?:i[346]|x)86)[;\)]/i // IA32
|
||||||
|
], [[ARCHITECTURE, 'ia32']], [
|
||||||
|
|
||||||
|
// PocketPC mistakenly identified as PowerPC
|
||||||
|
/windows\s(ce|mobile);\sppc;/i
|
||||||
|
], [[ARCHITECTURE, 'arm']], [
|
||||||
|
|
||||||
|
/((?:ppc|powerpc)(?:64)?)(?:\smac|;|\))/i // PowerPC
|
||||||
|
], [[ARCHITECTURE, /ower/, '', util.lowerize]], [
|
||||||
|
|
||||||
|
/(sun4\w)[;\)]/i // SPARC
|
||||||
|
], [[ARCHITECTURE, 'sparc']], [
|
||||||
|
|
||||||
|
/((?:avr32|ia64(?=;))|68k(?=\))|arm(?:64|(?=v\d+;))|(?=atmel\s)avr|(?:irix|mips|sparc)(?:64)?(?=;)|pa-risc)/i
|
||||||
|
// IA64, 68K, ARM/64, AVR/32, IRIX/64, MIPS/64, SPARC/64, PA-RISC
|
||||||
|
], [[ARCHITECTURE, util.lowerize]]
|
||||||
|
],
|
||||||
|
|
||||||
|
device : [[
|
||||||
|
|
||||||
|
/\((ipad|playbook);[\w\s\);-]+(rim|apple)/i // iPad/PlayBook
|
||||||
|
], [MODEL, VENDOR, [TYPE, TABLET]], [
|
||||||
|
|
||||||
|
/applecoremedia\/[\w\.]+ \((ipad)/ // iPad
|
||||||
|
], [MODEL, [VENDOR, 'Apple'], [TYPE, TABLET]], [
|
||||||
|
|
||||||
|
/(apple\s{0,1}tv)/i // Apple TV
|
||||||
|
], [[MODEL, 'Apple TV'], [VENDOR, 'Apple']], [
|
||||||
|
|
||||||
|
/(archos)\s(gamepad2?)/i, // Archos
|
||||||
|
/(hp).+(touchpad)/i, // HP TouchPad
|
||||||
|
/(kindle)\/([\w\.]+)/i, // Kindle
|
||||||
|
/\s(nook)[\w\s]+build\/(\w+)/i, // Nook
|
||||||
|
/(dell)\s(strea[kpr\s\d]*[\dko])/i // Dell Streak
|
||||||
|
], [VENDOR, MODEL, [TYPE, TABLET]], [
|
||||||
|
|
||||||
|
/(kf[A-z]+)\sbuild\/[\w\.]+.*silk\//i // Kindle Fire HD
|
||||||
|
], [MODEL, [VENDOR, 'Amazon'], [TYPE, TABLET]], [
|
||||||
|
/(sd|kf)[0349hijorstuw]+\sbuild\/[\w\.]+.*silk\//i // Fire Phone
|
||||||
|
], [[MODEL, mapper.str, maps.device.amazon.model], [VENDOR, 'Amazon'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/\((ip[honed|\s\w*]+);.+(apple)/i // iPod/iPhone
|
||||||
|
], [MODEL, VENDOR, [TYPE, MOBILE]], [
|
||||||
|
/\((ip[honed|\s\w*]+);/i // iPod/iPhone
|
||||||
|
], [MODEL, [VENDOR, 'Apple'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/(blackberry)[\s-]?(\w+)/i, // BlackBerry
|
||||||
|
/(blackberry|benq|palm(?=\-)|sonyericsson|acer|asus|dell|huawei|meizu|motorola|polytron)[\s_-]?([\w-]+)*/i,
|
||||||
|
// BenQ/Palm/Sony-Ericsson/Acer/Asus/Dell/Huawei/Meizu/Motorola/Polytron
|
||||||
|
/(hp)\s([\w\s]+\w)/i, // HP iPAQ
|
||||||
|
/(asus)-?(\w+)/i // Asus
|
||||||
|
], [VENDOR, MODEL, [TYPE, MOBILE]], [
|
||||||
|
/\(bb10;\s(\w+)/i // BlackBerry 10
|
||||||
|
], [MODEL, [VENDOR, 'BlackBerry'], [TYPE, MOBILE]], [
|
||||||
|
// Asus Tablets
|
||||||
|
/android.+(transfo[prime\s]{4,10}\s\w+|eeepc|slider\s\w+|nexus 7)/i
|
||||||
|
], [MODEL, [VENDOR, 'Asus'], [TYPE, TABLET]], [
|
||||||
|
|
||||||
|
/(sony)\s(tablet\s[ps])\sbuild\//i, // Sony
|
||||||
|
/(sony)?(?:sgp.+)\sbuild\//i
|
||||||
|
], [[VENDOR, 'Sony'], [MODEL, 'Xperia Tablet'], [TYPE, TABLET]], [
|
||||||
|
/(?:sony)?(?:(?:(?:c|d)\d{4})|(?:so[-l].+))\sbuild\//i
|
||||||
|
], [[VENDOR, 'Sony'], [MODEL, 'Xperia Phone'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/\s(ouya)\s/i, // Ouya
|
||||||
|
/(nintendo)\s([wids3u]+)/i // Nintendo
|
||||||
|
], [VENDOR, MODEL, [TYPE, CONSOLE]], [
|
||||||
|
|
||||||
|
/android.+;\s(shield)\sbuild/i // Nvidia
|
||||||
|
], [MODEL, [VENDOR, 'Nvidia'], [TYPE, CONSOLE]], [
|
||||||
|
|
||||||
|
/(playstation\s[3portablevi]+)/i // Playstation
|
||||||
|
], [MODEL, [VENDOR, 'Sony'], [TYPE, CONSOLE]], [
|
||||||
|
|
||||||
|
/(sprint\s(\w+))/i // Sprint Phones
|
||||||
|
], [[VENDOR, mapper.str, maps.device.sprint.vendor], [MODEL, mapper.str, maps.device.sprint.model], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/(lenovo)\s?(S(?:5000|6000)+(?:[-][\w+]))/i // Lenovo tablets
|
||||||
|
], [VENDOR, MODEL, [TYPE, TABLET]], [
|
||||||
|
|
||||||
|
/(htc)[;_\s-]+([\w\s]+(?=\))|\w+)*/i, // HTC
|
||||||
|
/(zte)-(\w+)*/i, // ZTE
|
||||||
|
/(alcatel|geeksphone|huawei|lenovo|nexian|panasonic|(?=;\s)sony)[_\s-]?([\w-]+)*/i
|
||||||
|
// Alcatel/GeeksPhone/Huawei/Lenovo/Nexian/Panasonic/Sony
|
||||||
|
], [VENDOR, [MODEL, /_/g, ' '], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/(nexus\s9)/i // HTC Nexus 9
|
||||||
|
], [MODEL, [VENDOR, 'HTC'], [TYPE, TABLET]], [
|
||||||
|
|
||||||
|
/[\s\(;](xbox(?:\sone)?)[\s\);]/i // Microsoft Xbox
|
||||||
|
], [MODEL, [VENDOR, 'Microsoft'], [TYPE, CONSOLE]], [
|
||||||
|
/(kin\.[onetw]{3})/i // Microsoft Kin
|
||||||
|
], [[MODEL, /\./g, ' '], [VENDOR, 'Microsoft'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
// Motorola
|
||||||
|
/\s(milestone|droid(?:[2-4x]|\s(?:bionic|x2|pro|razr))?(:?\s4g)?)[\w\s]+build\//i,
|
||||||
|
/mot[\s-]?(\w+)*/i,
|
||||||
|
/(XT\d{3,4}) build\//i
|
||||||
|
], [MODEL, [VENDOR, 'Motorola'], [TYPE, MOBILE]], [
|
||||||
|
/android.+\s(mz60\d|xoom[\s2]{0,2})\sbuild\//i
|
||||||
|
], [MODEL, [VENDOR, 'Motorola'], [TYPE, TABLET]], [
|
||||||
|
|
||||||
|
/android.+((sch-i[89]0\d|shw-m380s|gt-p\d{4}|gt-n8000|sgh-t8[56]9|nexus 10))/i,
|
||||||
|
/((SM-T\w+))/i
|
||||||
|
], [[VENDOR, 'Samsung'], MODEL, [TYPE, TABLET]], [ // Samsung
|
||||||
|
/((s[cgp]h-\w+|gt-\w+|galaxy\snexus|sm-n900))/i,
|
||||||
|
/(sam[sung]*)[\s-]*(\w+-?[\w-]*)*/i,
|
||||||
|
/sec-((sgh\w+))/i
|
||||||
|
], [[VENDOR, 'Samsung'], MODEL, [TYPE, MOBILE]], [
|
||||||
|
/(samsung);smarttv/i
|
||||||
|
], [VENDOR, MODEL, [TYPE, SMARTTV]], [
|
||||||
|
|
||||||
|
/\(dtv[\);].+(aquos)/i // Sharp
|
||||||
|
], [MODEL, [VENDOR, 'Sharp'], [TYPE, SMARTTV]], [
|
||||||
|
/sie-(\w+)*/i // Siemens
|
||||||
|
], [MODEL, [VENDOR, 'Siemens'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/(maemo|nokia).*(n900|lumia\s\d+)/i, // Nokia
|
||||||
|
/(nokia)[\s_-]?([\w-]+)*/i
|
||||||
|
], [[VENDOR, 'Nokia'], MODEL, [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/android\s3\.[\s\w;-]{10}(a\d{3})/i // Acer
|
||||||
|
], [MODEL, [VENDOR, 'Acer'], [TYPE, TABLET]], [
|
||||||
|
|
||||||
|
/android\s3\.[\s\w;-]{10}(lg?)-([06cv9]{3,4})/i // LG Tablet
|
||||||
|
], [[VENDOR, 'LG'], MODEL, [TYPE, TABLET]], [
|
||||||
|
/(lg) netcast\.tv/i // LG SmartTV
|
||||||
|
], [VENDOR, MODEL, [TYPE, SMARTTV]], [
|
||||||
|
/(nexus\s[45])/i, // LG
|
||||||
|
/lg[e;\s\/-]+(\w+)*/i
|
||||||
|
], [MODEL, [VENDOR, 'LG'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/android.+(ideatab[a-z0-9\-\s]+)/i // Lenovo
|
||||||
|
], [MODEL, [VENDOR, 'Lenovo'], [TYPE, TABLET]], [
|
||||||
|
|
||||||
|
/linux;.+((jolla));/i // Jolla
|
||||||
|
], [VENDOR, MODEL, [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/((pebble))app\/[\d\.]+\s/i // Pebble
|
||||||
|
], [VENDOR, MODEL, [TYPE, WEARABLE]], [
|
||||||
|
|
||||||
|
/android.+;\s(glass)\s\d/i // Google Glass
|
||||||
|
], [MODEL, [VENDOR, 'Google'], [TYPE, WEARABLE]], [
|
||||||
|
|
||||||
|
/android.+(\w+)\s+build\/hm\1/i, // Xiaomi Hongmi 'numeric' models
|
||||||
|
/android.+(hm[\s\-_]*note?[\s_]*(?:\d\w)?)\s+build/i, // Xiaomi Hongmi
|
||||||
|
/android.+(mi[\s\-_]*(?:one|one[\s_]plus)?[\s_]*(?:\d\w)?)\s+build/i // Xiaomi Mi
|
||||||
|
], [[MODEL, /_/g, ' '], [VENDOR, 'Xiaomi'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/(mobile|tablet);.+rv\:.+gecko\//i // Unidentifiable
|
||||||
|
], [[TYPE, util.lowerize], VENDOR, MODEL]
|
||||||
|
|
||||||
|
/*//////////////////////////
|
||||||
|
// TODO: move to string map
|
||||||
|
////////////////////////////
|
||||||
|
|
||||||
|
/(C6603)/i // Sony Xperia Z C6603
|
||||||
|
], [[MODEL, 'Xperia Z C6603'], [VENDOR, 'Sony'], [TYPE, MOBILE]], [
|
||||||
|
/(C6903)/i // Sony Xperia Z 1
|
||||||
|
], [[MODEL, 'Xperia Z 1'], [VENDOR, 'Sony'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/(SM-G900[F|H])/i // Samsung Galaxy S5
|
||||||
|
], [[MODEL, 'Galaxy S5'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
|
||||||
|
/(SM-G7102)/i // Samsung Galaxy Grand 2
|
||||||
|
], [[MODEL, 'Galaxy Grand 2'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
|
||||||
|
/(SM-G530H)/i // Samsung Galaxy Grand Prime
|
||||||
|
], [[MODEL, 'Galaxy Grand Prime'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
|
||||||
|
/(SM-G313HZ)/i // Samsung Galaxy V
|
||||||
|
], [[MODEL, 'Galaxy V'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
|
||||||
|
/(SM-T805)/i // Samsung Galaxy Tab S 10.5
|
||||||
|
], [[MODEL, 'Galaxy Tab S 10.5'], [VENDOR, 'Samsung'], [TYPE, TABLET]], [
|
||||||
|
/(SM-G800F)/i // Samsung Galaxy S5 Mini
|
||||||
|
], [[MODEL, 'Galaxy S5 Mini'], [VENDOR, 'Samsung'], [TYPE, MOBILE]], [
|
||||||
|
/(SM-T311)/i // Samsung Galaxy Tab 3 8.0
|
||||||
|
], [[MODEL, 'Galaxy Tab 3 8.0'], [VENDOR, 'Samsung'], [TYPE, TABLET]], [
|
||||||
|
|
||||||
|
/(R1001)/i // Oppo R1001
|
||||||
|
], [MODEL, [VENDOR, 'OPPO'], [TYPE, MOBILE]], [
|
||||||
|
/(X9006)/i // Oppo Find 7a
|
||||||
|
], [[MODEL, 'Find 7a'], [VENDOR, 'Oppo'], [TYPE, MOBILE]], [
|
||||||
|
/(R2001)/i // Oppo YOYO R2001
|
||||||
|
], [[MODEL, 'Yoyo R2001'], [VENDOR, 'Oppo'], [TYPE, MOBILE]], [
|
||||||
|
/(R815)/i // Oppo Clover R815
|
||||||
|
], [[MODEL, 'Clover R815'], [VENDOR, 'Oppo'], [TYPE, MOBILE]], [
|
||||||
|
/(U707)/i // Oppo Find Way S
|
||||||
|
], [[MODEL, 'Find Way S'], [VENDOR, 'Oppo'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/(T3C)/i // Advan Vandroid T3C
|
||||||
|
], [MODEL, [VENDOR, 'Advan'], [TYPE, TABLET]], [
|
||||||
|
/(ADVAN T1J\+)/i // Advan Vandroid T1J+
|
||||||
|
], [[MODEL, 'Vandroid T1J+'], [VENDOR, 'Advan'], [TYPE, TABLET]], [
|
||||||
|
/(ADVAN S4A)/i // Advan Vandroid S4A
|
||||||
|
], [[MODEL, 'Vandroid S4A'], [VENDOR, 'Advan'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/(V972M)/i // ZTE V972M
|
||||||
|
], [MODEL, [VENDOR, 'ZTE'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/(i-mobile)\s(IQ\s[\d\.]+)/i // i-mobile IQ
|
||||||
|
], [VENDOR, MODEL, [TYPE, MOBILE]], [
|
||||||
|
/(IQ6.3)/i // i-mobile IQ IQ 6.3
|
||||||
|
], [[MODEL, 'IQ 6.3'], [VENDOR, 'i-mobile'], [TYPE, MOBILE]], [
|
||||||
|
/(i-mobile)\s(i-style\s[\d\.]+)/i // i-mobile i-STYLE
|
||||||
|
], [VENDOR, MODEL, [TYPE, MOBILE]], [
|
||||||
|
/(i-STYLE2.1)/i // i-mobile i-STYLE 2.1
|
||||||
|
], [[MODEL, 'i-STYLE 2.1'], [VENDOR, 'i-mobile'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/(mobiistar touch LAI 512)/i // mobiistar touch LAI 512
|
||||||
|
], [[MODEL, 'Touch LAI 512'], [VENDOR, 'mobiistar'], [TYPE, MOBILE]], [
|
||||||
|
|
||||||
|
/////////////
|
||||||
|
// END TODO
|
||||||
|
///////////*/
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
engine : [[
|
||||||
|
|
||||||
|
/windows.+\sedge\/([\w\.]+)/i // EdgeHTML
|
||||||
|
], [VERSION, [NAME, 'EdgeHTML']], [
|
||||||
|
|
||||||
|
/(presto)\/([\w\.]+)/i, // Presto
|
||||||
|
/(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m
|
||||||
|
/(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i, // KHTML/Tasman/Links
|
||||||
|
/(icab)[\/\s]([23]\.[\d\.]+)/i // iCab
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/rv\:([\w\.]+).*(gecko)/i // Gecko
|
||||||
|
], [VERSION, NAME]
|
||||||
|
],
|
||||||
|
|
||||||
|
os : [[
|
||||||
|
|
||||||
|
// Windows based
|
||||||
|
/microsoft\s(windows)\s(vista|xp)/i // Windows (iTunes)
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
/(windows)\snt\s6\.2;\s(arm)/i, // Windows RT
|
||||||
|
/(windows\sphone(?:\sos)*|windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i
|
||||||
|
], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [
|
||||||
|
/(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i
|
||||||
|
], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [
|
||||||
|
|
||||||
|
// Mobile/Embedded OS
|
||||||
|
/\((bb)(10);/i // BlackBerry 10
|
||||||
|
], [[NAME, 'BlackBerry'], VERSION], [
|
||||||
|
/(blackberry)\w*\/?([\w\.]+)*/i, // Blackberry
|
||||||
|
/(tizen)[\/\s]([\w\.]+)/i, // Tizen
|
||||||
|
/(android|webos|palm\sos|qnx|bada|rim\stablet\sos|meego|contiki)[\/\s-]?([\w\.]+)*/i,
|
||||||
|
// Android/WebOS/Palm/QNX/Bada/RIM/MeeGo/Contiki
|
||||||
|
/linux;.+(sailfish);/i // Sailfish OS
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
/(symbian\s?os|symbos|s60(?=;))[\/\s-]?([\w\.]+)*/i // Symbian
|
||||||
|
], [[NAME, 'Symbian'], VERSION], [
|
||||||
|
/\((series40);/i // Series 40
|
||||||
|
], [NAME], [
|
||||||
|
/mozilla.+\(mobile;.+gecko.+firefox/i // Firefox OS
|
||||||
|
], [[NAME, 'Firefox OS'], VERSION], [
|
||||||
|
|
||||||
|
// Console
|
||||||
|
/(nintendo|playstation)\s([wids3portablevu]+)/i, // Nintendo/Playstation
|
||||||
|
|
||||||
|
// GNU/Linux based
|
||||||
|
/(mint)[\/\s\(]?(\w+)*/i, // Mint
|
||||||
|
/(mageia|vectorlinux)[;\s]/i, // Mageia/VectorLinux
|
||||||
|
/(joli|[kxln]?ubuntu|debian|[open]*suse|gentoo|arch|slackware|fedora|mandriva|centos|pclinuxos|redhat|zenwalk|linpus)[\/\s-]?([\w\.-]+)*/i,
|
||||||
|
// Joli/Ubuntu/Debian/SUSE/Gentoo/Arch/Slackware
|
||||||
|
// Fedora/Mandriva/CentOS/PCLinuxOS/RedHat/Zenwalk/Linpus
|
||||||
|
/(hurd|linux)\s?([\w\.]+)*/i, // Hurd/Linux
|
||||||
|
/(gnu)\s?([\w\.]+)*/i // GNU
|
||||||
|
], [NAME, VERSION], [
|
||||||
|
|
||||||
|
/(cros)\s[\w]+\s([\w\.]+\w)/i // Chromium OS
|
||||||
|
], [[NAME, 'Chromium OS'], VERSION],[
|
||||||
|
|
||||||
|
// Solaris
|
||||||
|
/(sunos)\s?([\w\.]+\d)*/i // Solaris
|
||||||
|
], [[NAME, 'Solaris'], VERSION], [
|
||||||
|
|
||||||
|
// BSD based
|
||||||
|
/\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly
|
||||||
|
], [NAME, VERSION],[
|
||||||
|
|
||||||
|
/(ip[honead]+)(?:.*os\s*([\w]+)*\slike\smac|;\sopera)/i // iOS
|
||||||
|
], [[NAME, 'iOS'], [VERSION, /_/g, '.']], [
|
||||||
|
|
||||||
|
/(mac\sos\sx)\s?([\w\s\.]+\w)*/i,
|
||||||
|
/(macintosh|mac(?=_powerpc)\s)/i // Mac OS
|
||||||
|
], [[NAME, 'Mac OS'], [VERSION, /_/g, '.']], [
|
||||||
|
|
||||||
|
// Other
|
||||||
|
/((?:open)?solaris)[\/\s-]?([\w\.]+)*/i, // Solaris
|
||||||
|
/(haiku)\s(\w+)/i, // Haiku
|
||||||
|
/(aix)\s((\d)(?=\.|\)|\s)[\w\.]*)*/i, // AIX
|
||||||
|
/(plan\s9|minix|beos|os\/2|amigaos|morphos|risc\sos|openvms)/i,
|
||||||
|
// Plan9/Minix/BeOS/OS2/AmigaOS/MorphOS/RISCOS/OpenVMS
|
||||||
|
/(unix)\s?([\w\.]+)*/i // UNIX
|
||||||
|
], [NAME, VERSION]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////
|
||||||
|
// Constructor
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
|
||||||
|
var UAParser = function (uastring, extensions) {
|
||||||
|
|
||||||
|
if (!(this instanceof UAParser)) {
|
||||||
|
return new UAParser(uastring, extensions).getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY);
|
||||||
|
var rgxmap = extensions ? util.extend(regexes, extensions) : regexes;
|
||||||
|
|
||||||
|
this.getBrowser = function () {
|
||||||
|
var browser = mapper.rgx.apply(this, rgxmap.browser);
|
||||||
|
browser.major = util.major(browser.version);
|
||||||
|
return browser;
|
||||||
|
};
|
||||||
|
this.getCPU = function () {
|
||||||
|
return mapper.rgx.apply(this, rgxmap.cpu);
|
||||||
|
};
|
||||||
|
this.getDevice = function () {
|
||||||
|
return mapper.rgx.apply(this, rgxmap.device);
|
||||||
|
};
|
||||||
|
this.getEngine = function () {
|
||||||
|
return mapper.rgx.apply(this, rgxmap.engine);
|
||||||
|
};
|
||||||
|
this.getOS = function () {
|
||||||
|
return mapper.rgx.apply(this, rgxmap.os);
|
||||||
|
};
|
||||||
|
this.getResult = function() {
|
||||||
|
return {
|
||||||
|
ua : this.getUA(),
|
||||||
|
browser : this.getBrowser(),
|
||||||
|
engine : this.getEngine(),
|
||||||
|
os : this.getOS(),
|
||||||
|
device : this.getDevice(),
|
||||||
|
cpu : this.getCPU()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
this.getUA = function () {
|
||||||
|
return ua;
|
||||||
|
};
|
||||||
|
this.setUA = function (uastring) {
|
||||||
|
ua = uastring;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
this.setUA(ua);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
UAParser.VERSION = LIBVERSION;
|
||||||
|
UAParser.BROWSER = {
|
||||||
|
NAME : NAME,
|
||||||
|
MAJOR : MAJOR, // deprecated
|
||||||
|
VERSION : VERSION
|
||||||
|
};
|
||||||
|
UAParser.CPU = {
|
||||||
|
ARCHITECTURE : ARCHITECTURE
|
||||||
|
};
|
||||||
|
UAParser.DEVICE = {
|
||||||
|
MODEL : MODEL,
|
||||||
|
VENDOR : VENDOR,
|
||||||
|
TYPE : TYPE,
|
||||||
|
CONSOLE : CONSOLE,
|
||||||
|
MOBILE : MOBILE,
|
||||||
|
SMARTTV : SMARTTV,
|
||||||
|
TABLET : TABLET,
|
||||||
|
WEARABLE: WEARABLE,
|
||||||
|
EMBEDDED: EMBEDDED
|
||||||
|
};
|
||||||
|
UAParser.ENGINE = {
|
||||||
|
NAME : NAME,
|
||||||
|
VERSION : VERSION
|
||||||
|
};
|
||||||
|
UAParser.OS = {
|
||||||
|
NAME : NAME,
|
||||||
|
VERSION : VERSION
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
///////////
|
||||||
|
// Export
|
||||||
|
//////////
|
||||||
|
|
||||||
|
|
||||||
|
// check js environment
|
||||||
|
if (typeof(exports) !== UNDEF_TYPE) {
|
||||||
|
// nodejs env
|
||||||
|
if (typeof module !== UNDEF_TYPE && module.exports) {
|
||||||
|
exports = module.exports = UAParser;
|
||||||
|
}
|
||||||
|
exports.UAParser = UAParser;
|
||||||
|
} else {
|
||||||
|
// requirejs env (optional)
|
||||||
|
if (typeof(define) === FUNC_TYPE && define.amd) {
|
||||||
|
define(function () {
|
||||||
|
return UAParser;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// browser env
|
||||||
|
window.UAParser = UAParser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// jQuery/Zepto specific (optional)
|
||||||
|
// Note:
|
||||||
|
// In AMD env the global scope should be kept clean, but jQuery is an exception.
|
||||||
|
// jQuery always exports to global scope, unless jQuery.noConflict(true) is used,
|
||||||
|
// and we should catch that.
|
||||||
|
var $ = window.jQuery || window.Zepto;
|
||||||
|
if (typeof $ !== UNDEF_TYPE) {
|
||||||
|
var parser = new UAParser();
|
||||||
|
$.ua = parser.getResult();
|
||||||
|
$.ua.get = function() {
|
||||||
|
return parser.getUA();
|
||||||
|
};
|
||||||
|
$.ua.set = function (uastring) {
|
||||||
|
parser.setUA(uastring);
|
||||||
|
var result = parser.getResult();
|
||||||
|
for (var prop in result) {
|
||||||
|
$.ua[prop] = result[prop];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
})(typeof window === 'object' ? window : this);
|
|
@ -3,7 +3,7 @@
|
||||||
<h1><%- @T( @header ) %> <small><%- @T( @subHeader ) %></small></h1>
|
<h1><%- @T( @header ) %> <small><%- @T( @subHeader ) %></small></h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav nav-tabs <% if @tabs.length <= 1: %>hide<% end %>" role="tablist">
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<% for tab in @tabs: %>
|
<% for tab in @tabs: %>
|
||||||
<li><a href="#<%= tab.target %>" role="tab" data-toggle="tab"><%- @T( tab.name ) %></a></li>
|
<li><a href="#<%= tab.target %>" role="tab" data-toggle="tab"><%- @T( tab.name ) %></a></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -86,7 +86,7 @@ class ApplicationController < ActionController::Base
|
||||||
# check if remote ip need to be updated
|
# check if remote ip need to be updated
|
||||||
if !session[:remote_id] || session[:remote_id] != request.remote_ip
|
if !session[:remote_id] || session[:remote_id] != request.remote_ip
|
||||||
session[:remote_id] = request.remote_ip
|
session[:remote_id] = request.remote_ip
|
||||||
session[:geo] = GeoIp.location( request.remote_ip )
|
session[:geo] = Service::GeoIp.location( request.remote_ip )
|
||||||
end
|
end
|
||||||
|
|
||||||
# fill user agent
|
# fill user agent
|
||||||
|
|
|
@ -32,11 +32,13 @@ class SettingsController < ApplicationController
|
||||||
# PUT /settings/1
|
# PUT /settings/1
|
||||||
def update
|
def update
|
||||||
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
||||||
|
return if !check_access
|
||||||
model_update_render(Setting, params)
|
model_update_render(Setting, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
# PUT /settings/image/:id
|
# PUT /settings/image/:id
|
||||||
def update_image
|
def update_image
|
||||||
|
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
||||||
|
|
||||||
if !params[:logo]
|
if !params[:logo]
|
||||||
render json: {
|
render json: {
|
||||||
|
@ -91,4 +93,16 @@ class SettingsController < ApplicationController
|
||||||
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
||||||
model_destory_render(Setting, params)
|
model_destory_render(Setting, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def check_access
|
||||||
|
return true if !Setting.get('system_online_service')
|
||||||
|
|
||||||
|
setting = Setting.find(params[:id])
|
||||||
|
return true if setting.preferences && !setting.preferences[:online_service_disable]
|
||||||
|
|
||||||
|
response_access_deny
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,7 @@ class TranslationsController < ApplicationController
|
||||||
# POST /translations/sync
|
# POST /translations/sync
|
||||||
def sync
|
def sync
|
||||||
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
||||||
|
Locale.load
|
||||||
Translation.load
|
Translation.load
|
||||||
render json: { message: 'ok' }, status: :ok
|
render json: { message: 'ok' }, status: :ok
|
||||||
end
|
end
|
||||||
|
|
|
@ -130,7 +130,7 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
# fetch org logo
|
# fetch org logo
|
||||||
if user.email
|
if user.email
|
||||||
Zammad::BigData::Organization.suggest_system_image(user.email)
|
Service::Image.organization_suggest(user.email)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,7 @@ add a avatar
|
||||||
end
|
end
|
||||||
|
|
||||||
# fetch image
|
# fetch image
|
||||||
image = Zammad::BigData::User.image(data[:url])
|
image = Service::Image.user(data[:url])
|
||||||
return if !image
|
return if !image
|
||||||
if !data[:resize]
|
if !data[:resize]
|
||||||
data[:resize] = {}
|
data[:resize] = {}
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Channel::Facebook
|
||||||
def fetch (channel)
|
def fetch (channel)
|
||||||
|
|
||||||
@channel = channel
|
@channel = channel
|
||||||
@facebook = Facebook.new( @channel[:options][:auth] )
|
@facebook = Facebook.new( @channel[:options] )
|
||||||
@sync = @channel[:options][:sync]
|
@sync = @channel[:options][:sync]
|
||||||
|
|
||||||
Rails.logger.debug 'facebook fetch started'
|
Rails.logger.debug 'facebook fetch started'
|
||||||
|
@ -22,7 +22,7 @@ class Channel::Facebook
|
||||||
def send(article, _notification = false)
|
def send(article, _notification = false)
|
||||||
|
|
||||||
@channel = Channel.find_by( area: 'Facebook::Inbound', active: true )
|
@channel = Channel.find_by( area: 'Facebook::Inbound', active: true )
|
||||||
@facebook = Facebook.new( @channel[:options][:auth] )
|
@facebook = Facebook.new( @channel[:options] )
|
||||||
|
|
||||||
tweet = @facebook.from_article(article)
|
tweet = @facebook.from_article(article)
|
||||||
disconnect
|
disconnect
|
||||||
|
|
|
@ -13,6 +13,7 @@ class Locale < ApplicationModel
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fail "Can't load locales from #{url}" if !result
|
||||||
fail "Can't load locales from #{url}: #{result.error}" if !result.success?
|
fail "Can't load locales from #{url}: #{result.error}" if !result.success?
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
|
|
|
@ -54,7 +54,7 @@ class Observer::User::Geo < ActiveRecord::Observer
|
||||||
return if address == ''
|
return if address == ''
|
||||||
|
|
||||||
# lookup
|
# lookup
|
||||||
latlng = GeoLocation.geocode( address )
|
latlng = Service::GeoLocation.geocode( address )
|
||||||
return if !latlng
|
return if !latlng
|
||||||
|
|
||||||
# store data
|
# store data
|
||||||
|
|
|
@ -30,7 +30,7 @@ module Ticket::Number::Increment
|
||||||
if config[:checksum]
|
if config[:checksum]
|
||||||
min_digs = min_digs.to_i - 1
|
min_digs = min_digs.to_i - 1
|
||||||
end
|
end
|
||||||
fillup = Setting.get('system_id') || '1'
|
fillup = Setting.get('system_id').to_s || '1'
|
||||||
( 1..100 ).each {
|
( 1..100 ).each {
|
||||||
|
|
||||||
next if ( fillup.length.to_i + counter_increment.to_s.length.to_i ) >= min_digs.to_i
|
next if ( fillup.length.to_i + counter_increment.to_s.length.to_i ) >= min_digs.to_i
|
||||||
|
|
76
db/migrate/20150715000001_update_services.rb
Normal file
76
db/migrate/20150715000001_update_services.rb
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
class UpdateServices < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Image Service',
|
||||||
|
name: 'image_backend',
|
||||||
|
area: 'System::Services',
|
||||||
|
description: 'Defines the backend for user and organization image lookups.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'image_backend',
|
||||||
|
tag: 'select',
|
||||||
|
options: {
|
||||||
|
'' => '-',
|
||||||
|
'Service::Image::Zammad' => 'Zammad Image Service',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: 'Service::Image::Zammad',
|
||||||
|
preferences: { prio: 1 },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Geo IP Service',
|
||||||
|
name: 'geo_ip_backend',
|
||||||
|
area: 'System::Services',
|
||||||
|
description: 'Defines the backend for geo IP lookups. Show also location of an IP address if an IP address is shown.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'geo_ip_backend',
|
||||||
|
tag: 'select',
|
||||||
|
options: {
|
||||||
|
'' => '-',
|
||||||
|
'Service::GeoIp::Zammad' => 'Zammad GeoIP Service',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: 'Service::GeoIp::Zammad',
|
||||||
|
preferences: { prio: 2 },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Geo Location Service',
|
||||||
|
name: 'geo_location_backend',
|
||||||
|
area: 'System::Services',
|
||||||
|
description: 'Defines the backend for geo location lookups to store geo locations for addresses.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'geo_location_backend',
|
||||||
|
tag: 'select',
|
||||||
|
options: {
|
||||||
|
'' => '-',
|
||||||
|
'Service::GeoLocation::Gmaps' => 'Google Maps',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: 'Service::GeoLocation::Gmaps',
|
||||||
|
preferences: { prio: 3 },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
69
db/migrate/20150716000001_update_settings.rb
Normal file
69
db/migrate/20150716000001_update_settings.rb
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
class UpdateSettings < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Organization',
|
||||||
|
name: 'organization',
|
||||||
|
area: 'System::Branding',
|
||||||
|
description: 'Will be shown in the app and is included in email footers.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: false,
|
||||||
|
name: 'organization',
|
||||||
|
tag: 'input',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: '',
|
||||||
|
preferences: { prio: 2 },
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Send client stats',
|
||||||
|
name: 'ui_send_client_stats',
|
||||||
|
area: 'System::UI',
|
||||||
|
description: 'Send client stats/error message to central server to improve the usability.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'ui_send_client_stats',
|
||||||
|
tag: 'boolean',
|
||||||
|
options: {
|
||||||
|
true => 'yes',
|
||||||
|
false => 'no',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: true,
|
||||||
|
preferences: { prio: 1 },
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Client storage',
|
||||||
|
name: 'ui_client_storage',
|
||||||
|
area: 'System::UI',
|
||||||
|
description: 'Use client storage to cache data to perform speed of application.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'ui_client_storage',
|
||||||
|
tag: 'boolean',
|
||||||
|
options: {
|
||||||
|
true => 'yes',
|
||||||
|
false => 'no',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: false,
|
||||||
|
preferences: { prio: 2 },
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
265
db/migrate/20150716000003_add_setting_online_service.rb
Normal file
265
db/migrate/20150716000003_add_setting_online_service.rb
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
class AddSettingOnlineService < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
|
||||||
|
# return if it's a new setup
|
||||||
|
return if !Setting.find_by(name: 'system_init_done')
|
||||||
|
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'System Init Done',
|
||||||
|
name: 'system_init_done',
|
||||||
|
area: 'Core',
|
||||||
|
description: 'Defines if application is in init mode.',
|
||||||
|
options: {},
|
||||||
|
state: Setting.get('system_init_done'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Developer System',
|
||||||
|
name: 'developer_mode',
|
||||||
|
area: 'Core::Develop',
|
||||||
|
description: 'Defines if application is in developer mode (useful for developer, all users have the same password, password reset will work without email delivery).',
|
||||||
|
options: {},
|
||||||
|
state: Setting.get('developer_mode'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Online Service',
|
||||||
|
name: 'system_online_service',
|
||||||
|
area: 'Core',
|
||||||
|
description: 'Defines if application is used as online service.',
|
||||||
|
options: {},
|
||||||
|
state: Setting.get('system_online_service'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'SystemID',
|
||||||
|
name: 'system_id',
|
||||||
|
area: 'System::Base',
|
||||||
|
description: 'Defines the system identifier. Every ticket number contains this ID. This ensures that only tickets which belong to your system will be processed as follow-ups (useful when communicating between two instances of Zammad).',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'system_id',
|
||||||
|
tag: 'select',
|
||||||
|
options: {
|
||||||
|
'10' => '10',
|
||||||
|
'11' => '11',
|
||||||
|
'12' => '12',
|
||||||
|
'13' => '13',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: '10',
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Fully Qualified Domain Name',
|
||||||
|
name: 'fqdn',
|
||||||
|
area: 'System::Base',
|
||||||
|
description: 'Defines the fully qualified domain name of the system. This setting is used as a variable, #{setting.fqdn} which is found in all forms of messaging used by the application, to build links to the tickets within your system.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: false,
|
||||||
|
name: 'fqdn',
|
||||||
|
tag: 'input',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: Setting.get('fqdn'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'websocket port',
|
||||||
|
name: 'websocket_port',
|
||||||
|
area: 'System::WebSocket',
|
||||||
|
description: 'Defines the port of the websocket server.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: false,
|
||||||
|
name: 'websocket_port',
|
||||||
|
tag: 'input',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: Setting.get('websocket_port'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'http type',
|
||||||
|
name: 'http_type',
|
||||||
|
area: 'System::Base',
|
||||||
|
description: 'Defines the type of protocol, used by the web server, to serve the application. If https protocol will be used instead of plain http, it must be specified in here. Since this has no affect on the web server\'s settings or behavior, it will not change the method of access to the application and, if it is wrong, it will not prevent you from logging into the application. This setting is used as a variable, #{setting.http_type} which is found in all forms of messaging used by the application, to build links to the tickets within your system.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'http_type',
|
||||||
|
tag: 'select',
|
||||||
|
options: {
|
||||||
|
'https' => 'https',
|
||||||
|
'http' => 'http',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: Setting.get('http_type'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: true
|
||||||
|
)
|
||||||
|
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Storage Mechanism',
|
||||||
|
name: 'storage',
|
||||||
|
area: 'System::Storage',
|
||||||
|
description: '"Database" stores all attachments in the database (not recommended for storing large amounts of data). "Filesystem" stores the data on the filesystem. You can switch between the modules even on a system that is already in production without any loss of data.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'storage',
|
||||||
|
tag: 'select',
|
||||||
|
options: {
|
||||||
|
'DB' => 'Database',
|
||||||
|
'FS' => 'Filesystem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: Setting.get('storage'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Max. Email Size',
|
||||||
|
name: 'postmaster_max_size',
|
||||||
|
area: 'Email::Base',
|
||||||
|
description: 'Maximal size in MB of emails.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'postmaster_max_size',
|
||||||
|
tag: 'select',
|
||||||
|
options: {
|
||||||
|
1 => 1,
|
||||||
|
2 => 2,
|
||||||
|
3 => 3,
|
||||||
|
4 => 4,
|
||||||
|
5 => 5,
|
||||||
|
6 => 6,
|
||||||
|
7 => 7,
|
||||||
|
8 => 8,
|
||||||
|
9 => 9,
|
||||||
|
10 => 10,
|
||||||
|
11 => 11,
|
||||||
|
12 => 12,
|
||||||
|
13 => 13,
|
||||||
|
14 => 14,
|
||||||
|
15 => 15,
|
||||||
|
16 => 16,
|
||||||
|
17 => 17,
|
||||||
|
18 => 18,
|
||||||
|
19 => 19,
|
||||||
|
20 => 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: Setting.get('postmaster_max_size'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Notification Sender',
|
||||||
|
name: 'notification_sender',
|
||||||
|
area: 'Email::Base',
|
||||||
|
description: 'Defines the sender of email notifications.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: false,
|
||||||
|
name: 'notification_sender',
|
||||||
|
tag: 'input',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: Setting.get('notification_sender'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Elasticsearch Endpoint URL',
|
||||||
|
name: 'es_url',
|
||||||
|
area: 'SearchIndex::Elasticsearch',
|
||||||
|
description: 'Define endpoint of Elastic Search.',
|
||||||
|
state: Setting.get('es_url'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Elasticsearch Endpoint User',
|
||||||
|
name: 'es_user',
|
||||||
|
area: 'SearchIndex::Elasticsearch',
|
||||||
|
description: 'Define http basic auth user of Elasticsearch.',
|
||||||
|
state: Setting.get('es_user'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Elastic Search Endpoint Password',
|
||||||
|
name: 'es_password',
|
||||||
|
area: 'SearchIndex::Elasticsearch',
|
||||||
|
description: 'Define http basic auth password of Elasticsearch.',
|
||||||
|
state: Setting.get('es_password'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Elastic Search Endpoint Index',
|
||||||
|
name: 'es_index',
|
||||||
|
area: 'SearchIndex::Elasticsearch',
|
||||||
|
description: 'Define Elasticsearch index name.',
|
||||||
|
state: Setting.get('es_index'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Elastic Search Attachment Extentions',
|
||||||
|
name: 'es_attachment_ignore',
|
||||||
|
area: 'SearchIndex::Elasticsearch',
|
||||||
|
description: 'Define attachment extentions which are ignored for Elasticsearch.',
|
||||||
|
state: Setting.get('es_attachment_ignore'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
Setting.create_or_update(
|
||||||
|
title: 'Elastic Search Attachment Size',
|
||||||
|
name: 'es_attachment_max_size_in_mb',
|
||||||
|
area: 'SearchIndex::Elasticsearch',
|
||||||
|
description: 'Define max. attachment size for Elasticsearch.',
|
||||||
|
state: Setting.get('es_attachment_max_size_in_mb'),
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
17
db/migrate/20150717000001_facebook_article_types.rb
Normal file
17
db/migrate/20150717000001_facebook_article_types.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
class FacebookArticleTypes < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
facebook_at = Ticket::Article::Type.find_by( name: 'facebook' )
|
||||||
|
|
||||||
|
return if !facebook_at
|
||||||
|
|
||||||
|
facebook_at.name = 'facebook feed post'
|
||||||
|
facebook_at.save
|
||||||
|
|
||||||
|
Ticket::Article::Type.create(
|
||||||
|
name: 'facebook feed comment',
|
||||||
|
communication: true,
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
83
db/seeds.rb
83
db/seeds.rb
|
@ -13,6 +13,7 @@ Setting.create_if_not_exists(
|
||||||
description: 'Defines if application is in init mode.',
|
description: 'Defines if application is in init mode.',
|
||||||
options: {},
|
options: {},
|
||||||
state: false,
|
state: false,
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: true
|
frontend: true
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -22,6 +23,7 @@ Setting.create_if_not_exists(
|
||||||
description: 'Defines if application is in developer mode (useful for developer, all users have the same password, password reset will work without email delivery).',
|
description: 'Defines if application is in developer mode (useful for developer, all users have the same password, password reset will work without email delivery).',
|
||||||
options: {},
|
options: {},
|
||||||
state: false,
|
state: false,
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: true
|
frontend: true
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -31,6 +33,7 @@ Setting.create_if_not_exists(
|
||||||
description: 'Defines if application is used as online service.',
|
description: 'Defines if application is used as online service.',
|
||||||
options: {},
|
options: {},
|
||||||
state: false,
|
state: false,
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: true
|
frontend: true
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -49,7 +52,7 @@ Setting.create_if_not_exists(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
preferences: { render: true, session_check: true, prio: 1 },
|
preferences: { render: true, session_check: true, prio: 1 },
|
||||||
state: 'Zammad',
|
state: 'Zammad Helpdesk',
|
||||||
frontend: true
|
frontend: true
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -75,7 +78,7 @@ Setting.create_if_not_exists(
|
||||||
title: 'Organization',
|
title: 'Organization',
|
||||||
name: 'organization',
|
name: 'organization',
|
||||||
area: 'System::Branding',
|
area: 'System::Branding',
|
||||||
description: 'Will be shown in the app and is included in email headers.',
|
description: 'Will be shown in the app and is included in email footers.',
|
||||||
options: {
|
options: {
|
||||||
form: [
|
form: [
|
||||||
{
|
{
|
||||||
|
@ -113,6 +116,7 @@ Setting.create_if_not_exists(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
state: '10',
|
state: '10',
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: true
|
frontend: true
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -131,6 +135,7 @@ Setting.create_if_not_exists(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
state: 'zammad.example.com',
|
state: 'zammad.example.com',
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: true
|
frontend: true
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -149,6 +154,7 @@ Setting.create_if_not_exists(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
state: '6042',
|
state: '6042',
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: true
|
frontend: true
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -171,6 +177,7 @@ Setting.create_if_not_exists(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
state: 'http',
|
state: 'http',
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: true
|
frontend: true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -194,35 +201,39 @@ Setting.create_if_not_exists(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
state: 'DB',
|
state: 'DB',
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
|
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
title: 'Geo Location Backend',
|
title: 'Image Service',
|
||||||
name: 'geo_location_backend',
|
name: 'image_backend',
|
||||||
area: 'System::Geo',
|
area: 'System::Services',
|
||||||
description: 'Defines the backend for geo location lookups.',
|
description: 'Defines the backend for user and organization image lookups.',
|
||||||
options: {
|
options: {
|
||||||
form: [
|
form: [
|
||||||
{
|
{
|
||||||
display: '',
|
display: '',
|
||||||
null: true,
|
null: true,
|
||||||
name: 'geo_location_backend',
|
name: 'image_backend',
|
||||||
tag: 'select',
|
tag: 'select',
|
||||||
options: {
|
options: {
|
||||||
'' => '-',
|
'' => '-',
|
||||||
'GeoLocation::Gmaps' => 'Google Maps',
|
'Service::Image::Zammad' => 'Zammad Image Service',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
state: 'GeoLocation::Gmaps',
|
state: 'Service::Image::Zammad',
|
||||||
|
preferences: { prio: 1 },
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
|
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
title: 'Geo IP Backend',
|
title: 'Geo IP Service',
|
||||||
name: 'geo_ip_backend',
|
name: 'geo_ip_backend',
|
||||||
area: 'System::Geo',
|
area: 'System::Services',
|
||||||
description: 'Defines the backend for geo ip lookups.',
|
description: 'Defines the backend for geo IP lookups. Show also location of an IP address if an IP address is shown.',
|
||||||
options: {
|
options: {
|
||||||
form: [
|
form: [
|
||||||
{
|
{
|
||||||
|
@ -232,12 +243,37 @@ Setting.create_if_not_exists(
|
||||||
tag: 'select',
|
tag: 'select',
|
||||||
options: {
|
options: {
|
||||||
'' => '-',
|
'' => '-',
|
||||||
'GeoIp::ZammadGeoIp' => 'Zammad GeoIP Service',
|
'Service::GeoIp::Zammad' => 'Zammad GeoIP Service',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
state: 'GeoIp::ZammadGeoIp',
|
state: 'Service::GeoIp::Zammad',
|
||||||
|
preferences: { prio: 2 },
|
||||||
|
frontend: false
|
||||||
|
)
|
||||||
|
|
||||||
|
Setting.create_if_not_exists(
|
||||||
|
title: 'Geo Location Service',
|
||||||
|
name: 'geo_location_backend',
|
||||||
|
area: 'System::Services',
|
||||||
|
description: 'Defines the backend for geo location lookups to store geo locations for addresses.',
|
||||||
|
options: {
|
||||||
|
form: [
|
||||||
|
{
|
||||||
|
display: '',
|
||||||
|
null: true,
|
||||||
|
name: 'geo_location_backend',
|
||||||
|
tag: 'select',
|
||||||
|
options: {
|
||||||
|
'' => '-',
|
||||||
|
'Service::GeoLocation::Gmaps' => 'Google Maps',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: 'Service::GeoLocation::Gmaps',
|
||||||
|
preferences: { prio: 3 },
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -245,7 +281,7 @@ Setting.create_if_not_exists(
|
||||||
title: 'Send client stats',
|
title: 'Send client stats',
|
||||||
name: 'ui_send_client_stats',
|
name: 'ui_send_client_stats',
|
||||||
area: 'System::UI',
|
area: 'System::UI',
|
||||||
description: 'Send client stats to central server.',
|
description: 'Send client stats/error message to central server to improve the usability.',
|
||||||
options: {
|
options: {
|
||||||
form: [
|
form: [
|
||||||
{
|
{
|
||||||
|
@ -261,6 +297,7 @@ Setting.create_if_not_exists(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
state: true,
|
state: true,
|
||||||
|
preferences: { prio: 1 },
|
||||||
frontend: true
|
frontend: true
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -283,6 +320,7 @@ Setting.create_if_not_exists(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
state: false,
|
state: false,
|
||||||
|
preferences: { prio: 2 },
|
||||||
frontend: true
|
frontend: true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1034,6 +1072,7 @@ Setting.create_if_not_exists(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
state: 10,
|
state: 10,
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1078,6 +1117,7 @@ Setting.create_if_not_exists(
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
state: 'Notification Master <noreply@#{config.fqdn}>',
|
state: 'Notification Master <noreply@#{config.fqdn}>',
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1140,6 +1180,7 @@ Setting.create_if_not_exists(
|
||||||
area: 'SearchIndex::Elasticsearch',
|
area: 'SearchIndex::Elasticsearch',
|
||||||
description: 'Define endpoint of Elastic Search.',
|
description: 'Define endpoint of Elastic Search.',
|
||||||
state: '',
|
state: '',
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -1148,6 +1189,7 @@ Setting.create_if_not_exists(
|
||||||
area: 'SearchIndex::Elasticsearch',
|
area: 'SearchIndex::Elasticsearch',
|
||||||
description: 'Define http basic auth user of Elasticsearch.',
|
description: 'Define http basic auth user of Elasticsearch.',
|
||||||
state: '',
|
state: '',
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -1156,6 +1198,7 @@ Setting.create_if_not_exists(
|
||||||
area: 'SearchIndex::Elasticsearch',
|
area: 'SearchIndex::Elasticsearch',
|
||||||
description: 'Define http basic auth password of Elasticsearch.',
|
description: 'Define http basic auth password of Elasticsearch.',
|
||||||
state: '',
|
state: '',
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -1164,6 +1207,7 @@ Setting.create_if_not_exists(
|
||||||
area: 'SearchIndex::Elasticsearch',
|
area: 'SearchIndex::Elasticsearch',
|
||||||
description: 'Define Elasticsearch index name.',
|
description: 'Define Elasticsearch index name.',
|
||||||
state: 'zammad',
|
state: 'zammad',
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -1172,6 +1216,7 @@ Setting.create_if_not_exists(
|
||||||
area: 'SearchIndex::Elasticsearch',
|
area: 'SearchIndex::Elasticsearch',
|
||||||
description: 'Define attachment extentions which are ignored for Elasticsearch.',
|
description: 'Define attachment extentions which are ignored for Elasticsearch.',
|
||||||
state: [ '.png', '.jpg', '.jpeg', '.mpeg', '.mpg', '.mov', '.bin', '.exe', '.box', '.mbox' ],
|
state: [ '.png', '.jpg', '.jpeg', '.mpeg', '.mpg', '.mov', '.bin', '.exe', '.box', '.mbox' ],
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
Setting.create_if_not_exists(
|
Setting.create_if_not_exists(
|
||||||
|
@ -1180,6 +1225,7 @@ Setting.create_if_not_exists(
|
||||||
area: 'SearchIndex::Elasticsearch',
|
area: 'SearchIndex::Elasticsearch',
|
||||||
description: 'Define max. attachment size for Elasticsearch.',
|
description: 'Define max. attachment size for Elasticsearch.',
|
||||||
state: 50,
|
state: 50,
|
||||||
|
preferences: { online_service_disable: true },
|
||||||
frontend: false
|
frontend: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1455,9 +1501,10 @@ Ticket::Article::Type.create_if_not_exists( id: 4, name: 'fax', communication: t
|
||||||
Ticket::Article::Type.create_if_not_exists( id: 5, name: 'phone', communication: true )
|
Ticket::Article::Type.create_if_not_exists( id: 5, name: 'phone', communication: true )
|
||||||
Ticket::Article::Type.create_if_not_exists( id: 6, name: 'twitter status', communication: true )
|
Ticket::Article::Type.create_if_not_exists( id: 6, name: 'twitter status', communication: true )
|
||||||
Ticket::Article::Type.create_if_not_exists( id: 7, name: 'twitter direct-message', communication: true )
|
Ticket::Article::Type.create_if_not_exists( id: 7, name: 'twitter direct-message', communication: true )
|
||||||
Ticket::Article::Type.create_if_not_exists( id: 8, name: 'facebook', communication: true )
|
Ticket::Article::Type.create_if_not_exists( id: 8, name: 'facebook feed post', communication: true )
|
||||||
Ticket::Article::Type.create_if_not_exists( id: 9, name: 'note', communication: false )
|
Ticket::Article::Type.create_if_not_exists( id: 9, name: 'facebook feed comment', communication: true )
|
||||||
Ticket::Article::Type.create_if_not_exists( id: 10, name: 'web', communication: true )
|
Ticket::Article::Type.create_if_not_exists( id: 10, name: 'note', communication: false )
|
||||||
|
Ticket::Article::Type.create_if_not_exists( id: 11, name: 'web', communication: true )
|
||||||
|
|
||||||
Ticket::Article::Sender.create_if_not_exists( id: 1, name: 'Agent' )
|
Ticket::Article::Sender.create_if_not_exists( id: 1, name: 'Agent' )
|
||||||
Ticket::Article::Sender.create_if_not_exists( id: 2, name: 'Customer' )
|
Ticket::Article::Sender.create_if_not_exists( id: 2, name: 'Customer' )
|
||||||
|
|
|
@ -111,7 +111,7 @@ returns
|
||||||
|
|
||||||
# fetch org logo
|
# fetch org logo
|
||||||
if admin_user.email
|
if admin_user.email
|
||||||
Zammad::BigData::Organization.suggest_system_image(admin_user.email)
|
Service::Image.organization_suggest(admin_user.email)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -130,18 +130,19 @@ class Facebook
|
||||||
ticket.save
|
ticket.save
|
||||||
end
|
end
|
||||||
|
|
||||||
user = to_user(comment)
|
user = to_user(post)
|
||||||
return if !user
|
return if !user
|
||||||
|
|
||||||
feed_post = {
|
feed_post = {
|
||||||
from: user.name,
|
from: "#{user.firstname} #{user.lastname}",
|
||||||
body: post['message'],
|
body: post['message'],
|
||||||
message_id: post['id'],
|
message_id: post['id'],
|
||||||
type: Ticket::Article::Type.find_by( name: 'facebook feed post' ),
|
type_id: Ticket::Article::Type.find_by( name: 'facebook feed post' ).id,
|
||||||
}
|
}
|
||||||
articles = []
|
articles = []
|
||||||
articles.push( feed_post )
|
articles.push( feed_post )
|
||||||
|
|
||||||
|
if post['comments']
|
||||||
post['comments']['data'].each { |comment|
|
post['comments']['data'].each { |comment|
|
||||||
|
|
||||||
user = to_user(comment)
|
user = to_user(comment)
|
||||||
|
@ -149,18 +150,21 @@ class Facebook
|
||||||
next if !user
|
next if !user
|
||||||
|
|
||||||
post_comment = {
|
post_comment = {
|
||||||
from: user.name,
|
from: "#{user.firstname} #{user.lastname}",
|
||||||
body: comment['message'],
|
body: comment['message'],
|
||||||
message_id: comment['id'],
|
message_id: comment['id'],
|
||||||
type: Ticket::Article::Type.find_by( name: 'facebook feed comment' ),
|
type_id: Ticket::Article::Type.find_by( name: 'facebook feed comment' ).id,
|
||||||
}
|
}
|
||||||
articles.push( post_comment )
|
articles.push( post_comment )
|
||||||
|
|
||||||
# TODO: sub-comments
|
# TODO: sub-comments
|
||||||
# comment_data = @client.get_object( comment['id'] )
|
# comment_data = @client.get_object( comment['id'] )
|
||||||
}
|
}
|
||||||
|
end
|
||||||
|
|
||||||
articles.invert.each { |article|
|
inverted_articles = articles.reverse
|
||||||
|
|
||||||
|
inverted_articles.each { |article|
|
||||||
|
|
||||||
break if Ticket::Article.find_by( message_id: article[:message_id] )
|
break if Ticket::Article.find_by( message_id: article[:message_id] )
|
||||||
|
|
||||||
|
@ -168,6 +172,9 @@ class Facebook
|
||||||
to: @account['name'],
|
to: @account['name'],
|
||||||
ticket_id: ticket.id,
|
ticket_id: ticket.id,
|
||||||
internal: false,
|
internal: false,
|
||||||
|
sender_id: Ticket::Article::Sender.lookup( name: 'Customer' ).id,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
}.merge( article )
|
}.merge( article )
|
||||||
|
|
||||||
Ticket::Article.create( article )
|
Ticket::Article.create( article )
|
||||||
|
@ -204,12 +211,11 @@ class Facebook
|
||||||
def from_article(article)
|
def from_article(article)
|
||||||
|
|
||||||
post = nil
|
post = nil
|
||||||
# TODO: article[:type] == 'facebook feed post'
|
|
||||||
if article[:type] == 'facebook feed comment'
|
if article[:type] == 'facebook feed comment'
|
||||||
|
|
||||||
Rails.logger.debug 'Create feed comment from article...'
|
Rails.logger.debug 'Create feed comment from article...'
|
||||||
|
|
||||||
post = @client.put_wall_post(article[:body], {}, article[:in_reply_to])
|
post = @client.put_comment(article[:in_reply_to], article[:body])
|
||||||
else
|
else
|
||||||
fail "Can't handle unknown facebook article type '#{article[:type]}'."
|
fail "Can't handle unknown facebook article type '#{article[:type]}'."
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
|
||||||
|
|
||||||
class GeoLocation
|
|
||||||
include ApplicationLib
|
|
||||||
|
|
||||||
=begin
|
|
||||||
|
|
||||||
lookup lat and lng for address
|
|
||||||
|
|
||||||
result = GeoLocation.geocode( 'Marienstrasse 13, 10117 Berlin' )
|
|
||||||
|
|
||||||
returns
|
|
||||||
|
|
||||||
result = [ 4.21312, 1.3123 ]
|
|
||||||
|
|
||||||
=end
|
|
||||||
|
|
||||||
def self.geocode(address)
|
|
||||||
|
|
||||||
# load backend
|
|
||||||
backend = load_adapter_by_setting( 'geo_location_backend' )
|
|
||||||
return if !backend
|
|
||||||
|
|
||||||
# db lookup
|
|
||||||
backend.geocode(address)
|
|
||||||
end
|
|
||||||
|
|
||||||
=begin
|
|
||||||
|
|
||||||
lookup address for lat and lng
|
|
||||||
|
|
||||||
result = GeoLocation.reverse_geocode( 4.21312, 1.3123 )
|
|
||||||
|
|
||||||
returns
|
|
||||||
|
|
||||||
result = 'some address'
|
|
||||||
|
|
||||||
=end
|
|
||||||
|
|
||||||
def self.reverse_geocode(lat, lng)
|
|
||||||
|
|
||||||
# load backend
|
|
||||||
backend = load_adapter_by_setting( 'geo_location_backend' )
|
|
||||||
return if !backend
|
|
||||||
|
|
||||||
# db lookup
|
|
||||||
backend.reverse_geocode(lat, lng)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
module Service
|
||||||
class GeoIp
|
class GeoIp
|
||||||
include ApplicationLib
|
include ApplicationLib
|
||||||
|
|
||||||
|
@ -7,7 +8,7 @@ class GeoIp
|
||||||
|
|
||||||
lookup location based on ip or hostname
|
lookup location based on ip or hostname
|
||||||
|
|
||||||
result = GeoIp.location( '172.0.0.1' )
|
result = Service::GeoIp.location( '172.0.0.1' )
|
||||||
|
|
||||||
returns
|
returns
|
||||||
|
|
||||||
|
@ -37,3 +38,4 @@ returns
|
||||||
backend.location(address)
|
backend.location(address)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'cache'
|
require 'cache'
|
||||||
|
|
||||||
class GeoIp::ZammadGeoIp
|
class Service::GeoIp::Zammad
|
||||||
def self.location(address)
|
def self.location(address)
|
||||||
|
|
||||||
# check cache
|
# check cache
|
51
lib/service/geo_location.rb
Normal file
51
lib/service/geo_location.rb
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
module Service
|
||||||
|
class GeoLocation
|
||||||
|
include ApplicationLib
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
lookup lat and lng for address
|
||||||
|
|
||||||
|
result = Service::GeoLocation.geocode( 'Marienstrasse 13, 10117 Berlin' )
|
||||||
|
|
||||||
|
returns
|
||||||
|
|
||||||
|
result = [ 4.21312, 1.3123 ]
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def self.geocode(address)
|
||||||
|
|
||||||
|
# load backend
|
||||||
|
backend = load_adapter_by_setting( 'geo_location_backend' )
|
||||||
|
return if !backend
|
||||||
|
|
||||||
|
# db lookup
|
||||||
|
backend.geocode(address)
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
lookup address for lat and lng
|
||||||
|
|
||||||
|
result = GeoLocation.reverse_geocode( 4.21312, 1.3123 )
|
||||||
|
|
||||||
|
returns
|
||||||
|
|
||||||
|
result = 'some address'
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def self.reverse_geocode(lat, lng)
|
||||||
|
|
||||||
|
# load backend
|
||||||
|
backend = load_adapter_by_setting( 'geo_location_backend' )
|
||||||
|
return if !backend
|
||||||
|
|
||||||
|
# db lookup
|
||||||
|
backend.reverse_geocode(lat, lng)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,6 @@
|
||||||
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
class GeoLocation::Gmaps
|
class Service::GeoLocation::Gmaps
|
||||||
|
|
||||||
def self.geocode(address)
|
def self.geocode(address)
|
||||||
url = "http://maps.googleapis.com/maps/api/geocode/json?address=#{CGI.escape address}&sensor=true"
|
url = "http://maps.googleapis.com/maps/api/geocode/json?address=#{CGI.escape address}&sensor=true"
|
79
lib/service/image.rb
Normal file
79
lib/service/image.rb
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
module Service
|
||||||
|
class Image
|
||||||
|
include ApplicationLib
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
lookup user image based on email address
|
||||||
|
|
||||||
|
file = Service::Image.user( 'skywalker@zammad.org' )
|
||||||
|
|
||||||
|
returns
|
||||||
|
|
||||||
|
{
|
||||||
|
content: content,
|
||||||
|
mime_type: mime_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def self.user(address)
|
||||||
|
|
||||||
|
# load backend
|
||||||
|
backend = load_adapter_by_setting( 'image_backend' )
|
||||||
|
return if !backend
|
||||||
|
|
||||||
|
backend.user(address)
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
lookup organization image based on domain
|
||||||
|
|
||||||
|
file = Service::Image.organization('edenhofer.de')
|
||||||
|
|
||||||
|
file = Service::Image.organization('user@edenhofer.de') # will just use domain
|
||||||
|
|
||||||
|
returns
|
||||||
|
|
||||||
|
{
|
||||||
|
content: content,
|
||||||
|
mime_type: mime_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def self.organization(domain)
|
||||||
|
|
||||||
|
# load backend
|
||||||
|
backend = load_adapter_by_setting( 'image_backend' )
|
||||||
|
return if !backend
|
||||||
|
|
||||||
|
backend.organization(domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
find organization image suggestion
|
||||||
|
|
||||||
|
result = Service::Image.organization_suggest('edenhofer.de')
|
||||||
|
|
||||||
|
returns
|
||||||
|
|
||||||
|
true # or false
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def self.organization_suggest(domain)
|
||||||
|
|
||||||
|
# load backend
|
||||||
|
backend = load_adapter_by_setting( 'image_backend' )
|
||||||
|
return if !backend
|
||||||
|
|
||||||
|
backend.organization_suggest(domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
74
lib/service/image/zammad.rb
Normal file
74
lib/service/image/zammad.rb
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
class Service::Image::Zammad
|
||||||
|
|
||||||
|
# rubocop:disable Style/ClassVars
|
||||||
|
@@api_host = 'https://images.zammad.com'
|
||||||
|
@@open_timeout = 4
|
||||||
|
@@read_timeout = 6
|
||||||
|
|
||||||
|
def self.user(email)
|
||||||
|
|
||||||
|
# fetch image
|
||||||
|
response = UserAgent.post(
|
||||||
|
"#{@@api_host}/api/v1/person/image",
|
||||||
|
{
|
||||||
|
email: email,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
open_timeout: @@open_timeout,
|
||||||
|
read_timeout: @@read_timeout,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if !response.success?
|
||||||
|
Rails.logger.info "Can't fetch image for '#{email}' (maybe no avatar available), http code: #{response.code}"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
Rails.logger.info "Fetched image for '#{email}', http code: #{response.code}"
|
||||||
|
mime_type = 'image/jpeg'
|
||||||
|
{
|
||||||
|
content: response.body,
|
||||||
|
mime_type: mime_type,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.organization(domain)
|
||||||
|
|
||||||
|
# strip, just use domain name
|
||||||
|
domain = domain.sub(/^.+?@(.+?)$/, '\1')
|
||||||
|
|
||||||
|
# fetch org logo
|
||||||
|
response = UserAgent.post(
|
||||||
|
"#{@@api_host}/api/v1/organization/image",
|
||||||
|
{
|
||||||
|
domain: domain
|
||||||
|
},
|
||||||
|
{
|
||||||
|
open_timeout: @@open_timeout,
|
||||||
|
read_timeout: @@read_timeout,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if !response.success?
|
||||||
|
Rails.logger.info "Can't fetch image for '#{domain}' (maybe no avatar available), http code: #{response.code}"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
Rails.logger.info "Fetched image for '#{domain}', http code: #{response.code}"
|
||||||
|
mime_type = 'image/png'
|
||||||
|
|
||||||
|
{
|
||||||
|
content: response.body,
|
||||||
|
mime_type: mime_type,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.organization_suggest(domain)
|
||||||
|
image = organization(domain)
|
||||||
|
return false if !image
|
||||||
|
|
||||||
|
# store image 1:1
|
||||||
|
product_logo = StaticAssets.store_raw( image[:content], image[:mime_type] )
|
||||||
|
Setting.set('product_logo', product_logo)
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
17
lib/tweet.rb
17
lib/tweet.rb
|
@ -97,7 +97,15 @@ class Tweet
|
||||||
Rails.logger.debug group_id.inspect
|
Rails.logger.debug group_id.inspect
|
||||||
|
|
||||||
if tweet.class.to_s == 'Twitter::DirectMessage'
|
if tweet.class.to_s == 'Twitter::DirectMessage'
|
||||||
|
|
||||||
|
article = Ticket::Article.find_by(
|
||||||
|
from: 'me_bauer',
|
||||||
|
type_id: Ticket::Article::Type.find_by( name: 'twitter direct-message' ).id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if article
|
||||||
ticket = Ticket.find_by(
|
ticket = Ticket.find_by(
|
||||||
|
id: article.ticket_id,
|
||||||
customer_id: user.id,
|
customer_id: user.id,
|
||||||
state: Ticket::State.where.not(
|
state: Ticket::State.where.not(
|
||||||
state_type_id: Ticket::StateType.where(
|
state_type_id: Ticket::StateType.where(
|
||||||
|
@ -107,13 +115,14 @@ class Tweet
|
||||||
)
|
)
|
||||||
return ticket if ticket
|
return ticket if ticket
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Ticket.create(
|
Ticket.create(
|
||||||
customer_id: user.id,
|
customer_id: user.id,
|
||||||
title: "#{tweet.text[0, 37]}...",
|
title: "#{tweet.text[0, 37]}...",
|
||||||
group_id: group_id,
|
group_id: group_id,
|
||||||
state: Ticket::State.find_by( name: 'new' ),
|
state_id: Ticket::State.find_by( name: 'new' ).id,
|
||||||
priority: Ticket::Priority.find_by( name: '2 normal' ),
|
priority_id: Ticket::Priority.find_by( name: '2 normal' ).id,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -153,8 +162,8 @@ class Tweet
|
||||||
message_id: tweet.id,
|
message_id: tweet.id,
|
||||||
ticket_id: ticket.id,
|
ticket_id: ticket.id,
|
||||||
in_reply_to: in_reply_to,
|
in_reply_to: in_reply_to,
|
||||||
type: Ticket::Article::Type.find_by( name: article_type ),
|
type_id: Ticket::Article::Type.find_by( name: article_type ).id,
|
||||||
sender: Ticket::Article::Sender.find_by( name: 'Customer' ),
|
sender_id: Ticket::Article::Sender.find_by( name: 'Customer' ).id,
|
||||||
internal: false,
|
internal: false,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
module Zammad
|
|
||||||
module BigData
|
|
||||||
class Base
|
|
||||||
# rubocop:disable Style/ClassVars
|
|
||||||
@@api_host = 'https://bigdata.zammad.com'
|
|
||||||
@@open_timeout = 4
|
|
||||||
@@read_timeout = 6
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,69 +0,0 @@
|
||||||
module Zammad
|
|
||||||
module BigData
|
|
||||||
class Organization < Zammad::BigData::Base
|
|
||||||
|
|
||||||
=begin
|
|
||||||
|
|
||||||
file = Zammad::BigData::Organization.image('edenhofer.de')
|
|
||||||
|
|
||||||
file = Zammad::BigData::Organization.image('user@edenhofer.de') # will just use domain
|
|
||||||
|
|
||||||
returns
|
|
||||||
|
|
||||||
{
|
|
||||||
content: content,
|
|
||||||
mime_type: mime_type,
|
|
||||||
}
|
|
||||||
|
|
||||||
=end
|
|
||||||
def self.image(domain)
|
|
||||||
|
|
||||||
# strip, just use domain name
|
|
||||||
domain = domain.sub(/^.+?@(.+?)$/, '\1')
|
|
||||||
|
|
||||||
# fetch org logo
|
|
||||||
response = UserAgent.post(
|
|
||||||
"#{@@api_host}/api/v1/organization/image",
|
|
||||||
{
|
|
||||||
domain: domain
|
|
||||||
},
|
|
||||||
{
|
|
||||||
open_timeout: @@open_timeout,
|
|
||||||
read_timeout: @@read_timeout,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if !response.success?
|
|
||||||
Rails.logger.info "Can't fetch image for '#{domain}' (maybe no avatar available), http code: #{response.code}"
|
|
||||||
return
|
|
||||||
end
|
|
||||||
Rails.logger.info "Fetched image for '#{domain}', http code: #{response.code}"
|
|
||||||
mime_type = 'image/png'
|
|
||||||
|
|
||||||
{
|
|
||||||
content: response.body,
|
|
||||||
mime_type: mime_type,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
=begin
|
|
||||||
|
|
||||||
result = Zammad::BigData::Organization.suggest_system_image('edenhofer.de')
|
|
||||||
|
|
||||||
returns
|
|
||||||
|
|
||||||
true # or false
|
|
||||||
|
|
||||||
=end
|
|
||||||
def self.suggest_system_image(domain)
|
|
||||||
image = self.image(domain)
|
|
||||||
return false if !image
|
|
||||||
|
|
||||||
# store image 1:1
|
|
||||||
product_logo = StaticAssets.store_raw( image[:content], image[:mime_type] )
|
|
||||||
Setting.set('product_logo', product_logo)
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,45 +0,0 @@
|
||||||
module Zammad
|
|
||||||
module BigData
|
|
||||||
class User < Zammad::BigData::Base
|
|
||||||
|
|
||||||
=begin
|
|
||||||
|
|
||||||
file = Zammad::BigData::User.image('client@edenhofer.de')
|
|
||||||
|
|
||||||
returns
|
|
||||||
|
|
||||||
{
|
|
||||||
content: content,
|
|
||||||
mime_type: mime_type,
|
|
||||||
}
|
|
||||||
|
|
||||||
=end
|
|
||||||
|
|
||||||
def self.image(email)
|
|
||||||
|
|
||||||
# fetch logo
|
|
||||||
response = UserAgent.post(
|
|
||||||
"#{@@api_host}/api/v1/person/image",
|
|
||||||
{
|
|
||||||
email: email,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
open_timeout: @@open_timeout,
|
|
||||||
read_timeout: @@read_timeout,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if !response.success?
|
|
||||||
Rails.logger.info "Can't fetch image for '#{email}' (maybe no avatar available), http code: #{response.code}"
|
|
||||||
return
|
|
||||||
end
|
|
||||||
Rails.logger.info "Fetched image for '#{email}', http code: #{response.code}"
|
|
||||||
mime_type = 'image/jpeg'
|
|
||||||
{
|
|
||||||
content: response.body,
|
|
||||||
mime_type: mime_type,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -50,6 +50,15 @@ sleep 15
|
||||||
#export REMOTE_URL='http://192.168.178.32:4444/wd/hub'
|
#export REMOTE_URL='http://192.168.178.32:4444/wd/hub'
|
||||||
#export REMOTE_URL='http://192.168.178.45:4444/wd/hub'
|
#export REMOTE_URL='http://192.168.178.45:4444/wd/hub'
|
||||||
|
|
||||||
|
export RAILS_ENV=test
|
||||||
|
|
||||||
|
echo "rake db:drop"
|
||||||
|
time rake db:drop
|
||||||
|
echo "rake db:create"
|
||||||
|
time rake db:create
|
||||||
|
echo "rake db:migrate"
|
||||||
|
time rake db:migrate
|
||||||
|
|
||||||
rake test:browser["BROWSER_URL=http://localhost:4444"]
|
rake test:browser["BROWSER_URL=http://localhost:4444"]
|
||||||
#rake test:browser["BROWSER_URL=http://localhost:4444 BROWSER=chrome"]
|
#rake test:browser["BROWSER_URL=http://localhost:4444 BROWSER=chrome"]
|
||||||
#rake test:browser["BROWSER_URL=http://192.168.178.28:4444"]
|
#rake test:browser["BROWSER_URL=http://192.168.178.28:4444"]
|
||||||
|
|
|
@ -59,11 +59,10 @@ if ARGV[0] != 'start' && ARGV[0] != 'stop'
|
||||||
exit
|
exit
|
||||||
end
|
end
|
||||||
|
|
||||||
puts "Starting websocket server on #{@options[:b]}:#{@options[:p]} (secure:#{@options[:s]},pid:#{@options[:i]})"
|
|
||||||
#puts options.inspect
|
|
||||||
|
|
||||||
if ARGV[0] == 'stop'
|
if ARGV[0] == 'stop'
|
||||||
|
|
||||||
|
puts "Stopping websocket server (pid:#{@options[:i]})"
|
||||||
|
|
||||||
# read pid
|
# read pid
|
||||||
pid = File.open( @options[:i].to_s ).read
|
pid = File.open( @options[:i].to_s ).read
|
||||||
pid.gsub!(/\r|\n/, '')
|
pid.gsub!(/\r|\n/, '')
|
||||||
|
@ -74,6 +73,8 @@ if ARGV[0] == 'stop'
|
||||||
end
|
end
|
||||||
if ARGV[0] == 'start' && @options[:d]
|
if ARGV[0] == 'start' && @options[:d]
|
||||||
|
|
||||||
|
puts "Starting websocket server on #{@options[:b]}:#{@options[:p]} (secure:#{@options[:s]},pid:#{@options[:i]})"
|
||||||
|
|
||||||
Daemons.daemonize
|
Daemons.daemonize
|
||||||
|
|
||||||
# create pid file
|
# create pid file
|
||||||
|
|
|
@ -206,7 +206,7 @@ class AgentTicketActionsLevel3Test < TestCase
|
||||||
css: '.content.active .js-reset',
|
css: '.content.active .js-reset',
|
||||||
browser: browser2,
|
browser: browser2,
|
||||||
)
|
)
|
||||||
sleep 2
|
sleep 5
|
||||||
ticket_verify(
|
ticket_verify(
|
||||||
browser: browser2,
|
browser: browser2,
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -104,11 +104,11 @@ class AgentTicketOverviewLevel0Test < TestCase
|
||||||
|
|
||||||
# check if number and article count is shown
|
# check if number and article count is shown
|
||||||
match(
|
match(
|
||||||
css: '.active table th:nth-child(3)',
|
css: '.active table th:nth-child(7)',
|
||||||
value: '#',
|
value: '#',
|
||||||
)
|
)
|
||||||
match(
|
match(
|
||||||
css: '.active table th:nth-child(8)',
|
css: '.active table th:nth-child(4)',
|
||||||
value: 'Article#',
|
value: 'Article#',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -118,11 +118,11 @@ class AgentTicketOverviewLevel0Test < TestCase
|
||||||
|
|
||||||
# check if number and article count is shown
|
# check if number and article count is shown
|
||||||
match(
|
match(
|
||||||
css: '.active table th:nth-child(3)',
|
css: '.active table th:nth-child(7)',
|
||||||
value: '#',
|
value: '#',
|
||||||
)
|
)
|
||||||
match(
|
match(
|
||||||
css: '.active table th:nth-child(8)',
|
css: '.active table th:nth-child(4)',
|
||||||
value: 'Article#',
|
value: 'Article#',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,22 @@ class TestCase < Test::Unit::TestCase
|
||||||
ENV['BROWSER'] || 'firefox'
|
ENV['BROWSER'] || 'firefox'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def profile
|
||||||
|
browser_profile = nil
|
||||||
|
if browser == 'firefox'
|
||||||
|
browser_profile = Selenium::WebDriver::Firefox::Profile.new
|
||||||
|
|
||||||
|
browser_profile['intl.locale.matchOS'] = false
|
||||||
|
browser_profile['intl.accept_languages'] = 'en-US'
|
||||||
|
browser_profile['general.useragent.locale'] = 'en-US'
|
||||||
|
elsif browser == 'chrome'
|
||||||
|
browser_profile = Selenium::WebDriver::Chrome::Profile.new
|
||||||
|
|
||||||
|
browser_profile['intl.accept_languages'] = 'en'
|
||||||
|
end
|
||||||
|
browser_profile
|
||||||
|
end
|
||||||
|
|
||||||
def browser_support_cookies
|
def browser_support_cookies
|
||||||
if browser =~ /(internet_explorer|ie)/i
|
if browser =~ /(internet_explorer|ie)/i
|
||||||
return false
|
return false
|
||||||
|
@ -25,7 +41,7 @@ class TestCase < Test::Unit::TestCase
|
||||||
@browsers = {}
|
@browsers = {}
|
||||||
end
|
end
|
||||||
if !ENV['REMOTE_URL'] || ENV['REMOTE_URL'].empty?
|
if !ENV['REMOTE_URL'] || ENV['REMOTE_URL'].empty?
|
||||||
local_browser = Selenium::WebDriver.for( browser.to_sym )
|
local_browser = Selenium::WebDriver.for( browser.to_sym, profile: profile )
|
||||||
browser_instance_preferences(local_browser)
|
browser_instance_preferences(local_browser)
|
||||||
@browsers[local_browser.hash] = local_browser
|
@browsers[local_browser.hash] = local_browser
|
||||||
return local_browser
|
return local_browser
|
||||||
|
@ -1226,7 +1242,7 @@ wait untill text in selector disabppears
|
||||||
found = nil
|
found = nil
|
||||||
(1..10).each {
|
(1..10).each {
|
||||||
|
|
||||||
next if found
|
break if found
|
||||||
|
|
||||||
begin
|
begin
|
||||||
text = instance.find_elements( { css: '.content.active .js-reset' } )[0].text
|
text = instance.find_elements( { css: '.content.active .js-reset' } )[0].text
|
||||||
|
@ -1240,6 +1256,7 @@ wait untill text in selector disabppears
|
||||||
}
|
}
|
||||||
if !found
|
if !found
|
||||||
screenshot( browser: instance, comment: 'ticket_update_discard_message_failed' )
|
screenshot( browser: instance, comment: 'ticket_update_discard_message_failed' )
|
||||||
|
|
||||||
fail 'no discard message found'
|
fail 'no discard message found'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
154
test/integration/facebook_test.rb
Normal file
154
test/integration/facebook_test.rb
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
require 'integration_test_helper'
|
||||||
|
|
||||||
|
class FacebookTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
|
# set system mode to done / to activate
|
||||||
|
Setting.set('system_init_done', true)
|
||||||
|
|
||||||
|
# needed to check correct behavior
|
||||||
|
Group.create_if_not_exists(
|
||||||
|
id: 2,
|
||||||
|
name: 'Facebook',
|
||||||
|
note: 'All Facebook feed posts.',
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1
|
||||||
|
)
|
||||||
|
|
||||||
|
provider_key = 'CAACEdEose0cBAC56WJvrGb5avKbTlH0c7P4xZCZBfT8zG4nkgEWeFKGnnpNZC8xeedXzmqZCxEUrAumX245T4MborvAmRW52PSpuDiXwXXSMjYaZCJOih5v6CsP3xrZAfGxhPWBbI8dSoquBv8eRbUAMSir9SDSoDeKJSdSfhuytqx5wfveE8YibzT2ZAwYz0d7d2QZAN4b10d9j9UpBhXCCCahj4hyk9JQZD'
|
||||||
|
consumer_key = 'CAACEdEose0cBAHZCXAQ68snZBf2C7jT6G7pVXaWajbZCZAZAFWRZAVUb9FAMXHZBQECZBX0iL5qOeTsZA0mnR0586XTq9vYiWP8Y3qCzftrd9hnsP7J9VB6APnR67NEdY8SozxIFtctQA9Xp4Lb8lbxBmig2v5oXRIH513kImPYXJoCFUlQs0aJeZBCtRG6BekfPs5GPZB8tieQE3yGgtZBTZA3HI2TtQLZBNXyLAZD'
|
||||||
|
|
||||||
|
provider_page_name = 'Hansi Merkurs Hutfabrik'
|
||||||
|
provider_options = {
|
||||||
|
auth: {
|
||||||
|
access_token: provider_key
|
||||||
|
},
|
||||||
|
sync: {
|
||||||
|
page: provider_page_name,
|
||||||
|
group_id: 2,
|
||||||
|
limit: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# add channel
|
||||||
|
current = Channel.where( adapter: 'Facebook' )
|
||||||
|
current.each(&:destroy)
|
||||||
|
Channel.create(
|
||||||
|
adapter: 'Facebook',
|
||||||
|
area: 'Facebook::Inbound',
|
||||||
|
options: provider_options,
|
||||||
|
active: true,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test 'pages' do
|
||||||
|
|
||||||
|
provider_options_clone = provider_options
|
||||||
|
|
||||||
|
provider_options_clone[:sync].delete(:page)
|
||||||
|
|
||||||
|
facebook = Facebook.new( provider_options_clone )
|
||||||
|
|
||||||
|
pages = facebook.pages
|
||||||
|
|
||||||
|
page_found = false
|
||||||
|
pages.each { |page|
|
||||||
|
|
||||||
|
next if page[:name] != provider_page_name
|
||||||
|
page_found = true
|
||||||
|
}
|
||||||
|
|
||||||
|
assert( page_found, "Page lookup for '#{provider_page_name}'" )
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'feed post to ticket' do
|
||||||
|
|
||||||
|
consumer_client = Koala::Facebook::API.new( consumer_key )
|
||||||
|
feed_post = "I've got an issue with my hat, serial number ##{rand(9999)}"
|
||||||
|
|
||||||
|
facebook = Facebook.new( provider_options )
|
||||||
|
|
||||||
|
post = consumer_client.put_wall_post(feed_post, {}, facebook.account['id'])
|
||||||
|
|
||||||
|
# fetch check system account
|
||||||
|
Channel.fetch
|
||||||
|
|
||||||
|
# check if first article has been created
|
||||||
|
article = Ticket::Article.find_by( message_id: post['id'] )
|
||||||
|
|
||||||
|
assert( article, "article post '#{post['id']}' imported" )
|
||||||
|
assert_equal( article.body, feed_post, 'ticket article inbound body' )
|
||||||
|
assert_equal( 1, article.ticket.articles.count, 'ticket article inbound count' )
|
||||||
|
assert_equal( feed_post, article.ticket.articles.last.body, 'ticket article inbound body' )
|
||||||
|
|
||||||
|
post_comment = "Any updates yet? It's urgent. I love my hat."
|
||||||
|
comment = consumer_client.put_comment(post['id'], post_comment)
|
||||||
|
|
||||||
|
# fetch check system account
|
||||||
|
Channel.fetch
|
||||||
|
|
||||||
|
# check if second article has been created
|
||||||
|
article = Ticket::Article.find_by( message_id: comment['id'] )
|
||||||
|
|
||||||
|
assert( article, "article comment '#{comment['id']}' imported" )
|
||||||
|
assert_equal( article.body, post_comment, 'ticket article inbound body' )
|
||||||
|
assert_equal( 2, article.ticket.articles.count, 'ticket article inbound count' )
|
||||||
|
assert_equal( post_comment, article.ticket.articles.last.body, 'ticket article inbound body' )
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'feed post and comment reply' do
|
||||||
|
|
||||||
|
consumer_client = Koala::Facebook::API.new( consumer_key )
|
||||||
|
feed_post = "I've got an issue with my hat, serial number ##{rand(9999)}"
|
||||||
|
|
||||||
|
facebook = Facebook.new( provider_options )
|
||||||
|
|
||||||
|
post = consumer_client.put_wall_post(feed_post, {}, facebook.account['id'])
|
||||||
|
|
||||||
|
# fetch check system account
|
||||||
|
Channel.fetch
|
||||||
|
|
||||||
|
# check if first article has been created
|
||||||
|
article = Ticket::Article.find_by( message_id: post['id'] )
|
||||||
|
|
||||||
|
reply_text = "What's your issue Bernd?"
|
||||||
|
|
||||||
|
# reply via ticket
|
||||||
|
outbound_article = Ticket::Article.create(
|
||||||
|
ticket_id: article.ticket.id,
|
||||||
|
body: reply_text,
|
||||||
|
in_reply_to: post['id'],
|
||||||
|
type: Ticket::Article::Type.find_by( name: 'facebook feed comment' ),
|
||||||
|
sender: Ticket::Article::Sender.find_by( name: 'Agent' ),
|
||||||
|
internal: false,
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
assert( outbound_article, 'outbound article created' )
|
||||||
|
assert_equal( outbound_article.ticket.articles.count, 2, 'ticket article outbound count' )
|
||||||
|
|
||||||
|
post_comment = 'The peacock feather is fallen off.'
|
||||||
|
comment = consumer_client.put_comment(post['id'], post_comment)
|
||||||
|
|
||||||
|
# fetch check system account
|
||||||
|
Channel.fetch
|
||||||
|
|
||||||
|
reply_text = "Please send it to our address and add the ticket number #{article.ticket.number}."
|
||||||
|
|
||||||
|
# reply via ticket
|
||||||
|
outbound_article = Ticket::Article.create(
|
||||||
|
ticket_id: article.ticket.id,
|
||||||
|
body: reply_text,
|
||||||
|
in_reply_to: comment['id'],
|
||||||
|
type: Ticket::Article::Type.find_by( name: 'facebook feed comment' ),
|
||||||
|
sender: Ticket::Article::Sender.find_by( name: 'Agent' ),
|
||||||
|
internal: false,
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert( outbound_article, 'outbound article created' )
|
||||||
|
assert_equal( outbound_article.ticket.articles.count, 4, 'ticket article outbound count' )
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,7 +6,7 @@ class GeoIpTest < ActiveSupport::TestCase
|
||||||
# check
|
# check
|
||||||
test 'check some results' do
|
test 'check some results' do
|
||||||
|
|
||||||
result = GeoIp.location( '127.0.0.0.1' )
|
result = Service::GeoIp.location( '127.0.0.0.1' )
|
||||||
assert(result)
|
assert(result)
|
||||||
assert_equal(nil, result['country_name'])
|
assert_equal(nil, result['country_name'])
|
||||||
assert_equal(nil, result['city_name'])
|
assert_equal(nil, result['city_name'])
|
||||||
|
@ -15,7 +15,7 @@ class GeoIpTest < ActiveSupport::TestCase
|
||||||
assert_equal(nil, result['latitude'])
|
assert_equal(nil, result['latitude'])
|
||||||
assert_equal(nil, result['longitude'])
|
assert_equal(nil, result['longitude'])
|
||||||
|
|
||||||
result = GeoIp.location( '195.65.29.254' )
|
result = Service::GeoIp.location( '195.65.29.254' )
|
||||||
assert(result)
|
assert(result)
|
||||||
assert_equal('Switzerland', result['country_name'])
|
assert_equal('Switzerland', result['country_name'])
|
||||||
assert_equal('Regensdorf', result['city_name'])
|
assert_equal('Regensdorf', result['city_name'])
|
||||||
|
@ -24,7 +24,7 @@ class GeoIpTest < ActiveSupport::TestCase
|
||||||
assert_equal(47.4299, result['latitude'])
|
assert_equal(47.4299, result['latitude'])
|
||||||
assert_equal(8.465100000000007, result['longitude'])
|
assert_equal(8.465100000000007, result['longitude'])
|
||||||
|
|
||||||
result = GeoIp.location( '134.109.140.74' )
|
result = Service::GeoIp.location( '134.109.140.74' )
|
||||||
assert(result)
|
assert(result)
|
||||||
assert_equal('Germany', result['country_name'])
|
assert_equal('Germany', result['country_name'])
|
||||||
assert_equal('Chemnitz', result['city_name'])
|
assert_equal('Chemnitz', result['city_name'])
|
||||||
|
@ -33,7 +33,7 @@ class GeoIpTest < ActiveSupport::TestCase
|
||||||
assert_equal(50.83330000000001, result['latitude'])
|
assert_equal(50.83330000000001, result['latitude'])
|
||||||
assert_equal(12.916699999999992, result['longitude'])
|
assert_equal(12.916699999999992, result['longitude'])
|
||||||
|
|
||||||
result = GeoIp.location( '46.253.55.170' )
|
result = Service::GeoIp.location( '46.253.55.170' )
|
||||||
assert(result)
|
assert(result)
|
||||||
assert_equal('Germany', result['country_name'])
|
assert_equal('Germany', result['country_name'])
|
||||||
assert_equal('Halle', result['city_name'])
|
assert_equal('Halle', result['city_name'])
|
||||||
|
@ -42,7 +42,7 @@ class GeoIpTest < ActiveSupport::TestCase
|
||||||
assert_equal(51.5, result['latitude'])
|
assert_equal(51.5, result['latitude'])
|
||||||
assert_equal(12.0, result['longitude'])
|
assert_equal(12.0, result['longitude'])
|
||||||
|
|
||||||
result = GeoIp.location( '169.229.216.200' )
|
result = Service::GeoIp.location( '169.229.216.200' )
|
||||||
assert(result)
|
assert(result)
|
||||||
assert_equal('United States', result['country_name'])
|
assert_equal('United States', result['country_name'])
|
||||||
assert_equal('Berkeley', result['city_name'])
|
assert_equal('Berkeley', result['city_name'])
|
||||||
|
|
|
@ -6,22 +6,22 @@ class GeoLocationTest < ActiveSupport::TestCase
|
||||||
# check
|
# check
|
||||||
test 'check simple results' do
|
test 'check simple results' do
|
||||||
|
|
||||||
result = GeoLocation.geocode( 'Marienstrasse 13, 10117 Berlin' )
|
result = Service::GeoLocation.geocode( 'Marienstrasse 13, 10117 Berlin' )
|
||||||
assert(result)
|
assert(result)
|
||||||
assert_equal(52.52204, result[0])
|
assert_equal(52.52204, result[0])
|
||||||
assert_equal(13.38319, result[1])
|
assert_equal(13.38319, result[1])
|
||||||
|
|
||||||
result = GeoLocation.geocode( 'Marienstrasse 13 10117 Berlin' )
|
result = Service::GeoLocation.geocode( 'Marienstrasse 13 10117 Berlin' )
|
||||||
assert(result)
|
assert(result)
|
||||||
assert_equal(52.52204, result[0])
|
assert_equal(52.52204, result[0])
|
||||||
assert_equal(13.38319, result[1])
|
assert_equal(13.38319, result[1])
|
||||||
|
|
||||||
result = GeoLocation.geocode( 'Martinsbruggstrasse 35, 9016 St. Gallen' )
|
result = Service::GeoLocation.geocode( 'Martinsbruggstrasse 35, 9016 St. Gallen' )
|
||||||
assert(result)
|
assert(result)
|
||||||
assert_equal(47.4366664, result[0])
|
assert_equal(47.4366664, result[0])
|
||||||
assert_equal(9.409814899999999, result[1])
|
assert_equal(9.409814899999999, result[1])
|
||||||
|
|
||||||
result = GeoLocation.geocode( 'Martinsbruggstrasse 35 9016 St. Gallen' )
|
result = Service::GeoLocation.geocode( 'Martinsbruggstrasse 35 9016 St. Gallen' )
|
||||||
assert(result)
|
assert(result)
|
||||||
assert_equal(47.4366664, result[0])
|
assert_equal(47.4366664, result[0])
|
||||||
assert_equal(9.409814899999999, result[1])
|
assert_equal(9.409814899999999, result[1])
|
||||||
|
|
|
@ -147,8 +147,18 @@ class TwitterTest < ActiveSupport::TestCase
|
||||||
# fetch check system account
|
# fetch check system account
|
||||||
Channel.fetch
|
Channel.fetch
|
||||||
|
|
||||||
# check if follow up article has been created
|
# fetch check system account
|
||||||
|
article = nil
|
||||||
|
(1..4).each {
|
||||||
|
Channel.fetch
|
||||||
|
|
||||||
|
# check if ticket and article has been created
|
||||||
article = Ticket::Article.find_by( message_id: tweet.id )
|
article = Ticket::Article.find_by( message_id: tweet.id )
|
||||||
|
|
||||||
|
break if article
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
}
|
||||||
assert(article)
|
assert(article)
|
||||||
ticket = article.ticket
|
ticket = article.ticket
|
||||||
|
|
||||||
|
@ -220,25 +230,86 @@ class TwitterTest < ActiveSupport::TestCase
|
||||||
sleep 5
|
sleep 5
|
||||||
}
|
}
|
||||||
|
|
||||||
assert( article, 'inbound article created' )
|
assert( article, "inbound article '#{text}' created" )
|
||||||
ticket = article.ticket
|
ticket = article.ticket
|
||||||
assert( ticket, 'ticket of inbound article exists' )
|
assert( ticket, 'ticket of inbound article exists' )
|
||||||
assert( ticket.articles, 'ticket.articles exists' )
|
assert( ticket.articles, 'ticket.articles exists' )
|
||||||
assert_equal( ticket.articles.count, 1, 'ticket article inbound count' )
|
assert_equal( 1, ticket.articles.count, 'ticket article inbound count' )
|
||||||
assert_equal( ticket.state.name, 'new' )
|
assert_equal( ticket.state.name, 'new' )
|
||||||
|
|
||||||
# reply via ticket
|
# reply via ticket
|
||||||
outbound_article = Ticket::Article.create(
|
outbound_article = Ticket::Article.create(
|
||||||
ticket_id: ticket.id,
|
ticket_id: ticket.id,
|
||||||
to: 'me_bauer',
|
to: 'me_bauer',
|
||||||
body: text,
|
body: 'Will call you later!',
|
||||||
type: Ticket::Article::Type.find_by( name: 'twitter direct-message' ),
|
type: Ticket::Article::Type.find_by( name: 'twitter direct-message' ),
|
||||||
sender: Ticket::Article::Sender.find_by( name: 'Agent' ),
|
sender: Ticket::Article::Sender.find_by( name: 'Agent' ),
|
||||||
internal: false,
|
internal: false,
|
||||||
updated_by_id: 1,
|
updated_by_id: 1,
|
||||||
created_by_id: 1,
|
created_by_id: 1,
|
||||||
)
|
)
|
||||||
|
ticket.state = Ticket::State.find_by( name: 'pending reminder' )
|
||||||
|
ticket.save
|
||||||
|
|
||||||
assert( outbound_article, 'outbound article created' )
|
assert( outbound_article, 'outbound article created' )
|
||||||
assert_equal( outbound_article.ticket.articles.count, 2, 'ticket article outbound count' )
|
assert_equal( 2, outbound_article.ticket.articles.count, 'ticket article outbound count' )
|
||||||
|
|
||||||
|
text = 'Ok. ' + hash
|
||||||
|
dm = client.create_direct_message(
|
||||||
|
'armin_theo',
|
||||||
|
text,
|
||||||
|
)
|
||||||
|
assert( dm, "second dm with ##{hash} created" )
|
||||||
|
|
||||||
|
# fetch check system account
|
||||||
|
article = nil
|
||||||
|
(1..4).each {
|
||||||
|
Channel.fetch
|
||||||
|
|
||||||
|
# check if ticket and article has been created
|
||||||
|
article = Ticket::Article.find_by( message_id: dm.id )
|
||||||
|
|
||||||
|
break if article
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
}
|
||||||
|
|
||||||
|
assert( article, "inbound article '#{text}' created" )
|
||||||
|
ticket = article.ticket
|
||||||
|
assert( ticket, 'ticket of inbound article exists' )
|
||||||
|
assert( ticket.articles, 'ticket.articles exists' )
|
||||||
|
assert_equal( 3, ticket.articles.count, 'ticket article inbound count' )
|
||||||
|
assert_equal( ticket.state.name, 'open' )
|
||||||
|
|
||||||
|
# close dm ticket, next dm should open a new
|
||||||
|
ticket.state = Ticket::State.find_by( name: 'closed' )
|
||||||
|
ticket.save
|
||||||
|
|
||||||
|
text = 'Thanks for your call . I just have one question. ' + hash
|
||||||
|
dm = client.create_direct_message(
|
||||||
|
'armin_theo',
|
||||||
|
text,
|
||||||
|
)
|
||||||
|
assert( dm, "third dm with ##{hash} created" )
|
||||||
|
|
||||||
|
# fetch check system account
|
||||||
|
article = nil
|
||||||
|
(1..4).each {
|
||||||
|
Channel.fetch
|
||||||
|
|
||||||
|
# check if ticket and article has been created
|
||||||
|
article = Ticket::Article.find_by( message_id: dm.id )
|
||||||
|
|
||||||
|
break if article
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
}
|
||||||
|
|
||||||
|
assert( article, "inbound article '#{text}' created" )
|
||||||
|
ticket = article.ticket
|
||||||
|
assert( ticket, 'ticket of inbound article exists' )
|
||||||
|
assert( ticket.articles, 'ticket.articles exists' )
|
||||||
|
assert_equal( 1, ticket.articles.count, 'ticket article inbound count' )
|
||||||
|
assert_equal( ticket.state.name, 'new' )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue