Merge branch 'develop' into feature/ui2

This commit is contained in:
Martin Edenhofer 2013-08-19 15:29:13 +02:00
commit 88cc1cc401
43 changed files with 1789 additions and 945 deletions

View file

@ -99,6 +99,9 @@ class _i18nSingleton extends Spine.Module
locale = 'en' locale = 'en'
@locale = locale @locale = locale
# set lang attribute of html tag
$('html').prop( 'lang', locale.substr(0, 2) )
@map = {} @map = {}
App.Ajax.request( App.Ajax.request(
id: 'i18n-set-' + locale, id: 'i18n-set-' + locale,

View file

@ -71,3 +71,8 @@ jQuery.event.special.remove = {
if (e.handler) e.handler(); if (e.handler) e.handler();
} }
}; };
// start application
jQuery(function(){
new App.Run();
});

View file

@ -1,7 +1,5 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/ # Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
require 'geoip'
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
# http_basic_authenticate_with :name => "test", :password => "ttt" # http_basic_authenticate_with :name => "test", :password => "ttt"
@ -82,7 +80,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] = GeoIp.location( request.remote_ip )
end end
# fill user agent # fill user agent

View file

@ -8,7 +8,7 @@ class TicketOverviewsController < ApplicationController
# get navbar overview data # get navbar overview data
if !params[:view] if !params[:view]
result = Ticket.overview( result = Ticket::Overview.list(
:current_user => current_user, :current_user => current_user,
) )
render :json => result render :json => result
@ -17,7 +17,7 @@ class TicketOverviewsController < ApplicationController
# get real overview data # get real overview data
if params[:array] if params[:array]
overview = Ticket.overview( overview = Ticket::Overview.list(
:view => params[:view], :view => params[:view],
:current_user => current_user, :current_user => current_user,
:array => true, :array => true,
@ -36,7 +36,7 @@ class TicketOverviewsController < ApplicationController
} }
return return
end end
overview = Ticket.overview( overview = Ticket::Overview.list(
:view => params[:view], :view => params[:view],
# :view_mode => params[:view_mode], # :view_mode => params[:view_mode],
:current_user => User.find( current_user.id ), :current_user => User.find( current_user.id ),
@ -70,7 +70,7 @@ class TicketOverviewsController < ApplicationController
group_ids.push group.id group_ids.push group.id
} }
agents = {} agents = {}
Ticket.agents.each { |user| Ticket::ScreenOptions.agents.each { |user|
agents[ user.id ] = 1 agents[ user.id ] = 1
} }
groups_users = {} groups_users = {}

View file

@ -111,7 +111,7 @@ class TicketsController < ApplicationController
def ticket_customer def ticket_customer
# return result # return result
result = Ticket.list_by_customer( result = Ticket::ScreenOptions.list_by_customer(
:customer_id => params[:customer_id], :customer_id => params[:customer_id],
:limit => 15, :limit => 15,
) )
@ -217,6 +217,9 @@ class TicketsController < ApplicationController
if !users[ data['created_by_id'] ] if !users[ data['created_by_id'] ]
users[ data['created_by_id'] ] = User.user_data_full( data['created_by_id'] ) users[ data['created_by_id'] ] = User.user_data_full( data['created_by_id'] )
end end
if !users[ data['updated_by_id'] ]
users[ data['updated_by_id'] ] = User.user_data_full( data['updated_by_id'] )
end
} }
recent_viewed = RecentView.list_fulldata( current_user, 8 ) recent_viewed = RecentView.list_fulldata( current_user, 8 )
@ -326,7 +329,7 @@ class TicketsController < ApplicationController
end end
# get attributes to update # get attributes to update
attributes_to_change = Ticket.attributes_to_change( :user => current_user, :ticket => ticket ) attributes_to_change = Ticket::ScreenOptions.attributes_to_change( :user => current_user, :ticket => ticket )
attributes_to_change[:owner_id].each { |user_id| attributes_to_change[:owner_id].each { |user_id|
if !users[user_id] if !users[user_id]
@ -384,7 +387,7 @@ class TicketsController < ApplicationController
def ticket_create def ticket_create
# get attributes to update # get attributes to update
attributes_to_change = Ticket.attributes_to_change( attributes_to_change = Ticket::ScreenOptions.attributes_to_change(
:user => current_user, :user => current_user,
# :ticket_id => params[:ticket_id], # :ticket_id => params[:ticket_id],
# :article_id => params[:article_id] # :article_id => params[:article_id]

View file

@ -25,6 +25,18 @@ class ApplicationModel < ActiveRecord::Base
end end
end end
=begin
remove all not used model attributes of params
result = Model.param_cleanup(params)
returns
result = params # params with valid attributes of model
=end
def self.param_cleanup(params) def self.param_cleanup(params)
# only use object attributes # only use object attributes
@ -40,6 +52,18 @@ class ApplicationModel < ActiveRecord::Base
self.param_validation(data) self.param_validation(data)
end end
=begin
remove all not used params of object (per default :updated_at, :created_at, :updated_by_id and :created_by_id)
result = Model.param_validation(params)
returns
result = params # params without listed attributes
=end
def self.param_validation(data) def self.param_validation(data)
# we do want to set this via database # we do want to set this via database
@ -51,7 +75,20 @@ class ApplicationModel < ActiveRecord::Base
data data
end end
# set created_by_id & updated_by_id if not given based on UserInfo =begin
set created_by_id & updated_by_id if not given based on UserInfo (current session)
Used as before_create callback, no own use needed
result = Model.fill_up_user_create(params)
returns
result = params # params with updated_by_id & created_by_id if not given based on UserInfo (current session)
=end
def fill_up_user_create def fill_up_user_create
if self.class.column_names.include? 'updated_by_id' if self.class.column_names.include? 'updated_by_id'
if UserInfo.current_user_id if UserInfo.current_user_id
@ -71,7 +108,20 @@ class ApplicationModel < ActiveRecord::Base
end end
end end
# set updated_by_id if not given based on UserInfo =begin
set updated_by_id if not given based on UserInfo (current session)
Used as before_update callback, no own use needed
result = Model.fill_up_user_update(params)
returns
result = params # params with updated_by_id & created_by_id if not given based on UserInfo (current session)
=end
def fill_up_user_update def fill_up_user_update
return if !self.class.column_names.include? 'updated_by_id' return if !self.class.column_names.include? 'updated_by_id'
if UserInfo.current_user_id if UserInfo.current_user_id
@ -129,6 +179,20 @@ class ApplicationModel < ActiveRecord::Base
Cache.get( key.to_s ) Cache.get( key.to_s )
end end
=begin
lookup model from cache (if exists) or retrieve it from db, id, name or login possible
result = Model.lookup( :id => 123 )
result = Model.lookup( :name => 'some name' )
result = Model.lookup( :login => 'some login' )
returns
result = model # with all attributes
=end
def self.lookup(data) def self.lookup(data)
if data[:id] if data[:id]
# puts "GET- + #{self.to_s}.#{data[:id].to_s}" # puts "GET- + #{self.to_s}.#{data[:id].to_s}"
@ -168,6 +232,18 @@ class ApplicationModel < ActiveRecord::Base
end end
end end
=begin
create model if not exists (check exists based on id, name, login or locale)
result = Model.create_if_not_exists( attributes )
returns
result = model # with all attributes
=end
def self.create_if_not_exists(data) def self.create_if_not_exists(data)
if data[:id] if data[:id]
record = self.where( :id => data[:id] ).first record = self.where( :id => data[:id] ).first
@ -191,6 +267,18 @@ class ApplicationModel < ActiveRecord::Base
self.create(data) self.create(data)
end end
=begin
create or update model (check exists based on name, login or locale)
result = Model.create_or_update( attributes )
returns
result = model # with all attributes
=end
def self.create_or_update(data) def self.create_or_update(data)
if data[:name] if data[:name]
records = self.where( :name => data[:name] ) records = self.where( :name => data[:name] )
@ -214,11 +302,37 @@ class ApplicationModel < ActiveRecord::Base
record = self.new( data ) record = self.new( data )
record.save record.save
return record return record
elsif data[:locale]
records = self.where( :locale => data[:locale] )
records.each {|record|
if record.locale.downcase == data[:locale].downcase
record.update_attributes( data )
return record
end
}
record = self.new( data )
record.save
return record
else else
raise "Need name or login for create_or_update()" raise "Need name, login or locale for create_or_update()"
end end
end end
=begin
notify_clients_after_create after model got created
used as callback in model file
class OwnModel < ApplicationModel
after_create :notify_clients_after_create
after_update :notify_clients_after_update
after_destroy :notify_clients_after_destroy
[...]
=end
def notify_clients_after_create def notify_clients_after_create
# return if we run import mode # return if we run import mode
@ -232,6 +346,21 @@ class ApplicationModel < ActiveRecord::Base
) )
end end
=begin
notify_clients_after_update after model got updated
used as callback in model file
class OwnModel < ApplicationModel
after_create :notify_clients_after_create
after_update :notify_clients_after_update
after_destroy :notify_clients_after_destroy
[...]
=end
def notify_clients_after_update def notify_clients_after_update
# return if we run import mode # return if we run import mode
@ -245,6 +374,20 @@ class ApplicationModel < ActiveRecord::Base
) )
end end
=begin
notify_clients_after_destroy after model got destroyed
used as callback in model file
class OwnModel < ApplicationModel
after_create :notify_clients_after_create
after_update :notify_clients_after_update
after_destroy :notify_clients_after_destroy
[...]
=end
def notify_clients_after_destroy def notify_clients_after_destroy
# return if we run import mode # return if we run import mode

View file

@ -333,7 +333,7 @@ class Channel::EmailParser
UserInfo.current_user_id = user.id UserInfo.current_user_id = user.id
# get ticket# from subject # get ticket# from subject
ticket = Ticket.number_check( mail[:subject] ) ticket = Ticket::Number.check( mail[:subject] )
# set ticket state to open if not new # set ticket state to open if not new
if ticket if ticket

View file

@ -53,13 +53,8 @@ class Observer::User::Geo < ActiveRecord::Observer
# return if no address is given # return if no address is given
return if address == '' return if address == ''
# load adapter # lookup
adapter = Setting.get('geo_backend') latlng = GeoLocation.geocode( address )
return if !adapter
adapter_module = Object.const_get(adapter)
# db lookup
latlng = adapter_module.geocode(address)
return if !latlng return if !latlng
# store data # store data

View file

@ -14,6 +14,6 @@ class Sla < ApplicationModel
private private
def escalation_calculation_rebuild def escalation_calculation_rebuild
Cache.delete( 'SLA::List::Active' ) Cache.delete( 'SLA::List::Active' )
Ticket.escalation_calculation_rebuild Ticket::Escalation.rebuild_all
end end
end end

View file

@ -4,7 +4,7 @@ require 'time_calculation'
require 'sla' require 'sla'
class Ticket < ApplicationModel class Ticket < ApplicationModel
before_create :number_generate, :check_defaults before_create :check_generate, :check_defaults
before_update :check_defaults before_update :check_defaults
before_destroy :destroy_dependencies before_destroy :destroy_dependencies
after_create :notify_clients_after_create after_create :notify_clients_after_create
@ -22,103 +22,44 @@ class Ticket < ApplicationModel
belongs_to :create_article_type, :class_name => 'Ticket::Article::Type' belongs_to :create_article_type, :class_name => 'Ticket::Article::Type'
belongs_to :create_article_sender, :class_name => 'Ticket::Article::Sender' belongs_to :create_article_sender, :class_name => 'Ticket::Article::Sender'
include Ticket::Escalation
include Ticket::Subject
include Ticket::Permission
extend Ticket::Search
attr_accessor :callback_loop attr_accessor :callback_loop
def self.number_check (string) =begin
self.number_adapter.number_check_item(string)
end list of agents in group of ticket
ticket = Ticket.find(123)
result = ticket.agent_of_group
returns
result = [user1, user2, ...]
=end
def agent_of_group def agent_of_group
Group.find( self.group_id ).users.where( :active => true ).joins(:roles).where( 'roles.name' => 'Agent', 'roles.active' => true ).uniq() Group.find( self.group_id ).users.where( :active => true ).joins(:roles).where( 'roles.name' => 'Agent', 'roles.active' => true ).uniq()
end end
def self.agents =begin
User.where( :active => true ).joins(:roles).where( 'roles.name' => 'Agent', 'roles.active' => true ).uniq()
end
def self.attributes_to_change(params) merge tickets
if params[:ticket_id]
params[:ticket] = self.find( params[:ticket_id] )
end
if params[:article_id]
params[:article] = self.find( params[:article_id] )
end
# get ticket states ticket = Ticket.find(123)
ticket_state_ids = [] result = ticket.merge_to(
if params[:ticket] :ticket_id => 123,
ticket_state_type = params[:ticket].ticket_state.state_type )
end
ticket_state_types = ['open', 'closed', 'pending action', 'pending reminder']
if ticket_state_type && !ticket_state_types.include?(ticket_state_type.name)
ticket_state_ids.push params[:ticket].ticket_state.id
end
ticket_state_types.each {|type|
ticket_state_type = Ticket::StateType.where( :name => type ).first
if ticket_state_type
ticket_state_type.states.each {|ticket_state|
ticket_state_ids.push ticket_state.id
}
end
}
# get owner returns
owner_ids = []
if params[:ticket]
params[:ticket].agent_of_group.each { |user|
owner_ids.push user.id
}
end
# get group result = true|false
group_ids = []
Group.where( :active => true ).each { |group|
group_ids.push group.id
}
# get group / user relations =end
agents = {}
Ticket.agents.each { |user|
agents[ user.id ] = 1
}
groups_users = {}
group_ids.each {|group_id|
groups_users[ group_id ] = []
Group.find( group_id ).users.each {|user|
next if !agents[ user.id ]
groups_users[ group_id ].push user.id
}
}
# get priorities
ticket_priority_ids = []
Ticket::Priority.where( :active => true ).each { |priority|
ticket_priority_ids.push priority.id
}
ticket_article_type_ids = []
if params[:ticket]
ticket_article_types = ['note', 'phone']
if params[:ticket].group.email_address_id
ticket_article_types.push 'email'
end
ticket_article_types.each {|ticket_article_type_name|
ticket_article_type = Ticket::Article::Type.lookup( :name => ticket_article_type_name )
if ticket_article_type
ticket_article_type_ids.push ticket_article_type.id
end
}
end
return {
:ticket_article_type_id => ticket_article_type_ids,
:ticket_state_id => ticket_state_ids,
:ticket_priority_id => ticket_priority_ids,
:owner_id => owner_ids,
:group_id => group_ids,
:group_id__owner_id => groups_users,
}
end
def merge_to(data) def merge_to(data)
@ -157,724 +98,32 @@ class Ticket < ApplicationModel
self.save self.save
end end
# def self.agent
# Role.where( :name => ['Agent'], :active => true ).first.users.where( :active => true ).uniq()
# end
def subject_build (subject)
# clena subject
subject = self.subject_clean(subject)
ticket_hook = Setting.get('ticket_hook')
ticket_hook_divider = Setting.get('ticket_hook_divider')
# none position
if Setting.get('ticket_hook_position') == 'none'
return subject
end
# right position
if Setting.get('ticket_hook_position') == 'right'
return subject + " [#{ticket_hook}#{ticket_hook_divider}#{self.number}] "
end
# left position
return "[#{ticket_hook}#{ticket_hook_divider}#{self.number}] " + subject
end
def subject_clean (subject)
ticket_hook = Setting.get('ticket_hook')
ticket_hook_divider = Setting.get('ticket_hook_divider')
ticket_subject_size = Setting.get('ticket_subject_size')
# remove all possible ticket hook formats with []
subject = subject.gsub /\[#{ticket_hook}: #{self.number}\](\s+?|)/, ''
subject = subject.gsub /\[#{ticket_hook}:#{self.number}\](\s+?|)/, ''
subject = subject.gsub /\[#{ticket_hook}#{ticket_hook_divider}#{self.number}\](\s+?|)/, ''
# remove all possible ticket hook formats without []
subject = subject.gsub /#{ticket_hook}: #{self.number}(\s+?|)/, ''
subject = subject.gsub /#{ticket_hook}:#{self.number}(\s+?|)/, ''
subject = subject.gsub /#{ticket_hook}#{ticket_hook_divider}#{self.number}(\s+?|)/, ''
# remove leading "..:\s" and "..[\d+]:\s" e. g. "Re: " or "Re[5]: "
subject = subject.gsub /^(..(\[\d+\])?:\s)+/, ''
# resize subject based on config
if subject.length > ticket_subject_size.to_i
subject = subject[ 0, ticket_subject_size.to_i ] + '[...]'
end
return subject
end
# ticket.permission(
# :current_user => 123
# )
def permission (data)
# check customer
if data[:current_user].is_role('Customer')
# access ok if its own ticket
return true if self.customer_id == data[:current_user].id
# access ok if its organization ticket
if data[:current_user].organization_id && self.organization_id
return true if self.organization_id == data[:current_user].organization_id
end
# no access
return false
end
# check agent
# access if requestor is owner
return true if self.owner_id == data[:current_user].id
# access if requestor is in group
data[:current_user].groups.each {|group|
return true if self.group.id == group.id
}
return false
end
# Ticket.search(
# :current_user => 123,
# :query => 'search something',
# :limit => 15,
# )
def self.search (params)
# get params
query = params[:query]
limit = params[:limit] || 12
current_user = params[:current_user]
conditions = []
if current_user.is_role('Agent')
group_ids = Group.select( 'groups.id' ).joins(:users).
where( 'groups_users.user_id = ?', current_user.id ).
where( 'groups.active = ?', true ).
map( &:id )
conditions = [ 'group_id IN (?)', group_ids ]
else
if !current_user.organization || ( !current_user.organization.shared || current_user.organization.shared == false )
conditions = [ 'customer_id = ?', current_user.id ]
else
conditions = [ '( customer_id = ? OR organization_id = ? )', current_user.id, current_user.organization.id ]
end
end
# do query
tickets_all = Ticket.select('DISTINCT(tickets.id)').
where(conditions).
where( '( `tickets`.`title` LIKE ? OR `tickets`.`number` LIKE ? OR `ticket_articles`.`body` LIKE ? OR `ticket_articles`.`from` LIKE ? OR `ticket_articles`.`to` LIKE ? OR `ticket_articles`.`subject` LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" ).
joins(:articles).
limit(limit).
order('`tickets`.`created_at` DESC')
# build result list
tickets = []
users = {}
tickets_all.each do |ticket|
ticket_tmp = Ticket.lookup( :id => ticket.id )
tickets.push ticket_tmp
end
return tickets
end
# Ticket.overview_list(
# :current_user => 123,
# )
def self.overview_list (data)
# get customer overviews
if data[:current_user].is_role('Customer')
role = data[:current_user].is_role( 'Customer' )
if data[:current_user].organization_id && data[:current_user].organization.shared
overviews = Overview.where( :role_id => role.id, :active => true )
else
overviews = Overview.where( :role_id => role.id, :organization_shared => false, :active => true )
end
return overviews
end
# get agent overviews
role = data[:current_user].is_role( 'Agent' )
overviews = Overview.where( :role_id => role.id, :active => true )
return overviews
end
# Ticket.overview(
# :view => 'some_view_url',
# :current_user => OBJECT,
# )
def self.overview (data)
overviews = self.overview_list(data)
# build up attributes hash
overview_selected = nil
overview_selected_raw = nil
overviews.each { |overview|
# remember selected view
if data[:view] && data[:view] == overview.link
overview_selected = overview
overview_selected_raw = Marshal.load( Marshal.dump(overview.attributes) )
end
# replace e.g. 'current_user.id' with current_user.id
overview.condition.each { |item, value |
if value && value.class.to_s == 'String'
parts = value.split( '.', 2 )
if parts[0] && parts[1] && parts[0] == 'current_user'
overview.condition[item] = data[:current_user][parts[1].to_sym]
end
end
}
}
if data[:view] && !overview_selected
return
end
# sortby
# prio
# state
# group
# customer
# order
# asc
# desc
# groupby
# prio
# state
# group
# customer
# all = attributes[:myopenassigned]
# all.merge( { :group_id => groups } )
# @tickets = Ticket.where(:group_id => groups, attributes[:myopenassigned] ).limit(params[:limit])
# get only tickets with permissions
if data[:current_user].is_role('Customer')
group_ids = Group.select( 'groups.id' ).
where( 'groups.active = ?', true ).
map( &:id )
else
group_ids = Group.select( 'groups.id' ).joins(:users).
where( 'groups_users.user_id = ?', [ data[:current_user].id ] ).
where( 'groups.active = ?', true ).
map( &:id )
end
# overview meta for navbar
if !overview_selected
# loop each overview
result = []
overviews.each { |overview|
# get count
count = Ticket.where( :group_id => group_ids ).where( self._condition( overview.condition ) ).count()
# get meta info
all = {
:name => overview.name,
:prio => overview.prio,
:link => overview.link,
}
# push to result data
result.push all.merge( { :count => count } )
}
return result
end
# get result list
if data[:array]
order_by = overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s
if overview_selected.group_by && !overview_selected.group_by.empty?
order_by = overview_selected.group_by + '_id, ' + order_by
end
tickets = Ticket.select( 'id' ).
where( :group_id => group_ids ).
where( self._condition( overview_selected.condition ) ).
order( order_by ).
limit( 500 )
ticket_ids = []
tickets.each { |ticket|
ticket_ids.push ticket.id
}
tickets_count = Ticket.where( :group_id => group_ids ).
where( self._condition( overview_selected.condition ) ).
count()
return {
:ticket_list => ticket_ids,
:tickets_count => tickets_count,
:overview => overview_selected_raw,
}
end
# get tickets for overview
data[:start_page] ||= 1
tickets = Ticket.where( :group_id => group_ids ).
where( self._condition( overview_selected.condition ) ).
order( overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s )#.
# limit( overview_selected.view[ data[:view_mode].to_sym ][:per_page] ).
# offset( overview_selected.view[ data[:view_mode].to_sym ][:per_page].to_i * ( data[:start_page].to_i - 1 ) )
tickets_count = Ticket.where( :group_id => group_ids ).
where( self._condition( overview_selected.condition ) ).
count()
return {
:tickets => tickets,
:tickets_count => tickets_count,
:overview => overview_selected_raw,
}
end
def self._condition(condition)
sql = ''
bind = [nil]
condition.each {|key, value|
if sql != ''
sql += ' AND '
end
if value.class == Array
sql += " #{key} IN (?)"
bind.push value
elsif value.class == Hash || value.class == ActiveSupport::HashWithIndifferentAccess
time = Time.now
if value['area'] == 'minute'
if value['direction'] == 'last'
time -= value['count'].to_i * 60
else
time += value['count'].to_i * 60
end
elsif value['area'] == 'hour'
if value['direction'] == 'last'
time -= value['count'].to_i * 60 * 60
else
time += value['count'].to_i * 60 * 60
end
elsif value['area'] == 'day'
if value['direction'] == 'last'
time -= value['count'].to_i * 60 * 60 * 24
else
time += value['count'].to_i * 60 * 60 * 24
end
elsif value['area'] == 'month'
if value['direction'] == 'last'
time -= value['count'].to_i * 60 * 60 * 24 * 31
else
time += value['count'].to_i * 60 * 60 * 24 * 31
end
elsif value['area'] == 'year'
if value['direction'] == 'last'
time -= value['count'].to_i * 60 * 60 * 24 * 365
else
time += value['count'].to_i * 60 * 60 * 24 * 365
end
end
if value['direction'] == 'last'
sql += " #{key} > ?"
bind.push time
else
sql += " #{key} < ?"
bind.push time
end
else
sql += " #{key} = ?"
bind.push value
end
}
bind[0] = sql
return bind
end
def self.number_adapter
# load backend based on config
adapter_name = Setting.get('ticket_number')
adapter = nil
case adapter_name
when Symbol, String
require "ticket/number/#{adapter_name.to_s.downcase}"
adapter = Ticket::Number.const_get("#{adapter_name.to_s.capitalize}")
else
raise "Missing number_adapter '#{adapter_name}'"
end
return adapter
end
def self.escalation_calculation_rebuild
ticket_state_list_open = Ticket::State.by_category( 'open' )
tickets = Ticket.where( :ticket_state_id => ticket_state_list_open )
tickets.each {|ticket|
ticket.escalation_calculation
}
end
def _escalation_calculation_get_sla
sla_selected = nil
sla_list = Cache.get( 'SLA::List::Active' )
if sla_list == nil
sla_list = Sla.where( :active => true ).all
Cache.write( 'SLA::List::Active', sla_list, { :expires_in => 1.hour } )
end
sla_list.each {|sla|
if !sla.condition || sla.condition.empty?
sla_selected = sla
elsif sla.condition
hit = false
map = [
[ 'tickets.ticket_priority_id', 'ticket_priority_id' ],
[ 'tickets.group_id', 'group_id' ]
]
map.each {|item|
if sla.condition[ item[0] ]
if sla.condition[ item[0] ].class == String
sla.condition[ item[0] ] = [ sla.condition[ item[0] ] ]
end
if sla.condition[ item[0] ].include?( self[ item[1] ].to_s )
hit = true
else
hit = false
end
end
}
if hit
sla_selected = sla
end
end
}
return sla_selected
end
def _escalation_calculation_higher_time(escalation_time, check_time, done_time)
return escalation_time if done_time
return check_time if !escalation_time
return escalation_time if !check_time
return check_time if escalation_time > check_time
return escalation_time
end
def escalation_calculation
# set escalation off if ticket is already closed
ticket_state = Ticket::State.lookup( :id => self.ticket_state_id )
if ticket_state.ignore_escalation?
self.escalation_time = nil
# self.first_response_escal_date = nil
# self.close_time_escal_date = nil
self.callback_loop = true
self.save
return true
end
# get sla for ticket
sla_selected = self._escalation_calculation_get_sla
# reset escalation if no sla is set
if !sla_selected
self.escalation_time = nil
# self.first_response_escal_date = nil
# self.close_time_escal_date = nil
self.callback_loop = true
self.save
return true
end
# puts sla_selected.inspect
# puts days.inspect
self.escalation_time = nil
self.first_response_escal_date = nil
self.update_time_escal_date = nil
self.close_time_escal_date = nil
# first response
if sla_selected.first_response_time
# get escalation date without pending time
self.first_response_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.first_response_time, sla_selected.data, sla_selected.timezone )
# get pending time between created and first response escal. time
time_in_pending = escalation_suspend( self.created_at, self.first_response_escal_date, 'relative', sla_selected, sla_selected.first_response_time )
# get new escalation time (original escal_date + time_in_pending)
self.first_response_escal_date = TimeCalculation.dest_time( self.first_response_escal_date, time_in_pending.to_i, sla_selected.data, sla_selected.timezone )
# set ticket escalation
self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.first_response_escal_date, self.first_response )
end
if self.first_response# && !self.first_response_in_min
# get response time in min between created and first response
self.first_response_in_min = escalation_suspend( self.created_at, self.first_response, 'real', sla_selected )
end
# set time to show if sla is raised ot in
if sla_selected.first_response_time && self.first_response_in_min
self.first_response_diff_in_min = sla_selected.first_response_time - self.first_response_in_min
end
# update time
last_update = self.last_contact_agent
if !last_update
last_update = self.created_at
end
if sla_selected.update_time
self.update_time_escal_date = TimeCalculation.dest_time( last_update, sla_selected.update_time, sla_selected.data, sla_selected.timezone )
# get pending time between created and update escal. time
time_in_pending = escalation_suspend( last_update, self.update_time_escal_date, 'relative', sla_selected, sla_selected.update_time )
# get new escalation time (original escal_date + time_in_pending)
self.update_time_escal_date = TimeCalculation.dest_time( self.update_time_escal_date, time_in_pending.to_i, sla_selected.data, sla_selected.timezone )
# set ticket escalation
self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.update_time_escal_date, false )
end
if self.last_contact_agent
self.update_time_in_min = TimeCalculation.business_time_diff( self.created_at, self.last_contact_agent, sla_selected.data, sla_selected.timezone )
end
# set sla time
if sla_selected.update_time && self.update_time_in_min
self.update_time_diff_in_min = sla_selected.update_time - self.update_time_in_min
end
# close time
if sla_selected.close_time
# get escalation date without pending time
self.close_time_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.close_time, sla_selected.data, sla_selected.timezone )
# get pending time between created and close escal. time
extended_escalation = escalation_suspend( self.created_at, self.close_time_escal_date, 'relative', sla_selected, sla_selected.close_time )
# get new escalation time (original escal_date + time_in_pending)
self.close_time_escal_date = TimeCalculation.dest_time( self.close_time_escal_date, extended_escalation.to_i, sla_selected.data, sla_selected.timezone )
# set ticket escalation
self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.close_time_escal_date, self.close_time )
end
if self.close_time # && !self.close_time_in_min
self.close_time_in_min = escalation_suspend( self.created_at, self.close_time, 'real', sla_selected )
end
# set sla time
if sla_selected.close_time && self.close_time_in_min
self.close_time_diff_in_min = sla_selected.close_time - self.close_time_in_min
end
self.callback_loop = true
self.save
end
=begin
list tickets by customer groupd in state categroie open and closed
result = Ticket.list_by_customer(
:customer_id => 123,
:limit => 15, # optional, default 15
)
returns
result = {
:open => tickets_open,
:closed => tickets_closed,
}
=end
def self.list_by_customer(data)
# get closed/open states
ticket_state_list_open = Ticket::State.by_category( 'open' )
ticket_state_list_closed = Ticket::State.by_category( 'closed' )
# get tickets
tickets_open = Ticket.where(
:customer_id => data[:customer_id],
:ticket_state_id => ticket_state_list_open
).limit( data[:limit] || 15 ).order('created_at DESC')
tickets_closed = Ticket.where(
:customer_id => data[:customer_id],
:ticket_state_id => ticket_state_list_closed
).limit( data[:limit] || 15 ).order('created_at DESC')
return {
:open => tickets_open,
:closed => tickets_closed,
}
end
private private
def number_generate def check_generate
return if self.number return if self.number
self.number = Ticket::Number.generate
# generate number
(1..25_000).each do |i|
number = Ticket.number_adapter.number_generate_item()
ticket = Ticket.where( :number => number ).first
if ticket != nil
number = Ticket.number_adapter.number_generate_item()
else
self.number = number
return number
end
end
end end
def check_defaults def check_defaults
if !self.owner_id if !self.owner_id
self.owner_id = 1 self.owner_id = 1
end end
# if self.customer_id && ( !self.organization_id || self.organization_id.empty? )
if self.customer_id if self.customer_id
customer = User.find( self.customer_id ) customer = User.find( self.customer_id )
if self.organization_id != customer.organization_id if self.organization_id != customer.organization_id
self.organization_id = customer.organization_id self.organization_id = customer.organization_id
end end
end end
end end
def destroy_dependencies def destroy_dependencies
# delete history # delete history
History.remove( 'Ticket', self.id ) History.remove( 'Ticket', self.id )
# delete articles # delete articles
self.articles.destroy_all self.articles.destroy_all
end
#type could be:
# real - time without supsend state
# relative - only suspend time
def escalation_suspend (start_time, end_time, type, sla_selected, sla_time = 0)
if type == 'relative'
end_time += sla_time * 60
end
total_time_without_pending = 0
total_time = 0
#get history for ticket
history_list = History.list( 'Ticket', self.id )
#loop through hist. changes and get time
last_state = nil
last_state_change = nil
last_state_is_pending = false
history_list.each { |history_item|
# ignore if it isn't a state change
next if !history_item.history_attribute_id
history_attribute = History::Attribute.lookup( :id => history_item.history_attribute_id );
next if history_attribute.name != 'ticket_state'
# ignore all newer state before start_time
next if history_item.created_at < start_time
# ignore all older state changes after end_time
next if last_state_change && last_state_change > end_time
# if created_at is later then end_time, use end_time as last time
if history_item.created_at > end_time
history_item.created_at = end_time
end
# get initial state and time
if !last_state
last_state = history_item.value_from
last_state_change = start_time
end
# check if time need to be counted
counted = true
if history_item.value_from == 'pending'
counted = false
elsif history_item.value_from == 'close'
counted = false
end
diff = escalation_time_diff( last_state_change, history_item.created_at, sla_selected )
if counted
puts "Diff count #{history_item.value_from} -> #{history_item.value_to} / #{last_state_change} -> #{history_item.created_at}"
total_time_without_pending = total_time_without_pending + diff
else
puts "Diff not count #{history_item.value_from} -> #{history_item.value_to} / #{last_state_change} -> #{history_item.created_at}"
end
total_time = total_time + diff
if history_item.value_to == 'pending'
last_state_is_pending = true
else
last_state_is_pending = false
end
# remember for next loop last state
last_state = history_item.value_to
last_state_change = history_item.created_at
}
# if last state isnt pending, count rest
if !last_state_is_pending && last_state_change && last_state_change < end_time
diff = escalation_time_diff( last_state_change, end_time, sla_selected )
puts "Diff count last state was not pending #{diff.to_s} - #{last_state_change} - #{end_time}"
total_time_without_pending = total_time_without_pending + diff
total_time = total_time + diff
end
# if we have not had any state change
if !last_state_change
diff = escalation_time_diff( start_time, end_time, sla_selected )
puts 'Diff state has not changed ' + diff.to_s
total_time_without_pending = total_time_without_pending + diff
total_time = total_time + diff
end
#return sum
if type == 'real'
return total_time_without_pending
elsif type == 'relative'
relative = total_time - total_time_without_pending
return relative
else
raise "ERROR: Unknown type #{type}"
end
end
def escalation_time_diff( start_time, end_time, sla_selected )
if sla_selected
diff = TimeCalculation.business_time_diff( start_time, end_time, sla_selected.data, sla_selected.timezone)
else
diff = TimeCalculation.business_time_diff( start_time, end_time )
end
diff
end
class Number
end end
end end

View file

@ -0,0 +1,305 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
module Ticket::Escalation
=begin
rebuild escalations for all open tickets
result = Ticket::Escalation.rebuild_all
returns
result = true
=end
def self.rebuild_all
ticket_state_list_open = Ticket::State.by_category( 'open' )
tickets = Ticket.where( :ticket_state_id => ticket_state_list_open )
tickets.each {|ticket|
ticket.escalation_calculation
}
end
=begin
rebuild escalation for ticket
ticket = Ticket.find(123)
result = ticket.escalation_calculation
returns
result = true
=end
def escalation_calculation
# set escalation off if ticket is already closed
ticket_state = Ticket::State.lookup( :id => self.ticket_state_id )
if ticket_state.ignore_escalation?
self.escalation_time = nil
# self.first_response_escal_date = nil
# self.close_time_escal_date = nil
self.callback_loop = true
self.save
return true
end
# get sla for ticket
sla_selected = escalation_calculation_get_sla
# reset escalation if no sla is set
if !sla_selected
self.escalation_time = nil
# self.first_response_escal_date = nil
# self.close_time_escal_date = nil
self.callback_loop = true
self.save
return true
end
# puts sla_selected.inspect
# puts days.inspect
self.escalation_time = nil
self.first_response_escal_date = nil
self.update_time_escal_date = nil
self.close_time_escal_date = nil
# first response
if sla_selected.first_response_time
# get escalation date without pending time
self.first_response_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.first_response_time, sla_selected.data, sla_selected.timezone )
# get pending time between created and first response escal. time
time_in_pending = escalation_suspend( self.created_at, self.first_response_escal_date, 'relative', sla_selected, sla_selected.first_response_time )
# get new escalation time (original escal_date + time_in_pending)
self.first_response_escal_date = TimeCalculation.dest_time( self.first_response_escal_date, time_in_pending.to_i, sla_selected.data, sla_selected.timezone )
# set ticket escalation
self.escalation_time = calculation_higher_time( self.escalation_time, self.first_response_escal_date, self.first_response )
end
if self.first_response# && !self.first_response_in_min
# get response time in min between created and first response
self.first_response_in_min = escalation_suspend( self.created_at, self.first_response, 'real', sla_selected )
end
# set time to show if sla is raised ot in
if sla_selected.first_response_time && self.first_response_in_min
self.first_response_diff_in_min = sla_selected.first_response_time - self.first_response_in_min
end
# update time
last_update = self.last_contact_agent
if !last_update
last_update = self.created_at
end
if sla_selected.update_time
self.update_time_escal_date = TimeCalculation.dest_time( last_update, sla_selected.update_time, sla_selected.data, sla_selected.timezone )
# get pending time between created and update escal. time
time_in_pending = escalation_suspend( last_update, self.update_time_escal_date, 'relative', sla_selected, sla_selected.update_time )
# get new escalation time (original escal_date + time_in_pending)
self.update_time_escal_date = TimeCalculation.dest_time( self.update_time_escal_date, time_in_pending.to_i, sla_selected.data, sla_selected.timezone )
# set ticket escalation
self.escalation_time = calculation_higher_time( self.escalation_time, self.update_time_escal_date, false )
end
if self.last_contact_agent
self.update_time_in_min = TimeCalculation.business_time_diff( self.created_at, self.last_contact_agent, sla_selected.data, sla_selected.timezone )
end
# set sla time
if sla_selected.update_time && self.update_time_in_min
self.update_time_diff_in_min = sla_selected.update_time - self.update_time_in_min
end
# close time
if sla_selected.close_time
# get escalation date without pending time
self.close_time_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.close_time, sla_selected.data, sla_selected.timezone )
# get pending time between created and close escal. time
extended_escalation = escalation_suspend( self.created_at, self.close_time_escal_date, 'relative', sla_selected, sla_selected.close_time )
# get new escalation time (original escal_date + time_in_pending)
self.close_time_escal_date = TimeCalculation.dest_time( self.close_time_escal_date, extended_escalation.to_i, sla_selected.data, sla_selected.timezone )
# set ticket escalation
self.escalation_time = calculation_higher_time( self.escalation_time, self.close_time_escal_date, self.close_time )
end
if self.close_time # && !self.close_time_in_min
self.close_time_in_min = escalation_suspend( self.created_at, self.close_time, 'real', sla_selected )
end
# set sla time
if sla_selected.close_time && self.close_time_in_min
self.close_time_diff_in_min = sla_selected.close_time - self.close_time_in_min
end
self.callback_loop = true
self.save
end
private
#type could be:
# real - time without supsend state
# relative - only suspend time
def escalation_suspend (start_time, end_time, type, sla_selected, sla_time = 0)
if type == 'relative'
end_time += sla_time * 60
end
total_time_without_pending = 0
total_time = 0
#get history for ticket
history_list = History.list( 'Ticket', self.id )
#loop through hist. changes and get time
last_state = nil
last_state_change = nil
last_state_is_pending = false
history_list.each { |history_item|
# ignore if it isn't a state change
next if !history_item.history_attribute_id
history_attribute = History::Attribute.lookup( :id => history_item.history_attribute_id );
next if history_attribute.name != 'ticket_state'
# ignore all newer state before start_time
next if history_item.created_at < start_time
# ignore all older state changes after end_time
next if last_state_change && last_state_change > end_time
# if created_at is later then end_time, use end_time as last time
if history_item.created_at > end_time
history_item.created_at = end_time
end
# get initial state and time
if !last_state
last_state = history_item.value_from
last_state_change = start_time
end
# check if time need to be counted
counted = true
if history_item.value_from == 'pending'
counted = false
elsif history_item.value_from == 'close'
counted = false
end
diff = escalation_time_diff( last_state_change, history_item.created_at, sla_selected )
if counted
puts "Diff count #{history_item.value_from} -> #{history_item.value_to} / #{last_state_change} -> #{history_item.created_at}"
total_time_without_pending = total_time_without_pending + diff
else
puts "Diff not count #{history_item.value_from} -> #{history_item.value_to} / #{last_state_change} -> #{history_item.created_at}"
end
total_time = total_time + diff
if history_item.value_to == 'pending'
last_state_is_pending = true
else
last_state_is_pending = false
end
# remember for next loop last state
last_state = history_item.value_to
last_state_change = history_item.created_at
}
# if last state isnt pending, count rest
if !last_state_is_pending && last_state_change && last_state_change < end_time
diff = escalation_time_diff( last_state_change, end_time, sla_selected )
puts "Diff count last state was not pending #{diff.to_s} - #{last_state_change} - #{end_time}"
total_time_without_pending = total_time_without_pending + diff
total_time = total_time + diff
end
# if we have not had any state change
if !last_state_change
diff = escalation_time_diff( start_time, end_time, sla_selected )
puts 'Diff state has not changed ' + diff.to_s
total_time_without_pending = total_time_without_pending + diff
total_time = total_time + diff
end
#return sum
if type == 'real'
return total_time_without_pending
elsif type == 'relative'
relative = total_time - total_time_without_pending
return relative
else
raise "ERROR: Unknown type #{type}"
end
end
def escalation_time_diff( start_time, end_time, sla_selected )
if sla_selected
diff = TimeCalculation.business_time_diff( start_time, end_time, sla_selected.data, sla_selected.timezone)
else
diff = TimeCalculation.business_time_diff( start_time, end_time )
end
diff
end
def escalation_calculation_get_sla
sla_selected = nil
sla_list = Cache.get( 'SLA::List::Active' )
if sla_list == nil
sla_list = Sla.where( :active => true ).all
Cache.write( 'SLA::List::Active', sla_list, { :expires_in => 1.hour } )
end
sla_list.each {|sla|
if !sla.condition || sla.condition.empty?
sla_selected = sla
elsif sla.condition
hit = false
map = [
[ 'tickets.ticket_priority_id', 'ticket_priority_id' ],
[ 'tickets.group_id', 'group_id' ]
]
map.each {|item|
if sla.condition[ item[0] ]
if sla.condition[ item[0] ].class == String
sla.condition[ item[0] ] = [ sla.condition[ item[0] ] ]
end
if sla.condition[ item[0] ].include?( self[ item[1] ].to_s )
hit = true
else
hit = false
end
end
}
if hit
sla_selected = sla
end
end
}
return sla_selected
end
def calculation_higher_time(escalation_time, check_time, done_time)
return escalation_time if done_time
return check_time if !escalation_time
return escalation_time if !check_time
return check_time if escalation_time > check_time
return escalation_time
end
end

View file

@ -0,0 +1,58 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
class Ticket::Number < ApplicationLib
=begin
generate new ticket number
result = Ticket::Number.generate
returns
result = "1234556" # new ticket number
=end
def self.generate
# generate number
(1..50_000).each { |i|
number = adapter.generate
ticket = Ticket.where( :number => number ).first
return number if !ticket
}
raise "Can't generate new ticket number!"
end
=begin
check if string contrains a valid ticket number
result = Ticket::Number.check('some string [Ticket#123456]')
returns
result = ticket # Ticket model of ticket with matching ticket number
=end
def self.check(string)
adapter.check(string)
end
def self.adapter
# load backend based on config
adapter_name = Setting.get('ticket_number')
if !adapter_name
raise "Missing ticket_number setting option"
end
adapter = self.load_adapter(adapter_name)
if !adapter
raise "Can't load ticket_number adapter '#{adapter_name}'"
end
adapter
end
end

View file

@ -3,7 +3,7 @@
module Ticket::Number::Date module Ticket::Number::Date
extend self extend self
def number_generate_item def generate
# get config # get config
config = Setting.get('ticket_number_date') config = Setting.get('ticket_number_date')
@ -64,7 +64,7 @@ module Ticket::Number::Date
end end
return number return number
end end
def number_check_item (string) def check(string)
# get config # get config
system_id = Setting.get('system_id') || '' system_id = Setting.get('system_id') || ''

View file

@ -3,7 +3,7 @@
module Ticket::Number::Increment module Ticket::Number::Increment
extend self extend self
def number_generate_item def generate
# get config # get config
config = Setting.get('ticket_number_increment') config = Setting.get('ticket_number_increment')
@ -68,7 +68,7 @@ module Ticket::Number::Increment
return number return number
end end
def number_check_item (string) def check(string)
# get config # get config
system_id = Setting.get('system_id') || '' system_id = Setting.get('system_id') || ''

View file

@ -0,0 +1,253 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
require 'overview'
class Ticket::Overview
=begin
all overview by user
result = Ticket::Overview.all(
:current_user => User.find(123),
)
returns
result = [overview1, overview2]
=end
def self.all (data)
# get customer overviews
if data[:current_user].is_role('Customer')
role = data[:current_user].is_role( 'Customer' )
if data[:current_user].organization_id && data[:current_user].organization.shared
overviews = Overview.where( :role_id => role.id, :active => true )
else
overviews = Overview.where( :role_id => role.id, :organization_shared => false, :active => true )
end
return overviews
end
# get agent overviews
role = data[:current_user].is_role( 'Agent' )
overviews = Overview.where( :role_id => role.id, :active => true )
return overviews
end
=begin
selected overview by user
result = Ticket::Overview.list(
:current_user => User.find(123),
:view => 'some_view_url',
)
returns
result = {
:tickets => tickets, # [ticket1, ticket2, ticket3]
:tickets_count => tickets_count, # count of tickets
:overview => overview_selected_raw, # overview attributes
}
=end
def self.list (data)
overviews = self.all(data)
# build up attributes hash
overview_selected = nil
overview_selected_raw = nil
overviews.each { |overview|
# remember selected view
if data[:view] && data[:view] == overview.link
overview_selected = overview
overview_selected_raw = Marshal.load( Marshal.dump(overview.attributes) )
end
# replace e.g. 'current_user.id' with current_user.id
overview.condition.each { |item, value |
if value && value.class.to_s == 'String'
parts = value.split( '.', 2 )
if parts[0] && parts[1] && parts[0] == 'current_user'
overview.condition[item] = data[:current_user][parts[1].to_sym]
end
end
}
}
if data[:view] && !overview_selected
raise "No such view '#{ data[:view] }'"
end
# sortby
# prio
# state
# group
# customer
# order
# asc
# desc
# groupby
# prio
# state
# group
# customer
# all = attributes[:myopenassigned]
# all.merge( { :group_id => groups } )
# @tickets = Ticket.where(:group_id => groups, attributes[:myopenassigned] ).limit(params[:limit])
# get only tickets with permissions
if data[:current_user].is_role('Customer')
group_ids = Group.select( 'groups.id' ).
where( 'groups.active = ?', true ).
map( &:id )
else
group_ids = Group.select( 'groups.id' ).joins(:users).
where( 'groups_users.user_id = ?', [ data[:current_user].id ] ).
where( 'groups.active = ?', true ).
map( &:id )
end
# overview meta for navbar
if !overview_selected
# loop each overview
result = []
overviews.each { |overview|
# get count
count = Ticket.where( :group_id => group_ids ).where( _condition( overview.condition ) ).count()
# get meta info
all = {
:name => overview.name,
:prio => overview.prio,
:link => overview.link,
}
# push to result data
result.push all.merge( { :count => count } )
}
return result
end
# get result list
if data[:array]
order_by = overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s
if overview_selected.group_by && !overview_selected.group_by.empty?
order_by = overview_selected.group_by + '_id, ' + order_by
end
tickets = Ticket.select( 'id' ).
where( :group_id => group_ids ).
where( _condition( overview_selected.condition ) ).
order( order_by ).
limit( 500 )
ticket_ids = []
tickets.each { |ticket|
ticket_ids.push ticket.id
}
tickets_count = Ticket.where( :group_id => group_ids ).
where( _condition( overview_selected.condition ) ).
count()
return {
:ticket_list => ticket_ids,
:tickets_count => tickets_count,
:overview => overview_selected_raw,
}
end
# get tickets for overview
data[:start_page] ||= 1
tickets = Ticket.where( :group_id => group_ids ).
where( _condition( overview_selected.condition ) ).
order( overview_selected[:order][:by].to_s + ' ' + overview_selected[:order][:direction].to_s )#.
# limit( overview_selected.view[ data[:view_mode].to_sym ][:per_page] ).
# offset( overview_selected.view[ data[:view_mode].to_sym ][:per_page].to_i * ( data[:start_page].to_i - 1 ) )
tickets_count = Ticket.where( :group_id => group_ids ).
where( _condition( overview_selected.condition ) ).
count()
return {
:tickets => tickets,
:tickets_count => tickets_count,
:overview => overview_selected_raw,
}
end
private
def self._condition(condition)
sql = ''
bind = [nil]
condition.each {|key, value|
if sql != ''
sql += ' AND '
end
if value.class == Array
sql += " #{key} IN (?)"
bind.push value
elsif value.class == Hash || value.class == ActiveSupport::HashWithIndifferentAccess
time = Time.now
if value['area'] == 'minute'
if value['direction'] == 'last'
time -= value['count'].to_i * 60
else
time += value['count'].to_i * 60
end
elsif value['area'] == 'hour'
if value['direction'] == 'last'
time -= value['count'].to_i * 60 * 60
else
time += value['count'].to_i * 60 * 60
end
elsif value['area'] == 'day'
if value['direction'] == 'last'
time -= value['count'].to_i * 60 * 60 * 24
else
time += value['count'].to_i * 60 * 60 * 24
end
elsif value['area'] == 'month'
if value['direction'] == 'last'
time -= value['count'].to_i * 60 * 60 * 24 * 31
else
time += value['count'].to_i * 60 * 60 * 24 * 31
end
elsif value['area'] == 'year'
if value['direction'] == 'last'
time -= value['count'].to_i * 60 * 60 * 24 * 365
else
time += value['count'].to_i * 60 * 60 * 24 * 365
end
end
if value['direction'] == 'last'
sql += " #{key} > ?"
bind.push time
else
sql += " #{key} < ?"
bind.push time
end
else
sql += " #{key} = ?"
bind.push value
end
}
bind[0] = sql
return bind
end
end

View file

@ -0,0 +1,47 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
module Ticket::Permission
=begin
check if user has access to ticket
ticket = Ticket.find(123)
result = ticket.permission( :current_user => User.find(123) )
returns
result = true|false
=end
def permission (data)
# check customer
if data[:current_user].is_role('Customer')
# access ok if its own ticket
return true if self.customer_id == data[:current_user].id
# access ok if its organization ticket
if data[:current_user].organization_id && self.organization_id
return true if self.organization_id == data[:current_user].organization_id
end
# no access
return false
end
# check agent
# access if requestor is owner
return true if self.owner_id == data[:current_user].id
# access if requestor is in group
data[:current_user].groups.each {|group|
return true if self.group.id == group.id
}
return false
end
end

View file

@ -0,0 +1,170 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
module Ticket::ScreenOptions
=begin
list of active agents
result = Ticket::ScreenOptions.agents()
returns
result = [user1, user2]
=end
def self.agents
User.where( :active => true ).joins(:roles).where( 'roles.name' => 'Agent', 'roles.active' => true ).uniq()
end
=begin
list attributes
result = Ticket::ScreenOptions.attributes_to_change(
:ticket_id => 123,
:article_id => 123,
:ticket => ticket_model,
)
returns
result = {
:ticket_article_type_id => ticket_article_type_ids,
:ticket_state_id => ticket_state_ids,
:ticket_priority_id => ticket_priority_ids,
:owner_id => owner_ids,
:group_id => group_ids,
:group_id__owner_id => groups_users,
}
=end
def self.attributes_to_change(params)
if params[:ticket_id]
params[:ticket] = self.find( params[:ticket_id] )
end
if params[:article_id]
params[:article] = self.find( params[:article_id] )
end
# get ticket states
ticket_state_ids = []
if params[:ticket]
ticket_state_type = params[:ticket].ticket_state.state_type
end
ticket_state_types = ['open', 'closed', 'pending action', 'pending reminder']
if ticket_state_type && !ticket_state_types.include?(ticket_state_type.name)
ticket_state_ids.push params[:ticket].ticket_state.id
end
ticket_state_types.each {|type|
ticket_state_type = Ticket::StateType.where( :name => type ).first
if ticket_state_type
ticket_state_type.states.each {|ticket_state|
ticket_state_ids.push ticket_state.id
}
end
}
# get owner
owner_ids = []
if params[:ticket]
params[:ticket].agent_of_group.each { |user|
owner_ids.push user.id
}
end
# get group
group_ids = []
Group.where( :active => true ).each { |group|
group_ids.push group.id
}
# get group / user relations
agents = {}
Ticket::ScreenOptions.agents.each { |user|
agents[ user.id ] = 1
}
groups_users = {}
group_ids.each {|group_id|
groups_users[ group_id ] = []
Group.find( group_id ).users.each {|user|
next if !agents[ user.id ]
groups_users[ group_id ].push user.id
}
}
# get priorities
ticket_priority_ids = []
Ticket::Priority.where( :active => true ).each { |priority|
ticket_priority_ids.push priority.id
}
ticket_article_type_ids = []
if params[:ticket]
ticket_article_types = ['note', 'phone']
if params[:ticket].group.email_address_id
ticket_article_types.push 'email'
end
ticket_article_types.each {|ticket_article_type_name|
ticket_article_type = Ticket::Article::Type.lookup( :name => ticket_article_type_name )
if ticket_article_type
ticket_article_type_ids.push ticket_article_type.id
end
}
end
return {
:ticket_article_type_id => ticket_article_type_ids,
:ticket_state_id => ticket_state_ids,
:ticket_priority_id => ticket_priority_ids,
:owner_id => owner_ids,
:group_id => group_ids,
:group_id__owner_id => groups_users,
}
end
=begin
list tickets by customer groupd in state categroie open and closed
result = Ticket::ScreenOptions.list_by_customer(
:customer_id => 123,
:limit => 15, # optional, default 15
)
returns
result = {
:open => tickets_open,
:closed => tickets_closed,
}
=end
def self.list_by_customer(data)
# get closed/open states
ticket_state_list_open = Ticket::State.by_category( 'open' )
ticket_state_list_closed = Ticket::State.by_category( 'closed' )
# get tickets
tickets_open = Ticket.where(
:customer_id => data[:customer_id],
:ticket_state_id => ticket_state_list_open
).limit( data[:limit] || 15 ).order('created_at DESC')
tickets_closed = Ticket.where(
:customer_id => data[:customer_id],
:ticket_state_id => ticket_state_list_closed
).limit( data[:limit] || 15 ).order('created_at DESC')
return {
:open => tickets_open,
:closed => tickets_closed,
}
end
end

View file

@ -0,0 +1,62 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
module Ticket::Search
=begin
search tickets
result = Ticket.search(
:current_user => User.find(123),
:query => 'search something',
:limit => 15,
)
returns
result = [ticket_model1, ticket_model2]
=end
def search (params)
# get params
query = params[:query]
limit = params[:limit] || 12
current_user = params[:current_user]
conditions = []
if current_user.is_role('Agent')
group_ids = Group.select( 'groups.id' ).joins(:users).
where( 'groups_users.user_id = ?', current_user.id ).
where( 'groups.active = ?', true ).
map( &:id )
conditions = [ 'group_id IN (?)', group_ids ]
else
if !current_user.organization || ( !current_user.organization.shared || current_user.organization.shared == false )
conditions = [ 'customer_id = ?', current_user.id ]
else
conditions = [ '( customer_id = ? OR organization_id = ? )', current_user.id, current_user.organization.id ]
end
end
# do query
tickets_all = Ticket.select('DISTINCT(tickets.id)').
where(conditions).
where( '( `tickets`.`title` LIKE ? OR `tickets`.`number` LIKE ? OR `ticket_articles`.`body` LIKE ? OR `ticket_articles`.`from` LIKE ? OR `ticket_articles`.`to` LIKE ? OR `ticket_articles`.`subject` LIKE ?)', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%" ).
joins(:articles).
limit(limit).
order('`tickets`.`created_at` DESC')
# build result list
tickets = []
users = {}
tickets_all.each do |ticket|
ticket_tmp = Ticket.lookup( :id => ticket.id )
tickets.push ticket_tmp
end
return tickets
end
end

View file

@ -0,0 +1,78 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
module Ticket::Subject
=begin
build new subject with ticket number in there
ticket = Ticket.find(123)
result = ticket.subject_build('some subject')
returns
result = "[Ticket#1234567] some subject"
=end
def subject_build (subject)
# clena subject
subject = self.subject_clean(subject)
ticket_hook = Setting.get('ticket_hook')
ticket_hook_divider = Setting.get('ticket_hook_divider')
# none position
if Setting.get('ticket_hook_position') == 'none'
return subject
end
# right position
if Setting.get('ticket_hook_position') == 'right'
return subject + " [#{ticket_hook}#{ticket_hook_divider}#{self.number}] "
end
# left position
return "[#{ticket_hook}#{ticket_hook_divider}#{self.number}] " + subject
end
=begin
clean subject remove ticket number and other not needed chars
ticket = Ticket.find(123)
result = ticket.subject_clean('[Ticket#1234567] some subject')
returns
result = "some subject"
=end
def subject_clean (subject)
ticket_hook = Setting.get('ticket_hook')
ticket_hook_divider = Setting.get('ticket_hook_divider')
ticket_subject_size = Setting.get('ticket_subject_size')
# remove all possible ticket hook formats with []
subject = subject.gsub /\[#{ticket_hook}: #{self.number}\](\s+?|)/, ''
subject = subject.gsub /\[#{ticket_hook}:#{self.number}\](\s+?|)/, ''
subject = subject.gsub /\[#{ticket_hook}#{ticket_hook_divider}#{self.number}\](\s+?|)/, ''
# remove all possible ticket hook formats without []
subject = subject.gsub /#{ticket_hook}: #{self.number}(\s+?|)/, ''
subject = subject.gsub /#{ticket_hook}:#{self.number}(\s+?|)/, ''
subject = subject.gsub /#{ticket_hook}#{ticket_hook_divider}#{self.number}(\s+?|)/, ''
# remove leading "..:\s" and "..[\d+]:\s" e. g. "Re: " or "Re[5]: "
subject = subject.gsub /^(..(\[\d+\])?:\s)+/, ''
# resize subject based on config
if subject.length > ticket_subject_size.to_i
subject = subject[ 0, ticket_subject_size.to_i ] + '[...]'
end
return subject
end
end

View file

@ -19,6 +19,19 @@ class User < ApplicationModel
store :preferences store :preferences
=begin
fullname of user
user = User.find(123)
result = user.fulename
returns
result = "Bob Smith"
=end
def fullname def fullname
fullname = '' fullname = ''
if self.firstname if self.firstname
@ -33,6 +46,19 @@ class User < ApplicationModel
return fullname return fullname
end end
=begin
check if user is in role
user = User.find(123)
result = user.is_role('Customer')
returns
result = true|false
=end
def is_role( role_name ) def is_role( role_name )
self.roles.each { |role| self.roles.each { |role|
return role if role.name == role_name return role if role.name == role_name
@ -40,6 +66,18 @@ class User < ApplicationModel
return false return false
end end
=begin
authenticate user
result = User.authenticate(username, password)
returns
result = user_model # user model if authentication was successfully
=end
def self.authenticate( username, password ) def self.authenticate( username, password )
# do not authenticate with nothing # do not authenticate with nothing
@ -60,98 +98,52 @@ class User < ApplicationModel
return false return false
end end
# use auth backends user_auth = Auth.check( username, password, user )
config = [
{
:adapter => 'internal',
},
{
:adapter => 'test',
},
]
Setting.where( :area => 'Security::Authentication' ).each {|setting|
if setting.state[:value]
config.push setting.state[:value]
end
}
# try to login against configure auth backends
user_auth = nil
config.each {|config_item|
next if !config_item[:adapter]
next if config_item.class == TrueClass
file = "auth/#{config_item[:adapter]}"
require file
user_auth = Auth.const_get("#{config_item[:adapter].to_s.upcase}").check( username, password, config_item, user )
# auth ok
if user_auth
# remember last login date
user_auth.update_last_login
# reset login failed
user_auth.login_failed = 0
user_auth.save
return user_auth
end
}
# set login failed +1 # set login failed +1
if !user_auth && user if !user_auth && user
sleep 1
user.login_failed = user.login_failed + 1 user.login_failed = user.login_failed + 1
user.save user.save
end end
# auth failed # auth ok
sleep 1
return user_auth return user_auth
end end
=begin
authenticate user agains sso
result = User.sso(sso_params)
returns
result = user_model # user model if authentication was successfully
=end
def self.sso(params) def self.sso(params)
# use auth backends
config = [
{
:adapter => 'env',
},
{
:adapter => 'otrs',
},
]
# Setting.where( :area => 'Security::Authentication' ).each {|setting|
# if setting.state[:value]
# config.push setting.state[:value]
# end
# }
# try to login against configure auth backends # try to login against configure auth backends
user_auth = nil user_auth = Sso.check( params, user )
config.each {|config_item| return if !user_auth
next if !config_item[:adapter]
next if config_item.class == TrueClass
file = "sso/#{config_item[:adapter]}"
require file
user_auth = SSO.const_get("#{config_item[:adapter].to_s.upcase}").check( params, config_item )
# auth ok return user_auth
if user_auth
# remember last login date
user_auth.update_last_login
# reset login failed
user_auth.login_failed = 0
user_auth.save
return user_auth
end
}
return false
end end
=begin
create user from from omni auth hash
result = User.create_from_hash!(hash)
returns
result = user_model # user model if create was successfully
=end
def self.create_from_hash!(hash) def self.create_from_hash!(hash)
url = '' url = ''
if hash['info']['urls'] then if hash['info']['urls'] then
@ -173,6 +165,18 @@ class User < ApplicationModel
end end
=begin
send reset password email with token to user
result = User.password_reset_send(username)
returns
result = true|false
=end
def self.password_reset_send(username) def self.password_reset_send(username)
return if !username || username == '' return if !username || username == ''
@ -230,7 +234,18 @@ class User < ApplicationModel
return true return true
end end
# check token =begin
check reset password token
result = User.password_reset_check(token)
returns
result = user_model # user_model if token was verified
=end
def self.password_reset_check(token) def self.password_reset_check(token)
user = Token.check( :action => 'PasswordReset', :name => token ) user = Token.check( :action => 'PasswordReset', :name => token )
@ -242,6 +257,18 @@ class User < ApplicationModel
return user return user
end end
=begin
reset reset password with token and set new password
result = User.password_reset_via_token(token,password)
returns
result = user_model # user_model if token was verified
=end
def self.password_reset_via_token(token,password) def self.password_reset_via_token(token,password)
# check token # check token
@ -256,6 +283,22 @@ class User < ApplicationModel
return user return user
end end
=begin
search user
result = User.search(
:query => 'some search term'
:limit => 15,
:current_user => user_model,
)
returns
result = [user_model1, user_model2, ...]
=end
def self.search(params) def self.search(params)
# get params # get params
@ -378,6 +421,19 @@ class User < ApplicationModel
return user return user
end end
=begin
update last login date (is automatically done by auth and sso backend)
user = User.find(123)
result = user.update_last_login
returns
result = new_user_model
=end
def update_last_login def update_last_login
self.last_login = Time.now self.last_login = Time.now
self.save self.save

View file

@ -6,8 +6,3 @@
<div id="splash"> <div id="splash">
<div class="logo">booting...</div> <div class="logo">booting...</div>
</div> </div>
<script type="text/javascript">
jQuery(function(){
new App.Run();
})
</script>

View file

@ -3,7 +3,11 @@
<head> <head>
<title><%= Setting.get('product_name') %></title> <title><%= Setting.get('product_name') %></title>
<%= stylesheet_link_tag "application" %> <%= stylesheet_link_tag "application" %>
<% if Rails.configuration.assets.debug %>
<%= javascript_include_tag "application" %> <%= javascript_include_tag "application" %>
<% else %>
<%= javascript_include_tag "application", :defer => 'defer' %>
<% end %>
<%= csrf_meta_tags %> <%= csrf_meta_tags %>
</head> </head>
<body> <body>

View file

@ -0,0 +1,45 @@
Zammad::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Configure static asset server for tests with Cache-Control for performance
config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"
config.assets.compress = false
config.assets.compile = true
config.assets.digest = true
# Log error messages when you accidentally call methods on nil
config.whiny_nils = true
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = true
# Raise exceptions instead of rendering exception templates
config.action_dispatch.show_exceptions = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Raise exception on mass assignment protection for Active Record models
config.active_record.mass_assignment_sanitizer = :strict
# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
# Disable request forgery protection in test environment
config.action_controller.allow_forgery_protection = false
# autoload on
config.dependency_loading = true
end

View file

@ -0,0 +1,52 @@
class UpdateGeo2 < ActiveRecord::Migration
def up
Setting.where( :name => 'geo_backend' ).destroy_all
Setting.create_if_not_exists(
:title => 'Geo Location Backend',
:name => 'geo_location_backend',
:area => 'System::Geo',
:description => 'Defines the backend for geo location lookups.',
:options => {
:form => [
{
:display => '',
:null => true,
:name => 'geo_location_backend',
:tag => 'select',
:options => {
'' => '-',
'GeoLocation::Gmaps' => 'Google Maps',
},
},
],
},
:state => 'GeoLocation::Gmaps',
:frontend => false
)
Setting.create_if_not_exists(
:title => 'Geo IP Backend',
:name => 'geo_ip_backend',
:area => 'System::Geo',
:description => 'Defines the backend for geo ip lookups.',
:options => {
:form => [
{
:display => '',
:null => true,
:name => 'geo_ip_backend',
:tag => 'select',
:options => {
'' => '-',
'GeoIp::Freegeoip' => 'freegeoip.net',
},
},
],
},
:state => 'GeoIp::Freegeoip',
:frontend => false
)
end
def down
end
end

View file

@ -0,0 +1,33 @@
class UpdateTicketNumber < ActiveRecord::Migration
def up
Setting.create_or_update(
:title => 'Ticket Number Format',
:name => 'ticket_number',
:area => 'Ticket::Number',
:description => 'Selects the ticket number generator module. "Increment" increments the ticket
number, the SystemID and the counter are used with SystemID.Counter format (e.g. 1010138, 1010139).
With "Date" the ticket numbers will be generated by the current date, the SystemID and the counter.
The format looks like Year.Month.Day.SystemID.counter (e.g. 201206231010138, 201206231010139).
With param "Checksum => true" the counter will be appended as checksum to the string. The format
looks like SystemID.Counter.CheckSum (e. g. 10101384, 10101392) or Year.Month.Day.SystemID.Counter.CheckSum (e.g. 2012070110101520, 2012070110101535).',
:options => {
:form => [
{
:display => '',
:null => true,
:name => 'ticket_number',
:tag => 'select',
:options => {
'Ticket::Number::Increment' => 'Increment (SystemID.Counter)',
'Ticket::Number::Date' => 'Date (Year.Month.Day.SystemID.Counter)',
},
},
],
},
:state => 'Ticket::Number::Increment',
:frontend => false
)
end
def down
end
end

View file

@ -135,7 +135,7 @@ Setting.create_if_not_exists(
) )
Setting.create_if_not_exists( Setting.create_if_not_exists(
:title => 'Geo Location Backend', :title => 'Geo Location Backend',
:name => 'geo_backend', :name => 'geo_location_backend',
:area => 'System::Geo', :area => 'System::Geo',
:description => 'Defines the backend for geo location lookups.', :description => 'Defines the backend for geo location lookups.',
:options => { :options => {
@ -143,17 +143,39 @@ Setting.create_if_not_exists(
{ {
:display => '', :display => '',
:null => true, :null => true,
:name => 'geo_backend', :name => 'geo_location_backend',
:tag => 'select', :tag => 'select',
:options => { :options => {
'' => '-', '' => '-',
'Gmaps' => 'Google Maps', 'GeoLocation::Gmaps' => 'Google Maps',
}, },
}, },
], ],
}, },
:state => 'Gmaps', :state => 'GeoLocation::Gmaps',
:frontend => true :frontend => false
)
Setting.create_if_not_exists(
:title => 'Geo IP Backend',
:name => 'geo_ip_backend',
:area => 'System::Geo',
:description => 'Defines the backend for geo ip lookups.',
:options => {
:form => [
{
:display => '',
:null => true,
:name => 'geo_ip_backend',
:tag => 'select',
:options => {
'' => '-',
'GeoIp::Freegeoip' => 'freegeoip.net',
},
},
],
},
:state => 'GeoIp::Freegeoip',
:frontend => false
) )
Setting.create_if_not_exists( Setting.create_if_not_exists(
@ -228,7 +250,7 @@ Setting.create_if_not_exists(
:area => 'Security::Authentication', :area => 'Security::Authentication',
:description => 'Enables user authentication via OTRS.', :description => 'Enables user authentication via OTRS.',
:state => { :state => {
:adapter => 'otrs', :adapter => 'Auth::Otrs',
:required_group_ro => 'stats', :required_group_ro => 'stats',
:group_rw_role_map => { :group_rw_role_map => {
'admin' => 'Admin', 'admin' => 'Admin',
@ -249,7 +271,7 @@ Setting.create_if_not_exists(
:area => 'Security::Authentication', :area => 'Security::Authentication',
:description => 'Enables user authentication via LDAP.', :description => 'Enables user authentication via LDAP.',
:state => { :state => {
:adapter => 'ldap', :adapter => 'Auth::Ldap',
:host => 'localhost', :host => 'localhost',
:port => 389, :port => 389,
:bind_dn => 'cn=Manager,dc=example,dc=org', :bind_dn => 'cn=Manager,dc=example,dc=org',
@ -690,13 +712,13 @@ Setting.create_if_not_exists(
:name => 'ticket_number', :name => 'ticket_number',
:tag => 'select', :tag => 'select',
:options => { :options => {
'increment' => 'Increment (SystemID.Counter)', 'Ticket::Number::Increment' => 'Increment (SystemID.Counter)',
'date' => 'Date (Year.Month.Day.SystemID.Counter)', 'Ticket::Number::Date' => 'Date (Year.Month.Day.SystemID.Counter)',
}, },
}, },
], ],
}, },
:state => 'increment', :state => 'Ticket::Number::Increment',
:frontend => false :frontend => false
) )
Setting.create_if_not_exists( Setting.create_if_not_exists(

50
lib/application_lib.rb Normal file
View file

@ -0,0 +1,50 @@
class ApplicationLib
=begin
load adapter based on setting option
result = self.load_adapter_by_setting( 'some_setting_with_class_name' )
returns
result = Some::Classname
=end
def self.load_adapter_by_setting(setting)
adapter = Setting.get( setting )
return if !adapter
# load backend
self.load_adapter(adapter)
end
=begin
load adapter
result = self.load_adapter( 'Some::Classname' )
returns
result = Some::Classname
=end
def self.load_adapter(adapter)
# load adapter
# will only work on ruby 2.0
# Object.const_get(adapter)
# will work on ruby 1.9 and 2.0
# adapter.split('::').inject(Object) do |mod, class_name|
# mod.const_get(class_name)
# end
# will work with active_support
adapter.constantize
end
end

63
lib/auth.rb Normal file
View file

@ -0,0 +1,63 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
class Auth < ApplicationLib
=begin
authenticate user via username and password
result = Auth.check( username, password, user )
returns
result = user_model # if authentication was successfully
=end
def self.check(username, password, user)
# use std. auth backends
config = [
{
:adapter => 'Auth::Internal',
},
{
:adapter => 'Auth::Test',
},
]
# added configured backends
Setting.where( :area => 'Security::Authentication' ).each {|setting|
if setting.state[:value]
config.push setting.state[:value]
end
}
# try to login against configure auth backends
user_auth = nil
config.each {|config_item|
next if !config_item[:adapter]
# load backend
backend = self.load_adapter( config_item[:adapter] )
return if !backend
user_auth = backend.check( username, password, config_item, user )
# auth ok
if user_auth
# remember last login date
user_auth.update_last_login
# reset login failed
user_auth.login_failed = 0
user_auth.save
return user_auth
end
}
return
end
end

View file

@ -1,6 +1,6 @@
module Auth # Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
end
module Auth::INTERNAL module Auth::Internal
def self.check( username, password, config, user ) def self.check( username, password, config, user )
# return if no user exists # return if no user exists

View file

@ -1,8 +1,8 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
require 'net/ldap' require 'net/ldap'
module Auth module Auth::Ldap
end
module Auth::LDAP
def self.check( username, password, config, user ) def self.check( username, password, config, user )
scope = Net::LDAP::SearchScope_WholeSubtree scope = Net::LDAP::SearchScope_WholeSubtree

View file

@ -1,7 +1,8 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
require 'import/otrs' require 'import/otrs'
module Auth
end module Auth::Otrs
module Auth::OTRS
def self.check( username, password, config, user ) def self.check( username, password, config, user )
endpoint = Setting.get('import_otrs_endpoint') endpoint = Setting.get('import_otrs_endpoint')

View file

@ -1,6 +1,6 @@
module Auth # Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
end
module Auth::TEST module Auth::Test
def self.check( username, password, config, user ) def self.check( username, password, config, user )
# development systems # development systems

38
lib/geo_ip.rb Normal file
View file

@ -0,0 +1,38 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
class GeoIp < ApplicationLib
=begin
lookup location based on ip or hostname
result = GeoIp.location( '172.0.0.1' )
returns
result = {
"ip" => "172.0.0.1"
"country_code" => "DE",
"country_name" => "Germany",
"region_code" => "05",
"region_name" => "Hessen",
"city" => "Frankfurt Am Main"
"zipcode" => "12345",
"latitude" => 50.1167,
"longitude" => 8.6833,
"metro_code" => "",
"areacode" => ""
}
=end
def self.location(address)
# load backend
backend = self.load_adapter_by_setting( 'geo_ip_backend' )
return if !backend
# db lookup
backend.location(address)
end
end

View file

@ -1,11 +1,13 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
require 'faraday' require 'faraday'
require 'cache' require 'cache'
module Geoip class GeoIp::Freegeoip
def self.location(address) def self.location(address)
# check cache # check cache
cache_key = "geoip::#{address}" cache_key = "freegeoip::#{address}"
cache = Cache.get( cache_key ) cache = Cache.get( cache_key )
return cache if cache return cache if cache

48
lib/geo_location.rb Normal file
View file

@ -0,0 +1,48 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
class GeoLocation < 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 = self.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 = self.load_adapter_by_setting( 'geo_location_backend' )
return if !backend
# db lookup
backend.reverse_geocode(lat,lng)
end
end

View file

@ -1,8 +1,11 @@
module Gmaps # Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
class 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"
response = Net::HTTP.get_response( URI.parse(url) ) response = Net::HTTP.get_response( URI.parse(url) )
return if response.code.to_s != '200' return if ! response.kind_of? Net::HTTPSuccess
result = JSON.parse( response.body ) result = JSON.parse( response.body )
@ -14,7 +17,7 @@ module Gmaps
def self.reverse_geocode(lat,lng) def self.reverse_geocode(lat,lng)
url = "http://maps.googleapis.com/maps/api/geocode/json?latlng=#{lat},#{lng}&sensor=true" url = "http://maps.googleapis.com/maps/api/geocode/json?latlng=#{lat},#{lng}&sensor=true"
response = Net::HTTP.get_response( URI.parse(url) ) response = Net::HTTP.get_response( URI.parse(url) )
return if response.code.to_s != '200' return if ! response.kind_of? Net::HTTPSuccess
result = JSON.parse( response.body ) result = JSON.parse( response.body )

View file

@ -9,11 +9,11 @@ module RSS
response = Net::HTTP.get_response( URI.parse(url) ) response = Net::HTTP.get_response( URI.parse(url) )
# check if redirect is needed # check if redirect is needed
if response.code.to_s == '301' || response.code.to_s == '302' if response.kind_of? Net::HTTPRedirection
url = response.header['location'] url = response.header['location']
response = Net::HTTP.get_response( URI.parse( url ) ) response = Net::HTTP.get_response( URI.parse( url ) )
end end
if response.code.to_s != '200' if ! response.kind_of? Net::HTTPSuccess
raise "Can't fetch '#{url}', http code: #{response.code.to_s}" raise "Can't fetch '#{url}', http code: #{response.code.to_s}"
return return
end end

View file

@ -395,7 +395,7 @@ class UserState
# overview # overview
cache_key = @cache_key + '_overview' cache_key = @cache_key + '_overview'
if CacheIn.expired(cache_key) if CacheIn.expired(cache_key)
overview = Ticket.overview( overview = Ticket::Overview.list(
:current_user => user, :current_user => user,
) )
overview_cache = CacheIn.get( cache_key, { :re_expire => true } ) overview_cache = CacheIn.get( cache_key, { :re_expire => true } )
@ -405,20 +405,19 @@ class UserState
# puts overview.inspect # puts overview.inspect
# puts '------' # puts '------'
# puts overview_cache.inspect # puts overview_cache.inspect
CacheIn.set( cache_key, overview, { :expires_in => 3.seconds } ) CacheIn.set( cache_key, overview, { :expires_in => 4.seconds } )
end end
end end
# overview lists # overview lists
overviews = Ticket.overview_list( overviews = Ticket::Overview.all(
:current_user => user, :current_user => user,
) )
overviews.each { |overview| overviews.each { |overview|
cache_key = @cache_key + '_overview_data_' + overview.link cache_key = @cache_key + '_overview_data_' + overview.link
if CacheIn.expired(cache_key) if CacheIn.expired(cache_key)
overview_data = Ticket.overview( overview_data = Ticket::Overview.list(
:view => overview.link, :view => overview.link,
# :view_mode => params[:view_mode],
:current_user => user, :current_user => user,
:array => true, :array => true,
) )
@ -434,7 +433,7 @@ class UserState
# create_attributes # create_attributes
cache_key = @cache_key + '_ticket_create_attributes' cache_key = @cache_key + '_ticket_create_attributes'
if CacheIn.expired(cache_key) if CacheIn.expired(cache_key)
ticket_create_attributes = Ticket.attributes_to_change( ticket_create_attributes = Ticket::ScreenOptions.attributes_to_change(
:current_user_id => user.id, :current_user_id => user.id,
) )
ticket_create_attributes_cache = CacheIn.get( cache_key, { :re_expire => true } ) ticket_create_attributes_cache = CacheIn.get( cache_key, { :re_expire => true } )
@ -638,7 +637,7 @@ class ClientState
end end
# overview_data # overview_data
overviews = Ticket.overview_list( overviews = Ticket::Overview.all(
:current_user => user, :current_user => user,
) )
overviews.each { |overview| overviews.each { |overview|
@ -661,7 +660,7 @@ class ClientState
group_ids.push group.id group_ids.push group.id
} }
agents = {} agents = {}
Ticket.agents.each { |user| Ticket::ScreenOptions.agents.each { |user|
agents[ user.id ] = 1 agents[ user.id ] = 1
} }
groups_users = {} groups_users = {}

63
lib/sso.rb Normal file
View file

@ -0,0 +1,63 @@
# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
class Sso < ApplicationLib
=begin
authenticate user via username and password
result = Sso.check( params, config_item )
returns
result = user_model # if authentication was successfully
=end
def self.check(params)
# use std. auth backends
config = [
{
:adapter => 'Sso::Env',
},
{
:adapter => 'Sso::Otrs',
},
]
# added configured backends
Setting.where( :area => 'Security::SSO' ).each {|setting|
if setting.state[:value]
config.push setting.state[:value]
end
}
# try to login against configure auth backends
user_auth = nil
config.each {|config_item|
next if !config_item[:adapter]
# load backend
backend = self.load_adapter( config_item[:adapter] )
return if !backend
user_auth = backend.check( params, config_item )
# auth ok
if user_auth
# remember last login date
user_auth.update_last_login
# reset login failed
user_auth.login_failed = 0
user_auth.save
return user_auth
end
}
return
end
end

View file

@ -1,6 +1,6 @@
module SSO # Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
end
module SSO::ENV module Sso::Env
def self.check( params, config_item ) def self.check( params, config_item )
# try to find user based on login # try to find user based on login

View file

@ -1,6 +1,6 @@
module SSO # Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
end
module SSO::OTRS module Sso::Otrs
def self.check( params, config_item ) def self.check( params, config_item )
endpoint = Setting.get('import_otrs_endpoint') endpoint = Setting.get('import_otrs_endpoint')

View file

@ -55,7 +55,7 @@ class SignupTest < TestCase
}, },
{ {
:execute => 'wait', :execute => 'wait',
:value => 2, :value => 5,
}, },
# check action # check action

View file

@ -7,7 +7,7 @@ Setting.create_or_update(
:area => 'Security::Authentication', :area => 'Security::Authentication',
:description => 'Enables user authentication via LDAP.', :description => 'Enables user authentication via LDAP.',
:state => { :state => {
:adapter => 'ldap', :adapter => 'Auth::Ldap',
:host => 'localhost', :host => 'localhost',
:port => 389, :port => 389,
:bind_dn => 'cn=Manager,dc=example,dc=org', :bind_dn => 'cn=Manager,dc=example,dc=org',
@ -45,6 +45,7 @@ else
:created_by_id => 1 :created_by_id => 1
) )
end end
class AuthTest < ActiveSupport::TestCase class AuthTest < ActiveSupport::TestCase
test 'auth' do test 'auth' do
tests = [ tests = [