summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/api/v1/calls_controller.rb41
-rw-r--r--app/controllers/api/v1/phone_book_entries_controller.rb77
-rw-r--r--app/controllers/api/v1/switchboards_controller.rb12
-rw-r--r--app/controllers/call_forwards_controller.rb17
-rw-r--r--app/controllers/call_histories_controller.rb13
-rw-r--r--app/controllers/cdrs_controller.rb43
-rw-r--r--app/controllers/config_snom_controller.rb777
-rw-r--r--app/controllers/extension_modules_controller.rb73
-rw-r--r--app/controllers/gateway_headers_controller.rb57
-rw-r--r--app/controllers/sip_accounts_controller.rb1
-rw-r--r--app/controllers/switchboard_entries_controller.rb2
-rw-r--r--app/controllers/switchboards_controller.rb4
-rw-r--r--app/controllers/trigger_controller.rb2
-rw-r--r--app/controllers/voicemail_settings_controller.rb3
-rw-r--r--app/helpers/extension_modules_helper.rb2
-rw-r--r--app/mailers/notifications.rb3
-rw-r--r--app/models/ability.rb4
-rw-r--r--app/models/call.rb46
-rw-r--r--app/models/call_forward.rb40
-rw-r--r--app/models/cdr.rb17
-rw-r--r--app/models/extension_module.rb58
-rw-r--r--app/models/freeswitch_cdr.rb4
-rw-r--r--app/models/gateway.rb10
-rw-r--r--app/models/gateway_header.rb25
-rw-r--r--app/models/gateway_setting.rb5
-rw-r--r--app/models/group_membership.rb9
-rw-r--r--app/models/hunt_group.rb2
-rw-r--r--app/models/phone.rb5
-rw-r--r--app/models/sip_account.rb13
-rw-r--r--app/models/switchboard.rb11
-rw-r--r--app/models/user.rb13
-rw-r--r--app/models/voicemail_account.rb8
-rw-r--r--app/serializers/phone_book_entry_serializer.rb14
-rw-r--r--app/serializers/sip_account_serializer.rb10
-rw-r--r--app/serializers/switchboard_entry_serializer.rb2
-rw-r--r--app/serializers/switchboard_serializer.rb3
-rw-r--r--app/views/call_forwards/_form_core.html.haml4
-rw-r--r--app/views/call_histories/_index_core.html.haml5
-rw-r--r--app/views/cdrs/_index_core.html.haml33
-rw-r--r--app/views/cdrs/index.html.haml4
-rw-r--r--app/views/config_snom/snom_phone.xml.haml (renamed from app/views/config_snom/show.xml.haml)8
-rw-r--r--app/views/config_snom/snom_vision.xml.haml52
-rw-r--r--app/views/extension_modules/_form.html.haml7
-rw-r--r--app/views/extension_modules/_form_core.html.haml6
-rw-r--r--app/views/extension_modules/_index_core.html.haml13
-rw-r--r--app/views/extension_modules/edit.html.haml3
-rw-r--r--app/views/extension_modules/index.html.haml6
-rw-r--r--app/views/extension_modules/new.html.haml3
-rw-r--r--app/views/extension_modules/show.html.haml22
-rw-r--r--app/views/gateway_headers/_form.html.haml7
-rw-r--r--app/views/gateway_headers/_form_core.html.haml7
-rw-r--r--app/views/gateway_headers/_index_core.html.haml17
-rw-r--r--app/views/gateway_headers/edit.html.haml3
-rw-r--r--app/views/gateway_headers/index.html.haml6
-rw-r--r--app/views/gateway_headers/new.html.haml3
-rw-r--r--app/views/gateway_headers/show.html.haml19
-rw-r--r--app/views/gateways/show.html.haml6
-rw-r--r--app/views/group_memberships/_form_core.html.haml3
-rw-r--r--app/views/notifications/new_voicemail.text.erb2
-rw-r--r--app/views/phones/show.html.haml9
-rw-r--r--app/views/sip_accounts/_index_core.html.haml6
-rw-r--r--app/views/sip_accounts/show.html.haml5
-rw-r--r--app/views/switchboard_entries/_form_core.html.haml1
-rw-r--r--app/views/switchboard_entries/show.html.haml5
-rw-r--r--app/views/switchboards/_form_core.html.haml6
-rw-r--r--app/views/switchboards/show.html.erb119
-rw-r--r--app/views/voicemail_settings/_edit_form_core.html.haml2
-rw-r--r--config/locales/views/config_snom/de.yml2
-rw-r--r--config/locales/views/extension_modules/de.yml59
-rw-r--r--config/locales/views/extension_modules/en.yml59
-rw-r--r--config/locales/views/gateway_headers/de.yml66
-rw-r--r--config/locales/views/gateway_headers/en.yml66
-rw-r--r--config/locales/views/gateway_settings/de.yml15
-rw-r--r--config/locales/views/gateway_settings/en.yml15
-rw-r--r--config/locales/views/sip_accounts/de.yml1
-rw-r--r--config/locales/views/sip_accounts/en.yml1
-rw-r--r--config/locales/views/switchboard_entries/de.yml5
-rw-r--r--config/locales/views/switchboard_entries/en.yml5
-rw-r--r--config/locales/views/switchboards/de.yml23
-rw-r--r--config/locales/views/switchboards/en.yml20
-rw-r--r--config/routes.rb21
-rw-r--r--db/migrate/20130225091200_add_destinationable_to_call_forward.rb14
-rw-r--r--db/migrate/20130612120200_add_gateway_defaults.rb8
-rw-r--r--db/migrate/20130627103456_add_blind_transfer_to_switchboard.rb17
-rw-r--r--db/migrate/20130627162201_add_search_activated_to_switchboard.rb5
-rw-r--r--db/migrate/20130701084803_add_reverse_lookup_to_switchboard.rb5
-rw-r--r--db/migrate/20130718083656_create_extension_modules.rb19
-rw-r--r--db/migrate/20130730080331_add_forwarding_path_to_cdrs.rb5
-rw-r--r--db/migrate/20130731055839_add_clir_to_call_histories.rb5
-rw-r--r--db/migrate/20130802105300_create_gateway_headers.rb18
-rw-r--r--db/migrate/20130804201900_add_gateway_default_headers.rb28
-rw-r--r--db/migrate/20130805064700_change_rfc2833_profile_parameter.rb15
-rw-r--r--misc/freeswitch/conf/freeswitch.xml2
-rw-r--r--misc/freeswitch/scripts/common/array.lua3
-rw-r--r--misc/freeswitch/scripts/common/call_history.lua1
-rw-r--r--misc/freeswitch/scripts/common/conference.lua13
-rw-r--r--misc/freeswitch/scripts/common/gateway.lua94
-rw-r--r--misc/freeswitch/scripts/common/object.lua13
-rw-r--r--misc/freeswitch/scripts/configuration.lua6
-rw-r--r--misc/freeswitch/scripts/dialplan/acd.lua20
-rw-r--r--misc/freeswitch/scripts/dialplan/callthrough.lua4
-rw-r--r--misc/freeswitch/scripts/dialplan/dialplan.lua43
-rw-r--r--misc/freeswitch/scripts/dialplan/functions.lua68
-rw-r--r--misc/freeswitch/scripts/dialplan/hunt_group.lua6
-rw-r--r--misc/freeswitch/scripts/dialplan/session.lua10
-rw-r--r--misc/freeswitch/scripts/dialplan/sip_call.lua48
-rw-r--r--misc/freeswitch/scripts/dialplan/voicemail.lua5
-rw-r--r--misc/freeswitch/scripts/dialplan_default.lua2
-rw-r--r--misc/freeswitch/scripts/event/cdr_save.lua1
-rw-r--r--misc/freeswitch/scripts/event/presence_update.lua3
-rw-r--r--misc/freeswitch/scripts/phones/phone.lua42
-rw-r--r--misc/freeswitch/scripts/phones/snom_vision.lua40
-rw-r--r--public/js/app.js126
-rw-r--r--public/js/libs/handlebars.js155
-rw-r--r--public/js/libs/moment/lang/de.js4
-rw-r--r--public/js/libs/moment/moment.min.js6
-rw-r--r--test/unit/extension_module_test.rb7
117 files changed, 2434 insertions, 603 deletions
diff --git a/app/controllers/api/v1/calls_controller.rb b/app/controllers/api/v1/calls_controller.rb
new file mode 100644
index 0000000..329bd94
--- /dev/null
+++ b/app/controllers/api/v1/calls_controller.rb
@@ -0,0 +1,41 @@
+module Api
+ module V1
+ class CallsController < ApplicationController
+ respond_to :json
+
+ def index
+ @calls = Call.limit(10)
+
+ respond_with @calls
+ end
+
+ def show
+ @call = Call.find(params[:id])
+
+ if params[:transfer_blind]
+ @call.transfer_blind(params[:transfer_blind])
+ else
+ if params[:transfer_attended] && @call.b_sip_account.phones.first.phone_model.manufacturer.name == 'SNOM Technology AG'
+ phone = @call.b_sip_account.phones.first
+ ip_address = phone.ip_address
+ http_user = phone.http_user
+ http_password = phone.http_password
+
+ # Hold
+ open("http://#{ip_address}/command.htm?key=F_HOLD", :http_basic_authentication=>[http_user, http_password])
+
+ # Call the other party
+ (0..(params[:transfer_attended].length - 1)).each do |i|
+ digit = params[:transfer_attended][i]
+ open("http://#{ip_address}/command.htm?key=#{digit}", :http_basic_authentication=>[http_user, http_password])
+ end
+ open("http://#{ip_address}/command.htm?key=ENTER", :http_basic_authentication=>[http_user, http_password])
+ end
+ end
+
+ respond_with @call
+ end
+
+ end
+ end
+end
diff --git a/app/controllers/api/v1/phone_book_entries_controller.rb b/app/controllers/api/v1/phone_book_entries_controller.rb
new file mode 100644
index 0000000..6a39623
--- /dev/null
+++ b/app/controllers/api/v1/phone_book_entries_controller.rb
@@ -0,0 +1,77 @@
+module Api
+ module V1
+ class PhoneBookEntriesController < ApplicationController
+ respond_to :json
+
+ def index
+ query = params[:query]
+ switchboard = Switchboard.find(params[:switchboard_id])
+
+ return nil if query.blank?
+
+ current_ability = Ability.new(current_user)
+ phone_book_entries = PhoneBookEntry.accessible_by(current_ability)
+
+ if query.match(/^\+?\d+$/) != nil && switchboard && switchboard.reverse_lookup_activated
+ # Find by phone number
+ phone_book_entries_ids = phone_book_entries.map{|entry| entry.id}
+ found_phone_numbers = PhoneNumber.
+ where(:phone_numberable_type => 'PhoneBookEntry', :phone_numberable_id => phone_book_entries_ids).
+ where('number LIKE ?', "#{query}%")
+ search_result = phone_book_entries.where(:id => found_phone_numbers.map{|entry| entry.phone_numberable_id})
+ elsif query.match(/^[\"\'](.*)[\"\']$/) != nil
+ # The User searched for =>'example'<= so he wants an EXACT search for that.
+ # This is the fasted and most accurate way of searching.
+ # The order to search is: last_name, first_name and organization.
+ # It stops searching as soon as it finds results.
+ #
+ query = $1
+ search_result = phone_book_entries.where(:last_name => query)
+ search_result = phone_book_entries.where(:first_name => query) if search_result.blank?
+ search_result = phone_book_entries.where(:organization => query) if search_result.blank?
+
+ exact_search = true
+ else
+ # Search with SQL LIKE
+ #
+ search_result = phone_book_entries.
+ where( '( ( last_name LIKE ? ) OR ( first_name LIKE ? ) OR ( organization LIKE ? ) )',
+ "#{query}%", "#{query}%", "#{query}%" )
+
+ exact_search = false
+ end
+
+ # Let's have a run with our phonetic search.
+ #
+ phonetic_query = PhoneBookEntry.koelner_phonetik(query)
+ phonetic_search_result = phone_book_entries.where(:last_name_phonetic => phonetic_query)
+ phonetic_search_result = phone_book_entries.where(:first_name_phonetic => phonetic_query) if phonetic_search_result.blank?
+ phonetic_search_result = phone_book_entries.where(:organization_phonetic => phonetic_query) if phonetic_search_result.blank?
+
+ if phonetic_search_result.blank?
+ # Let's try the search with SQL LIKE. Just in case.
+ #
+ phonetic_search_result = phone_book_entries.where( 'last_name_phonetic LIKE ?', "#{phonetic_query}%" )
+ phonetic_search_result = phone_book_entries.where( 'first_name_phonetic LIKE ?', "#{phonetic_query}%" ) if phonetic_search_result.blank?
+ phonetic_search_result = phone_book_entries.where( 'organization_phonetic LIKE ?', "#{phonetic_query}%" ) if phonetic_search_result.blank?
+ end
+
+ phonetic_search = true if phonetic_search_result.any?
+
+ phone_book_entries = search_result
+
+ if phone_book_entries.count == 0 && exact_search == false && phonetic_search
+ phone_book_entries = phonetic_search_result
+ end
+
+ # Let's sort the results and do pagination.
+ #
+ phone_book_entries = phone_book_entries.
+ order([ :last_name, :first_name, :organization ]).limit(20)
+
+ respond_with phone_book_entries
+ end
+
+ end
+ end
+end
diff --git a/app/controllers/api/v1/switchboards_controller.rb b/app/controllers/api/v1/switchboards_controller.rb
index e6996ca..4d6607a 100644
--- a/app/controllers/api/v1/switchboards_controller.rb
+++ b/app/controllers/api/v1/switchboards_controller.rb
@@ -5,16 +5,20 @@ module Api
def index
@user = current_user
- @switchboards = @user.switchboards
+ @switchboards = Switchboard.all
- respond_with @switchboards
+ if can? :read, @switchboards
+ respond_with @switchboards
+ end
end
def show
@user = current_user
- @switchboard = @user.switchboards.find(params[:id])
+ @switchboard = Switchboard.find(params[:id])
- respond_with @switchboard
+ if can? :read, @switchboard
+ respond_with @switchboard
+ end
end
end
end
diff --git a/app/controllers/call_forwards_controller.rb b/app/controllers/call_forwards_controller.rb
index 1721aa3..0d0f946 100644
--- a/app/controllers/call_forwards_controller.rb
+++ b/app/controllers/call_forwards_controller.rb
@@ -29,8 +29,8 @@ class CallForwardsController < ApplicationController
@call_forward.depth = GsParameter.get('DEFAULT_CALL_FORWARD_DEPTH')
@call_forward.active = true
@call_forwarding_destinations = call_forwarding_destination_types()
+ @call_forward.destinationable_type = 'PhoneNumber'
@call_forward.destination = GsParameter.get('CALLFORWARD_DESTINATION_DEFAULT').to_s if defined?(GsParameter.get('CALLFORWARD_DESTINATION_DEFAULT'))
- @destination_phone_number = @call_forward.destination
@available_call_forward_cases = []
CallForwardCase.all.each do |available_call_forward_case|
@@ -66,7 +66,6 @@ class CallForwardsController < ApplicationController
@available_call_forward_cases = CallForwardCase.all
@call_forwarding_destinations = call_forwarding_destination_types()
@available_greetings = available_greetings()
- @destination_phone_number = @call_forward.destination if @call_forward.call_forwarding_destination == ':PhoneNumber'
end
def update
@@ -118,7 +117,7 @@ class CallForwardsController < ApplicationController
add_breadcrumb @parent, sip_account_phone_number_path(@sip_account, @parent)
elsif @parent.class == SipAccount
add_breadcrumb t("users.index.page_title"), tenant_users_path(@user.current_tenant)
- add_breadcrumb @user, tenant_users_path(@user.current_tenant, @user)
+ add_breadcrumb @user, tenant_user_path(@user.current_tenant, @user)
add_breadcrumb t("sip_accounts.index.page_title"), user_sip_accounts_path(@user)
end
end
@@ -188,6 +187,18 @@ class CallForwardsController < ApplicationController
end
end
+ if @parent.class == HuntGroup && @parent.tenant
+ @parent.tenant.voicemail_accounts.each do |voicemail_account|
+ call_forwards_destination = CallForwardingDestination.new()
+ call_forwards_destination.id = "#{voicemail_account.id}:VoicemailAccount"
+ call_forwards_destination.label = "VoicemailAccount: #{voicemail_account.to_s}"
+ if !destinations_hash[call_forwards_destination.id]
+ destinations_hash[call_forwards_destination.id] = true
+ call_forwarding_destinations << call_forwards_destination
+ end
+ end
+ end
+
if @parent.class == PhoneNumber
if @parent.phone_numberable.class == SipAccount
sip_account = @parent.phone_numberable
diff --git a/app/controllers/call_histories_controller.rb b/app/controllers/call_histories_controller.rb
index a4d0c21..edd19cb 100644
--- a/app/controllers/call_histories_controller.rb
+++ b/app/controllers/call_histories_controller.rb
@@ -71,8 +71,17 @@ class CallHistoriesController < ApplicationController
@call_history = CallHistory.where(:id => params[:id]).first
if can?(:call, @call_history) && @sip_account.registration
phone_number = @call_history.display_number
- if ! phone_number.blank?
- @sip_account.call(phone_number)
+ caller_id_number = phone_number
+ if ! phone_number.blank?
+ if @sip_account.clir != @call_history.clir
+ if @call_history.clir == true
+ phone_number = 'f-dcliron-' + phone_number
+ elsif call.clir == false
+ phone_number = 'f-dcliroff-' + phone_number
+ end
+ end
+
+ @sip_account.call(phone_number, caller_id_number, @call_history.display_name)
end
end
redirect_to(:back)
diff --git a/app/controllers/cdrs_controller.rb b/app/controllers/cdrs_controller.rb
new file mode 100644
index 0000000..3815f52
--- /dev/null
+++ b/app/controllers/cdrs_controller.rb
@@ -0,0 +1,43 @@
+class CdrsController < ApplicationController
+ load_and_authorize_resource :tenant
+
+ before_filter :set_and_authorize_parent
+ before_filter :spread_breadcrumbs
+
+ helper_method :sort_column, :sort_descending
+
+ def index
+ @cdrs = Cdr.order(sort_column + ' ' + (sort_descending ? 'DESC' : 'ASC')).paginate(
+ :page => params[:page],
+ :per_page => GsParameter.get('DEFAULT_PAGINATION_ENTRIES_PER_PAGE')
+ )
+ end
+
+ def show
+ end
+
+ def destroy
+ @cdr.destroy
+ m = method( :"#{@parent.class.name.underscore}_cdrs_url" )
+ redirect_to m.(@parent), :notice => t('cdrs.controller.successfuly_destroyed')
+ end
+
+ private
+ def set_and_authorize_parent
+ @parent = @user || @tenant
+ authorize! :read, @parent
+ end
+
+ def spread_breadcrumbs
+ add_breadcrumb t("cdrs.index.page_title"), tenant_cdrs_path(@tenant)
+ end
+
+ def sort_descending
+ params[:desc].to_s == 'true'
+ end
+
+ def sort_column
+ Cdr.column_names.include?(params[:sort]) ? params[:sort] : 'start_stamp'
+ end
+
+end
diff --git a/app/controllers/config_snom_controller.rb b/app/controllers/config_snom_controller.rb
index 4b9489b..1cac4b2 100644
--- a/app/controllers/config_snom_controller.rb
+++ b/app/controllers/config_snom_controller.rb
@@ -1,6 +1,6 @@
class ConfigSnomController < ApplicationController
- MAX_SIP_ACCOUNTS_COUNT = 11
- MAX_SOFTKEYS_COUNT = 12 + (42 * 3) - 1
+ MAX_SIP_ACCOUNTS_COUNT = 11
+ MAX_SOFTKEYS_COUNT = 12 + (42 * 3) - 1
MAX_DIRECTORY_ENTRIES = 20
KEY_REGEXP = {
'0' => "[ -.,_0]+",
@@ -15,12 +15,115 @@ class ConfigSnomController < ApplicationController
'9' => "[wxyz9]",
}
- skip_authorization_check
-
- before_filter { |controller|
+ SNOM_PHONE_DEFAULTS = {
+ 'Snom 300' => {
+ :softkeys_physical => 6,
+ :softkeys_logical => 6,
+ },
+ 'Snom 320' => {
+ :softkeys_physical => 12,
+ :softkeys_logical => 12,
+ },
+ 'Snom 360' => {
+ :softkeys_physical => 12,
+ :softkeys_logical => 12,
+ },
+ 'Snom 360' => {
+ :softkeys_physical => 12,
+ :softkeys_logical => 12,
+ },
+ 'Snom 370' => {
+ :softkeys_physical => 12,
+ :softkeys_logical => 12,
+ },
+ 'Snom 820' => {
+ :softkeys_physical => 4,
+ :softkeys_logical => 16,
+ },
+ 'Snom 821' => {
+ :softkeys_physical => 4,
+ :softkeys_logical => 12,
+ },
+ 'Snom 870' => {
+ :softkeys_physical => 0,
+ :softkeys_logical => 16,
+ },
+ 'Snom vision' => {
+ :softkeys_physical => 16,
+ :softkeys_logical => 48,
+ :pages => 3,
+ },
+ }
+
+ skip_authorization_check
+
+ before_filter { |controller|
@mac_address = params[:mac_address].to_s.upcase.gsub(/[^0-9A-F]/,'')
@provisioning_authenticated = false
+ if !params[:phone].blank?
+ @phone = Phone.where({ :id => params[:phone].to_i }).first
+ if !params[:sip_account].blank?
+ @sip_account = @phone.sip_accounts.where({ :id => params[:sip_account].to_i }).first
+ end
+ end
+
+ @type = params[:type].blank? ? nil : params[:type].to_s.strip.downcase
+ @dialpad_keys = params[:keys].blank? ? nil : params[:keys].to_s.strip
+ }
+
+ def show
+ if @mac_address.blank?
+ render(
+ :status => 404,
+ :layout => false,
+ :content_type => 'text/plain',
+ :text => "<!-- Phone not found -->",
+ )
+ return
+ end
+
+ mac_address_to_model = {
+ '00041325' => 'Snom 300',
+ '00041328' => 'Snom 300',
+ '0004132D' => 'Snom 300',
+ '0004132F' => 'Snom 300',
+ '00041334' => 'Snom 300',
+ '00041350' => 'Snom 300',
+ '0004133B' => 'Snom 300',
+ '00041337' => 'Snom 300',
+ '00041324' => 'Snom 320',
+ '00041327' => 'Snom 320',
+ '0004132C' => 'Snom 320',
+ '00041331' => 'Snom 320',
+ '00041335' => 'Snom 320',
+ '00041338' => 'Snom 320',
+ '00041351' => 'Snom 320',
+ '00041323' => 'Snom 360',
+ '00041329' => 'Snom 360',
+ '0004132B' => 'Snom 360',
+ '00041339' => 'Snom 360',
+ '00041390' => 'Snom 360',
+ '00041326' => 'Snom 370',
+ '0004132E' => 'Snom 370',
+ '0004133A' => 'Snom 370',
+ '00041352' => 'Snom 370',
+ '00041340' => 'Snom 820',
+ '00041345' => 'Snom 821',
+ '00041348' => 'Snom 821',
+ '00041341' => 'Snom 870',
+ '00041332' => 'Snom meetingPoint',
+ '00041343' => 'Snom vision',
+ }
+ phone_model_str = mac_address_to_model[@mac_address[0, 8]]
+ if phone_model_str == 'Snom vision'
+ snom_vision
+ elsif !phone_model_str.blank?
+ snom_phone
+ end
+ end
+
+ def snom_phone
if !params[:provisioning_key].blank?
@phone = Phone.where({ :provisioning_key => params[:provisioning_key] }).first
if @phone
@@ -154,6 +257,7 @@ class ConfigSnomController < ApplicationController
:content_type => 'text/plain',
:text => "<!-- Phone not found -->",
)
+ return
end
if ! params[:sip_account].blank?
@@ -171,17 +275,16 @@ class ConfigSnomController < ApplicationController
end
end
- if ! params[:type].blank?
- @type = params[:type].to_s.strip.downcase
+ if ! @phone
+ render(
+ :status => 404,
+ :layout => false,
+ :content_type => 'text/plain',
+ :text => "<!-- Phone not found -->",
+ )
+ return
end
- if ! params[:keys].blank?
- @dialpad_keys = params[:keys].to_s.strip
- end
- }
-
-
- def show
send_sensitve = @provisioning_authenticated || !@phone.provisioning_key_active
@phone_settings = Hash.new()
@@ -230,15 +333,15 @@ class ConfigSnomController < ApplicationController
end
end
- if ! request.env['HTTP_USER_AGENT'].index('snom')
- Rails.logger.info "---> User-Agent indicates not a Snom phone (#{request.env['HTTP_USER_AGENT'].inspect})"
- else
- Rails.logger.info "---> Phone #{@mac_address.inspect}, IP address #{request_remote_ip.inspect}"
- @phone.update_attributes({ :ip_address => request_remote_ip })
- end
+ if ! request.env['HTTP_USER_AGENT'].index('snom')
+ Rails.logger.info "---> User-Agent indicates not a Snom phone (#{request.env['HTTP_USER_AGENT'].inspect})"
+ else
+ Rails.logger.info "---> Phone #{@mac_address.inspect}, IP address #{request_remote_ip.inspect}"
+ @phone.update_attributes({ :ip_address => request_remote_ip })
+ end
@softkeys = Array.new()
- @sip_accounts = Array.new()
+ @sip_accounts = Array.new()
phone_sip_accounts = Array.new()
if send_sensitve
@@ -252,16 +355,16 @@ class ConfigSnomController < ApplicationController
phone_sip_accounts.each do |sip_account|
if (sip_account.sip_accountable_type == @phone.phoneable_type) and (sip_account.sip_accountable_id == @phone.phoneable_id)
- snom_sip_account = {
+ snom_sip_account = {
:id => sip_account.id,
:active => 'on',
- :pname => sip_account.auth_name,
- :pass => sip_account.password,
- :host => sip_account.host,
- :outbound => sip_account.host,
- :name => sip_account.auth_name,
- :realname => 'Call',
- :user_idle_text => sip_account.caller_name,
+ :pname => sip_account.auth_name,
+ :pass => sip_account.password,
+ :host => sip_account.host,
+ :outbound => sip_account.host,
+ :name => sip_account.auth_name,
+ :realname => 'Call',
+ :user_idle_text => sip_account.caller_name,
:expiry => expiry_seconds,
}
@@ -282,217 +385,11 @@ class ConfigSnomController < ApplicationController
@sip_accounts.push(snom_sip_account)
sip_account_index = @sip_accounts.length
sip_account.softkeys.order(:position).each do |softkey|
- if softkey.softkey_function
- softkey_function = softkey.softkey_function.name
- end
- case softkey_function
- when 'blf'
- @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "blf <sip:#{softkey.number}@#{sip_account.host}>|f-ia-"})
- when 'speed_dial'
- @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "speed #{softkey.number}"})
- when 'dtmf'
- @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "dtmf #{softkey.number}"})
- when 'log_out'
- @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "speed f-lo"})
- when 'log_in'
- @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "speed f-li-#{softkey.number}"})
- when 'conference'
- conference = softkey.softkeyable
- if conference.class == Conference
- @softkeys.push({
- :context => sip_account_index,
- :function => softkey.softkey_function.name,
- :label => softkey.label,
- :softkey => softkey,
- :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
- :subscription => {
- :to => "sip:conference#{conference.id}@#{sip_account.host}",
- :for => "sip:conference#{conference.id}@#{sip_account.host}",
- },
- :actions => [{
- :type => :dial,
- :target => "f-ta-#{softkey.number}",
- :when => 'on press',
- :states => 'connected,holding',
- },{
- :type => :dial,
- :target => softkey.number,
- :when => 'on press',
- }],
- })
- end
- when 'parking_stall'
- parking_stall = softkey.softkeyable
- if parking_stall.class == ParkingStall
- @softkeys.push({
- :context => sip_account_index,
- :function => softkey.softkey_function.name,
- :label => softkey.label,
- :softkey => softkey,
- :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
- :subscription => {
- :to => "sip:park+#{parking_stall.name}@#{sip_account.host}",
- :for => "sip:park+#{parking_stall.name}@#{sip_account.host}",
- },
- :actions => [{
- :type => :dial,
- :target => "f-cpa-#{parking_stall.name}",
- :when => 'on press',
- :states => 'connected,holding',
- },{
- :type => :dial,
- :target => "f-cpa-#{parking_stall.name}",
- :when => 'on press',
- }],
- })
- end
- when 'call_forwarding'
- if softkey.softkeyable.class == CallForward then
- @softkeys.push({
- :context => sip_account_index,
- :function => softkey.softkey_function.name,
- :label => softkey.label,
- :softkey => softkey,
- :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
- :subscription => {
- :to => "sip:f-cftg-#{softkey.softkeyable_id}@#{sip_account.host}",
- :for => "sip:f-cftg-#{softkey.softkeyable_id}@#{sip_account.host}"
- },
- :actions => [{
- :type => :url,
- :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{snom_sip_account[:id]}/call_forwarding.xml?id=#{softkey.softkeyable_id}&function=toggle",
- :when => 'on press',
- }],
- })
- end
- when 'call_forwarding_always'
- phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'SipAccount').first
- if phone_number
- account_param = (phone_number.phone_numberable_id != snom_sip_account[:id] ? "&account=#{phone_number.phone_numberable_id}" : '')
- else
- phone_number = sip_account.phone_numbers.first
- account_param = ''
- end
-
- @softkeys.push({
- :context => sip_account_index,
- :function => softkey.softkey_function.name,
- :label => softkey.label,
- :softkey => softkey,
- :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
- :subscription => {
- :to => "f-cfutg-#{phone_number.id}@#{sip_account.host}",
- :for => "#{sip_account.auth_name}@#{sip_account.host}"
- },
- :actions => [{
- :type => :url,
- :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{snom_sip_account[:id]}/call_forwarding.xml?type=always&function=toggle#{account_param}",
- :when => 'on press',
- }],
- })
- when 'call_forwarding_assistant'
- phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'SipAccount').first
- if phone_number
- account_param = (phone_number.phone_numberable_id != snom_sip_account[:id] ? "&account=#{phone_number.phone_numberable_id}" : '')
- else
- phone_number = sip_account.phone_numbers.first
- account_param = ''
- end
-
- @softkeys.push({
- :context => sip_account_index,
- :function => softkey.softkey_function.name,
- :label => softkey.label,
- :softkey => softkey,
- :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
- :subscription => {
- :to => "f-cfatg-#{phone_number.id}@#{sip_account.host}",
- :for => "#{sip_account.auth_name}@#{sip_account.host}"
- },
- :actions => [{
- :type => :url,
- :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{snom_sip_account[:id]}/call_forwarding.xml?type=assistant&function=toggle#{account_param}",
- :when => 'on press',
- }],
- })
- when 'hunt_group_membership'
- phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'HuntGroup').first
- if phone_number
- hunt_group = HuntGroup.where(:id => phone_number.phone_numberable_id).first
- end
-
- sip_account_phone_numbers = Array.new()
- SipAccount.where(:id => @sip_accounts.first[:id]).first.phone_numbers.each do |phone_number|
- sip_account_phone_numbers.push(phone_number.number)
- end
-
- hunt_group_member_numbers = PhoneNumber.where(:number => sip_account_phone_numbers, :phone_numberable_type => 'HuntGroupMember')
-
- hunt_group_member = nil
- if hunt_group and hunt_group_member_numbers
- hunt_group_member_numbers.each do |hunt_group_member_number|
- hunt_group_member = hunt_group.hunt_group_members.where(:id => hunt_group_member_number.phone_numberable_id).first
- if hunt_group_member
- break
- end
- end
- end
-
- if hunt_group_member
- @softkeys.push({
- :context => sip_account_index,
- :function => softkey.softkey_function.name,
- :label => softkey.label,
- :softkey => softkey,
- :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
- :subscription => {
- :to => "f-hgmtg-#{hunt_group_member.id}@#{sip_account.host}",
- :for => "#{sip_account.auth_name}@#{sip_account.host}"
- },
- :actions => [{
- :type => :url,
- :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{snom_sip_account[:id]}/hunt_group.xml?group=#{hunt_group.id}&account=#{hunt_group_member.id}&function=toggle",
- :when => 'on press',
- }],
- })
- else
- @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => 'none'})
- end
- when 'acd_membership'
- acd_agent = nil
- phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'AutomaticCallDistributor').first
- if phone_number
- acd = AutomaticCallDistributor.where(:id => phone_number.phone_numberable_id).first
- if acd
- acd_agent = acd.acd_agents.where(:destination_type => 'SipAccount', :destination_id => sip_account.id).first
- end
- end
-
- if acd_agent
- @softkeys.push({
- :context => sip_account_index,
- :function => softkey.softkey_function.name,
- :label => softkey.label,
- :softkey => softkey,
- :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
- :subscription => {
- :to => "f-acdmtg-#{acd_agent.id}@#{sip_account.host}",
- :for => "#{sip_account.auth_name}@#{sip_account.host}"
- },
- :actions => [{
- :type => :url,
- :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{snom_sip_account[:id]}/acd.xml?acd=#{acd.id}&agent=#{acd_agent.id}&function=toggle",
- :when => 'on press',
- }],
- })
- else
- @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => 'none'})
- end
- when 'hold'
- @softkeys.push({:context => sip_account_index, :label => softkey.label, :data => "keyevent F_R"})
- else
- @softkeys.push({:label => softkey.label, :data => 'none'})
- end
+ @softkeys.push(format_key(softkey, sip_account_index))
+ end
+ if @softkeys.any? && @phone.extension_modules.any?
+ phone_defaults = SNOM_PHONE_DEFAULTS[@phone.phone_model.name]
+ @softkeys = @softkeys.first(phone_defaults[:softkeys_physical])
end
end
end
@@ -544,23 +441,22 @@ class ConfigSnomController < ApplicationController
'41' => 'SWI', # Switzerland
}
+ set_language
+
if @phone.phoneable
if @phone.phoneable_type == 'Tenant'
tenant = @phone.phoneable
- language = tenant.language.code
elsif @phone.phoneable_type == 'User'
tenant = @phone.phoneable.current_tenant
- language = @phone.phoneable.language.code
end
end
if tenant && tenant.country
tone_scheme = tenant.country.country_code
+ @phone_settings[:tone_scheme] = tone_schemes_map.include?(tone_scheme.to_s) ? tone_schemes_map[tone_scheme.to_s] : 'USA'
end
+ @phone_settings[:language] = languages_map.include?(I18n.locale.to_s) ? languages_map[I18n.locale.to_s] : 'English'
- @phone_settings[:tone_scheme] = tone_schemes_map.include?(tone_scheme.to_s) ? tone_schemes_map[tone_scheme.to_s] : 'USA'
- @phone_settings[:language] = languages_map.include?(language.to_s) ? languages_map[language.to_s] : 'English'
-
xml_applications_url = "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{(@sip_accounts.blank? ? '0' : @sip_accounts.first[:id])}"
@dkeys = {
:menu => 'keyevent F_SETTINGS',
@@ -603,18 +499,132 @@ class ConfigSnomController < ApplicationController
end
@softkeys.length().upto(MAX_SOFTKEYS_COUNT) do |index|
- @softkeys.push({:label => "", :data => "none"})
+ @softkeys.push({:label => "", :type => 'none', :value => ''})
end
@state_settings_url = "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/state_settings.xml"
- respond_to { |format|
- format.any {
- self.formats = [ :xml ]
- render
- }
- }
- end
+ extension_module = @phone.extension_modules.where(:active => true, :model => 'snom_vision').first
+ if extension_module
+ @phone_settings[:vision_mac_address] = extension_module.mac_address
+ @phone_settings[:vision_provisioning_url] = "#{provisioning_protocol}#{request.host_with_port}/settings-#{extension_module.mac_address}.xml"
+ end
+
+ respond_to { |format|
+ format.any {
+ self.formats = [ :xml ]
+ render :snom_phone
+ }
+ }
+ end
+
+ def snom_vision
+ if !params[:provisioning_key].blank?
+ @extension_module = ExtensionModule.where({ :provisioning_key => params[:provisioning_key] }).first
+ if @extension_module
+ @provisioning_authenticated = true
+ @mac_address = @extension_module.mac_address
+ end
+ elsif @mac_address
+ @extension_module = ExtensionModule.where({ :mac_address => @mac_address }).first
+ end
+
+
+ if !@extension_module
+ render(
+ :status => 404,
+ :layout => false,
+ :content_type => 'text/plain',
+ :text => "<!-- Extension module not found -->",
+ )
+ return
+ end
+
+ if ! request.env['HTTP_USER_AGENT'].index('snom')
+ Rails.logger.info "---> User-Agent indicates not a Snom Vision (#{request.env['HTTP_USER_AGENT'].inspect})"
+ else
+ Rails.logger.info "---> Extension module #{@mac_address.inspect}, IP address #{request_remote_ip.inspect}"
+ @extension_module.update_attributes({ :ip_address => request_remote_ip })
+ end
+
+ @phone = @extension_module.phone
+
+ set_language
+
+ provisioning_protocol = request.protocol
+
+ @settings = {
+ :auth_type => 'basic',
+ :provisioning_server => "#{provisioning_protocol}#{request.host_with_port}/settings-#{@extension_module.mac_address}.xml",
+ :user => '',
+ :passwd => '',
+ :phone_ip => '',
+ }
+
+ @buttons = Array.new()
+ softkeys = Array.new()
+ send_sensitve = @provisioning_authenticated || !@extension_module.provisioning_key_active
+
+ if send_sensitve && @phone
+ if @provisioning_authenticated && !@extension_module.provisioning_key_active
+ @extension_module.update_attributes({ :provisioning_key_active => true })
+ end
+
+ @settings[:user] = @phone.http_user
+ @settings[:passwd] = @phone.http_password
+ @settings[:phone_ip] = @phone.ip_address
+
+ if !GsParameter.get('PROVISIONING_KEY_LENGTH').nil? && GsParameter.get('PROVISIONING_KEY_LENGTH') > 0 && !@extension_module.provisioning_key.blank?
+ @settings[:provisioning_server] = "#{provisioning_protocol}#{request.host_with_port}/snom_vision-#{@extension_module.provisioning_key}.xml"
+ end
+
+ @phone.sip_accounts.each do |sip_account|
+ softkeys.concat(sip_account.softkeys.order(:position))
+ end
+
+ phone_defaults = SNOM_PHONE_DEFAULTS[@phone.phone_model.name]
+ softkeys.shift(phone_defaults[:softkeys_physical] * @extension_module.position)
+
+ else
+ @buttons << {
+ :imageurl => '',
+ :label => 'No provisioning key!',
+ :type => 'none',
+ :value => '',
+ }
+ end
+
+ softkeys.each do |softkey|
+ image_url = nil
+ if softkey.softkeyable.class == SipAccount
+ if softkey.softkeyable.sip_accountable.class == User
+ user = softkey.softkeyable.sip_accountable
+ if user.image?
+ image_url = "#{provisioning_protocol}#{request.host_with_port}#{user.image_url(:small)}"
+ end
+ end
+ elsif softkey.softkeyable.class == PhoneBookEntry
+ entry = softkey.softkeyable
+ if entry.image?
+ image_url = "#{provisioning_protocol}#{request.host_with_port}#{entry.image_url(:small)}"
+ end
+ end
+
+ button = format_key(softkey)
+ if button
+ button[:imageurl] = image_url
+ end
+ @buttons.push(button)
+ end
+
+ respond_to { |format|
+ format.any {
+ self.formats = [ :xml ]
+ render :snom_vision
+ }
+ }
+ end
+
def idle_screen
@@ -669,7 +679,7 @@ AAAA'
end
def log_in
-
+ set_language
base_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.split("?")[0]}"
exit_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.rpartition("/")[0]}/exit.xml"
@@ -758,6 +768,8 @@ AAAA'
return
end
+ set_language
+
exit_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.rpartition("/")[0]}/exit.xml"
if @phone.user_logout()
@@ -800,6 +812,8 @@ AAAA'
return
end
+ set_language
+
@phone_xml_object = {
:name => 'snom_phone_directory',
:title => "#{t('config_snom.phone_book.title')} #{@dialpad_keys}".strip,
@@ -834,7 +848,7 @@ AAAA'
phone_book_entries.each do |phone_book_entry|
if phone_book_entry.phone_numbers.count > 1
- @phone_xml_object[:entries] << { :text => phone_book_entry.to_s, :number => phone_book_entry.phone_numbers.first }
+ @phone_xml_object[:entries] << { :text => phone_book_entry.to_s, :number => phone_book_entry.phone_numbers.first.number.to_s }
end
phone_book_entry.phone_numbers.each do |phone_number|
if phone_book_entry.phone_numbers.count > 1
@@ -875,6 +889,8 @@ AAAA'
return
end
+ set_language
+
if ['dialed', 'missed', 'received', 'forwarded'].include? @type
@phone_xml_object = {
:name => "snom_phone_directory",
@@ -893,11 +909,24 @@ AAAA'
calls.each do |call|
display_name = call.display_name
phone_number = call.display_number
+
+ if phone_number.blank?
+ next
+ end
+
phone_book_entry = call.phone_book_entry_by_number(phone_number)
if display_name.blank?
display_name = phone_book_entry.to_s
end
+ if @sip_account.clir != call.clir
+ if call.clir == true
+ phone_number = 'f-dcliron-' + phone_number
+ elsif call.clir == false
+ phone_number = 'f-dcliroff-' + phone_number
+ end
+ end
+
@phone_xml_object[:entries].push({
:selected => false,
:number => phone_number,
@@ -947,6 +976,7 @@ AAAA'
return
end
+ set_language
account = @sip_account.voicemail_account
if ['read', 'unread'].include? @type
@@ -1013,6 +1043,8 @@ AAAA'
end
end
+ set_language
+
respond_to { |format|
format.any {
self.formats = [ :xml ]
@@ -1053,6 +1085,7 @@ AAAA'
return
end
+ set_language
exit_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.rpartition("/")[0]}/exit.xml"
if @function == 'toggle'
@@ -1168,6 +1201,7 @@ AAAA'
return
end
+ set_language
exit_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.rpartition("/")[0]}/exit.xml"
if @function == 'toggle'
@@ -1265,6 +1299,7 @@ AAAA'
return
end
+ set_language
exit_url = "#{request.protocol}#{request.host_with_port}#{request.fullpath.rpartition("/")[0]}/exit.xml"
if @function == 'toggle'
@@ -1329,4 +1364,236 @@ AAAA'
return date.strftime('%d.%m %H:%M')
end
+ def format_key(softkey, sip_account_index = nil)
+ if !softkey.softkey_function
+ return nil
+ end
+
+ sip_account = softkey.sip_account
+
+ case softkey.softkey_function.name
+ when 'blf'
+ return {:context => sip_account_index, :label => softkey.label, :type => 'blf', :value => "<sip:#{softkey.number}@#{sip_account.host}>|f-ia-"}
+ when 'speed_dial'
+ return {:context => sip_account_index, :label => softkey.label, :type => 'speed', :value => softkey.number.to_s}
+ when 'dtmf'
+ return {:context => sip_account_index, :label => softkey.label, :type => 'dtmf', :value => softkey.number.to_s}
+ when 'log_out'
+ return {:context => sip_account_index, :label => softkey.label, :type => 'speed', :value => 'f-lo'}
+ when 'log_in'
+ return {:context => sip_account_index, :label => softkey.label, :type => 'speed', :value => "f-li-#{softkey.number}"}
+ when 'conference'
+ conference = softkey.softkeyable
+ if conference.class == Conference
+ return {
+ :context => sip_account_index,
+ :function => softkey.softkey_function.name,
+ :label => softkey.label,
+ :softkey => softkey,
+ :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
+ :subscription => {
+ :to => "sip:conference#{conference.id}@#{sip_account.host}",
+ :for => "sip:conference#{conference.id}@#{sip_account.host}",
+ },
+ :actions => [{
+ :type => :dial,
+ :target => "f-ta-#{softkey.number}",
+ :when => 'on press',
+ :states => 'connected,holding',
+ },{
+ :type => :dial,
+ :target => softkey.number,
+ :when => 'on press',
+ }],
+ }
+ end
+ when 'parking_stall'
+ parking_stall = softkey.softkeyable
+ if parking_stall.class == ParkingStall
+ return {
+ :context => sip_account_index,
+ :function => softkey.softkey_function.name,
+ :label => softkey.label,
+ :softkey => softkey,
+ :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
+ :subscription => {
+ :to => "sip:park+#{parking_stall.name}@#{sip_account.host}",
+ :for => "sip:park+#{parking_stall.name}@#{sip_account.host}",
+ },
+ :actions => [{
+ :type => :dial,
+ :target => "f-cpa-#{parking_stall.name}",
+ :when => 'on press',
+ :states => 'connected,holding',
+ },{
+ :type => :dial,
+ :target => "f-cpa-#{parking_stall.name}",
+ :when => 'on press',
+ }],
+ }
+ end
+ when 'call_forwarding'
+ if softkey.softkeyable.class == CallForward then
+ return {
+ :context => sip_account_index,
+ :function => softkey.softkey_function.name,
+ :label => softkey.label,
+ :softkey => softkey,
+ :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
+ :subscription => {
+ :to => "sip:f-cftg-#{softkey.softkeyable_id}@#{sip_account.host}",
+ :for => "sip:f-cftg-#{softkey.softkeyable_id}@#{sip_account.host}"
+ },
+ :actions => [{
+ :type => :url,
+ :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{sip_account.id}/call_forwarding.xml?id=#{softkey.softkeyable_id}&function=toggle",
+ :when => 'on press',
+ }],
+ }
+ end
+ when 'call_forwarding_always'
+ phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'SipAccount').first
+ if phone_number
+ account_param = (phone_number.phone_numberable_id != sip_account.id ? "&account=#{phone_number.phone_numberable_id}" : '')
+ else
+ phone_number = sip_account.phone_numbers.first
+ account_param = ''
+ end
+
+ return {
+ :context => sip_account_index,
+ :function => softkey.softkey_function.name,
+ :label => softkey.label,
+ :softkey => softkey,
+ :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
+ :subscription => {
+ :to => "sip:f-cfutg-#{phone_number.id}@#{sip_account.host}",
+ :for => "sip:f-cfutg-#{phone_number.id}@#{sip_account.host}",
+ },
+ :actions => [{
+ :type => :url,
+ :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{sip_account.id}/call_forwarding.xml?type=always&function=toggle#{account_param}",
+ :when => 'on press',
+ }],
+ }
+ when 'call_forwarding_assistant'
+ phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'SipAccount').first
+ if phone_number
+ account_param = (phone_number.phone_numberable_id != sip_account.id ? "&account=#{phone_number.phone_numberable_id}" : '')
+ else
+ phone_number = sip_account.phone_numbers.first
+ account_param = ''
+ end
+
+ return {
+ :context => sip_account_index,
+ :function => softkey.softkey_function.name,
+ :label => softkey.label,
+ :softkey => softkey,
+ :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
+ :subscription => {
+ :to => "sip:f-cfatg-#{phone_number.id}@#{sip_account.host}",
+ :for => "sip:f-cfatg-#{phone_number.id}@#{sip_account.host}",
+ },
+ :actions => [{
+ :type => :url,
+ :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{sip_account.id}/call_forwarding.xml?type=assistant&function=toggle#{account_param}",
+ :when => 'on press',
+ }],
+ }
+ when 'hunt_group_membership'
+ phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'HuntGroup').first
+ if phone_number
+ hunt_group = HuntGroup.where(:id => phone_number.phone_numberable_id).first
+ end
+
+ sip_account_phone_numbers = Array.new()
+ SipAccount.where(:id => @sip_accounts.first[:id]).first.phone_numbers.each do |phone_number|
+ sip_account_phone_numbers.push(phone_number.number)
+ end
+
+ hunt_group_member_numbers = PhoneNumber.where(:number => sip_account_phone_numbers, :phone_numberable_type => 'HuntGroupMember')
+
+ hunt_group_member = nil
+ if hunt_group and hunt_group_member_numbers
+ hunt_group_member_numbers.each do |hunt_group_member_number|
+ hunt_group_member = hunt_group.hunt_group_members.where(:id => hunt_group_member_number.phone_numberable_id).first
+ if hunt_group_member
+ break
+ end
+ end
+ end
+
+ if hunt_group_member
+ return {
+ :context => sip_account_index,
+ :function => softkey.softkey_function.name,
+ :label => softkey.label,
+ :softkey => softkey,
+ :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
+ :subscription => {
+ :to => "sip:f-hgmtg-#{hunt_group_member.id}@#{sip_account.host}",
+ :for => "sip:f-hgmtg-#{hunt_group_member.id}@#{sip_account.host}",
+ },
+ :actions => [{
+ :type => :url,
+ :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{sip_account.id}/hunt_group.xml?group=#{hunt_group.id}&account=#{hunt_group_member.id}&function=toggle",
+ :when => 'on press',
+ }],
+ }
+ else
+ return {:context => sip_account_index, :label => softkey.label, :type => 'none', :value => ''}
+ end
+ when 'acd_membership'
+ acd_agent = nil
+ phone_number = PhoneNumber.where(:number => softkey.number, :phone_numberable_type => 'AutomaticCallDistributor').first
+ if phone_number
+ acd = AutomaticCallDistributor.where(:id => phone_number.phone_numberable_id).first
+ if acd
+ acd_agent = acd.acd_agents.where(:destination_type => 'SipAccount', :destination_id => sip_account.id).first
+ end
+ end
+
+ if acd_agent
+ return {
+ :context => sip_account_index,
+ :function => softkey.softkey_function.name,
+ :label => softkey.label,
+ :softkey => softkey,
+ :general_type => t("softkeys.functions.#{softkey.softkey_function.name}"),
+ :subscription => {
+ :to => "sip:f-acdmtg-#{acd_agent.id}@#{sip_account.host}",
+ :for => "sip:f-acdmtg-#{acd_agent.id}@#{sip_account.host}",
+ },
+ :actions => [{
+ :type => :url,
+ :target => "#{request.protocol}#{request.host_with_port}/config_snom/#{@phone.id}/#{sip_account.id}/acd.xml?acd=#{acd.id}&agent=#{acd_agent.id}&function=toggle",
+ :when => 'on press',
+ }],
+ }
+ else
+ return {:context => sip_account_index, :label => softkey.label, :type => 'none', :value => ''}
+ end
+ when 'hold'
+ return {:context => sip_account_index, :label => softkey.label, :type => 'keyevent', :value => 'F_R'}
+ else
+ return {:label => softkey.label, :type => 'none', :value => ''}
+ end
+ end
+
+ private
+ def set_language
+ if @sip_account && !@sip_account.language_code.blank?
+ I18n.locale = @sip_account.language_code
+ elsif @phone
+ sip_account = @phone.sip_accounts.first
+ if sip_account && !sip_account.language_code.blank?
+ I18n.locale = sip_account.language_code
+ @locale = sip_account.language_code
+ elsif @phone.phoneable && @phone.phoneable.respond_to?('language') && @phone.phoneable.language
+ I18n.locale = @phone.phoneable.language.code
+ end
+ end
+ end
+
end
diff --git a/app/controllers/extension_modules_controller.rb b/app/controllers/extension_modules_controller.rb
new file mode 100644
index 0000000..3f8fe98
--- /dev/null
+++ b/app/controllers/extension_modules_controller.rb
@@ -0,0 +1,73 @@
+class ExtensionModulesController < ApplicationController
+ load_resource :phone
+ load_and_authorize_resource :extension_module, :through => [:phone]
+
+ before_filter :spread_breadcrumbs
+
+ def index
+ @extension_modules = @phone.extension_modules.all
+ end
+
+ def show
+ @extension_module = @phone.extension_modules.find(params[:id])
+ end
+
+ def new
+ @extension_module = @phone.extension_modules.build()
+ end
+
+ def create
+ @extension_module = @phone.extension_modules.build(params[:extension_module])
+ if @extension_module.save
+ redirect_to phone_extension_module_path(@phone, @extension_module), :notice => t('extension_modules.controller.successfuly_created')
+ else
+ render :new
+ end
+ end
+
+ def edit
+ @extension_module = @phone.extension_modules.find(params[:id])
+ end
+
+ def update
+ @extension_module = @phone.extension_modules.find(params[:id])
+ if @extension_module.update_attributes(params[:extension_module])
+ redirect_to phone_extension_module_path(@phone, @extension_module), :notice => t('extension_modules.controller.successfuly_updated')
+ else
+ render :edit
+ end
+ end
+
+ def destroy
+ @extension_module = @phone.extension_modules.find(params[:id])
+ @extension_module.destroy
+ redirect_to phone_extension_modules_url(@phone), :notice => t('extension_modules.controller.successfuly_destroyed')
+ end
+
+ def restart
+ if @extension_module.resync
+ redirect_to phone_extension_module_path(@phone, @extension_module), :notice => t('extension_modules.controller.restart_invoked')
+ else
+ redirect_to phone_extension_module_path(@phone, @extension_module), :error => t('extension_modules.controller.restart_failed')
+ end
+ end
+
+ private
+ def spread_breadcrumbs
+ if @phone.phoneable.class == User
+ add_breadcrumb t('users.index.page_title'), tenant_users_path(@phone.phoneable.current_tenant)
+ add_breadcrumb @phone.phoneable, tenant_user_path(@phone.phoneable.current_tenant, @phone.phoneable)
+ add_breadcrumb t('phones.index.page_title'), user_phones_path(@phone.phoneable)
+ elsif @phone.phoneable.class == Tenant
+ add_breadcrumb t('phones.index.page_title'), tenant_phones_path(@phone.phoneable)
+ end
+
+ add_breadcrumb @phone, method( :"#{@phone.phoneable.class.name.underscore}_phone_path" ).(@phone.phoneable, @phone)
+ add_breadcrumb t("extension_modules.index.page_title"), phone_extension_modules_path(@phone)
+
+ if @extension_module && !@extension_module.new_record?
+ add_breadcrumb @extension_module
+ end
+
+ end
+end
diff --git a/app/controllers/gateway_headers_controller.rb b/app/controllers/gateway_headers_controller.rb
new file mode 100644
index 0000000..60c16c4
--- /dev/null
+++ b/app/controllers/gateway_headers_controller.rb
@@ -0,0 +1,57 @@
+class GatewayHeadersController < ApplicationController
+ load_and_authorize_resource :gateway
+ load_and_authorize_resource :gateway_header, :through => [:gateway]
+
+ before_filter :spread_breadcrumbs
+
+ def index
+ @gateway_headers = @gateway.gateway_headers
+ end
+
+ def show
+ @gateway_header = @gateway.gateway_headers.find(params[:id])
+ end
+
+ def new
+ @gateway_header = @gateway.gateway_headers.build
+ end
+
+ def create
+ @gateway_header = @gateway.gateway_headers.build(params[:gateway_header])
+ if @gateway_header.save
+ redirect_to @gateway, :notice => t('gateway_headers.controller.successfuly_created')
+ else
+ render :new
+ end
+ end
+
+ def edit
+ @gateway_header = @gateway.gateway_headers.find(params[:id])
+ end
+
+ def update
+ @gateway_header = @gateway.gateway_headers.find(params[:id])
+ if @gateway_header.update_attributes(params[:gateway_header])
+ redirect_to @gateway, :notice => t('gateway_headers.controller.successfuly_updated')
+ else
+ render :edit
+ end
+ end
+
+ def destroy
+ @gateway_header = @gateway.gateway_headers.find(params[:id])
+ @gateway_header.destroy
+ redirect_to gateway_path(@gateway), :notice => t('gateway_headers.controller.successfuly_destroyed')
+ end
+
+ private
+ def spread_breadcrumbs
+ add_breadcrumb t("gateways.index.page_title"), gateways_path
+ add_breadcrumb @gateway, @gateway
+ add_breadcrumb t("gateway_headers.index.page_title"), gateway_gateway_headers_url(@gateway)
+
+ if @gateway_header && !@gateway_header.new_record?
+ add_breadcrumb @gateway_header
+ end
+ end
+end
diff --git a/app/controllers/sip_accounts_controller.rb b/app/controllers/sip_accounts_controller.rb
index cd34953..3dc5a54 100644
--- a/app/controllers/sip_accounts_controller.rb
+++ b/app/controllers/sip_accounts_controller.rb
@@ -85,6 +85,7 @@ class SipAccountsController < ApplicationController
m = method( :"#{@parent.class.name.underscore}_sip_account_path" )
redirect_to m.( @parent, @sip_account ), :notice => t('sip_accounts.controller.successfuly_updated')
else
+ possible_voicemail_accounts
render :edit
end
end
diff --git a/app/controllers/switchboard_entries_controller.rb b/app/controllers/switchboard_entries_controller.rb
index ef6c72e..5b41816 100644
--- a/app/controllers/switchboard_entries_controller.rb
+++ b/app/controllers/switchboard_entries_controller.rb
@@ -58,7 +58,7 @@ class SwitchboardEntriesController < ApplicationController
private
def switchboard_entry_params
- params.require(:switchboard_entry).permit(:name, :sip_account_id)
+ params.require(:switchboard_entry).permit(:name, :sip_account_id, :switchable)
end
def spread_breadcrumbs
diff --git a/app/controllers/switchboards_controller.rb b/app/controllers/switchboards_controller.rb
index 3e2f8d6..03fd73e 100644
--- a/app/controllers/switchboards_controller.rb
+++ b/app/controllers/switchboards_controller.rb
@@ -19,6 +19,8 @@ class SwitchboardsController < ApplicationController
@switchboard.entry_width = 2
@switchboard.reload_interval = 2000
@switchboard.amount_of_displayed_phone_numbers = 1
+ @switchboard.blind_transfer_activated = true
+ @switchboard.attended_transfer_activated = false
spread_breadcrumbs
end
@@ -56,7 +58,7 @@ class SwitchboardsController < ApplicationController
private
def switchboard_params
- params.require(:switchboard).permit(:name, :reload_interval, :show_avatars, :entry_width, :amount_of_displayed_phone_numbers)
+ params.require(:switchboard).permit(:name, :reload_interval, :show_avatars, :entry_width, :amount_of_displayed_phone_numbers, :blind_transfer_activated, :attended_transfer_activated, :search_activated)
end
def spread_breadcrumbs
diff --git a/app/controllers/trigger_controller.rb b/app/controllers/trigger_controller.rb
index 290a1fc..ee4ddca 100644
--- a/app/controllers/trigger_controller.rb
+++ b/app/controllers/trigger_controller.rb
@@ -178,7 +178,7 @@ class TriggerController < ApplicationController
if errors.count == 0
# Indicate a new fax in the navigation bar.
#
- if @last_fax_document.fax_account.fax_accountable.class == User
+ if @last_fax_document && @last_fax_document.fax_account.fax_accountable.class == User
user = @last_fax_document.fax_account.fax_accountable
PrivatePub.publish_to("/users/#{user.id}/messages/new", "$('#new_voicemail_or_fax_indicator').hide('fast').show('slow');")
PrivatePub.publish_to("/users/#{user.id}/messages/new", "document.title = '* ' + document.title.replace( '* ' , '');")
diff --git a/app/controllers/voicemail_settings_controller.rb b/app/controllers/voicemail_settings_controller.rb
index f270c3d..ca3ae86 100644
--- a/app/controllers/voicemail_settings_controller.rb
+++ b/app/controllers/voicemail_settings_controller.rb
@@ -42,6 +42,9 @@ class VoicemailSettingsController < ApplicationController
@voicemail_setting = @voicemail_account.voicemail_settings.find(params[:id])
@input_type = VoicemailSetting::VOICEMAIL_SETTINGS.fetch(@voicemail_setting.name,{}).fetch(:input, 'String')
@input_html = VoicemailSetting::VOICEMAIL_SETTINGS.fetch(@voicemail_setting.name,{}).fetch(:html, {})
+ if @input_type == :boolean && @voicemail_setting.value == 'true'
+ @input_html[:checked] = true
+ end
end
def update
diff --git a/app/helpers/extension_modules_helper.rb b/app/helpers/extension_modules_helper.rb
new file mode 100644
index 0000000..653769f
--- /dev/null
+++ b/app/helpers/extension_modules_helper.rb
@@ -0,0 +1,2 @@
+module ExtensionModulesHelper
+end
diff --git a/app/mailers/notifications.rb b/app/mailers/notifications.rb
index 44a6a7a..ec81d35 100644
--- a/app/mailers/notifications.rb
+++ b/app/mailers/notifications.rb
@@ -55,7 +55,8 @@ class Notifications < ActionMailer::Base
if caller_number.blank?
caller_number = 'anonymous'
end
- attachments["#{Time.at(freeswitch_voicemail_msg.created_epoch).getlocal.strftime('%Y%m%d-%H%M%S')}-#{caller_number}.wav"] = File.read(freeswitch_voicemail_msg.file_path)
+ @voicemail[:file_name] = "#{Time.at(freeswitch_voicemail_msg.created_epoch).getlocal.strftime('%Y%m%d-%H%M%S')}-#{caller_number}.wav"
+ attachments[@voicemail[:file_name]] = File.read(freeswitch_voicemail_msg.file_path)
end
mail(from: Tenant.find(GsParameter.get('DEFAULT_API_TENANT_ID')).from_field_voicemail_email, to: email, :subject => "New Voicemail from #{@voicemail[:from]}, received #{Time.at(freeswitch_voicemail_msg.created_epoch).getlocal.to_s}")
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 8718dc4..66f3c60 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -95,6 +95,10 @@ class Ability
#
cannot :manage, RestoreJob
+ # Admin can manage all switchboards.
+ #
+ can :manage, Switchboard
+
else
# Any user can do the following stuff.
#
diff --git a/app/models/call.rb b/app/models/call.rb
index 2bbd08b..6755a96 100644
--- a/app/models/call.rb
+++ b/app/models/call.rb
@@ -44,11 +44,7 @@ class Call < ActiveRecord::Base
return nil
end
- if call_leg == :bleg
- channel_uuid = self.b_uuid
- else
- channel_uuid = self.uuid
- end
+ channel_uuid = call_leg == :bleg ? self.b_uuid : channel_uuid = self.uuid
if channel_uuid.blank?
return nil
@@ -63,24 +59,28 @@ class Call < ActiveRecord::Base
return FreeswitchAPI.api_result(FreeswitchAPI.api('uuid_transfer', channel_uuid, destination))
end
+ def hold(call_leg=:aleg, action=:hold)
+ channel_uuid = call_leg == :bleg ? self.b_uuid : channel_uuid = self.uuid
+ hold_off = action == :retrieve ? 'off' : ''
- def self.bridge(call_uuid1, call_uuid2, hangup_uuids=[])
- if call_uuid1.blank? || call_uuid2.blank?
+ if channel_uuid.blank?
return nil
end
require 'freeswitch_event'
- result = FreeswitchAPI.api_result(FreeswitchAPI.api('uuid_bridge', call_uuid1, call_uuid2))
-
- if result
- hangup_uuids.each do |kill_uuid|
- FreeswitchAPI.execute('uuid_kill', kill_uuid, true)
- end
+ result = FreeswitchAPI.api_result(FreeswitchAPI.api('uuid_hold', hold_off, channel_uuid))
+ end
+
+ def park(call_leg=:aleg)
+ channel_uuid = call_leg == :bleg ? self.b_uuid : channel_uuid = self.uuid
+
+ if channel_uuid.blank?
+ return nil
end
- return result
+ require 'freeswitch_event'
+ result = FreeswitchAPI.api_result(FreeswitchAPI.api('uuid_park', channel_uuid))
end
-
def get_variable_from_uuid(channel_uuid, variable_name)
if channel_uuid.blank?
@@ -105,4 +105,20 @@ class Call < ActiveRecord::Base
return get_variable_from_uuid(self.b_uuid, variable_name);
end
+ def self.bridge(call_uuid1, call_uuid2, hangup_uuids=[])
+ if call_uuid1.blank? || call_uuid2.blank?
+ return nil
+ end
+
+ require 'freeswitch_event'
+ result = FreeswitchAPI.api_result(FreeswitchAPI.api('uuid_bridge', call_uuid1, call_uuid2))
+
+ if result
+ hangup_uuids.each do |kill_uuid|
+ FreeswitchAPI.execute('uuid_kill', kill_uuid, true)
+ end
+ end
+
+ return result
+ end
end
diff --git a/app/models/call_forward.rb b/app/models/call_forward.rb
index a4bfbb5..874a0de 100644
--- a/app/models/call_forward.rb
+++ b/app/models/call_forward.rb
@@ -7,7 +7,8 @@ class CallForward < ActiveRecord::Base
:hunt_group_id,
:call_forwardable_type, :call_forwardable_id,
:call_forwarding_destination, :position, :uuid,
- :destinationable_type, :destinationable_id
+ :destinationable_type, :destinationable_id,
+ :destination_phone_number, :destination_greeting
belongs_to :call_forwardable, :polymorphic => true
belongs_to :destinationable, :polymorphic => true
@@ -23,12 +24,6 @@ class CallForward < ActiveRecord::Base
:if => Proc.new { |cf| cf.to_voicemail == true }
belongs_to :call_forward_case
-
- validates_numericality_of :depth,
- :allow_nil => true,
- :only_integer => true,
- :greater_than_or_equal_to => 1,
- :less_than_or_equal_to => (GsParameter.get('MAX_CALL_FORWARD_DEPTH').nil? ? 0 : GsParameter.get('MAX_CALL_FORWARD_DEPTH'))
before_validation {
self.timeout = nil if self.call_forward_case_id != 3
@@ -49,13 +44,6 @@ class CallForward < ActiveRecord::Base
validates_presence_of :uuid
validates_uniqueness_of :uuid
- # Make sure the call forward's parent can't be changed:
- before_validation { |cfwd|
- if cfwd.id && (cfwd.call_forwardable_id != cfwd.call_forwardable_id_was || cfwd.call_forwardable_type != cfwd.call_forwardable_type_was)
- errors.add( :call_forwardable_id, "cannot be changed." )
- end
- }
-
before_validation :resolve_prerouting
after_save :set_presence
@@ -88,6 +76,30 @@ class CallForward < ActiveRecord::Base
self.destinationable_id, delimeter, self.destinationable_type = destination_record.to_s.partition(':')
end
+ def destination_phone_number
+ if self.destinationable_type.to_s.downcase == 'phonenumber'
+ return self.destination
+ end
+ end
+
+ def destination_phone_number=(destination_number)
+ if self.destinationable_type.to_s.downcase == 'phonenumber'
+ self.destination = destination_number
+ end
+ end
+
+ def destination_greeting
+ if self.destinationable_type.to_s.downcase == 'voicemailaccount'
+ return self.destination
+ end
+ end
+
+ def destination_greeting=(destination_file)
+ if self.destinationable_type.to_s.downcase == 'voicemailaccount'
+ self.destination = destination_file
+ end
+ end
+
def toggle
self.active = ! self.active
return self.save
diff --git a/app/models/cdr.rb b/app/models/cdr.rb
new file mode 100644
index 0000000..ff9198f
--- /dev/null
+++ b/app/models/cdr.rb
@@ -0,0 +1,17 @@
+class Cdr < ActiveRecord::Base
+ self.table_name = 'cdrs'
+ self.primary_key = 'uuid'
+
+ belongs_to :account
+ belongs_to :bleg_account
+ belongs_to :forwarding_account
+
+
+ def self.seconds_to_minutes_seconds(call_seconds)
+ if call_seconds.to_i > 0
+ minutes = (call_seconds / 1.minutes).to_i
+ seconds = call_seconds - minutes.minutes.seconds
+ return '%i:%02i' % [minutes, seconds]
+ end
+ end
+end
diff --git a/app/models/extension_module.rb b/app/models/extension_module.rb
new file mode 100644
index 0000000..98ef700
--- /dev/null
+++ b/app/models/extension_module.rb
@@ -0,0 +1,58 @@
+class ExtensionModule < ActiveRecord::Base
+ attr_accessible :model, :mac_address, :phone_id, :ip_address, :position, :active, :provisioning_key, :provisioning_key_active
+
+ MODELS = ['snom_vision']
+
+ belongs_to :phone
+ before_save :remove_ip_address_when_mac_address_was_changed
+
+ before_save :generate_key
+
+ def to_s
+ mac_address
+ end
+
+ def resync()
+ if ! self.model == 'snom_vision'
+ return false
+ end
+
+ http_user = nil
+ http_password = nil
+
+ if self.phone
+ http_user = self.phone.http_user
+ http_password = self.phone.http_password
+ end
+
+ require 'open-uri'
+ begin
+ if open("http://#{self.ip_address}/ConfigurationModule/restart", :http_basic_authentication=>[http_user, http_password], :proxy => nil)
+ return true
+ end
+ rescue
+ return false
+ end
+ end
+
+ private
+ def sanitize_mac_address
+ if self.mac_address.split(/:/).count == 6 && self.mac_address.length < 17
+ splitted_mac_address = self.mac_address.split(/:/)
+ self.mac_address = splitted_mac_address.map{|part| (part.size == 1 ? "0#{part}" : part)}.join('')
+ end
+ self.mac_address = self.mac_address.to_s.upcase.gsub( /[^A-F0-9]/, '' )
+ end
+
+ def remove_ip_address_when_mac_address_was_changed
+ if self.mac_address_changed?
+ self.ip_address = nil
+ end
+ end
+
+ def generate_key
+ if !GsParameter.get('PROVISIONING_KEY_LENGTH').nil? && GsParameter.get('PROVISIONING_KEY_LENGTH') > 0 && self.provisioning_key.blank?
+ self.provisioning_key = SecureRandom.hex(GsParameter.get('PROVISIONING_KEY_LENGTH'))
+ end
+ end
+end
diff --git a/app/models/freeswitch_cdr.rb b/app/models/freeswitch_cdr.rb
deleted file mode 100644
index fd0eb75..0000000
--- a/app/models/freeswitch_cdr.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-class FreeswitchCdr < ActiveRecord::Base
- self.table_name = 'cdrs'
- self.primary_key = 'uuid'
-end
diff --git a/app/models/gateway.rb b/app/models/gateway.rb
index 01b29b2..437d3af 100644
--- a/app/models/gateway.rb
+++ b/app/models/gateway.rb
@@ -6,6 +6,7 @@ class Gateway < ActiveRecord::Base
has_many :gateway_settings, :dependent => :destroy
has_many :gateway_parameters, :dependent => :destroy
+ has_many :gateway_headers, :dependent => :destroy
has_many :call_routes, :as => :endpoint, :dependent => :nullify
validates :name,
@@ -72,6 +73,15 @@ class Gateway < ActiveRecord::Base
GsParameter.where(:entity => 'sip_gateways', :section => 'settings').each do |default_setting|
self.gateway_settings.create(:name => default_setting.name, :value => default_setting.value, :class_type => default_setting.class_type, :description => default_setting.description)
end
+ GsParameter.where(:entity => 'sip_gateways', :section => 'headers_default').each do |default_header|
+ self.gateway_headers.create(:name => default_header.name, :value => default_header.value, :header_type => 'default', :description => default_header.description)
+ end
+ GsParameter.where(:entity => 'sip_gateways', :section => 'headers_default_clir_off').each do |default_header|
+ self.gateway_headers.create(:constraint_value => 'clir=false', :name => default_header.name, :value => default_header.value, :header_type => 'default', :description => default_header.description)
+ end
+ GsParameter.where(:entity => 'sip_gateways', :section => 'headers_default_clir_on').each do |default_header|
+ self.gateway_headers.create(:constraint_value => 'clir=true', :name => default_header.name, :value => default_header.value, :header_type => 'default', :description => default_header.description)
+ end
end
end
diff --git a/app/models/gateway_header.rb b/app/models/gateway_header.rb
new file mode 100644
index 0000000..474bfac
--- /dev/null
+++ b/app/models/gateway_header.rb
@@ -0,0 +1,25 @@
+class GatewayHeader < ActiveRecord::Base
+ HEADER_TYPES = [
+ 'default',
+ 'invite',
+ # 'provisional',
+ # 'request',
+ # 'bye',
+ ]
+
+ attr_accessible :gateway_id, :header_type, :constraint_source, :constraint_value, :name, :value, :description
+
+ belongs_to :gateway, :touch => true
+
+ validates :name,
+ :presence => true,
+ :uniqueness => {:scope => [:gateway_id, :header_type, :constraint_source, :constraint_value]}
+
+ validates :header_type,
+ :presence => true,
+ :inclusion => { :in => HEADER_TYPES }
+
+ def to_s
+ name
+ end
+end
diff --git a/app/models/gateway_setting.rb b/app/models/gateway_setting.rb
index e96bb52..a71b615 100644
--- a/app/models/gateway_setting.rb
+++ b/app/models/gateway_setting.rb
@@ -14,10 +14,7 @@ class GatewaySetting < ActiveRecord::Base
'contact' => 'String',
'dial_string' => 'String',
'profile' => 'String',
- 'from' => 'String',
- 'from_clir' => 'String',
- 'asserted_identity' => 'String',
- 'asserted_identity_clir' => 'String',
+ 'dtmf_type' => 'String',
},
'xmpp' => {
'server' => 'String',
diff --git a/app/models/group_membership.rb b/app/models/group_membership.rb
index 0f04ae1..d9d48e7 100644
--- a/app/models/group_membership.rb
+++ b/app/models/group_membership.rb
@@ -12,8 +12,6 @@ class GroupMembership < ActiveRecord::Base
:presence => true,
:uniqueness => { :scope => [:group_id, :item_id] }
- validate :validate_item_type_consitency
-
validates :item,
:presence => true
@@ -27,11 +25,4 @@ class GroupMembership < ActiveRecord::Base
return fist_item.class.name
end
end
-
- def validate_item_type_consitency
- type_allowed = self.item_type_allowed
- if type_allowed && type_allowed != self.item_type
- errors.add(:item_type, "must be of type: #{type_allowed}")
- end
- end
end
diff --git a/app/models/hunt_group.rb b/app/models/hunt_group.rb
index 93279ae..fac0cc5 100644
--- a/app/models/hunt_group.rb
+++ b/app/models/hunt_group.rb
@@ -2,7 +2,7 @@ class HuntGroup < ActiveRecord::Base
attr_accessible :name, :strategy, :seconds_between_jumps, :phone_numbers_attributes
belongs_to :tenant, :touch => true
- has_many :destrination_call_forwards, :as => :destinationable, :dependent => :destroy
+ has_many :destrination_call_forwards, :class_name => 'CallForward', :as => :destinationable, :dependent => :destroy
has_many :call_forwards, :as => :call_forwardable, :dependent => :destroy
validates_uniqueness_of :name, :scope => :tenant_id,
diff --git a/app/models/phone.rb b/app/models/phone.rb
index 8b41b59..93553a2 100644
--- a/app/models/phone.rb
+++ b/app/models/phone.rb
@@ -13,6 +13,8 @@ class Phone < ActiveRecord::Base
has_many :phone_sip_accounts, :dependent => :destroy, :uniq => true, :order => :position
has_many :sip_accounts, :through => :phone_sip_accounts
+
+ has_many :extension_modules
belongs_to :tenant
belongs_to :fallback_sip_account, :class_name => "SipAccount"
@@ -195,8 +197,7 @@ class Phone < ActiveRecord::Base
return true
end
- private
-
+ private
# Sanitize MAC address.
#
def sanitize_mac_address
diff --git a/app/models/sip_account.rb b/app/models/sip_account.rb
index 668fbfe..a2644e9 100644
--- a/app/models/sip_account.rb
+++ b/app/models/sip_account.rb
@@ -154,14 +154,15 @@ class SipAccount < ActiveRecord::Base
return SipRegistration.where(:sip_user => self.auth_name).first
end
- def call( phone_number, origin_cid_number = nil, origin_cid_name = 'Call' )
- origin_cid_number = origin_cid_number || phone_number
+ def call( phone_number, caller_id_number = nil, caller_id_name = 'Call', auto_answer = false )
require 'freeswitch_event'
- return FreeswitchAPI.execute(
+ origination_uuid = UUID.new.generate
+ if FreeswitchAPI.execute(
'originate',
- "{origination_uuid=#{UUID.new.generate},origination_caller_id_number='#{phone_number}',origination_caller_id_name='#{origin_cid_name}'}user/#{self.auth_name} #{phone_number}",
- true
- );
+ "{origination_uuid=#{origination_uuid},origination_caller_id_number='#{caller_id_number||phone_number}',origination_caller_id_name='#{caller_id_name}',sip_auto_answer='#{auto_answer}'}user/#{self.auth_name} #{phone_number}",
+ true)
+ return origination_uuid
+ end
end
def target_group_ids_by_permission(permission)
diff --git a/app/models/switchboard.rb b/app/models/switchboard.rb
index 095f878..d62657f 100644
--- a/app/models/switchboard.rb
+++ b/app/models/switchboard.rb
@@ -25,8 +25,13 @@ class Switchboard < ActiveRecord::Base
}
belongs_to :user, :touch => true
+
has_many :switchboard_entries, :dependent => :destroy
+ has_many :switchable_switchboard_entries, :class_name => "SwitchboardEntry", :conditions => {:switchable => true}
+
has_many :sip_accounts, :through => :switchboard_entries
+ has_many :switchable_sip_accounts, :source => :sip_account, :through => :switchable_switchboard_entries, :uniq => true
+
has_many :phone_numbers, :through => :sip_accounts
before_validation :convert_0_to_nil
@@ -36,7 +41,11 @@ class Switchboard < ActiveRecord::Base
end
def active_calls
- self.switchboard_entries.where(:switchable => true).map{|se| se.sip_account}.uniq.map{|sip_account| sip_account.calls}.flatten
+ Call.where("sip_account_id = ? or b_sip_account_id = ?", self.switchable_sip_account_ids, self.switchable_sip_account_ids).order(:start_stamp)
+ end
+
+ def dispatchable_incoming_calls
+ Call.where("b_sip_account_id = ?", self.switchable_sip_account_ids).order(:start_stamp)
end
private
diff --git a/app/models/user.rb b/app/models/user.rb
index 5e97459..70c9042 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -107,6 +107,8 @@ class User < ActiveRecord::Base
after_save :become_a_member_of_default_user_groups
+ after_save :change_language_of_child_objects
+
def destroy
clean_whitelist_entries
super
@@ -246,4 +248,15 @@ class User < ActiveRecord::Base
end
end
+ def change_language_of_child_objects
+ if !self.language_id_changed?
+ return nil
+ end
+
+ code = self.language.code
+ self.sip_accounts.each do |sip_account|
+ sip_account.update_attributes(:language_code => code)
+ end
+ end
+
end
diff --git a/app/models/voicemail_account.rb b/app/models/voicemail_account.rb
index 8ec181f..298516e 100644
--- a/app/models/voicemail_account.rb
+++ b/app/models/voicemail_account.rb
@@ -21,9 +21,9 @@ class VoicemailAccount < ActiveRecord::Base
def notify_to
send_notification = nil
- if self.voicemail_settings.where(:name => 'notify', :value => true).first
+ if self.voicemail_settings.where(:name => 'notify', :value => 'true').first
send_notification = true
- elsif self.voicemail_settings.where(:name => 'notify', :value => false).first
+ elsif self.voicemail_settings.where(:name => 'notify', :value => 'false').first
send_notification = false
end
@@ -49,9 +49,9 @@ class VoicemailAccount < ActiveRecord::Base
def notification_setting(name)
setting = nil
- if self.voicemail_settings.where(:name => name, :value => true).first
+ if self.voicemail_settings.where(:name => name, :value => 'true').first
setting = true
- elsif self.voicemail_settings.where(:name => name, :value => false).first
+ elsif self.voicemail_settings.where(:name => name, :value => 'false').first
setting = false
end
diff --git a/app/serializers/phone_book_entry_serializer.rb b/app/serializers/phone_book_entry_serializer.rb
new file mode 100644
index 0000000..ac25832
--- /dev/null
+++ b/app/serializers/phone_book_entry_serializer.rb
@@ -0,0 +1,14 @@
+class PhoneBookEntrySerializer < ActiveModel::Serializer
+ embed :ids, :include => true
+
+ attributes :id, :first_name, :last_name, :organization, :search_result_display
+ has_many :phone_numbers
+
+ def search_result_display
+ result = "#{object.last_name}, #{object.first_name}".strip.gsub(/^, /,'').gsub(/,$/,'')
+ if result.blank?
+ result = "#{object.organization}"
+ end
+ return result
+ end
+end
diff --git a/app/serializers/sip_account_serializer.rb b/app/serializers/sip_account_serializer.rb
index aa749b0..7465a17 100644
--- a/app/serializers/sip_account_serializer.rb
+++ b/app/serializers/sip_account_serializer.rb
@@ -1,7 +1,15 @@
class SipAccountSerializer < ActiveModel::Serializer
embed :ids, :include => true
- attributes :id, :auth_name, :caller_name, :sip_accountable_id
+ attributes :id, :auth_name, :caller_name, :sip_accountable_id, :is_registrated
has_many :phone_numbers
has_many :calls
+
+ def is_registrated
+ if object.registration
+ true
+ else
+ false
+ end
+ end
end
diff --git a/app/serializers/switchboard_entry_serializer.rb b/app/serializers/switchboard_entry_serializer.rb
index 1b6c761..5d76e16 100644
--- a/app/serializers/switchboard_entry_serializer.rb
+++ b/app/serializers/switchboard_entry_serializer.rb
@@ -1,5 +1,5 @@
class SwitchboardEntrySerializer < ActiveModel::Serializer
- attributes :id, :name, :path_to_user, :avatar_src, :callstate
+ attributes :id, :name, :path_to_user, :avatar_src, :callstate, :switchable
has_one :sip_account, embed: :ids
has_one :switchboard, embed: :ids
diff --git a/app/serializers/switchboard_serializer.rb b/app/serializers/switchboard_serializer.rb
index 6d39667..8a8bd42 100644
--- a/app/serializers/switchboard_serializer.rb
+++ b/app/serializers/switchboard_serializer.rb
@@ -1,9 +1,10 @@
class SwitchboardSerializer < ActiveModel::Serializer
embed :ids, :include => true
- attributes :id, :name
+ attributes :id, :name, :show_avatars, :blind_transfer_activated, :attended_transfer_activated, :search_activated
has_many :switchboard_entries
has_many :sip_accounts, :through => :switchboard_entries
has_many :phone_numbers
has_many :active_calls
+ has_many :dispatchable_incoming_calls
end
diff --git a/app/views/call_forwards/_form_core.html.haml b/app/views/call_forwards/_form_core.html.haml
index b730941..1c9ec30 100644
--- a/app/views/call_forwards/_form_core.html.haml
+++ b/app/views/call_forwards/_form_core.html.haml
@@ -7,11 +7,11 @@
= f.input :call_forwarding_destination , :as => :select, :collection => @call_forwarding_destinations, :label => t('call_forwards.form.call_forwarding_destination.label'), :hint => conditional_hint('call_forwards.form.call_forwarding_destination.hint'), :include_blank => false
%div{:id => 'destination_phone_number_div'}
- = f.input :destination, :label => t('call_forwards.form.destination_phone_number.label'), :hint => conditional_hint('call_forwards.form.destination_phone_number.hint'), :input_html => { :id => 'destination_phone_number', :value => @destination_phone_number }
+ = f.input :destination_phone_number, :label => t('call_forwards.form.destination_phone_number.label'), :hint => conditional_hint('call_forwards.form.destination_phone_number.hint'), :input_html => { :id => 'destination_phone_number' }
- if @available_greetings.any?
%div{:id => 'destination_greeting_div'}
- = f.input :destination, :as => :select, :collection => @available_greetings, :label => t('call_forwards.form.destination_greeting.label'), :hint => conditional_hint('call_forwards.form.destination_greeting.hint'), :input_html => { :id => 'destination_greeting' }
+ = f.input :destination_greeting, :as => :select, :collection => @available_greetings, :label => t('call_forwards.form.destination_greeting.label'), :hint => conditional_hint('call_forwards.form.destination_greeting.hint'), :input_html => { :id => 'destination_greeting' }
= f.input :active, :label => t('call_forwards.form.active.label'), :hint => conditional_hint('call_forwards.form.active.hint')
diff --git a/app/views/call_histories/_index_core.html.haml b/app/views/call_histories/_index_core.html.haml
index 9c2cf39..2915f35 100644
--- a/app/views/call_histories/_index_core.html.haml
+++ b/app/views/call_histories/_index_core.html.haml
@@ -65,7 +65,10 @@
- else
= call_history.destination_number
- elsif call_history.entry_type == 'dialed'
- = call_history.caller_id_number
+ - if call_history.clir
+ %del= call_history.caller_id_number
+ - else
+ = call_history.caller_id_number
- else
= call_history.destination_number
diff --git a/app/views/cdrs/_index_core.html.haml b/app/views/cdrs/_index_core.html.haml
new file mode 100644
index 0000000..39e9e7f
--- /dev/null
+++ b/app/views/cdrs/_index_core.html.haml
@@ -0,0 +1,33 @@
+
+- if defined?(cdrs.total_pages)
+ = will_paginate cdrs, :renderer => BootstrapPagination::Rails, :previous_label => raw('<i class = "icon-chevron-left"></i>'), :next_label => raw('<i class = "icon-chevron-right"></i>')
+
+%table.table.table-striped
+ %thead
+ %tr
+ %th= sortable :start_stamp, t('cdrs.index.start_stamp')
+ %th= sortable :caller_id_number, t('cdrs.index.caller_id_number')
+ %th= sortable :caller_id_name, t('cdrs.index.caller_id_name')
+ %th= sortable :dialed_number, t('cdrs.index.dialed_number')
+ %th= sortable :destination_number, t('cdrs.index.destination_number')
+ %th= sortable :callee_id_number, t('cdrs.index.callee_id_number')
+ %th= sortable :callee_id_name, t('cdrs.index.callee_id_name')
+ %th= sortable :billsec, t('cdrs.index.billsec')
+ %th= sortable :hangup_cause, t('cdrs.index.hangup_cause')
+ %th= sortable :dialstatus, t('cdrs.index.dialstatus')
+
+ %tbody
+ - for cdr in cdrs
+ %tr{:class => (cdr.dialstatus == 'SUCCESS' ? '' : 'warning')}
+ %td= l cdr.start_stamp, :format => :short
+ %td= cdr.caller_id_number
+ %td= cdr.caller_id_name
+ %td= cdr.dialed_number
+ %td= cdr.destination_number
+ %td= cdr.callee_id_number
+ %td= cdr.callee_id_name
+ %td= Cdr.seconds_to_minutes_seconds(cdr.billsec)
+ %td= cdr.hangup_cause
+ %td= cdr.dialstatus
+
+ /=render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => @tenant, :child => cdr}
diff --git a/app/views/cdrs/index.html.haml b/app/views/cdrs/index.html.haml
new file mode 100644
index 0000000..b260b38
--- /dev/null
+++ b/app/views/cdrs/index.html.haml
@@ -0,0 +1,4 @@
+- content_for :title, t("cdrs.index.page_title")
+
+- if @cdrs.count > 0
+ = render "index_core", :cdrs => @cdrs
diff --git a/app/views/config_snom/show.xml.haml b/app/views/config_snom/snom_phone.xml.haml
index 22df8bc..39b5958 100644
--- a/app/views/config_snom/show.xml.haml
+++ b/app/views/config_snom/snom_phone.xml.haml
@@ -37,6 +37,7 @@
%display_method{:perm => 'RW'}= 'display_name_number'
%callpickup_dialoginfo{:perm => 'RW'}= 'on'
%pickup_indication{:perm => 'RW'}= 'off'
+ %auto_connect_indication{:perm => 'RW'}= 'off'
%show_local_line{:perm => 'RW'}= 'off'
%mwi_notification{:perm => 'RW'}= 'silent'
%mwi_dialtone{:perm => 'RW'}= 'normal'
@@ -55,6 +56,9 @@
%guess_start_length{:perm => 'RW'}= '3'
%ieee8021x_eap_md5_username{:perm => 'RW'}= GsParameter.get('PROVISIONING_IEEE8021X_EAP_USERNAME')
%ieee8021x_eap_md5_password{:perm => 'RW'}= GsParameter.get('PROVISIONING_IEEE8021X_EAP_PASSWORD')
+ %vision_connected_mac{:perm => 'RW'}= @phone_settings[:vision_mac_address]
+ %vision_provisioning_url{:perm => 'RW'}= @phone_settings[:vision_provisioning_url]
+ %vision_reconnect_timeout{:perm => 'RW'}= '15'
- 0.upto(9) do |ringer_idx|
%internal_ringer_text{:idx => ringer_idx, :perm => 'RW'}= "Ringer#{(ringer_idx+1)}"
@@ -170,8 +174,8 @@
%functionKeys
- @softkeys.each_with_index do |softkey, index|
- - if softkey[:data]
- %fkey{:idx => index, :context => (softkey[:context] ? softkey[:context].to_s : 'active'), :label => softkey[:label], :perm => 'RW'}= softkey[:data]
+ - if softkey[:type]
+ %fkey{:idx => index, :context => (softkey[:context] ? softkey[:context].to_s : 'active'), :label => softkey[:label], :perm => 'RW'}= "#{softkey[:type]} #{softkey[:value]}"
- elsif softkey[:general_type]
%fkey{:idx => index, :context => (softkey[:context] ? softkey[:context].to_s : 'active'), :label => softkey[:label], :perm => 'RW'}
%general{:type => softkey[:general_type]}
diff --git a/app/views/config_snom/snom_vision.xml.haml b/app/views/config_snom/snom_vision.xml.haml
new file mode 100644
index 0000000..053feb9
--- /dev/null
+++ b/app/views/config_snom/snom_vision.xml.haml
@@ -0,0 +1,52 @@
+!!! XML
+%settings
+ %webserver
+ %auth_type{:perm => '$'}= @settings[:auth_type]
+ %user{:perm => '!'}= @settings[:user]
+ %passwd{:perm => '!'}= @settings[:passwd]
+
+ %global
+ %periodic_provisioning{:perm => '$'}
+ %periodic_provisioning_interval{:perm => '$'}
+ %provisioning_server{:perm => '!'}= @settings[:provisioning_server]
+
+ %gui
+ %phone_ip{:perm => '!'}= @settings[:phone_ip]
+ %phone_scheme{:perm => '$'}https
+ %phone_user{:perm => '!'}= @settings[:user]
+ %phone_passwd{:perm => '!'}= @settings[:passwd]
+
+ != "\<!-- active buttons: #{@buttons.count} --\>"
+ - index_left = 0
+ - index_right = 0
+ - for page in 1..3 do
+ != "\<!-- page: #{page} --\>"
+ - for key in 1..8 do
+ - index_left = index_left + 1
+ - button = @buttons.shift
+ - if !button
+ - button = {}
+
+ %left_button_imageurl{:idx=> index_left, :perm=>"!"}= button[:imageurl]
+ %left_button_name{:idx=> index_left, :perm=>"!"}= button[:label]
+ - if button[:general_type].blank?
+ %left_button_type{:idx=> index_left, :perm=>"!"}= button[:type]
+ %left_button_value{:idx=> index_left, :perm=>"!"}= button[:value]
+ - else
+ %left_button_type{:idx=> index_left, :perm=>"!"} xml
+ %left_button_value{:idx=> index_left, :perm=>"!"}
+
+ - for key in 1..8 do
+ - index_right = index_right + 1
+ - button = @buttons.shift
+ - if !button
+ - button = {}
+
+ %right_button_imageurl{:idx=> index_right, :perm=>"!"}= button[:imageurl]
+ %right_button_name{:idx=> index_right, :perm=>"!"}= button[:label]
+ - if button[:general_type].blank?
+ %right_button_type{:idx=> index_right, :perm=>"!"}= button[:type]
+ %right_button_value{:idx=> index_right, :perm=>"!"}= button[:value]
+ - else
+ %right_button_type{:idx=> index_right, :perm=>"!"} xml
+ %right_button_value{:idx=> index_right, :perm=>"!"}
diff --git a/app/views/extension_modules/_form.html.haml b/app/views/extension_modules/_form.html.haml
new file mode 100644
index 0000000..496e695
--- /dev/null
+++ b/app/views/extension_modules/_form.html.haml
@@ -0,0 +1,7 @@
+= simple_form_for([@phone, @extension_module]) do |f|
+ = f.error_notification
+
+ = render "form_core", :f => f
+
+ .form-actions
+ = f.button :submit, conditional_t('extension_modules.form.submit')
diff --git a/app/views/extension_modules/_form_core.html.haml b/app/views/extension_modules/_form_core.html.haml
new file mode 100644
index 0000000..bea4a77
--- /dev/null
+++ b/app/views/extension_modules/_form_core.html.haml
@@ -0,0 +1,6 @@
+.inputs
+ = f.input :model, :collection => ExtensionModule::MODELS, :label => t('extension_modules.form.model.label'), :hint => conditional_hint('extension_modules.form.model.hint'), :include_blank => false
+ = f.input :mac_address, :label => t('extension_modules.form.mac_address.label'), :hint => conditional_hint('extension_modules.form.mac_address.hint')
+ = f.input :position, :label => t('extension_modules.form.position.label'), :hint => conditional_hint('extension_modules.form.position.hint')
+ = f.input :active, :label => t('extension_modules.form.active.label'), :hint => conditional_hint('extension_modules.form.active.hint')
+ = f.input :provisioning_key_active, :label => t('extension_modules.form.provisioning_key_active.label'), :hint => conditional_hint('extension_modules.form.provisioning_key_active.hint')
diff --git a/app/views/extension_modules/_index_core.html.haml b/app/views/extension_modules/_index_core.html.haml
new file mode 100644
index 0000000..d5aef48
--- /dev/null
+++ b/app/views/extension_modules/_index_core.html.haml
@@ -0,0 +1,13 @@
+%table.table.table-striped
+ %tr
+ %th= t('extension_modules.index.model')
+ %th= t('extension_modules.index.mac_address')
+ %th= t('extension_modules.index.active')
+
+
+ - for extension_module in extension_modules
+ %tr
+ %td= extension_module.model
+ %td= extension_module.mac_address
+ %td= extension_module.active
+ =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => @phone, :child => extension_module} \ No newline at end of file
diff --git a/app/views/extension_modules/edit.html.haml b/app/views/extension_modules/edit.html.haml
new file mode 100644
index 0000000..4fcb09b
--- /dev/null
+++ b/app/views/extension_modules/edit.html.haml
@@ -0,0 +1,3 @@
+- content_for :title, t("extension_modules.edit.page_title")
+
+= render "form" \ No newline at end of file
diff --git a/app/views/extension_modules/index.html.haml b/app/views/extension_modules/index.html.haml
new file mode 100644
index 0000000..7a57eb5
--- /dev/null
+++ b/app/views/extension_modules/index.html.haml
@@ -0,0 +1,6 @@
+- content_for :title, t("extension_modules.index.page_title")
+
+- if @extension_modules && @extension_modules.count > 0
+ = render "index_core", :extension_modules => @extension_modules
+
+= render :partial => 'shared/create_link', :locals => {:parent => @phone, :child_class => ExtensionModule} \ No newline at end of file
diff --git a/app/views/extension_modules/new.html.haml b/app/views/extension_modules/new.html.haml
new file mode 100644
index 0000000..2b4ae9a
--- /dev/null
+++ b/app/views/extension_modules/new.html.haml
@@ -0,0 +1,3 @@
+- content_for :title, t("extension_modules.new.page_title")
+
+= render "form" \ No newline at end of file
diff --git a/app/views/extension_modules/show.html.haml b/app/views/extension_modules/show.html.haml
new file mode 100644
index 0000000..ac9d099
--- /dev/null
+++ b/app/views/extension_modules/show.html.haml
@@ -0,0 +1,22 @@
+- content_for :title, t("extension_modules.show.page_title")
+
+%p
+ %strong= t('extension_modules.show.model') + ":"
+ = @extension_module.model
+%p
+ %strong= t('extension_modules.show.mac_address') + ":"
+ = @extension_module.mac_address
+%p
+ %strong= t('extension_modules.show.position') + ":"
+ = @extension_module.position
+%p
+ %strong= t('extension_modules.show.active') + ":"
+ = @extension_module.active
+%p
+ %strong= t('extension_modules.show.provisioning_key_active') + ":"
+ = @extension_module.provisioning_key_active
+
+%p
+ = link_to raw("<i class = 'icon-repeat'></i> ") + t('extension_modules.show.actions.restart'), restart_phone_extension_module_path(@phone, @extension_module), :method => :put, :class => 'btn btn-mini'
+
+= render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @phone, :child => @extension_module }
diff --git a/app/views/gateway_headers/_form.html.haml b/app/views/gateway_headers/_form.html.haml
new file mode 100644
index 0000000..0bd8f31
--- /dev/null
+++ b/app/views/gateway_headers/_form.html.haml
@@ -0,0 +1,7 @@
+= simple_form_for([@gateway, @gateway_header]) do |f|
+ = f.error_notification
+
+ = render "form_core", :f => f
+
+ .form-actions
+ = f.button :submit, conditional_t('gateway_headers.form.submit')
diff --git a/app/views/gateway_headers/_form_core.html.haml b/app/views/gateway_headers/_form_core.html.haml
new file mode 100644
index 0000000..9a917d4
--- /dev/null
+++ b/app/views/gateway_headers/_form_core.html.haml
@@ -0,0 +1,7 @@
+.inputs
+ = f.input :header_type, :collection => GatewayHeader::HEADER_TYPES, :label => t('gateway_headers.form.header_type.label'), :hint => conditional_hint('gateway_headers.form.header_type.hint'), :include_blank => false
+ = f.input :constraint_value, :label => t('gateway_headers.form.constraint_value.label'), :hint => conditional_hint('gateway_headers.form.constraint_value.hint')
+ = f.input :name, :label => t('gateway_headers.form.name.label'), :hint => conditional_hint('gateway_headers.form.name.hint'), :autofocus => true
+ = f.input :value, :label => t('gateway_headers.form.value.label'), :hint => conditional_hint('gateway_headers.form.value.hint')
+
+ = f.input :description, :label => t('gateway_headers.form.description.label'), :hint => conditional_hint('gateway_headers.form.description.hint')
diff --git a/app/views/gateway_headers/_index_core.html.haml b/app/views/gateway_headers/_index_core.html.haml
new file mode 100644
index 0000000..69b1cf2
--- /dev/null
+++ b/app/views/gateway_headers/_index_core.html.haml
@@ -0,0 +1,17 @@
+%table.table.table-striped
+ %thead
+ %tr
+ %th= t('gateway_headers.index.header_type')
+ %th= t('gateway_headers.index.constraint')
+ %th= t('gateway_headers.index.name')
+ %th= t('gateway_headers.index.value')
+
+ %tbody
+ - for gateway_header in gateway_headers
+ %tr
+ %td= gateway_header.header_type
+ %td
+ = gateway_header.constraint_value
+ %td= gateway_header.name
+ %td= gateway_header.value
+ =render :partial => 'shared/index_view_edit_destroy_part', :locals => {:parent => gateway_header.gateway, :child => gateway_header}
diff --git a/app/views/gateway_headers/edit.html.haml b/app/views/gateway_headers/edit.html.haml
new file mode 100644
index 0000000..461ee17
--- /dev/null
+++ b/app/views/gateway_headers/edit.html.haml
@@ -0,0 +1,3 @@
+- content_for :title, t("gateway_headers.edit.page_title")
+
+= render "form" \ No newline at end of file
diff --git a/app/views/gateway_headers/index.html.haml b/app/views/gateway_headers/index.html.haml
new file mode 100644
index 0000000..8760b5e
--- /dev/null
+++ b/app/views/gateway_headers/index.html.haml
@@ -0,0 +1,6 @@
+- content_for :title, t("gateway_headers.index.page_title")
+
+- if @gateway_headers && @gateway_headers.count > 0
+ = render "index_core", :gateway_headers => @gateway_headers
+
+= render :partial => 'shared/create_link', :locals => {:parent => @gateway, :child_class => GatewayHeader} \ No newline at end of file
diff --git a/app/views/gateway_headers/new.html.haml b/app/views/gateway_headers/new.html.haml
new file mode 100644
index 0000000..3f6ea8e
--- /dev/null
+++ b/app/views/gateway_headers/new.html.haml
@@ -0,0 +1,3 @@
+- content_for :title, t("gateway_headers.new.page_title")
+
+= render "form" \ No newline at end of file
diff --git a/app/views/gateway_headers/show.html.haml b/app/views/gateway_headers/show.html.haml
new file mode 100644
index 0000000..4f1af49
--- /dev/null
+++ b/app/views/gateway_headers/show.html.haml
@@ -0,0 +1,19 @@
+- content_for :title, t("gateway_headers.show.page_title")
+
+%p
+ %strong= t('gateway_headers.show.gateway') + ":"
+ = @gateway_header.gateway
+%p
+ %strong= t('gateway_headers.show.name') + ":"
+ = @gateway_header.name
+%p
+ %strong= t('gateway_headers.show.value') + ":"
+ = @gateway_header.value
+%p
+ %strong= t('gateway_headers.show.class_type') + ":"
+ = @gateway_header.class_type
+%p
+ %strong= t('gateway_headers.show.description') + ":"
+ = @gateway_header.description
+
+= render :partial => 'shared/show_edit_destroy_part', :locals => { :parent => @gateway, :child => @gateway_header } \ No newline at end of file
diff --git a/app/views/gateways/show.html.haml b/app/views/gateways/show.html.haml
index 19b1304..397b9cf 100644
--- a/app/views/gateways/show.html.haml
+++ b/app/views/gateways/show.html.haml
@@ -118,6 +118,12 @@
%br
= render :partial => 'shared/create_link', :locals => { :parent => @gateway, :child_class => GatewaySetting }
+%h3= t('gateway_headers.index.page_title')
+- if @gateway.gateway_headers.any?
+ = render "gateway_headers/index_core", :gateway_headers => @gateway.gateway_headers
+ %br
+= render :partial => 'shared/create_link', :locals => { :parent => @gateway, :child_class => GatewayHeader }
+
%h3= t('gateway_parameters.index.page_title')
- if @gateway.gateway_parameters.any?
= render "gateway_parameters/index_core", :gateway_parameters => @gateway.gateway_parameters
diff --git a/app/views/group_memberships/_form_core.html.haml b/app/views/group_memberships/_form_core.html.haml
index 4a28a06..cadd33a 100644
--- a/app/views/group_memberships/_form_core.html.haml
+++ b/app/views/group_memberships/_form_core.html.haml
@@ -1,4 +1,3 @@
.inputs
- - if @group.group_memberships.count < 1
- = f.input :item_type, :label => t('group_memberships.form.item_type.label'), :hint => conditional_hint('group_memberships.form.item_type.hint')
+ = f.input :item_type, :label => t('group_memberships.form.item_type.label'), :hint => conditional_hint('group_memberships.form.item_type.hint')
= f.input :item_id, :label => t('group_memberships.form.item_id.label'), :hint => conditional_hint('group_memberships.form.item_id.hint')
diff --git a/app/views/notifications/new_voicemail.text.erb b/app/views/notifications/new_voicemail.text.erb
index 926610b..09edc0c 100644
--- a/app/views/notifications/new_voicemail.text.erb
+++ b/app/views/notifications/new_voicemail.text.erb
@@ -6,4 +6,6 @@ You've just received a voicemail on your Gemeinschaft account <%= @voicemail[:de
To: <%= @voicemail[:to] %>
Receiving Time: <%= @voicemail[:date] %>
Message length: <%= @voicemail[:duration] %>
+<% if !@voicemail[:file_name].blank? %>
File: <%= @voicemail[:file_name] %>
+<% end %>
diff --git a/app/views/phones/show.html.haml b/app/views/phones/show.html.haml
index 1996d48..1e9c7ed 100644
--- a/app/views/phones/show.html.haml
+++ b/app/views/phones/show.html.haml
@@ -96,3 +96,12 @@
= render "phone_sip_accounts/index_core", :phone_sip_accounts => @phone.phone_sip_accounts
= render :partial => 'shared/create_link', :locals => {:parent => @phone, :child_class => PhoneSipAccount}
+
+.row
+ .span12
+ %h2= t("extension_modules.index.page_title")
+ - if @phone.extension_modules.any?
+ = render "extension_modules/index_core", :extension_modules => @phone.extension_modules
+
+ = render :partial => 'shared/create_link', :locals => {:parent => @phone, :child_class => ExtensionModule}
+
diff --git a/app/views/sip_accounts/_index_core.html.haml b/app/views/sip_accounts/_index_core.html.haml
index be1f4cb..57641ce 100644
--- a/app/views/sip_accounts/_index_core.html.haml
+++ b/app/views/sip_accounts/_index_core.html.haml
@@ -1,6 +1,6 @@
-- if defined?(users.total_pages)
- = will_paginate users, :renderer => BootstrapPagination::Rails, :previous_label => raw('<i class = "icon-chevron-left"></i>'), :next_label => raw('<i class = "icon-chevron-right"></i>')
+- if defined?(sip_accounts.total_pages)
+ = will_paginate sip_accounts, :renderer => BootstrapPagination::Rails, :previous_label => raw('<i class = "icon-chevron-left"></i>'), :next_label => raw('<i class = "icon-chevron-right"></i>')
%table.table.table-striped
%thead
@@ -26,7 +26,7 @@
%td
= sip_account.sip_accountable
%td
- - if sip_account.registration
+ - if sip_account.registration && (sip_account.registration.expires - Time.now.to_i) > 0
%i.icon-ok
- else
%i.icon-ban-circle
diff --git a/app/views/sip_accounts/show.html.haml b/app/views/sip_accounts/show.html.haml
index a7cd3ce..0974ee5 100644
--- a/app/views/sip_accounts/show.html.haml
+++ b/app/views/sip_accounts/show.html.haml
@@ -42,6 +42,11 @@
%strong= t('sip_accounts.show.voicemail_account') + ":"
%td
= @sip_account.voicemail_account
+ %tr
+ %td
+ %strong= t('sip_accounts.show.language_code') + ":"
+ %td
+ = @sip_account.language_code
- if @sip_account.registration.try(:network_ip) && @sip_account.registration.try(:network_port)
%tr
diff --git a/app/views/switchboard_entries/_form_core.html.haml b/app/views/switchboard_entries/_form_core.html.haml
index 6f340c2..2caaba5 100644
--- a/app/views/switchboard_entries/_form_core.html.haml
+++ b/app/views/switchboard_entries/_form_core.html.haml
@@ -1,3 +1,4 @@
.inputs
= f.association :sip_account, :collection => @sip_accounts, :label => t('switchboard_entries.form.sip_account_id.label'), :hint => conditional_hint('switchboard_entries.form.sip_account_id.hint'), :autofocus => true, :include_blank => false
= f.input :name, :label => t('switchboard_entries.form.name.label'), :hint => conditional_hint('switchboard_entries.form.name.hint')
+ = f.input :switchable, :as => :boolean, :label => t('switchboard_entries.form.switchable.label'), :hint => conditional_hint('switchboard_entries.form.switchable.hint') \ No newline at end of file
diff --git a/app/views/switchboard_entries/show.html.haml b/app/views/switchboard_entries/show.html.haml
index b519781..85b8166 100644
--- a/app/views/switchboard_entries/show.html.haml
+++ b/app/views/switchboard_entries/show.html.haml
@@ -18,5 +18,10 @@
%strong= t('switchboard_entries.show.position') + ":"
%td
= @switchboard_entry.position
+ %tr
+ %td
+ %strong= t('switchboard_entries.show.switchable') + ":"
+ %td
+ = @switchboard_entry.switchable
= render :partial => 'shared/show_edit_destroy_part', :locals => {:parent => @switchboard, :child => @switchboard_entry } \ No newline at end of file
diff --git a/app/views/switchboards/_form_core.html.haml b/app/views/switchboards/_form_core.html.haml
index 2258640..db843ad 100644
--- a/app/views/switchboards/_form_core.html.haml
+++ b/app/views/switchboards/_form_core.html.haml
@@ -3,4 +3,8 @@
= f.input :reload_interval, :label => t('switchboards.form.reload_interval.label'), :hint => conditional_hint('switchboards.form.reload_interval.hint')
= f.input :show_avatars, :label => t('switchboards.form.show_avatars.label'), :hint => conditional_hint('switchboards.form.show_avatars.hint')
= f.input :entry_width, :label => t('switchboards.form.entry_width.label'), :hint => conditional_hint('switchboards.form.entry_width.hint')
- = f.input :amount_of_displayed_phone_numbers, :label => t('switchboards.form.amount_of_displayed_phone_numbers.label'), :hint => conditional_hint('switchboards.form.amount_of_displayed_phone_numbers.hint') \ No newline at end of file
+ = f.input :amount_of_displayed_phone_numbers, :label => t('switchboards.form.amount_of_displayed_phone_numbers.label'), :hint => conditional_hint('switchboards.form.amount_of_displayed_phone_numbers.hint')
+ = f.input :blind_transfer_activated, :label => t('switchboards.form.blind_transfer_activated.label'), :hint => conditional_hint('switchboards.form.blind_transfer_activated.hint')
+ = f.input :attended_transfer_activated, :label => t('switchboards.form.attended_transfer_activated.label'), :hint => conditional_hint('switchboards.form.attended_transfer_activated.hint')
+ = f.input :search_activated, :label => t('switchboards.form.search_activated.label'), :hint => conditional_hint('switchboards.form.search_activated.hint')
+ = f.input :reverse_lookup_activated, :label => t('switchboards.form.reverse_lookup_activated.label'), :hint => conditional_hint('switchboards.form.reverse_lookup_activated.hint') \ No newline at end of file
diff --git a/app/views/switchboards/show.html.erb b/app/views/switchboards/show.html.erb
index 2a2765f..b2cdbd4 100644
--- a/app/views/switchboards/show.html.erb
+++ b/app/views/switchboards/show.html.erb
@@ -16,53 +16,120 @@
</script>
<script type="text/x-handlebars" data-template-name="switchboard">
- <h2>{{name}}</h2>
+ {{#if activeCalls.length}}
+ {{#each activeCall in activeCalls}}
+ <div {{bindAttr class=":alert activeCall.isActive:alert-success"}}>
+ {{#if activeCall.isRinging}}<i class="icon-bell"></i> Neuer {{/if}}
+ <button type="button" class="close" data-dismiss="alert">&times;</button>
+ Anruf von {{activeCall.b_caller_id_number}} an {{activeCall.destination}} ({{from_now activeCall.start_stamp}}).
+ </div>
+ {{/each}}
+ {{/if}}
+
+ {{#if search_activated}}
+ <div class="well span3 pull-right">
+ <p>
+ {{input type="text" value=searchText size="10" placeholder="Suchen..."}}
+ </p>
+ <ul>
+ {{#each phoneBookEntry in searchResults}}
+ <li>
+ {{phoneBookEntry.search_result_display}}<br />
+
+ {{#each phoneNumber in phoneBookEntry.phoneNumbers}}
+ <span class="label">
+ {{phoneNumber.number}}
+ </span>
+
+ {{#each dispatchableIncomingCall in dispatchableIncomingCalls}}
+ {{#if blind_transfer_activated}}
+ <button {{action transfer_blind dispatchableIncomingCall.id phoneNumber.number}} class="btn btn-small">Transfer</button>
+ {{/if}}
+ {{#if attended_transfer_activated}}
+ <button {{action transfer_attended dispatchableIncomingCall.id phoneNumber.number}} class="btn btn-small">Attended Transfer</button>
+ {{/if}}
+ {{/each}}
+ {{/each}}
+ </li>
+ {{/each}}
+ </ul>
+ </div>
+ {{/if}}
{{#if switchboardEntrys.length}}
<ul class="thumbnails">
{{#each switchboardEntry in switchboardEntrys}}
<li class="span2">
<div class="thumbnail">
- {{avatar_img switchboardEntry.avatar_src}}
- <small>
- <p>
+ {{#if switchboardEntry.switchboard.show_avatars}}
+ {{avatar_img switchboardEntry.avatar_src}}
+ {{/if}}
+ <small>
+ <p></p>
+ {{#if switchboardEntry.name}}
+ <p class="text-center">
<span class="label">{{switchboardEntry.name}}</span>
+ </p>
+ {{/if}}
+ {{#if switchboardEntry.sipAccount.is_registrated}}
+ {{#if switchboardEntry.switchable}}
+ <p>
{{#each phoneNumber in switchboardEntry.sipAccount.phoneNumberShortList}}
<span class="label">
{{phoneNumber.number}}
</span>
{{/each}}
- </p>
-
- {{show_callstate switchboardEntry.callstate}}
- {{#if switchboardEntry.sipAccount.calls.length}}
+ </p>
+ {{else}}
<p>
- Anrufe:
- <br>
- {{#each call in switchboardEntry.sipAccount.calls}}
- <span {{bindAttr class=":label call.isActive:label-success"}}>
- {{call.b_caller_id_number}} -> {{call.destination}}
- {{#if call.isActive}}
- *
- {{/if}}
+ {{#each phoneNumber in switchboardEntry.sipAccount.phoneNumberShortList}}
+ <span class="label">
+ {{phoneNumber.number}}
</span>
+
+ {{#if dispatchableIncomingCalls.length}}
+ <p>
+ {{#each dispatchableIncomingCall in dispatchableIncomingCalls}}
+ {{#if switchboardEntry.switchboard.blind_transfer_activated}}
+ <button {{action transfer_blind dispatchableIncomingCall.id phoneNumber.number}} class="btn btn-small">Transfer</button>
+ {{/if}}
+ {{#if switchboardEntry.switchboard.attended_transfer_activated}}
+ <button {{action transfer_attended dispatchableIncomingCall.id phoneNumber.number}} class="btn btn-small">Attended Transfer</button>
+ {{/if}}
+ {{/each}}
+ </p>
+ {{/if}}
{{/each}}
</p>
{{/if}}
- {{#if activeCalls.length}}
+ {{#if switchboardEntry.sipAccount.calls.length}}
<p>
- Verbinden mit:
- <br>
- {{#each activeCall in activeCalls}}
- <button {{action blind_transfer}}>
- {{activeCall.b_caller_id_number}}
- </button>
- {{/each}}
+ {{#each call in switchboardEntry.sipAccount.calls}}
+ <span {{bindAttr class=":label call.isRinging:label-warning:label-success"}}>
+ {{#if call.isRinging}}<i class="icon-bell icon-white"></i>{{/if}}
+ von {{call.b_caller_id_number}} an {{call.destination}}
+ </span>
+ {{/each}}
</p>
{{/if}}
- </small>
+ {{else}}
+ <p>
+ {{#each phoneNumber in switchboardEntry.sipAccount.phoneNumberShortList}}
+ <span class="label">
+ {{phoneNumber.number}}
+ </span>
+ {{/each}}
+ </p>
+
+ <p>
+ <span class="label label-warning">
+ offline
+ </span>
+ </p>
+ {{/if}}
+ </small>
</div>
</li>
{{/each}}
@@ -73,6 +140,8 @@
</div>
</div>
+<script src="/js/libs/moment/moment.min.js"></script>
+<script src="/js/libs/moment/lang/de.js"></script>
<script src="/js/libs/handlebars.js"></script>
<script src="/js/libs/ember.js"></script>
<script src="/js/libs/ember-data.js"></script>
diff --git a/app/views/voicemail_settings/_edit_form_core.html.haml b/app/views/voicemail_settings/_edit_form_core.html.haml
index 4352f24..81ab2f8 100644
--- a/app/views/voicemail_settings/_edit_form_core.html.haml
+++ b/app/views/voicemail_settings/_edit_form_core.html.haml
@@ -1,2 +1,2 @@
.inputs
- = f.input :value, :label => t("voicemail_settings.settings.#{@voicemail_setting.name.to_s}"), :hint => @voicemail_setting.description, :as => @input_type, input_html: @input_html
+ = f.input :value, :label => t("voicemail_settings.settings.#{@voicemail_setting.name.to_s}"), :hint => @voicemail_setting.description, :as => @input_type, input_html: @input_html
diff --git a/config/locales/views/config_snom/de.yml b/config/locales/views/config_snom/de.yml
index 82742a9..19c39a1 100644
--- a/config/locales/views/config_snom/de.yml
+++ b/config/locales/views/config_snom/de.yml
@@ -1,4 +1,4 @@
-en:
+de:
config_snom:
main_menu:
phone_book: 'Telefonbuch'
diff --git a/config/locales/views/extension_modules/de.yml b/config/locales/views/extension_modules/de.yml
new file mode 100644
index 0000000..3669b26
--- /dev/null
+++ b/config/locales/views/extension_modules/de.yml
@@ -0,0 +1,59 @@
+de:
+ extension_modules:
+ name: 'Erweiterungsmodul'
+ controller:
+ successfuly_created: 'Ein neues Erweiterungsmodul wurde in der Datenbank eingetragen.'
+ successfuly_updated: 'Die Daten zum Erweiterungsmodul wurden aktualisiert.'
+ successfuly_destroyed: 'Das Erweiterungsmodul wurde aus dem System entfernt.'
+ restart_invoked: 'Neustart ausgeführt'
+ restart_failed: 'Neustart fehlgeschlagen'
+ index:
+ page_title: 'Erweiterungsmodule'
+ mac_address: 'MAC-Adresse'
+ model: 'Modell'
+ ip_address: 'IP'
+ active: 'Aktiv'
+ actions:
+ confirm_destroy: 'Sind Sie sicher, dass Sie diese Erweiterungsmodul aus Gemeinschaft entfernen möchten?'
+ destroy: 'Löschen'
+ edit: 'Bearbeiten'
+ show: 'Anzeigen'
+ create: 'Neu anlegen'
+ create_for: 'Neues Erweiterungsmodul für %{resource} anlegen'
+ show:
+ page_title: 'Erweiterungsmodul'
+ mac_address: 'MAC-Adresse'
+ model: 'Modell'
+ hot_deskable: 'Hot-Desk fähig'
+ ip_address: 'IP-Adresse'
+ active: 'Aktiv'
+ actions:
+ confirm_destroy: 'Sind Sie sicher, dass Sie dieses Erweiterungsmodul aus Gemeinschaft entfernen möchten?'
+ destroy: 'Löschen'
+ edit: 'Bearbeiten'
+ view_all: 'Alle Erweiterungsmodule anzeigen'
+ restart: 'Neu starten'
+ new:
+ page_title: 'Neues Erweiterungsmodul'
+ edit:
+ page_title: 'Erweiterungsmoduldaten bearbeiten'
+ form:
+ mac_address:
+ label: 'MAC-Adresse'
+ hint: ''
+ model:
+ label: 'Modell'
+ hint: ''
+ ip_address:
+ label: 'IP-Adresse'
+ hint: ''
+ position:
+ label: 'Position'
+ hint: ''
+ active:
+ label: 'Aktiv'
+ hint: ''
+ provisioning_key_active:
+ label: 'Provisioning Schlüssel aktiv'
+ hint: ''
+ submit: 'Absenden'
diff --git a/config/locales/views/extension_modules/en.yml b/config/locales/views/extension_modules/en.yml
new file mode 100644
index 0000000..1d37db8
--- /dev/null
+++ b/config/locales/views/extension_modules/en.yml
@@ -0,0 +1,59 @@
+en:
+ extension_modules:
+ name: 'Extension module'
+ controller:
+ successfuly_created: 'Successfully created extension_module.'
+ successfuly_updated: 'Successfully updated extension_module.'
+ successfuly_destroyed: 'Successfully destroyed extension_module.'
+ restart_invoked: 'Restart invoked'
+ restart_failed: 'Restart failed'
+ index:
+ page_title: 'Extension modules'
+ model: 'Model'
+ mac_address: 'Mac address'
+ ip_address: 'IP address'
+ active: 'Active'
+ actions:
+ confirm_destroy: 'Are you sure you want to delete this extension_module?'
+ destroy: 'Delete'
+ edit: 'Edit'
+ show: 'View'
+ create: 'New'
+ create_for: 'New extension_module for %{resource}'
+ show:
+ page_title: 'Show extension_module'
+ model: 'Model'
+ mac_address: 'Mac address'
+ ip_address: 'IP address'
+ active: 'Active'
+ provisioning_key_active: 'Provisioning key active'
+ actions:
+ confirm_destroy: 'Are you sure you want to delete this extension_module?'
+ destroy: 'Delete extension_module'
+ edit: 'Edit extension_module'
+ view_all: 'View all extension_modules'
+ restart: 'Restart'
+ new:
+ page_title: 'New extension_module'
+ edit:
+ page_title: 'Editing Extension module'
+ form:
+ mac_address:
+ label: 'Mac address'
+ hint: ''
+ model:
+ label: 'Model'
+ hint: ''
+ ip_address:
+ label: 'IP address'
+ hint: ''
+ position:
+ label: 'Position'
+ hint: ''
+ active:
+ label: 'Active'
+ hint: ''
+ provisioning_key_active:
+ label: 'Provisioning key active'
+ hint: ''
+ submit: 'Submit'
diff --git a/config/locales/views/gateway_headers/de.yml b/config/locales/views/gateway_headers/de.yml
new file mode 100644
index 0000000..3f3786c
--- /dev/null
+++ b/config/locales/views/gateway_headers/de.yml
@@ -0,0 +1,66 @@
+de:
+ gateway_headers:
+ name: 'Header'
+ controller:
+ successfuly_created: 'Header wurde angelegt.'
+ successfuly_updated: 'Header wurde aktualisiert.'
+ successfuly_destroyed: 'Header wurde gelöscht.'
+ index:
+ page_title: 'Header'
+ gateway_id: 'Gateway'
+ name: 'Name'
+ value: 'Value'
+ class_type: 'Class type'
+ description: 'Description'
+ actions:
+ confirm_destroy: 'Sind Sie sicher, dass Sie folgendes löschen möchten: Header'
+ destroy: 'Löschen'
+ edit: 'Bearbeiten'
+ show: 'Anzeigen'
+ create: 'Neu anlegen'
+ create_for: 'Header neu anlegen für %{resource}'
+ show:
+ page_title: 'Header bearbeiten'
+ gateway: 'Gateway'
+ name: 'Name'
+ value: 'Value'
+ class_type: 'Class type'
+ description: 'Description'
+ actions:
+ confirm_destroy: 'Sind Sie sicher, dass die diesen Header löschen möchten?'
+ destroy: 'Löschen'
+ edit: 'Bearbeiten'
+ view_all: 'Alle anzeigen'
+ new:
+ page_title: 'Header neu anlegen'
+ actions:
+ back_to_list: 'Zurück zur Übersicht'
+ edit:
+ page_title: 'Header bearbeiten'
+ actions:
+ back_to_list: 'Zurück zur Übersicht'
+ edit: 'Bearbeiten'
+ view_all: 'Alle anzeigen'
+ form:
+ gateway_id:
+ label: 'Gateway'
+ hint: ''
+ name:
+ label: 'Header'
+ hint: ''
+ value:
+ label: 'Wert'
+ hint: ''
+ header_type:
+ label: 'Typ'
+ hint: ''
+ description:
+ label: 'Beschreibung'
+ hint: ''
+ constraint_source:
+ label: 'Beschränkungsquelle'
+ hint: ''
+ constraint_value:
+ label: 'Beschränkung'
+ hint: ''
+ submit: 'Absenden' \ No newline at end of file
diff --git a/config/locales/views/gateway_headers/en.yml b/config/locales/views/gateway_headers/en.yml
new file mode 100644
index 0000000..4fca18a
--- /dev/null
+++ b/config/locales/views/gateway_headers/en.yml
@@ -0,0 +1,66 @@
+en:
+ gateway_headers:
+ name: 'Header'
+ controller:
+ successfuly_created: 'Successfully created Header.'
+ successfuly_updated: 'Successfully updated Header.'
+ successfuly_destroyed: 'Successfully destroyed Header.'
+ index:
+ page_title: 'Headers'
+ gateway_id: 'Gateway'
+ name: 'Name'
+ value: 'Value'
+ class_type: 'Class type'
+ description: 'Description'
+ actions:
+ confirm_destroy: 'Are you sure you want to delete this Header?'
+ destroy: 'Delete'
+ edit: 'Edit'
+ show: 'View'
+ create: 'New'
+ create_for: 'New Header for %{resource}'
+ show:
+ page_title: 'Show Header'
+ gateway: 'Gateway'
+ name: 'Name'
+ value: 'Value'
+ class_type: 'Class type'
+ description: 'Description'
+ actions:
+ confirm_destroy: 'Are you sure you want to delete this element?'
+ destroy: 'Delete'
+ edit: 'Edit'
+ view_all: 'View All'
+ new:
+ page_title: 'New Header'
+ actions:
+ back_to_list: 'Back to Index'
+ edit:
+ page_title: 'Editing Header'
+ actions:
+ back_to_list: 'Back to Index'
+ edit: 'Edit'
+ view_all: 'View All'
+ form:
+ gateway_id:
+ label: 'Gateway'
+ hint: ''
+ name:
+ label: 'Header'
+ hint: ''
+ value:
+ label: 'Value'
+ hint: ''
+ header_type:
+ label: 'Type'
+ hint: ''
+ description:
+ label: 'Description'
+ hint: ''
+ constraint_source:
+ label: 'Constraint source'
+ hint: ''
+ constraint_value:
+ label: 'Constraint'
+ hint: ''
+ submit: 'Submit' \ No newline at end of file
diff --git a/config/locales/views/gateway_settings/de.yml b/config/locales/views/gateway_settings/de.yml
index 210bf07..66b8a2c 100644
--- a/config/locales/views/gateway_settings/de.yml
+++ b/config/locales/views/gateway_settings/de.yml
@@ -96,18 +96,9 @@ de:
profile:
label: 'Profile'
hint: 'Name of SIP profile'
- from:
- label: 'From header'
- hint: '"From" header to be sent to gateway'
- from_clir:
- label: 'From on CLIR'
- hint: '"From" header with supressed caller ID'
- asserted_identity:
- label: 'Asserted identity'
- hint: '"P-Asserted-Identity" header to be sent to gateway'
- asserted_identity_clir:
- label: 'Asserted identity CLIR'
- hint: '"P-Asserted-Identity" header with supressed caller ID'
+ dtmf_type:
+ label: 'DTMF Typ'
+ hint: 'DTMF Typ: rfc2833, info, inband oder none'
xmpp:
server:
label: 'Server'
diff --git a/config/locales/views/gateway_settings/en.yml b/config/locales/views/gateway_settings/en.yml
index 1699a9d..a485fa4 100644
--- a/config/locales/views/gateway_settings/en.yml
+++ b/config/locales/views/gateway_settings/en.yml
@@ -96,18 +96,9 @@ en:
profile:
label: 'Profile'
hint: 'Name of SIP profile'
- from:
- label: 'From header'
- hint: '"From" header to be sent to gateway'
- from_clir:
- label: 'From on CLIR'
- hint: '"From" header with supressed caller ID'
- asserted_identity:
- label: 'Asserted identity'
- hint: '"P-Asserted-Identity" header to be sent to gateway'
- asserted_identity_clir:
- label: 'Asserted identity CLIR'
- hint: '"P-Asserted-Identity" header with supressed caller ID'
+ dtmf_type:
+ label: 'DTMF type'
+ hint: '"DTMF type: rfc2833, info, inband or none'
xmpp:
server:
label: 'Server'
diff --git a/config/locales/views/sip_accounts/de.yml b/config/locales/views/sip_accounts/de.yml
index b80e2a5..76f0d4c 100644
--- a/config/locales/views/sip_accounts/de.yml
+++ b/config/locales/views/sip_accounts/de.yml
@@ -39,6 +39,7 @@ de:
hotdeskable: 'Hot-Desk fähig'
clip_no_screening: 'Spezifische Rufnummernanzeige (CLIP -no screening-)'
callforward_rules_act_per_sip_account: 'Rufweiterleitungen gelten für das gesamte SIP-Account'
+ language_code: 'Sprache'
registration: 'Registrierung'
expires: 'Läuft ab'
user_agent: 'User Agent'
diff --git a/config/locales/views/sip_accounts/en.yml b/config/locales/views/sip_accounts/en.yml
index 2c66962..303bef8 100644
--- a/config/locales/views/sip_accounts/en.yml
+++ b/config/locales/views/sip_accounts/en.yml
@@ -39,6 +39,7 @@ en:
hotdeskable: 'Hotdeskable'
clip_no_screening: 'CLIP -no screening-'
callforward_rules_act_per_sip_account: 'Callforwards work for the whole sip account'
+ language_code: 'Language'
registration: 'Registration'
expires: 'Expires'
user_agent: 'User Agent'
diff --git a/config/locales/views/switchboard_entries/de.yml b/config/locales/views/switchboard_entries/de.yml
index 41804b5..e79b6e8 100644
--- a/config/locales/views/switchboard_entries/de.yml
+++ b/config/locales/views/switchboard_entries/de.yml
@@ -11,6 +11,7 @@ de:
sip_account_id: 'SIP-Account'
name: 'Name'
position: 'Position'
+ switchable: 'Vermittelbar'
actions:
confirm_destroy: 'Sind Sie sicher, dass Sie folgendes löschen möchten: Switchboard-Eintrag'
destroy: 'Löschen'
@@ -24,6 +25,7 @@ de:
sip_account_id: 'SIP-Account'
name: 'Name'
position: 'Position'
+ switchable: 'Vermittelbar'
actions:
confirm_destroy: 'Sind Sie sicher, dass die dieses Element löschen möchten?'
destroy: 'Löschen'
@@ -52,4 +54,7 @@ de:
position:
label: 'Position'
hint: ''
+ switchable:
+ label: 'Vermittelbarer SIP-Account'
+ hint: 'Gespräche an diesen SIP-Account können im Switchboard vermittelt werden.'
submit: 'Absenden' \ No newline at end of file
diff --git a/config/locales/views/switchboard_entries/en.yml b/config/locales/views/switchboard_entries/en.yml
index 71f2fe4..a964a41 100644
--- a/config/locales/views/switchboard_entries/en.yml
+++ b/config/locales/views/switchboard_entries/en.yml
@@ -11,6 +11,7 @@ en:
sip_account_id: 'Sip account'
name: 'Name'
position: 'Position'
+ switchable: 'Switchable'
actions:
confirm_destroy: 'Are you sure you want to delete this Switchboardentry?'
destroy: 'Delete'
@@ -24,6 +25,7 @@ en:
sip_account_id: 'Sip account'
name: 'Name'
position: 'Position'
+ switchable: 'Switchable'
actions:
confirm_destroy: 'Are you sure you want to delete this element?'
destroy: 'Delete'
@@ -52,4 +54,7 @@ en:
position:
label: 'Position'
hint: ''
+ switchable:
+ label: 'Switchable SIP-Account'
+ hint: 'Only calls to switchable SIP-Accounts can be transfered.'
submit: 'Submit' \ No newline at end of file
diff --git a/config/locales/views/switchboards/de.yml b/config/locales/views/switchboards/de.yml
index 1f66b9f..61cd5c6 100644
--- a/config/locales/views/switchboards/de.yml
+++ b/config/locales/views/switchboards/de.yml
@@ -13,6 +13,10 @@ de:
show_avatars: 'Show avatars'
entry_width: 'Entry width'
amount_of_displayed_phone_numbers: 'Anzahl der angezeigten Telefonnummern'
+ blind_transfer_activated: 'Transfer'
+ attended_transfer_activated: 'Transfer mit Rückfrage'
+ search_activated: 'Suche'
+ reverse_lookup: 'Inverssuche'
actions:
confirm_destroy: 'Sind Sie sicher, dass Sie folgendes löschen möchten: Switchboard'
destroy: 'Löschen'
@@ -27,7 +31,12 @@ de:
reload_interval:: 'Reload Interval'
show_avatars: 'Show avatars'
entry_width: 'Entry width'
+
amount_of_displayed_phone_numbers: 'Anzahl der angezeigten Telefonnummern'
+ blind_transfer_activated: 'Transfer'
+ attended_transfer_activated: 'Transfer mit Rückfrage'
+ search_activated: 'Suche'
+ reverse_lookup: 'Inverssuche'
actions:
confirm_destroy: 'Sind Sie sicher, dass die dieses Element löschen möchten?'
destroy: 'Löschen'
@@ -61,5 +70,17 @@ de:
hint: '1 - 4'
amount_of_displayed_phone_numbers:
label: 'Anzahl Telefonnummern'
- hint: 'Wie viel Telefonnummern werden in einem Eintrag angezeigt.'
+ hint: 'Wie viel Telefonnummern werden in einem Eintrag angezeigt.'
+ blind_transfer_activated:
+ label: 'Transfer'
+ hint: ''
+ attended_transfer_activated:
+ label: 'Transfer mit Rückfrage'
+ hint: 'Funktioniert nicht mit allen Telefonen.'
+ search_activated:
+ label: 'Suche aktivieren'
+ hint: ''
+ reverse_lookup:
+ label: 'Inverssuche aktivieren'
+ hint: 'Bei der Eingabe eine Ziffernfolge wird nach einem Telefonbucheintrag gesucht.'
submit: 'Absenden' \ No newline at end of file
diff --git a/config/locales/views/switchboards/en.yml b/config/locales/views/switchboards/en.yml
index fd160b5..15c7067 100644
--- a/config/locales/views/switchboards/en.yml
+++ b/config/locales/views/switchboards/en.yml
@@ -13,6 +13,10 @@ en:
show_avatars: 'Show avatars'
entry_width: 'Entry width'
amount_of_displayed_phone_numbers: 'Amount of displayed phone numbers'
+ blind_transfer_activated: 'Blind Transfer'
+ attended_transfer_activated: 'Attended Transfer'
+ search_activated: 'Search function'
+ reverse_lookup: 'Reverse lookup'
actions:
confirm_destroy: 'Are you sure you want to delete this Switchboard?'
destroy: 'Delete'
@@ -28,6 +32,10 @@ en:
show_avatars: 'Show avatars'
entry_width: 'Entry width'
amount_of_displayed_phone_numbers: 'Amount of displayed phone numbers'
+ blind_transfer_activated: 'Blind Transfer'
+ attended_transfer_activated: 'Attended Transfer'
+ search_activated: 'Search function'
+ reverse_lookup: 'Reverse lookup'
actions:
confirm_destroy: 'Are you sure you want to delete this element?'
destroy: 'Delete'
@@ -62,4 +70,16 @@ en:
amount_of_displayed_phone_numbers:
label: 'Amount of phone numbers'
hint: 'How many phone numbers should be displayed for a single entry.'
+ blind_transfer_activated:
+ label: 'Blind Transfer'
+ hint: ''
+ attended_transfer_activated:
+ label: 'Attended Transfer'
+ hint: 'Does not work with all phone models.'
+ search_activated:
+ label: 'Activate search function'
+ hint: ''
+ reverse_lookup:
+ label: 'Activate reverse lookup'
+ hint: 'Bei der Eingabe eine Ziffernfolge wird nach einem Telefonbucheintrag gesucht.'
submit: 'Submit' \ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index 46562eb..d958c87 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -11,6 +11,8 @@ Gemeinschaft42c::Application.routes.draw do
resources :sip_accounts, :only => [:show, :index]
resources :pager_groups
resources :phone_numbers, :only => [:show, :index]
+ resources :calls, :only => [:index, :show, :update]
+ resources :phone_book_entries, :only => [:index]
end
resources :rows
@@ -74,6 +76,7 @@ Gemeinschaft42c::Application.routes.draw do
resources :gateways do
resources :gateway_settings
resources :gateway_parameters
+ resources :gateway_headers
end
resources :gs_parameters, :only => [:show, :index, :update, :edit]
@@ -204,13 +207,24 @@ Gemeinschaft42c::Application.routes.draw do
# Unified path for Snom phones.
# Enter e.g. "http://192.168.1.105:3000/settings"
# as the Setting URL (Advanced -> Update).
- match 'snom-:provisioning_key' => 'config_snom#show',
+ match 'snom-:provisioning_key' => 'config_snom#snom_phone',
+ :via => [:get],
+ :format => 'xml'
+ match 'snom-:provisioning_key-:mac_address' => 'config_snom#snom_phone',
+ :constraints => { :mac_address => /000413[0-9A-F]{6}/i },
:via => [:get],
:format => 'xml'
match 'settings-:mac_address' => 'config_snom#show',
:constraints => { :mac_address => /000413[0-9A-F]{6}/i },
:via => [:get],
:format => 'xml'
+ match 'snom_vision-:provisioning_key' => 'config_snom#snom_vision',
+ :via => [:get],
+ :format => 'xml'
+ match 'snom_vision-:provisioning_key-:mac_address' => 'config_snom#snom_vision',
+ :constraints => { :mac_address => /000413[0-9A-F]{6}/i },
+ :via => [:get],
+ :format => 'xml'
match "/DeploymentService/LoginService" => 'config_siemens#index',
:via => [:post],
:format => 'xml'
@@ -325,6 +339,11 @@ Gemeinschaft42c::Application.routes.draw do
resources :phones, :only => [] do
resources :phone_sip_accounts
+ resources :extension_modules do
+ member do
+ put 'restart'
+ end
+ end
end
# Display all phone book entries that the current user can access:
diff --git a/db/migrate/20130225091200_add_destinationable_to_call_forward.rb b/db/migrate/20130225091200_add_destinationable_to_call_forward.rb
index 89e4b3b..56f7df4 100644
--- a/db/migrate/20130225091200_add_destinationable_to_call_forward.rb
+++ b/db/migrate/20130225091200_add_destinationable_to_call_forward.rb
@@ -4,7 +4,19 @@ class AddDestinationableToCallForward < ActiveRecord::Migration
add_column :call_forwards, :destinationable_id, :integer
CallForward.all.each do |call_forward|
- call_forward.update_attributes(:destinationable_type => call_forward.call_forwardable_type, :destinationable_id => call_forward.call_forwardable_id, :call_forwardable_type => 'PhoneNumber', :call_forwardable_id => call_forward.phone_number_id )
+ phone_number = PhoneNumber.where(:id => call_forward.phone_number_id).first
+ if phone_number and phone_number.phone_numberable.class == SipAccount
+ account = phone_number.phone_numberable
+ result = call_forward.update_attributes(:destinationable_type => call_forward.call_forwardable_type, :destinationable_id => call_forward.call_forwardable_id, :call_forwardable_type => account.class.to_s, :call_forwardable_id => account.id)
+ else
+ call_forward.update_attributes(:destinationable_type => call_forward.call_forwardable_type, :destinationable_id => call_forward.call_forwardable_id, :call_forwardable_type => 'PhoneNumber', :call_forwardable_id => call_forward.phone_number_id)
+ end
+ end
+
+ cf_groups = CallForward.order('active DESC').all.group_by{|cf| [cf.call_forwardable_type, cf.call_forwardable_id, cf.call_forward_case_id, cf.destinationable_type, cf.destinationable_id, cf.destination, cf.source]}
+ cf_groups.values.each do |duplicates|
+ first_item = duplicates.shift
+ duplicates.each{|duplicate| duplicate.destroy}
end
remove_column :call_forwards, :phone_number_id
diff --git a/db/migrate/20130612120200_add_gateway_defaults.rb b/db/migrate/20130612120200_add_gateway_defaults.rb
index eddba9d..c6d1c9f 100644
--- a/db/migrate/20130612120200_add_gateway_defaults.rb
+++ b/db/migrate/20130612120200_add_gateway_defaults.rb
@@ -3,10 +3,10 @@ class AddGatewayDefaults < ActiveRecord::Migration
GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'domain', :value => '192.168.1.1', :class_type => 'String')
GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'auth_source', :value => 'sip_received_ip', :class_type => 'String')
GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'auth_pattern', :value => '^192.168.1.1$', :class_type => 'String')
- GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'from', :value => '"{caller_id_name}" <sip:{caller_id_number}@{domain}>', :class_type => 'String')
- GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'from_clir', :value => '"Anonymous" <sip:anonymous@{domain}>', :class_type => 'String')
- GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'asserted_identity', :value => '"{caller_id_name}" <sip:{caller_id_number}@{domain}>', :class_type => 'String')
- GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'asserted_identity_clir', :value => '"Anonymous" <sip:{caller_id_number}@{domain}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'from', :value => '"{caller_id_name}" <sip:{caller_id_number}@{domain_local}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'from_clir', :value => '"Anonymous" <sip:anonymous@{domain_local}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'asserted_identity', :value => '"{caller_id_name}" <sip:{caller_id_number}@{domain_local}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'asserted_identity_clir', :value => '"Anonymous" <sip:{caller_id_number}@{domain_local}>', :class_type => 'String')
end
def down
diff --git a/db/migrate/20130627103456_add_blind_transfer_to_switchboard.rb b/db/migrate/20130627103456_add_blind_transfer_to_switchboard.rb
new file mode 100644
index 0000000..a947a51
--- /dev/null
+++ b/db/migrate/20130627103456_add_blind_transfer_to_switchboard.rb
@@ -0,0 +1,17 @@
+class AddBlindTransferToSwitchboard < ActiveRecord::Migration
+ def up
+ add_column :switchboards, :blind_transfer_activated, :boolean
+ add_column :switchboards, :attended_transfer_activated, :boolean
+
+ Switchboard.all.each do |switchboard|
+ switchboard.blind_transfer_activated = true
+ switchboard.attended_transfer_activated = true
+ switchboard.save
+ end
+ end
+
+ def down
+ remove_column :switchboards, :blind_transfer_activated
+ remove_column :switchboards, :attended_transfer_activated
+ end
+end
diff --git a/db/migrate/20130627162201_add_search_activated_to_switchboard.rb b/db/migrate/20130627162201_add_search_activated_to_switchboard.rb
new file mode 100644
index 0000000..48916ac
--- /dev/null
+++ b/db/migrate/20130627162201_add_search_activated_to_switchboard.rb
@@ -0,0 +1,5 @@
+class AddSearchActivatedToSwitchboard < ActiveRecord::Migration
+ def change
+ add_column :switchboards, :search_activated, :boolean
+ end
+end
diff --git a/db/migrate/20130701084803_add_reverse_lookup_to_switchboard.rb b/db/migrate/20130701084803_add_reverse_lookup_to_switchboard.rb
new file mode 100644
index 0000000..e16e8e2
--- /dev/null
+++ b/db/migrate/20130701084803_add_reverse_lookup_to_switchboard.rb
@@ -0,0 +1,5 @@
+class AddReverseLookupToSwitchboard < ActiveRecord::Migration
+ def change
+ add_column :switchboards, :reverse_lookup_activated, :boolean
+ end
+end
diff --git a/db/migrate/20130718083656_create_extension_modules.rb b/db/migrate/20130718083656_create_extension_modules.rb
new file mode 100644
index 0000000..3372b5f
--- /dev/null
+++ b/db/migrate/20130718083656_create_extension_modules.rb
@@ -0,0 +1,19 @@
+class CreateExtensionModules < ActiveRecord::Migration
+ def self.up
+ create_table :extension_modules do |t|
+ t.string :model
+ t.string :mac_address
+ t.string :ip_address
+ t.string :provisioning_key
+ t.boolean :provisioning_key_active
+ t.integer :phone_id
+ t.integer :position
+ t.boolean :active
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :extension_modules
+ end
+end
diff --git a/db/migrate/20130730080331_add_forwarding_path_to_cdrs.rb b/db/migrate/20130730080331_add_forwarding_path_to_cdrs.rb
new file mode 100644
index 0000000..7954868
--- /dev/null
+++ b/db/migrate/20130730080331_add_forwarding_path_to_cdrs.rb
@@ -0,0 +1,5 @@
+class AddForwardingPathToCdrs < ActiveRecord::Migration
+ def change
+ add_column :cdrs, :forwarding_path, :string
+ end
+end
diff --git a/db/migrate/20130731055839_add_clir_to_call_histories.rb b/db/migrate/20130731055839_add_clir_to_call_histories.rb
new file mode 100644
index 0000000..27c7839
--- /dev/null
+++ b/db/migrate/20130731055839_add_clir_to_call_histories.rb
@@ -0,0 +1,5 @@
+class AddClirToCallHistories < ActiveRecord::Migration
+ def change
+ add_column :call_histories, :clir, :boolean
+ end
+end
diff --git a/db/migrate/20130802105300_create_gateway_headers.rb b/db/migrate/20130802105300_create_gateway_headers.rb
new file mode 100644
index 0000000..52c74dc
--- /dev/null
+++ b/db/migrate/20130802105300_create_gateway_headers.rb
@@ -0,0 +1,18 @@
+class CreateGatewayHeaders < ActiveRecord::Migration
+ def self.up
+ create_table :gateway_headers do |t|
+ t.integer :gateway_id
+ t.string :header_type
+ t.string :constraint_source
+ t.string :constraint_value
+ t.string :name
+ t.string :value
+ t.string :description
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :gateway_headers
+ end
+end
diff --git a/db/migrate/20130804201900_add_gateway_default_headers.rb b/db/migrate/20130804201900_add_gateway_default_headers.rb
new file mode 100644
index 0000000..4b50242
--- /dev/null
+++ b/db/migrate/20130804201900_add_gateway_default_headers.rb
@@ -0,0 +1,28 @@
+class AddGatewayDefaultHeaders < ActiveRecord::Migration
+ def up
+ GsParameter.where(:entity => 'sip_gateways', :section => 'settings', :name => 'from').destroy_all
+ GsParameter.where(:entity => 'sip_gateways', :section => 'settings', :name => 'from_clir').destroy_all
+ GsParameter.where(:entity => 'sip_gateways', :section => 'settings', :name => 'asserted_identity').destroy_all
+ GsParameter.where(:entity => 'sip_gateways', :section => 'settings', :name => 'asserted_identity_clir').destroy_all
+
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'dtmf_type', :value => 'rfc2833', :class_type => 'String')
+
+ GsParameter.create(:entity => 'sip_gateways', :section => 'headers_default', :name => 'INVITE', :value => 'sip:{destination_number}@{domain}', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'headers_default', :name => 'To', :value => '<sip:{destination_number}@{domain}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'headers_default_clir_off', :name => 'From', :value => '"{caller_id_name}" <sip:{caller_id_number}@{domain_local}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'headers_default_clir_on', :name => 'From', :value => '"Anonymous" <sip:anonymous@{domain_local}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'headers_default_clir_off', :name => 'P-Asserted-Identity', :value => '"{caller_id_name}" <sip:{caller_id_number}@{domain_local}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'headers_default_clir_on', :name => 'P-Asserted-Identity', :value => '"Anonymous" <sip:{caller_id_number}@{domain_local}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'headers_default_clir_on', :name => 'Privacy', :value => 'id', :class_type => 'String')
+ end
+
+ def down
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'domain', :value => '192.168.1.1', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'auth_source', :value => 'sip_received_ip', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'auth_pattern', :value => '^192.168.1.1$', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'from', :value => '"{caller_id_name}" <sip:{caller_id_number}@{domain_local}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'from_clir', :value => '"Anonymous" <sip:anonymous@{domain_local}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'asserted_identity', :value => '"{caller_id_name}" <sip:{caller_id_number}@{domain_local}>', :class_type => 'String')
+ GsParameter.create(:entity => 'sip_gateways', :section => 'settings', :name => 'asserted_identity_clir', :value => '"Anonymous" <sip:{caller_id_number}@{domain_local}>', :class_type => 'String')
+ end
+end
diff --git a/db/migrate/20130805064700_change_rfc2833_profile_parameter.rb b/db/migrate/20130805064700_change_rfc2833_profile_parameter.rb
new file mode 100644
index 0000000..78b2350
--- /dev/null
+++ b/db/migrate/20130805064700_change_rfc2833_profile_parameter.rb
@@ -0,0 +1,15 @@
+class ChangeRfc2833ProfileParameter < ActiveRecord::Migration
+ def up
+ pass_rfc2833 = GsParameter.where(:entity => 'sofia', :section => 'profile', :name => 'pass-rfc2833').first
+ if pass_rfc2833
+ pass_rfc2833.update_attributes(:value => 'false')
+ end
+ end
+
+ def down
+ pass_rfc2833 = GsParameter.where(:entity => 'sofia', :section => 'profile', :name => 'pass-rfc2833').first
+ if pass_rfc2833
+ pass_rfc2833.update_attributes(:value => 'true')
+ end
+ end
+end
diff --git a/misc/freeswitch/conf/freeswitch.xml b/misc/freeswitch/conf/freeswitch.xml
index 0a6538e..82da873 100644
--- a/misc/freeswitch/conf/freeswitch.xml
+++ b/misc/freeswitch/conf/freeswitch.xml
@@ -670,7 +670,7 @@
<action function="play-file" data="voicemail/vm-message_number.wav"/>
<action function="say" data="$1" method="pronounced" type="items"/>
<action function="play-file" data="voicemail/vm-received.wav"/>
- <action function="say" data="$2" method="pronounced" type="short_date_time"/>
+ <action function="say" data="$2" method="pronounced" type="current_date_time"/>
<action function="play-file" data="$3"/>
</match>
</input>
diff --git a/misc/freeswitch/scripts/common/array.lua b/misc/freeswitch/scripts/common/array.lua
index c3cabec..58a6798 100644
--- a/misc/freeswitch/scripts/common/array.lua
+++ b/misc/freeswitch/scripts/common/array.lua
@@ -41,10 +41,11 @@ end
function expand_variable(variable_path, variable_sets)
for index=1, #variable_sets do
local result = try(variable_sets[index], variable_path);
- if result then
+ if result ~= nil then
return result;
end
end
+ return nil;
end
-- replace variables in a string by array values
diff --git a/misc/freeswitch/scripts/common/call_history.lua b/misc/freeswitch/scripts/common/call_history.lua
index 7e1e22b..ee73e84 100644
--- a/misc/freeswitch/scripts/common/call_history.lua
+++ b/misc/freeswitch/scripts/common/call_history.lua
@@ -76,6 +76,7 @@ function CallHistory.insert_event(self, uuid, account_type, account_id, entry_ty
call_history.callee_account_id = common.str.to_sql(event:getHeader('variable_gs_destination_id'));
call_history.destination_number = common.str.to_sql(event:getHeader('variable_gs_destination_number'));
call_history.forwarding_service = common.str.to_sql(event:getHeader('variable_gs_forwarding_service'));
+ call_history.clir = common.str.to_sql(common.str.to_b(event:getHeader('variable_gs_clir')));
if not common.str.to_b(event:getHeader('variable_gs_clir')) then
call_history.caller_account_type = common.str.to_sql(camelize_type(event:getHeader('variable_gs_caller_account_type') or event:getHeader('variable_gs_account_type')));
diff --git a/misc/freeswitch/scripts/common/conference.lua b/misc/freeswitch/scripts/common/conference.lua
index 5694f62..423d564 100644
--- a/misc/freeswitch/scripts/common/conference.lua
+++ b/misc/freeswitch/scripts/common/conference.lua
@@ -52,8 +52,7 @@ function Conference.settings_get(self)
end
-function Conference.find_by_id(self, id)
- local sql_query = 'SELECT *, (NOW() >= `start` AND NOW() <= `end`) AS `open_now` FROM `conferences` WHERE `id`= ' .. tonumber(id) .. ' LIMIT 1';
+function Conference.find_sql(self, sql_query)
local conference = nil;
self.database:query(sql_query, function(conference_entry)
@@ -82,6 +81,16 @@ function Conference.find_by_id(self, id)
end
+function Conference.find_by_id(self, id)
+ return self:find_sql('SELECT *, (NOW() >= `start` AND NOW() <= `end`) AS `open_now` FROM `conferences` WHERE `id`= ' .. tonumber(id) .. ' LIMIT 1');
+end
+
+
+function Conference.find_by_uuid(self, uuid)
+ return self:find_sql('SELECT *, (NOW() >= `start` AND NOW() <= `end`) AS `open_now` FROM `conferences` WHERE `uuid`= ' .. self.database:escape(uuid, '"') .. ' LIMIT 1');
+end
+
+
function Conference.find_invitee_by_numbers(self, phone_numbers)
if not self.record then
return false;
diff --git a/misc/freeswitch/scripts/common/gateway.lua b/misc/freeswitch/scripts/common/gateway.lua
index 09e8c4b..8ea6b04 100644
--- a/misc/freeswitch/scripts/common/gateway.lua
+++ b/misc/freeswitch/scripts/common/gateway.lua
@@ -274,3 +274,97 @@ function Gateway.parameters_build(self, gateway_id, technology)
return parameters;
end
+
+
+function Gateway.headers_get(self, header_type, gateway_id)
+ gateway_id = gateway_id or self.id;
+
+ local sql_query = 'SELECT * FROM `gateway_headers` WHERE `gateway_id` = ' .. tonumber(gateway_id) .. ' AND `header_type` = ' .. self.database:escape(header_type, '"');
+
+ local headers = {};
+ self.database:query(sql_query, function(entry)
+ table.insert(headers, entry);
+ end)
+
+ return headers;
+end
+
+
+function Gateway.constraint_match(self, constraints_str, variable_sets)
+ if common.str.blank(constraints_str) then
+ entry_match = true;
+ else
+ local constraints = common.str.strip_to_a(constraints_str, ',')
+ for constraint_index=1, #constraints do
+ local variable_name, pattern = common.str.partition(constraints[constraint_index], '!=')
+ local invert = variable_name ~= nil;
+ if not variable_name then
+ variable_name, pattern = common.str.partition(constraints[constraint_index], '=')
+ end
+
+ if not common.str.blank(variable_name) and not common.str.blank(pattern) then
+ local search_string = common.array.expand_variable(variable_name, variable_sets);
+ if search_string ~= nil then
+ local success, result = pcall(string.find, tostring(search_string), pattern);
+
+ entry_match = common.str.to_b(result);
+
+ if invert then
+ entry_match = not entry_match;
+ end
+
+ if entry_match == false then
+ break;
+ end
+ end
+ end
+ end
+ end
+
+ return entry_match;
+end
+
+
+function Gateway.origination_variables(self, header_type, origination_variables, ...)
+ local dtmf = tostring(self.settings.dtmf_type):lower();
+ if dtmf == 'inband' then
+ table.insert(origination_variables, "dtmf_type=none");
+ elseif dtmf == 'none' or dtmf == 'info' then
+ table.insert(origination_variables, "dtmf_type=" .. dtmf);
+ else
+ table.insert(origination_variables, "dtmf_type=rfc2833");
+ end
+
+ local header_to_variable = {
+ default = {
+ default = 'sip_h_',
+ from = 'sip_full_from',
+ to = 'sip_full_to',
+ invite = 'sip_req_uri',
+ },
+ invite = {
+ default = 'sip_h_',
+ from = 'sip_invite_full_from',
+ to = 'sip_invite_full_to',
+ invite = 'sip_invite_req_uri',
+ route = 'sip_invite_route_uri',
+ ['Record-Route'] = 'sip_invite_record_route',
+ }
+ }
+
+ local variable_sets = {...};
+ local headers = self:headers_get('default');
+ for index, header in ipairs(self:headers_get(header_type)) do
+ table.insert(headers, header);
+ end
+
+ for index, header in ipairs(headers) do
+ local search_string = common.array.expand_variable(header.constraint_source, variable_sets);
+ if common.str.blank(header.constraint_value) or self:constraint_match(header.constraint_value, variable_sets) then
+ if header_to_variable[header.header_type] then
+ local origination_variable = header_to_variable[header.header_type][header.name:lower()] or header_to_variable[header.header_type].default .. header.name;
+ table.insert(origination_variables, origination_variable .. "='" .. common.array.expand_variables(header.value, unpack(variable_sets)) .. "'");
+ end
+ end
+ end
+end
diff --git a/misc/freeswitch/scripts/common/object.lua b/misc/freeswitch/scripts/common/object.lua
index 68e1361..5482168 100644
--- a/misc/freeswitch/scripts/common/object.lua
+++ b/misc/freeswitch/scripts/common/object.lua
@@ -116,3 +116,16 @@ function Object.find(self, attributes)
return object;
end
+
+-- local class from module
+function Object.load_one(self, module_path)
+ result, module_data = pcall(require, module_path);
+ if not result or not module_data then
+ return nil, module_data;
+ end
+ for object_name in pairs(module_data) do
+ if tostring(object_name[1]) ~= '_' then
+ return module_data[object_name];
+ end
+ end
+end
diff --git a/misc/freeswitch/scripts/configuration.lua b/misc/freeswitch/scripts/configuration.lua
index 2486565..5874752 100644
--- a/misc/freeswitch/scripts/configuration.lua
+++ b/misc/freeswitch/scripts/configuration.lua
@@ -189,10 +189,10 @@ function conf_conference(database)
local config = common.configuration_table.get(database, 'conferences');
local profiles = nil;
- local event_name = params:getHeader("Event-Name")
+ local event_name = params:getHeader("Event-Name") or '';
if event_name == 'COMMAND' then
- local conf_name = params:getHeader('conf_name');
- local profile_name = params:getHeader('profile_name');
+ local conf_name = params:getHeader('conf_name') or '';
+ local profile_name = params:getHeader('profile_name') or '';
if conf_name:find('^conference%d+') then
require 'common.conference';
diff --git a/misc/freeswitch/scripts/dialplan/acd.lua b/misc/freeswitch/scripts/dialplan/acd.lua
index 5ed8979..1e6e2ef 100644
--- a/misc/freeswitch/scripts/dialplan/acd.lua
+++ b/misc/freeswitch/scripts/dialplan/acd.lua
@@ -482,3 +482,23 @@ function AutomaticCallDistributor.run(self, dialplan_object, caller, destination
return result;
end
+
+function AutomaticCallDistributor.call_forwarding_on(self, service, destination, destination_type, timeout, source)
+
+ if not self.call_forwarding then
+ require 'common.call_forwarding';
+ self.call_forwarding = common.call_forwarding.CallForwarding:new{ log = self.log, database = self.database, parent = self, domain = self.domain };
+ end
+
+ return self.call_forwarding:call_forwarding_on(service, destination, destination_type, timeout, source)
+end
+
+
+function AutomaticCallDistributor.call_forwarding_off(self, service, source, delete)
+ if not self.call_forwarding then
+ require 'common.call_forwarding';
+ self.call_forwarding = common.call_forwarding.CallForwarding:new{ log = self.log, database = self.database, parent = self, domain = self.domain };
+ end
+
+ return self.call_forwarding:call_forwarding_off(service, source, delete)
+end
diff --git a/misc/freeswitch/scripts/dialplan/callthrough.lua b/misc/freeswitch/scripts/dialplan/callthrough.lua
index 69a0611..59f170a 100644
--- a/misc/freeswitch/scripts/dialplan/callthrough.lua
+++ b/misc/freeswitch/scripts/dialplan/callthrough.lua
@@ -52,8 +52,8 @@ function Callthrough.authenticate(self, caller)
end
self.log:debug('CALLTHROUGH_AUTHENTICATE - access_authorizations: ', #self.access_authorizations);
- for index, authorization in ipairs(self.access_authorizations) do
- if authorization.phone_number then
+ for index, authorization in ipairs(self.access_authorizations) do
+ if not common.str.blank(authorization.phone_number) then
if authorization.phone_number == caller.caller_phone_number then
if authorization.pin and authorization.pin ~= "" then
if caller.session:read(authorization.pin:len(), authorization.pin:len(), "ivr/ivr-please_enter_pin_followed_by_pound.wav", 3000, "#") ~= authorization.pin then
diff --git a/misc/freeswitch/scripts/dialplan/dialplan.lua b/misc/freeswitch/scripts/dialplan/dialplan.lua
index 913d7a5..1133f58 100644
--- a/misc/freeswitch/scripts/dialplan/dialplan.lua
+++ b/misc/freeswitch/scripts/dialplan/dialplan.lua
@@ -124,16 +124,19 @@ function Dialplan.auth_node(self)
end
-function Dialplan.auth_sip_account(self)
+function Dialplan.auth_account(self)
if not common.str.blank(self.caller.auth_account_type) then
- self.log:info('AUTH_SIP_ACCOUNT - ', self.caller.auth_account_type, '=', self.caller.account_id, '/', self.caller.account_uuid);
+ self.log:info('AUTH auth_account - ', self.caller.auth_account_type, '=', self.caller.account_id, '/', self.caller.account_uuid);
+ return true;
+ elseif not common.str.blank(self.caller.previous_destination_type) and not common.str.blank(self.caller.previous_destination_uuid) then
+ self.log:info('AUTH previous_destination - ', self.caller.previous_destination_type, '=', self.caller.previous_destination_id, '/', self.caller.previous_destination_uuid);
return true;
end
end
function Dialplan.auth_gateway(self)
- require 'common.gateway'
+ require 'common.gateway';
local gateway_class = common.gateway.Gateway:new{ log = self.log, database = self.database};
local gateway = false;
@@ -143,14 +146,14 @@ function Dialplan.auth_gateway(self)
name = self.caller:to_s('gs_gateway_name'),
id = self.caller:to_i('gs_gateway_id'),
}
- log:info('AUTH_GATEWAY - authenticaded by password and username: ', self.caller:to_s('username'), ', gateway=', gateway.id, '|', gateway.name, ', ip: ', self.caller.sip_contact_host);
+ log:info('AUTH gateway - authenticaded by password and username: ', self.caller:to_s('username'), ', gateway=', gateway.id, '|', gateway.name, ', ip: ', self.caller.sip_contact_host);
return gateway_class:find_by_id(gateway.id);
else
gateway = gateway_class:authenticate(self.caller);
end
if gateway then
- log:info('AUTH_GATEWAY - ', gateway.auth_source, ' ~ ', gateway.auth_pattern, ', gateway=', gateway.id, '|', gateway.name, ', ip: ', self.caller.sip_contact_host);
+ log:info('AUTH gateway - ', gateway.auth_source, ' ~ ', gateway.auth_pattern, ', gateway=', gateway.id, '|', gateway.name, ', ip: ', self.caller.sip_contact_host);
return gateway_class:find_by_id(gateway.id);
end
end
@@ -349,7 +352,7 @@ function Dialplan.dial(self, destination)
self.log:debug('RINGTONE - phonebookentry=', self.caller.phone_book_entry.id, ', ringtone: ', self.caller.phone_book_entry.bellcore_id);
self.caller:export_variable('alert_info', 'http://amooma.de;info=Ringer' .. self.caller.phone_book_entry.bellcore_id .. ';x-line-id=0');
end
- if self.caller.phone_book_entry.image then
+ if not common.str.blank(self.caller.phone_book_entry.image) then
self:set_caller_picture(self.caller.phone_book_entry.id, 'phonebookentry', self.caller.phone_book_entry.image);
elseif self.caller.account and self.caller.account.owner then
self:set_caller_picture(self.caller.account.owner.id, self.caller.account.owner.class);
@@ -726,14 +729,6 @@ function Dialplan.switch(self, destination)
destination.callee_id_number = destination.number;
destination.callee_id_name = nil;
- require 'dialplan.router'
- local routes = dialplan.router.Router:new{ log = self.log, database = self.database, caller = self.caller, variables = self.caller }:route_run('outbound');
-
- if not routes or #routes == 0 then
- self.log:notice('SWITCH - no route - number: ', destination.number);
- return { continue = false, code = 404, phrase = 'No route' }
- end
-
if self.phonebook_number_lookup then
local user_id = common.array.try(self.caller, 'account.owner.id');
local tenant_id = common.array.try(self.caller, 'account.owner.record.current_tenant_id');
@@ -763,7 +758,16 @@ function Dialplan.switch(self, destination)
self.caller:set_callee_id(destination.callee_id_number, destination.callee_id_name);
+ require 'dialplan.router'
+ local routes = dialplan.router.Router:new{ log = self.log, database = self.database, caller = self.caller, variables = self.caller }:route_run('outbound');
+
+ if not routes or #routes == 0 then
+ self.log:notice('SWITCH - no route - number: ', destination.number);
+ return { continue = false, code = 404, phrase = 'No route' }
+ end
+
for index, route in ipairs(routes) do
+ self.caller:set_callee_id(route.callee_id_number or destination.callee_id_number, route.callee_id_name or destination.callee_id_name);
if route.type == 'hangup' then
self.log:notice('SWITCH_HANGUP - code: ', route.code, ', phrase: ', route.phrase, ', cause: ', route.cause);
return { continue = false, code = route.code or '404', phrase = route.phrase, cause = route.cause }
@@ -893,9 +897,10 @@ function Dialplan.run(self, destination)
self.caller:set_variable('sound_prefix', common.array.try(self.config, 'sounds.' .. tostring(self.caller.language)));
self.log:info('DIALPLAN start - caller_id: ',self.caller.caller_id_number, ' "', self.caller.caller_id_name, '" , number: ', destination.number, ', language: ', self.caller.language);
-
self.caller.static_caller_id_number = self.caller.caller_id_number;
self.caller.static_caller_id_name = self.caller.caller_id_name;
+ self.caller:set_variable('gs_caller_id_number', self.caller.caller_id_number);
+ self.caller:set_variable('gs_caller_id_name', self.caller.caller_id_name);
local result = { continue = false };
local loop = self.caller.loop_count;
@@ -965,6 +970,14 @@ function Dialplan.run(self, destination)
destination,
result
);
+ local forwarding_path = self.caller:to_s('gs_forwarding_path');
+ if forwarding_path ~= '' then
+ forwarding_path = forwarding_path .. ',';
+ end
+
+ forwarding_path = forwarding_path .. auth_account.class:sub(1,1) .. ':' .. auth_account.id;
+ self.caller:set_variable('gs_forwarding_path', forwarding_path);
+ self.log:debug('FORWARDING_PATH: ', forwarding_path);
end
end
diff --git a/misc/freeswitch/scripts/dialplan/functions.lua b/misc/freeswitch/scripts/dialplan/functions.lua
index efd1f05..89f101d 100644
--- a/misc/freeswitch/scripts/dialplan/functions.lua
+++ b/misc/freeswitch/scripts/dialplan/functions.lua
@@ -98,6 +98,10 @@ function Functions.dialplan_function(self, caller, dialed_number)
result = self:call_forwarding_off(caller, 'busy');
elseif fid == "cfbdel" then
result = self:call_forwarding_off(caller, 'busy', true);
+ elseif fid == "cfun" then
+ result = self:call_forwarding_by_number(caller, parameters[3], false, 'always');
+ elseif fid == "cfunoff" then
+ result = self:call_forwarding_by_number(caller, parameters[3], true, 'always');
elseif fid == "vmleave" then
result = self:voicemail_message_leave(caller, parameters[3]);
elseif fid == "vmcheck" then
@@ -742,6 +746,70 @@ function Functions.call_forwarding_toggle(self, caller, call_forwarding_service,
end
+function Functions.call_forwarding_by_number(self, caller, number, call_forwarding_off, call_forwarding_service, delete)
+ -- Find caller's SipAccount
+ local caller_sip_account = self:ensure_caller_sip_account(caller);
+ if not caller_sip_account then
+ return { continue = false, code = 403, phrase = 'Incompatible caller', no_cdr = true }
+ end
+
+ if common.str.blank(number) then
+ self.log:notice('FUNCTION_CALL_FORWARDING_BY_NUMBER - no phone number specified');
+ return { continue = false, code = 500, phrase = 'No number specified', no_cdr = true }
+ end
+
+ require 'common.phone_number';
+ local phone_number = common.phone_number.PhoneNumber:new{ log = self.log, database = self.database, domain = caller_sip_account.domain }:find_by_number(number);
+
+ if not phone_number then
+ self.log:notice('FUNCTION_CALL_FORWARDING_BY_NUMBER - no phone number found: ', number);
+ return { continue = false, code = 500, phrase = 'Number not found', no_cdr = true }
+ end
+
+ require 'common.object';
+ parent = common.object.Object:new{ log = self.log, database = self.database}:find{class = phone_number.record.phone_numberable_type, id = phone_number.record.phone_numberable_id};
+ parent.domain = parent.domain or self.domain;
+
+ if not parent then
+ self.log:notice('FUNCTION_CALL_FORWARDING_BY_NUMBER - no parent found: ', phone_number.record.phone_numberable_type, '=', phone_number.record.phone_numberable_id);
+ return { continue = false, code = 500, phrase = 'Parent not found', no_cdr = true }
+ end
+
+ self.log:info('FUNCTION_CALL_FORWARDING_BY_NUMBER - account: ', parent.class, '=', parent.id, '/', parent.uuid);
+
+ if parent.class == 'automaticcalldistributor' then
+ if not parent:agent_find_by_acd_and_destination(parent.id, caller_sip_account.class, caller_sip_account.id) then
+ self.log:notice('FUNCTION_CALL_FORWARDING_BY_NUMBER - caller not allowed to set call forwarding for: ', phone_number.record.phone_numberable_type, '=', phone_number.record.phone_numberable_id);
+ return { continue = false, code = 500, phrase = 'Permission denied', no_cdr = true }
+ end
+ else
+ self.log:notice('FUNCTION_CALL_FORWARDING_BY_NUMBER - parent type not forwardable by caller: ', phone_number.record.phone_numberable_type);
+ return { continue = false, code = 500, phrase = 'Permission denied', no_cdr = true }
+ end
+
+ if call_forwarding_off then
+ if not parent:call_forwarding_off(call_forwarding_service, nil, delete) then
+ self.log:notice('FUNCTION_CALL_FORWARDING_BY_NUMBER - call forwarding could not be deactivated');
+ return { continue = false, code = 500, phrase = 'Call Forwarding could not be deactivated', no_cdr = true }
+ end
+ else
+ if not parent:call_forwarding_on(call_forwarding_service, nil, delete) then
+ self.log:notice('FUNCTION_CALL_FORWARDING_BY_NUMBER - call forwarding could not be activated');
+ return { continue = false, code = 500, phrase = 'Call Forwarding could not be activated', no_cdr = true }
+ end
+ end
+
+ caller:answer();
+ if call_forwarding_off then
+ caller:send_display('Call forwarding off');
+ else
+ caller:send_display('Call forwarding on');
+ end
+ caller:sleep(1000);
+ return { continue = false, code = 200, phrase = 'OK', no_cdr = true }
+end
+
+
function Functions.voicemail_message_leave(self, caller, phone_number)
require 'dialplan.voicemail'
local voicemail_account = dialplan.voicemail.Voicemail:new{ log = self.log, database = self.database }:find_by_number(phone_number);
diff --git a/misc/freeswitch/scripts/dialplan/hunt_group.lua b/misc/freeswitch/scripts/dialplan/hunt_group.lua
index e87c6b2..f12182c 100644
--- a/misc/freeswitch/scripts/dialplan/hunt_group.lua
+++ b/misc/freeswitch/scripts/dialplan/hunt_group.lua
@@ -165,7 +165,11 @@ function HuntGroup.run(self, dialplan_object, caller, destination)
table.insert(recursive_destinations, forwarding_destination);
end
require 'dialplan.sip_call'
- result = dialplan.sip_call.SipCall:new{ log = self.log, database = self.database, caller = caller }:fork( recursive_destinations, { callee_id_number = hunt_group_destination.number, timeout = member_timeout });
+ result = dialplan.sip_call.SipCall:new{ log = self.log, database = self.database, caller = caller }:fork( recursive_destinations,
+ { callee_id_number = hunt_group_destination.number,
+ timeout = member_timeout,
+ send_ringing = ( dialplan_object.send_ringing_to_gateways and caller.from_gateway ),
+ });
if result.disposition == 'SUCCESS' then
if result.fork_index then
result.destination = recursive_destinations[result.fork_index];
diff --git a/misc/freeswitch/scripts/dialplan/session.lua b/misc/freeswitch/scripts/dialplan/session.lua
index 1d907c5..7023c5e 100644
--- a/misc/freeswitch/scripts/dialplan/session.lua
+++ b/misc/freeswitch/scripts/dialplan/session.lua
@@ -8,6 +8,8 @@ Session = {}
-- create session object
function Session.new(self, arg)
+ require 'common.str';
+
arg = arg or {}
object = arg.object or {}
setmetatable(object, self);
@@ -29,8 +31,8 @@ function Session.init_channel_variables(self)
self.destination_number = self:expand_variables(self:to_s('destination_number'));
self.called_number = self.destination_number;
- self.caller_id_number = self:to_s('caller_id_number');
- self.caller_id_name = self:to_s('caller_id_name');
+ self.caller_id_number = self.session:getVariable('gs_caller_id_number') or self:to_s('caller_id_number');
+ self.caller_id_name = self.session:getVariable('gs_caller_id_name') or self:to_s('caller_id_name');
self.caller_phone_number = self.caller_id_number;
self.caller_phone_numbers = {self.caller_id_number};
@@ -93,25 +95,21 @@ end
-- Cast channel variable to string
function Session.to_s(self, variable_name)
- require 'common.str'
return common.str.to_s(self.session:getVariable(variable_name));
end
-- Cast channel variable to integer
function Session.to_i(self, variable_name)
- require 'common.str'
return common.str.to_i(self.session:getVariable(variable_name));
end
-- Cast channel variable to boolean
function Session.to_b(self, variable_name)
- require 'common.str'
return common.str.to_b(self.session:getVariable(variable_name));
end
-- Split channel variable to table
function Session.to_a(self, variable_name)
- require 'common.str'
return common.str.to_a(self.session:getVariable(variable_name));
end
diff --git a/misc/freeswitch/scripts/dialplan/sip_call.lua b/misc/freeswitch/scripts/dialplan/sip_call.lua
index 1966a41..1b5ee5d 100644
--- a/misc/freeswitch/scripts/dialplan/sip_call.lua
+++ b/misc/freeswitch/scripts/dialplan/sip_call.lua
@@ -175,49 +175,7 @@ function SipCall.fork(self, destinations, arg )
local gateway = common.gateway.Gateway:new{ log = self.log, database = self.database}:find_by_id(destination.id);
if gateway and gateway.outbound then
- local asserted_identity = tostring(gateway.settings.asserted_identity);
- local asserted_identity_clir = tostring(gateway.settings.asserted_identity);
- local caller_id_number = destination.caller_id_number or self.caller.caller_id_number;
- local caller_id_name = destination.caller_id_name or self.caller.caller_id_name;
- local from_uri = common.array.expand_variables(gateway.settings.from or '', destination, self.caller, { gateway = gateway });
-
- if gateway.settings.asserted_identity then
- local identity = common.array.expand_variables(gateway.settings.asserted_identity or '', destination, self.caller, { gateway = gateway })
-
- if self.caller.clir then
- caller_id_number = self.caller.anonymous_number or 'anonymous';
- caller_id_name = self.caller.anonymous_name or 'Anonymous';
- from_uri = common.array.expand_variables(gateway.settings.from_clir or '', destination, self.caller, { gateway = gateway }) or from_uri;
- identity = common.array.expand_variables(gateway.settings.asserted_identity_clir or '', destination, self.caller, { gateway = gateway }) or identity;
- table.insert(origination_variables, "origination_caller_id_number='" .. caller_id_number .. "'");
- table.insert(origination_variables, "origination_caller_id_name='" .. caller_id_name .. "'");
- table.insert(origination_variables, "sip_h_Privacy='id'");
- else
- if destination.caller_id_number then
- table.insert(origination_variables, "origination_caller_id_number='" .. destination.caller_id_number .. "'");
- end
- if destination.caller_id_name then
- table.insert(origination_variables, "origination_caller_id_name='" .. destination.caller_id_name .. "'");
- end
- end
-
- if from_uri then
- table.insert(origination_variables, "sip_from_uri='" .. from_uri .. "'");
- end
-
- if identity then
- table.insert(origination_variables, "sip_h_P-Asserted-Identity='" .. identity .. "'");
- end
-
- self.log:info('FORK ', index, '/', #destinations, ' - from: ', from_uri, ', identity: ', identity, ', privacy: ', self.caller.clir);
- else
- if destination.caller_id_number then
- table.insert(origination_variables, "origination_caller_id_number='" .. destination.caller_id_number .. "'");
- end
- if destination.caller_id_name then
- table.insert(origination_variables, "origination_caller_id_name='" .. destination.caller_id_name .. "'");
- end
- end
+ gateway:origination_variables('invite', origination_variables, gateway.settings, destination, self.caller);
if destination.channel_variables then
for key, value in pairs(destination.channel_variables) do
@@ -226,6 +184,10 @@ function SipCall.fork(self, destinations, arg )
end
table.insert(dial_strings, '[' .. table.concat(origination_variables , ',') .. ']' .. gateway:call_url(destination.number));
+
+ if tostring(gateway.settings.dtmf_type):lower() == 'inband' then
+ self.caller:execute('start_dtmf_generate');
+ end
else
self.log:notice('FORK - gateway not found - gateway=', destination.id);
end
diff --git a/misc/freeswitch/scripts/dialplan/voicemail.lua b/misc/freeswitch/scripts/dialplan/voicemail.lua
index 3358d2b..f436720 100644
--- a/misc/freeswitch/scripts/dialplan/voicemail.lua
+++ b/misc/freeswitch/scripts/dialplan/voicemail.lua
@@ -349,7 +349,10 @@ function Voicemail.leave(self, caller, greeting, number)
end
os.remove(record_file_name);
caller:send_display('Goodbye');
- caller.session:sayPhrase('voicemail_goodbye');
+
+ if common.str.blank(greeting_file) then
+ caller.session:sayPhrase('voicemail_goodbye');
+ end
end
diff --git a/misc/freeswitch/scripts/dialplan_default.lua b/misc/freeswitch/scripts/dialplan_default.lua
index 32789cb..eeeb5c2 100644
--- a/misc/freeswitch/scripts/dialplan_default.lua
+++ b/misc/freeswitch/scripts/dialplan_default.lua
@@ -74,7 +74,7 @@ if start_caller.from_node and not start_dialplan:auth_node() then
log:debug('DIALPLAN_DEFAULT - node unauthorized - node_id: ', start_caller.node_id, ', domain: ', start_dialplan.domain);
start_dialplan:hangup(401, start_dialplan.domain);
else
- if not start_dialplan:auth_sip_account() then
+ if not start_dialplan:auth_account() then
local gateway = start_dialplan:auth_gateway()
if gateway then
diff --git a/misc/freeswitch/scripts/event/cdr_save.lua b/misc/freeswitch/scripts/event/cdr_save.lua
index e7ac64a..03eccf7 100644
--- a/misc/freeswitch/scripts/event/cdr_save.lua
+++ b/misc/freeswitch/scripts/event/cdr_save.lua
@@ -81,6 +81,7 @@ function CdrSave.channel_destroy(self, event)
cdr.forwarding_service = common.str.to_sql(event:getHeader('variable_gs_forwarding_service'));
cdr.forwarding_account_id = common.str.to_sql(event:getHeader('variable_gs_auth_account_id'));
cdr.forwarding_account_type = common.str.to_sql(camelize_type(event:getHeader('variable_gs_auth_account_type')));
+ cdr.forwarding_path = common.str.to_sql(event:getHeader('variable_gs_forwarding_path'));
cdr.account_id = common.str.to_sql(event:getHeader('variable_gs_account_id'));
cdr.account_type = common.str.to_sql(camelize_type(event:getHeader('variable_gs_account_type')));
cdr.bleg_account_id = common.str.to_sql(event:getHeader('variable_gs_destination_id'));
diff --git a/misc/freeswitch/scripts/event/presence_update.lua b/misc/freeswitch/scripts/event/presence_update.lua
index fd85f03..cf29ca9 100644
--- a/misc/freeswitch/scripts/event/presence_update.lua
+++ b/misc/freeswitch/scripts/event/presence_update.lua
@@ -134,6 +134,9 @@ function PresenceUpdate.presence_in(self, event)
self.log:info('[', uuid,'] PRESENCE_CONFERENCE_', call_direction:upper(), ' ', common.str.to_i(account), ' - identifier: ', account, ', state: ', state);
self:conference(direction, account, domain, state, uuid);
elseif protocol == 'sip' or protocol == 'any' then
+ if common.str.blank(state) then
+ state = event:getHeader('answer-state');
+ end
if protocol == 'sip' and common.str.blank(state) then
self.log:debug('[', uuid,'] PRESENCE_', call_direction:upper(),' no state - protocol: ', protocol, ', account: ', account);
return;
diff --git a/misc/freeswitch/scripts/phones/phone.lua b/misc/freeswitch/scripts/phones/phone.lua
index 57997ba..6219eb1 100644
--- a/misc/freeswitch/scripts/phones/phone.lua
+++ b/misc/freeswitch/scripts/phones/phone.lua
@@ -114,15 +114,43 @@ function Phone.login(self, account_id, owner_id, owner_type)
end
function Phone.resync(self, arg)
- if not self.model then
+ local result = nil;
+
+ if self.record then
+ arg.ip_address = arg.ip_address or self.record.ip_address;
+ arg.http_user = arg.http_user or self.record.http_user;
+ arg.http_password = arg.http_password or self.record.http_password;
+ end
+
+ if self.model then
+ result = self.model:resync(arg);
+ else
self.log:notice('PHONE_RESYNC phone model not found - trying Snom resync');
- require 'phones.snom'
- return phones.snom.Snom:new{ log = self.log }:resync(arg);
+ require 'phones.snom';
+ result = phones.snom.Snom:new{ log = self.log }:resync(arg);
+ end
+
+ if self.record and self.record.id then
+ self:resync_extension_modules(arg);
end
- arg.ip_address = arg.ip_address or self.record.ip_address;
- arg.http_user = arg.http_user or self.record.http_user;
- arg.http_password = arg.http_password or self.record.http_password;
+ return result;
+end
+
+
+function Phone.resync_extension_modules(self, arg)
+ require('common.object');
+ local object_class = common.object.Object:new{ log = self.log };
+
+ local sql_query = 'SELECT * FROM `extension_modules` WHERE `active` IS TRUE AND `phone_id` = ' .. common.str.to_i(self.record.id);
+ self.database:query(sql_query, function(extension_module)
+ local extension_module_class, error_message = object_class:load_one('phones.' .. extension_module.model);
+ if not extension_module_class then
+ self.log:error('RESYNC_EXTENSION_MODULE - unsupported model: ', extension_module.model, ', error: ', error_message);
+ end
- return self.model:resync(arg);
+ arg.ip_address = extension_module.ip_address;
+ local result = extension_module_class:new{log = self.log}:resync(arg);
+ self.log:debug('RESYNC_EXTENSION_MODULE - ', extension_module_class.class, '=', extension_module.id, ', mac_address: ', extension_module.mac_address, ', ip_address: ', extension_module.ip_address, ', executed: ', tostring(result));
+ end);
end
diff --git a/misc/freeswitch/scripts/phones/snom_vision.lua b/misc/freeswitch/scripts/phones/snom_vision.lua
new file mode 100644
index 0000000..7adace4
--- /dev/null
+++ b/misc/freeswitch/scripts/phones/snom_vision.lua
@@ -0,0 +1,40 @@
+-- Gemeinschaft 5 module: snom vision extension module model class
+-- (c) AMOOMA GmbH 2012-2013
+--
+
+module(...,package.seeall)
+
+SnomVision = {}
+
+-- Create SnomVision object
+function SnomVision.new(self, arg)
+ arg = arg or {}
+ object = arg.object or {}
+ setmetatable(object, self);
+ self.__index = self;
+ self.log = arg.log;
+ self.reboot = arg.reboot or true;
+ self.class = 'snomvision';
+ return object;
+end
+
+-- send reload message to module
+function SnomVision.resync(self, arg)
+ if arg.ip_address then
+ return self:resync_http(arg.ip_address, arg.http_user, arg.http_password, arg.http_port);
+ end
+
+ return false;
+end
+
+
+function SnomVision.resync_http(self, ip_address, http_user, http_password, http_port)
+ local port_str = '';
+ if tonumber(http_port) then
+ port_str = ':' .. http_port;
+ end
+
+ local command = 'http_request.lua snom_vision_resync http://' .. tostring(ip_address):gsub('[^0-9%.]', '') .. port_str .. '/ConfigurationModule/restart ' .. (http_user or '') .. ' ' .. (http_password or '');
+ require 'common.fapi'
+ return common.fapi.FApi:new():execute('luarun', command);
+end
diff --git a/public/js/app.js b/public/js/app.js
index 51b3b71..e389b9a 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -1,5 +1,4 @@
App = Ember.Application.create({
- LOG_TRANSITIONS: true,
rootElement: '#emberjs-container',
// Reload the switchboard every x milliseconds
@@ -9,11 +8,6 @@ App = Ember.Application.create({
var switchboard = App.Switchboard.find(switchboard_id);
setInterval(function() {
switchboard.reload();
-
- // var switchboard_entries = App.SwitchboardEntry.find();
- // switchboard_entries.forEach(function(switchboard_entry) {
- // switchboard_entry.reload();
- // });
}, reload_interval);
}
}
@@ -32,16 +26,31 @@ App.SwitchboardRoute = Ember.Route.extend({
// Controller
App.SwitchboardController = Ember.ObjectController.extend({
- blind_transfer: function() {
- console.log('transfer')
- }
+ transfer_blind: function(call_id, destination) {
+ request_url = '/api/v1/calls/' + call_id + '.json';
+ jQuery.get(request_url, { transfer_blind: destination });
+ },
+
+ transfer_attended: function(call_id, destination) {
+ request_url = '/api/v1/calls/' + call_id + '.json';
+ jQuery.get(request_url, { transfer_attended: destination });
+ },
+
+ searchText: null,
+
+ searchResults: function() {
+ var searchText = this.get('searchText');
+ if (!searchText) { return; }
+ return App.PhoneBookEntry.find({ query: searchText, switchboard_id: switchboard_id });
+ }.property('searchText')
});
// Models
App.Store = DS.Store.extend();
DS.RESTAdapter.configure("plurals", {
- switchboard_entry: "switchboard_entries"
+ switchboard_entry: "switchboard_entries",
+ phone_book_entry: "phone_book_entries"
});
DS.RESTAdapter.reopen({
@@ -51,9 +60,12 @@ DS.RESTAdapter.reopen({
App.Switchboard = DS.Model.extend({
switchboardEntrys: DS.hasMany('App.SwitchboardEntry'),
activeCalls: DS.hasMany('App.ActiveCall'),
- name: DS.attr('string')
-
-
+ dispatchableIncomingCalls: DS.hasMany('App.DispatchableIncomingCall'),
+ name: DS.attr('string'),
+ show_avatars: DS.attr('boolean'),
+ blind_transfer_activated: DS.attr('boolean'),
+ search_activated: DS.attr('boolean'),
+ attended_transfer_activated: DS.attr('boolean')
});
App.SwitchboardEntry = DS.Model.extend({
@@ -62,7 +74,8 @@ App.SwitchboardEntry = DS.Model.extend({
name: DS.attr('string'),
path_to_user: DS.attr('string'),
avatar_src: DS.attr('string'),
- callstate: DS.attr('string')
+ callstate: DS.attr('string'),
+ switchable: DS.attr('boolean')
});
App.ActiveCall = DS.Model.extend({
@@ -70,7 +83,47 @@ App.ActiveCall = DS.Model.extend({
callstate: DS.attr('string'),
b_callstate: DS.attr('string'),
destination: DS.attr('string'),
- b_caller_id_number: DS.attr('string')
+ b_caller_id_number: DS.attr('string'),
+
+ isActive: function() {
+ if (this.get('b_callstate') == 'ACTIVE') {
+ return true
+ } else {
+ return false
+ }
+ }.property('b_callstate'),
+
+ isRinging: function() {
+ if (this.get('b_callstate') == 'RINGING') {
+ return true
+ } else {
+ return false
+ }
+ }.property('b_callstate')
+});
+
+App.DispatchableIncomingCall = DS.Model.extend({
+ start_stamp: DS.attr('number'),
+ callstate: DS.attr('string'),
+ b_callstate: DS.attr('string'),
+ destination: DS.attr('string'),
+ b_caller_id_number: DS.attr('string'),
+
+ isActive: function() {
+ if (this.get('b_callstate') == 'ACTIVE') {
+ return true
+ } else {
+ return false
+ }
+ }.property('b_callstate'),
+
+ isRinging: function() {
+ if (this.get('b_callstate') == 'RINGING') {
+ return true
+ } else {
+ return false
+ }
+ }.property('b_callstate')
});
App.Adapter = DS.RESTAdapter.extend();
@@ -87,6 +140,7 @@ App.SipAccount = DS.Model.extend({
phoneNumbers: DS.hasMany('App.PhoneNumber'),
callerName: DS.attr('string'),
authName: DS.attr('string'),
+ is_registrated: DS.attr('boolean'),
phoneNumberShortList: Ember.computed(function() {
var phoneNumbers = this.get('phoneNumbers');
@@ -95,12 +149,6 @@ App.SipAccount = DS.Model.extend({
});
-App.PhoneNumber = DS.Model.extend({
- name: DS.attr('string'),
- number: DS.attr('string'),
- destination: DS.attr('string')
-});
-
App.Call = DS.Model.extend({
start_stamp: DS.attr('number'),
callstate: DS.attr('string'),
@@ -114,16 +162,48 @@ App.Call = DS.Model.extend({
} else {
return false
}
- }.property('b_callstate')
+ }.property('b_callstate'),
+ isRinging: function() {
+ if (this.get('b_callstate') == 'RINGING') {
+ return true
+ } else {
+ return false
+ }
+ }.property('b_callstate')
+});
+
+App.PhoneBookEntry = DS.Model.extend({
+ first_name: DS.attr('string'),
+ last_name: DS.attr('string'),
+ organization: DS.attr('string'),
+ search_result_display: DS.attr('string'),
+ phoneNumbers: DS.hasMany('App.PhoneNumber')
+});
+
+App.PhoneNumber = DS.Model.extend({
+ name: DS.attr('string'),
+ number: DS.attr('string'),
+ destination: DS.attr('string')
});
App.store.adapter.serializer.configure(App.PhoneNumber, { sideloadAs: 'phone_numbers' });
+// Handlebar Helpers
+//
Ember.Handlebars.registerBoundHelper('avatar_img', function(value) {
return new Handlebars.SafeString('<img alt="Avatar image" class="img-rounded" src="' + value + '" style="width: 100px;">');
});
Ember.Handlebars.registerBoundHelper('show_callstate', function(value) {
- return new Handlebars.SafeString('<span class="label">' + value + '</span>');
+ if (value) {
+ return new Handlebars.SafeString('<span class="label">' + value + '</span>');
+ }
});
+
+Ember.Handlebars.registerBoundHelper('from_now', function(start_stamp) {
+ moment().lang('de');
+ var day = moment.unix(start_stamp).fromNow();
+ return day;
+});
+
diff --git a/public/js/libs/handlebars.js b/public/js/libs/handlebars.js
index c70f09d..96d86ea 100644
--- a/public/js/libs/handlebars.js
+++ b/public/js/libs/handlebars.js
@@ -29,14 +29,13 @@ var Handlebars = {};
;
// lib/handlebars/base.js
-Handlebars.VERSION = "1.0.0";
-Handlebars.COMPILER_REVISION = 4;
+Handlebars.VERSION = "1.0.0-rc.4";
+Handlebars.COMPILER_REVISION = 3;
Handlebars.REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
- 3: '== 1.0.0-rc.4',
- 4: '>= 1.0.0'
+ 3: '>= 1.0.0-rc.4'
};
Handlebars.helpers = {};
@@ -68,7 +67,7 @@ Handlebars.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
- throw new Error("Missing helper: '" + arg + "'");
+ throw new Error("Could not find property '" + arg + "'");
}
});
@@ -125,9 +124,6 @@ Handlebars.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
- var type = toString.call(context);
- if(type === functionType) { context = context.call(this); }
-
if (options.data) {
data = Handlebars.createFrame(options.data);
}
@@ -156,25 +152,22 @@ Handlebars.registerHelper('each', function(context, options) {
return ret;
});
-Handlebars.registerHelper('if', function(conditional, options) {
- var type = toString.call(conditional);
- if(type === functionType) { conditional = conditional.call(this); }
+Handlebars.registerHelper('if', function(context, options) {
+ var type = toString.call(context);
+ if(type === functionType) { context = context.call(this); }
- if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
+ if(!context || Handlebars.Utils.isEmpty(context)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
-Handlebars.registerHelper('unless', function(conditional, options) {
- return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
+Handlebars.registerHelper('unless', function(context, options) {
+ return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn});
});
Handlebars.registerHelper('with', function(context, options) {
- var type = toString.call(context);
- if(type === functionType) { context = context.call(this); }
-
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
});
@@ -188,9 +181,9 @@ Handlebars.registerHelper('log', function(context, options) {
var handlebars = (function(){
var parser = {trace: function trace() { },
yy: {},
-symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"params":27,"hash":28,"dataName":29,"param":30,"STRING":31,"INTEGER":32,"BOOLEAN":33,"hashSegments":34,"hashSegment":35,"ID":36,"EQUALS":37,"DATA":38,"pathSegments":39,"SEP":40,"$accept":0,"$end":1},
-terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",31:"STRING",32:"INTEGER",33:"BOOLEAN",36:"ID",37:"EQUALS",38:"DATA",40:"SEP"},
-productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[27,2],[27,1],[30,1],[30,1],[30,1],[30,1],[30,1],[28,1],[34,2],[34,1],[35,3],[35,3],[35,3],[35,3],[35,3],[26,1],[26,1],[26,1],[29,2],[21,1],[39,3],[39,1]],
+symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"OPEN_PARTIAL":24,"partialName":25,"params":26,"hash":27,"DATA":28,"param":29,"STRING":30,"INTEGER":31,"BOOLEAN":32,"hashSegments":33,"hashSegment":34,"ID":35,"EQUALS":36,"PARTIAL_NAME":37,"pathSegments":38,"SEP":39,"$accept":0,"$end":1},
+terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"OPEN_PARTIAL",28:"DATA",30:"STRING",31:"INTEGER",32:"BOOLEAN",35:"ID",36:"EQUALS",37:"PARTIAL_NAME",39:"SEP"},
+productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[26,2],[26,1],[29,1],[29,1],[29,1],[29,1],[29,1],[27,1],[33,2],[33,1],[34,3],[34,3],[34,3],[34,3],[34,3],[25,1],[21,1],[38,3],[38,1]],
performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
var $0 = $$.length - 1;
@@ -231,10 +224,7 @@ case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]);
break;
case 18: this.$ = $$[$0-1];
break;
-case 19:
- // Parsing out the '&' escape token at this level saves ~500 bytes after min due to the removal of one parser node.
- this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2][2] === '&');
-
+case 19: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]);
break;
case 20: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true);
break;
@@ -252,7 +242,7 @@ case 26: this.$ = [[$$[$0-1]], $$[$0]];
break;
case 27: this.$ = [[$$[$0]], null];
break;
-case 28: this.$ = [[$$[$0]], null];
+case 28: this.$ = [[new yy.DataNode($$[$0])], null];
break;
case 29: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
break;
@@ -266,7 +256,7 @@ case 33: this.$ = new yy.IntegerNode($$[$0]);
break;
case 34: this.$ = new yy.BooleanNode($$[$0]);
break;
-case 35: this.$ = $$[$0];
+case 35: this.$ = new yy.DataNode($$[$0]);
break;
case 36: this.$ = new yy.HashNode($$[$0]);
break;
@@ -282,26 +272,20 @@ case 41: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])];
break;
case 42: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])];
break;
-case 43: this.$ = [$$[$0-2], $$[$0]];
+case 43: this.$ = [$$[$0-2], new yy.DataNode($$[$0])];
break;
case 44: this.$ = new yy.PartialNameNode($$[$0]);
break;
-case 45: this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0]));
-break;
-case 46: this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0]));
+case 45: this.$ = new yy.IdNode($$[$0]);
break;
-case 47: this.$ = new yy.DataNode($$[$0]);
+case 46: $$[$0-2].push($$[$0]); this.$ = $$[$0-2];
break;
-case 48: this.$ = new yy.IdNode($$[$0]);
-break;
-case 49: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2];
-break;
-case 50: this.$ = [{part: $$[$0]}];
+case 47: this.$ = [$$[$0]];
break;
}
},
-table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],25:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],25:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],25:[1,16]},{17:23,18:[1,22],21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{4:30,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:31,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:32,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:33,21:24,29:25,36:[1,28],38:[1,27],39:26},{21:35,26:34,31:[1,36],32:[1,37],36:[1,28],39:26},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],25:[1,16]},{17:23,21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,4],7:38,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],25:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{5:[2,23],14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{18:[1,39]},{18:[2,27],21:44,24:[2,27],27:40,28:41,29:48,30:42,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,28],24:[2,28]},{18:[2,48],24:[2,48],31:[2,48],32:[2,48],33:[2,48],36:[2,48],38:[2,48],40:[1,51]},{21:52,36:[1,28],39:26},{18:[2,50],24:[2,50],31:[2,50],32:[2,50],33:[2,50],36:[2,50],38:[2,50],40:[2,50]},{10:53,20:[1,54]},{10:55,20:[1,54]},{18:[1,56]},{18:[1,57]},{24:[1,58]},{18:[1,59],21:60,36:[1,28],39:26},{18:[2,44],36:[2,44]},{18:[2,45],36:[2,45]},{18:[2,46],36:[2,46]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],25:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,25],21:44,24:[2,25],28:61,29:48,30:62,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,26],24:[2,26]},{18:[2,30],24:[2,30],31:[2,30],32:[2,30],33:[2,30],36:[2,30],38:[2,30]},{18:[2,36],24:[2,36],35:63,36:[1,64]},{18:[2,31],24:[2,31],31:[2,31],32:[2,31],33:[2,31],36:[2,31],38:[2,31]},{18:[2,32],24:[2,32],31:[2,32],32:[2,32],33:[2,32],36:[2,32],38:[2,32]},{18:[2,33],24:[2,33],31:[2,33],32:[2,33],33:[2,33],36:[2,33],38:[2,33]},{18:[2,34],24:[2,34],31:[2,34],32:[2,34],33:[2,34],36:[2,34],38:[2,34]},{18:[2,35],24:[2,35],31:[2,35],32:[2,35],33:[2,35],36:[2,35],38:[2,35]},{18:[2,38],24:[2,38],36:[2,38]},{18:[2,50],24:[2,50],31:[2,50],32:[2,50],33:[2,50],36:[2,50],37:[1,65],38:[2,50],40:[2,50]},{36:[1,66]},{18:[2,47],24:[2,47],31:[2,47],32:[2,47],33:[2,47],36:[2,47],38:[2,47]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:67,36:[1,28],39:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,68]},{18:[2,24],24:[2,24]},{18:[2,29],24:[2,29],31:[2,29],32:[2,29],33:[2,29],36:[2,29],38:[2,29]},{18:[2,37],24:[2,37],36:[2,37]},{37:[1,65]},{21:69,29:73,31:[1,70],32:[1,71],33:[1,72],36:[1,28],38:[1,27],39:26},{18:[2,49],24:[2,49],31:[2,49],32:[2,49],33:[2,49],36:[2,49],38:[2,49],40:[2,49]},{18:[1,74]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{18:[2,39],24:[2,39],36:[2,39]},{18:[2,40],24:[2,40],36:[2,40]},{18:[2,41],24:[2,41],36:[2,41]},{18:[2,42],24:[2,42],36:[2,42]},{18:[2,43],24:[2,43],36:[2,43]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]}],
-defaultActions: {17:[2,1]},
+table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],24:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],24:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],24:[1,16]},{17:23,18:[1,22],21:24,28:[1,25],35:[1,27],38:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],24:[2,8]},{4:28,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],24:[1,16]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],24:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],24:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],24:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],24:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],24:[2,15]},{17:30,21:24,28:[1,25],35:[1,27],38:26},{17:31,21:24,28:[1,25],35:[1,27],38:26},{17:32,21:24,28:[1,25],35:[1,27],38:26},{25:33,37:[1,34]},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],24:[1,16]},{17:23,21:24,28:[1,25],35:[1,27],38:26},{5:[2,4],7:35,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],24:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],24:[2,9]},{5:[2,23],14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],24:[2,23]},{18:[1,36]},{18:[2,27],21:41,26:37,27:38,28:[1,45],29:39,30:[1,42],31:[1,43],32:[1,44],33:40,34:46,35:[1,47],38:26},{18:[2,28]},{18:[2,45],28:[2,45],30:[2,45],31:[2,45],32:[2,45],35:[2,45],39:[1,48]},{18:[2,47],28:[2,47],30:[2,47],31:[2,47],32:[2,47],35:[2,47],39:[2,47]},{10:49,20:[1,50]},{10:51,20:[1,50]},{18:[1,52]},{18:[1,53]},{18:[1,54]},{18:[1,55],21:56,35:[1,27],38:26},{18:[2,44],35:[2,44]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],24:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],24:[2,17]},{18:[2,25],21:41,27:57,28:[1,45],29:58,30:[1,42],31:[1,43],32:[1,44],33:40,34:46,35:[1,47],38:26},{18:[2,26]},{18:[2,30],28:[2,30],30:[2,30],31:[2,30],32:[2,30],35:[2,30]},{18:[2,36],34:59,35:[1,60]},{18:[2,31],28:[2,31],30:[2,31],31:[2,31],32:[2,31],35:[2,31]},{18:[2,32],28:[2,32],30:[2,32],31:[2,32],32:[2,32],35:[2,32]},{18:[2,33],28:[2,33],30:[2,33],31:[2,33],32:[2,33],35:[2,33]},{18:[2,34],28:[2,34],30:[2,34],31:[2,34],32:[2,34],35:[2,34]},{18:[2,35],28:[2,35],30:[2,35],31:[2,35],32:[2,35],35:[2,35]},{18:[2,38],35:[2,38]},{18:[2,47],28:[2,47],30:[2,47],31:[2,47],32:[2,47],35:[2,47],36:[1,61],39:[2,47]},{35:[1,62]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],24:[2,10]},{21:63,35:[1,27],38:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],24:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],24:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],24:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],24:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],24:[2,21]},{18:[1,64]},{18:[2,24]},{18:[2,29],28:[2,29],30:[2,29],31:[2,29],32:[2,29],35:[2,29]},{18:[2,37],35:[2,37]},{36:[1,61]},{21:65,28:[1,69],30:[1,66],31:[1,67],32:[1,68],35:[1,27],38:26},{18:[2,46],28:[2,46],30:[2,46],31:[2,46],32:[2,46],35:[2,46],39:[2,46]},{18:[1,70]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],24:[2,22]},{18:[2,39],35:[2,39]},{18:[2,40],35:[2,40]},{18:[2,41],35:[2,41]},{18:[2,42],35:[2,42]},{18:[2,43],35:[2,43]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],24:[2,18]}],
+defaultActions: {17:[2,1],25:[2,28],38:[2,26],57:[2,24]},
parseError: function parseError(str, hash) {
throw new Error(str);
},
@@ -600,7 +584,7 @@ case 3:
break;
case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15;
break;
-case 5: return 25;
+case 5: this.begin("par"); return 24;
break;
case 6: return 16;
break;
@@ -612,7 +596,7 @@ case 9: return 19;
break;
case 10: return 23;
break;
-case 11: return 22;
+case 11: return 23;
break;
case 12: this.popState(); this.begin('com');
break;
@@ -620,44 +604,48 @@ case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return
break;
case 14: return 22;
break;
-case 15: return 37;
+case 15: return 36;
break;
-case 16: return 36;
+case 16: return 35;
break;
-case 17: return 36;
+case 17: return 35;
break;
-case 18: return 40;
+case 18: return 39;
break;
case 19: /*ignore whitespace*/
break;
-case 20: this.popState(); return 24;
+case 20: this.popState(); return 18;
break;
case 21: this.popState(); return 18;
break;
-case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 31;
+case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 30;
break;
-case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 31;
+case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 30;
break;
-case 24: return 38;
+case 24: yy_.yytext = yy_.yytext.substr(1); return 28;
break;
-case 25: return 33;
+case 25: return 32;
break;
-case 26: return 33;
+case 26: return 32;
break;
-case 27: return 32;
+case 27: return 31;
break;
-case 28: return 36;
+case 28: return 35;
break;
-case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 36;
+case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 35;
break;
case 30: return 'INVALID';
break;
-case 31: return 5;
+case 31: /*ignore whitespace*/
+break;
+case 32: this.popState(); return 37;
+break;
+case 33: return 5;
break;
}
};
-lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
-lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"INITIAL":{"rules":[0,1,2,31],"inclusive":true}};
+lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$:\-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$\-\/]+)/,/^(?:$)/];
+lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,33],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"par":{"rules":[31,32],"inclusive":false},"INITIAL":{"rules":[0,1,2,33],"inclusive":true}};
return lexer;})()
parser.lexer = lexer;
function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
@@ -743,24 +731,21 @@ Handlebars.AST.HashNode = function(pairs) {
Handlebars.AST.IdNode = function(parts) {
this.type = "ID";
+ this.original = parts.join(".");
- var original = "",
- dig = [],
- depth = 0;
+ var dig = [], depth = 0;
for(var i=0,l=parts.length; i<l; i++) {
- var part = parts[i].part;
- original += (parts[i].separator || '') + part;
+ var part = parts[i];
if (part === ".." || part === "." || part === "this") {
- if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + original); }
+ if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); }
else if (part === "..") { depth++; }
else { this.isScoped = true; }
}
else { dig.push(part); }
}
- this.original = original;
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
@@ -774,7 +759,7 @@ Handlebars.AST.IdNode = function(parts) {
Handlebars.AST.PartialNameNode = function(name) {
this.type = "PARTIAL_NAME";
- this.name = name.original;
+ this.name = name;
};
Handlebars.AST.DataNode = function(id) {
@@ -784,15 +769,13 @@ Handlebars.AST.DataNode = function(id) {
Handlebars.AST.StringNode = function(string) {
this.type = "STRING";
- this.original =
- this.string =
- this.stringModeValue = string;
+ this.string = string;
+ this.stringModeValue = string;
};
Handlebars.AST.IntegerNode = function(integer) {
this.type = "INTEGER";
- this.original =
- this.integer = integer;
+ this.integer = integer;
this.stringModeValue = Number(integer);
};
@@ -1179,15 +1162,7 @@ Compiler.prototype = {
DATA: function(data) {
this.options.data = true;
- if (data.id.isScoped || data.id.depth) {
- throw new Handlebars.Exception('Scoped data references are not supported: ' + data.original);
- }
-
- this.opcode('lookupData');
- var parts = data.id.parts;
- for(var i=0, l=parts.length; i<l; i++) {
- this.opcode('lookup', parts[i]);
- }
+ this.opcode('lookupData', data.id);
},
STRING: function(string) {
@@ -1386,9 +1361,8 @@ JavaScriptCompiler.prototype = {
if (!this.isChild) {
var namespace = this.namespace;
-
- var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
- if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
+ var copies = "helpers = helpers || " + namespace + ".helpers;";
+ if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
if (this.options.data) { copies = copies + " data = data || {};"; }
out.push(copies);
} else {
@@ -1417,9 +1391,7 @@ JavaScriptCompiler.prototype = {
// Generate minimizer alias mappings
if (!this.isChild) {
for (var alias in this.context.aliases) {
- if (this.context.aliases.hasOwnProperty(alias)) {
- this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
- }
+ this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
}
}
@@ -1638,7 +1610,7 @@ JavaScriptCompiler.prototype = {
//
// Push the result of looking up `id` on the current data
lookupData: function(id) {
- this.push('data');
+ this.push(this.nameLookup('data', id, 'data'));
},
// [pushStringParam]
@@ -1745,9 +1717,8 @@ JavaScriptCompiler.prototype = {
this.context.aliases.helperMissing = 'helpers.helperMissing';
var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
- var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
- this.push(helper.name + ' || ' + nonHelper);
+ this.push(helper.name);
this.replaceStack(function(name) {
return name + ' ? ' + name + '.call(' +
helper.callParams + ") " + ": helperMissing.call(" +
@@ -2192,16 +2163,6 @@ Handlebars.VM = {
}
return programWrapper;
},
- merge: function(param, common) {
- var ret = param || common;
-
- if (param && common) {
- ret = {};
- Handlebars.Utils.extend(ret, common);
- Handlebars.Utils.extend(ret, param);
- }
- return ret;
- },
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop,
compilerInfo: null
diff --git a/public/js/libs/moment/lang/de.js b/public/js/libs/moment/lang/de.js
new file mode 100644
index 0000000..8bad490
--- /dev/null
+++ b/public/js/libs/moment/lang/de.js
@@ -0,0 +1,4 @@
+// moment.js language configuration
+// language : german (de)
+// author : lluchs : https://github.com/lluchs
+!function(){function e(e){e.lang("de",{months:"Januar_Februar_M\xe4rz_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"H:mm [Uhr]",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT",sameElse:"L",nextDay:"[Morgen um] LT",nextWeek:"dddd [um] LT",lastDay:"[Gestern um] LT",lastWeek:"[letzten] dddd [um] LT"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:"einer Minute",mm:"%d Minuten",h:"einer Stunde",hh:"%d Stunden",d:"einem Tag",dd:"%d Tagen",M:"einem Monat",MM:"%d Monaten",y:"einem Jahr",yy:"%d Jahren"},ordinal:"%d.",week:{dow:1,doy:4}})}"function"==typeof define&&define.amd&&define(["moment"],e),"undefined"!=typeof window&&window.moment&&e(window.moment)}(); \ No newline at end of file
diff --git a/public/js/libs/moment/moment.min.js b/public/js/libs/moment/moment.min.js
new file mode 100644
index 0000000..4e8497a
--- /dev/null
+++ b/public/js/libs/moment/moment.min.js
@@ -0,0 +1,6 @@
+// moment.js
+// version : 2.0.0
+// author : Tim Wood
+// license : MIT
+// momentjs.com
+(function(e){function O(e,t){return function(n){return j(e.call(this,n),t)}}function M(e){return function(t){return this.lang().ordinal(e.call(this,t))}}function _(){}function D(e){H(this,e)}function P(e){var t=this._data={},n=e.years||e.year||e.y||0,r=e.months||e.month||e.M||0,i=e.weeks||e.week||e.w||0,s=e.days||e.day||e.d||0,o=e.hours||e.hour||e.h||0,u=e.minutes||e.minute||e.m||0,a=e.seconds||e.second||e.s||0,f=e.milliseconds||e.millisecond||e.ms||0;this._milliseconds=f+a*1e3+u*6e4+o*36e5,this._days=s+i*7,this._months=r+n*12,t.milliseconds=f%1e3,a+=B(f/1e3),t.seconds=a%60,u+=B(a/60),t.minutes=u%60,o+=B(u/60),t.hours=o%24,s+=B(o/24),s+=i*7,t.days=s%30,r+=B(s/30),t.months=r%12,n+=B(r/12),t.years=n}function H(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}function B(e){return e<0?Math.ceil(e):Math.floor(e)}function j(e,t){var n=e+"";while(n.length<t)n="0"+n;return n}function F(e,t,n){var r=t._milliseconds,i=t._days,s=t._months,o;r&&e._d.setTime(+e+r*n),i&&e.date(e.date()+i*n),s&&(o=e.date(),e.date(1).month(e.month()+s*n).date(Math.min(o,e.daysInMonth())))}function I(e){return Object.prototype.toString.call(e)==="[object Array]"}function q(e,t){var n=Math.min(e.length,t.length),r=Math.abs(e.length-t.length),i=0,s;for(s=0;s<n;s++)~~e[s]!==~~t[s]&&i++;return i+r}function R(e,t){return t.abbr=e,s[e]||(s[e]=new _),s[e].set(t),s[e]}function U(e){return e?(!s[e]&&o&&require("./lang/"+e),s[e]):t.fn._lang}function z(e){return e.match(/\[.*\]/)?e.replace(/^\[|\]$/g,""):e.replace(/\\/g,"")}function W(e){var t=e.match(a),n,r;for(n=0,r=t.length;n<r;n++)A[t[n]]?t[n]=A[t[n]]:t[n]=z(t[n]);return function(i){var s="";for(n=0;n<r;n++)s+=typeof t[n].call=="function"?t[n].call(i,e):t[n];return s}}function X(e,t){function r(t){return e.lang().longDateFormat(t)||t}var n=5;while(n--&&f.test(t))t=t.replace(f,r);return C[t]||(C[t]=W(t)),C[t](e)}function V(e){switch(e){case"DDDD":return p;case"YYYY":return d;case"YYYYY":return v;case"S":case"SS":case"SSS":case"DDD":return h;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":case"a":case"A":return m;case"X":return b;case"Z":case"ZZ":return g;case"T":return y;case"MM":case"DD":case"YY":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":return c;default:return new RegExp(e.replace("\\",""))}}function $(e,t,n){var r,i,s=n._a;switch(e){case"M":case"MM":s[1]=t==null?0:~~t-1;break;case"MMM":case"MMMM":r=U(n._l).monthsParse(t),r!=null?s[1]=r:n._isValid=!1;break;case"D":case"DD":case"DDD":case"DDDD":t!=null&&(s[2]=~~t);break;case"YY":s[0]=~~t+(~~t>68?1900:2e3);break;case"YYYY":case"YYYYY":s[0]=~~t;break;case"a":case"A":n._isPm=(t+"").toLowerCase()==="pm";break;case"H":case"HH":case"h":case"hh":s[3]=~~t;break;case"m":case"mm":s[4]=~~t;break;case"s":case"ss":s[5]=~~t;break;case"S":case"SS":case"SSS":s[6]=~~(("0."+t)*1e3);break;case"X":n._d=new Date(parseFloat(t)*1e3);break;case"Z":case"ZZ":n._useUTC=!0,r=(t+"").match(x),r&&r[1]&&(n._tzh=~~r[1]),r&&r[2]&&(n._tzm=~~r[2]),r&&r[0]==="+"&&(n._tzh=-n._tzh,n._tzm=-n._tzm)}t==null&&(n._isValid=!1)}function J(e){var t,n,r=[];if(e._d)return;for(t=0;t<7;t++)e._a[t]=r[t]=e._a[t]==null?t===2?1:0:e._a[t];r[3]+=e._tzh||0,r[4]+=e._tzm||0,n=new Date(0),e._useUTC?(n.setUTCFullYear(r[0],r[1],r[2]),n.setUTCHours(r[3],r[4],r[5],r[6])):(n.setFullYear(r[0],r[1],r[2]),n.setHours(r[3],r[4],r[5],r[6])),e._d=n}function K(e){var t=e._f.match(a),n=e._i,r,i;e._a=[];for(r=0;r<t.length;r++)i=(V(t[r]).exec(n)||[])[0],i&&(n=n.slice(n.indexOf(i)+i.length)),A[t[r]]&&$(t[r],i,e);e._isPm&&e._a[3]<12&&(e._a[3]+=12),e._isPm===!1&&e._a[3]===12&&(e._a[3]=0),J(e)}function Q(e){var t,n,r,i=99,s,o,u;while(e._f.length){t=H({},e),t._f=e._f.pop(),K(t),n=new D(t);if(n.isValid()){r=n;break}u=q(t._a,n.toArray()),u<i&&(i=u,r=n)}H(e,r)}function G(e){var t,n=e._i;if(w.exec(n)){e._f="YYYY-MM-DDT";for(t=0;t<4;t++)if(S[t][1].exec(n)){e._f+=S[t][0];break}g.exec(n)&&(e._f+=" Z"),K(e)}else e._d=new Date(n)}function Y(t){var n=t._i,r=u.exec(n);n===e?t._d=new Date:r?t._d=new Date(+r[1]):typeof n=="string"?G(t):I(n)?(t._a=n.slice(0),J(t)):t._d=n instanceof Date?new Date(+n):new Date(n)}function Z(e,t,n,r,i){return i.relativeTime(t||1,!!n,e,r)}function et(e,t,n){var i=r(Math.abs(e)/1e3),s=r(i/60),o=r(s/60),u=r(o/24),a=r(u/365),f=i<45&&["s",i]||s===1&&["m"]||s<45&&["mm",s]||o===1&&["h"]||o<22&&["hh",o]||u===1&&["d"]||u<=25&&["dd",u]||u<=45&&["M"]||u<345&&["MM",r(u/30)]||a===1&&["y"]||["yy",a];return f[2]=t,f[3]=e>0,f[4]=n,Z.apply({},f)}function tt(e,n,r){var i=r-n,s=r-e.day();return s>i&&(s-=7),s<i-7&&(s+=7),Math.ceil(t(e).add("d",s).dayOfYear()/7)}function nt(e){var n=e._i,r=e._f;return n===null||n===""?null:(typeof n=="string"&&(e._i=n=U().preparse(n)),t.isMoment(n)?(e=H({},n),e._d=new Date(+n._d)):r?I(r)?Q(e):K(e):Y(e),new D(e))}function rt(e,n){t.fn[e]=t.fn[e+"s"]=function(e){var t=this._isUTC?"UTC":"";return e!=null?(this._d["set"+t+n](e),this):this._d["get"+t+n]()}}function it(e){t.duration.fn[e]=function(){return this._data[e]}}function st(e,n){t.duration.fn["as"+e]=function(){return+this/n}}var t,n="2.0.0",r=Math.round,i,s={},o=typeof module!="undefined"&&module.exports,u=/^\/?Date\((\-?\d+)/i,a=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g,f=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,l=/([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,c=/\d\d?/,h=/\d{1,3}/,p=/\d{3}/,d=/\d{1,4}/,v=/[+\-]?\d{1,6}/,m=/[0-9]*[a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF]+\s*?[\u0600-\u06FF]+/i,g=/Z|[\+\-]\d\d:?\d\d/i,y=/T/i,b=/[\+\-]?\d+(\.\d{1,3})?/,w=/^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,E="YYYY-MM-DDTHH:mm:ssZ",S=[["HH:mm:ss.S",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],x=/([\+\-]|\d\d)/gi,T="Month|Date|Hours|Minutes|Seconds|Milliseconds".split("|"),N={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},C={},k="DDD w W M D d".split(" "),L="M D H h m s w W".split(" "),A={M:function(){return this.month()+1},MMM:function(e){return this.lang().monthsShort(this,e)},MMMM:function(e){return this.lang().months(this,e)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(e){return this.lang().weekdaysMin(this,e)},ddd:function(e){return this.lang().weekdaysShort(this,e)},dddd:function(e){return this.lang().weekdays(this,e)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return j(this.year()%100,2)},YYYY:function(){return j(this.year(),4)},YYYYY:function(){return j(this.year(),5)},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return~~(this.milliseconds()/100)},SS:function(){return j(~~(this.milliseconds()/10),2)},SSS:function(){return j(this.milliseconds(),3)},Z:function(){var e=-this.zone(),t="+";return e<0&&(e=-e,t="-"),t+j(~~(e/60),2)+":"+j(~~e%60,2)},ZZ:function(){var e=-this.zone(),t="+";return e<0&&(e=-e,t="-"),t+j(~~(10*e/6),4)},X:function(){return this.unix()}};while(k.length)i=k.pop(),A[i+"o"]=M(A[i]);while(L.length)i=L.pop(),A[i+i]=O(A[i],2);A.DDDD=O(A.DDD,3),_.prototype={set:function(e){var t,n;for(n in e)t=e[n],typeof t=="function"?this[n]=t:this["_"+n]=t},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(e){return this._months[e.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(e){return this._monthsShort[e.month()]},monthsParse:function(e){var n,r,i,s;this._monthsParse||(this._monthsParse=[]);for(n=0;n<12;n++){this._monthsParse[n]||(r=t([2e3,n]),i="^"+this.months(r,"")+"|^"+this.monthsShort(r,""),this._monthsParse[n]=new RegExp(i.replace(".",""),"i"));if(this._monthsParse[n].test(e))return n}},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(e){return this._weekdays[e.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(e){return this._weekdaysShort[e.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(e){return this._weekdaysMin[e.day()]},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(e){var t=this._longDateFormat[e];return!t&&this._longDateFormat[e.toUpperCase()]&&(t=this._longDateFormat[e.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(e){return e.slice(1)}),this._longDateFormat[e]=t),t},meridiem:function(e,t,n){return e>11?n?"pm":"PM":n?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[last] dddd [at] LT",sameElse:"L"},calendar:function(e,t){var n=this._calendar[e];return typeof n=="function"?n.apply(t):n},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(e,t,n,r){var i=this._relativeTime[n];return typeof i=="function"?i(e,t,n,r):i.replace(/%d/i,e)},pastFuture:function(e,t){var n=this._relativeTime[e>0?"future":"past"];return typeof n=="function"?n(t):n.replace(/%s/i,t)},ordinal:function(e){return this._ordinal.replace("%d",e)},_ordinal:"%d",preparse:function(e){return e},postformat:function(e){return e},week:function(e){return tt(e,this._week.dow,this._week.doy)},_week:{dow:0,doy:6}},t=function(e,t,n){return nt({_i:e,_f:t,_l:n,_isUTC:!1})},t.utc=function(e,t,n){return nt({_useUTC:!0,_isUTC:!0,_l:n,_i:e,_f:t})},t.unix=function(e){return t(e*1e3)},t.duration=function(e,n){var r=t.isDuration(e),i=typeof e=="number",s=r?e._data:i?{}:e,o;return i&&(n?s[n]=e:s.milliseconds=e),o=new P(s),r&&e.hasOwnProperty("_lang")&&(o._lang=e._lang),o},t.version=n,t.defaultFormat=E,t.lang=function(e,n){var r;if(!e)return t.fn._lang._abbr;n?R(e,n):s[e]||U(e),t.duration.fn._lang=t.fn._lang=U(e)},t.langData=function(e){return e&&e._lang&&e._lang._abbr&&(e=e._lang._abbr),U(e)},t.isMoment=function(e){return e instanceof D},t.isDuration=function(e){return e instanceof P},t.fn=D.prototype={clone:function(){return t(this)},valueOf:function(){return+this._d},unix:function(){return Math.floor(+this._d/1e3)},toString:function(){return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._d},toJSON:function(){return t.utc(this).format("YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var e=this;return[e.year(),e.month(),e.date(),e.hours(),e.minutes(),e.seconds(),e.milliseconds()]},isValid:function(){return this._isValid==null&&(this._a?this._isValid=!q(this._a,(this._isUTC?t.utc(this._a):t(this._a)).toArray()):this._isValid=!isNaN(this._d.getTime())),!!this._isValid},utc:function(){return this._isUTC=!0,this},local:function(){return this._isUTC=!1,this},format:function(e){var n=X(this,e||t.defaultFormat);return this.lang().postformat(n)},add:function(e,n){var r;return typeof e=="string"?r=t.duration(+n,e):r=t.duration(e,n),F(this,r,1),this},subtract:function(e,n){var r;return typeof e=="string"?r=t.duration(+n,e):r=t.duration(e,n),F(this,r,-1),this},diff:function(e,n,r){var i=this._isUTC?t(e).utc():t(e).local(),s=(this.zone()-i.zone())*6e4,o,u;return n&&(n=n.replace(/s$/,"")),n==="year"||n==="month"?(o=(this.daysInMonth()+i.daysInMonth())*432e5,u=(this.year()-i.year())*12+(this.month()-i.month()),u+=(this-t(this).startOf("month")-(i-t(i).startOf("month")))/o,n==="year"&&(u/=12)):(o=this-i-s,u=n==="second"?o/1e3:n==="minute"?o/6e4:n==="hour"?o/36e5:n==="day"?o/864e5:n==="week"?o/6048e5:o),r?u:B(u)},from:function(e,n){return t.duration(this.diff(e)).lang(this.lang()._abbr).humanize(!n)},fromNow:function(e){return this.from(t(),e)},calendar:function(){var e=this.diff(t().startOf("day"),"days",!0),n=e<-6?"sameElse":e<-1?"lastWeek":e<0?"lastDay":e<1?"sameDay":e<2?"nextDay":e<7?"nextWeek":"sameElse";return this.format(this.lang().calendar(n,this))},isLeapYear:function(){var e=this.year();return e%4===0&&e%100!==0||e%400===0},isDST:function(){return this.zone()<t([this.year()]).zone()||this.zone()<t([this.year(),5]).zone()},day:function(e){var t=this._isUTC?this._d.getUTCDay():this._d.getDay();return e==null?t:this.add({d:e-t})},startOf:function(e){e=e.replace(/s$/,"");switch(e){case"year":this.month(0);case"month":this.date(1);case"week":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return e==="week"&&this.day(0),this},endOf:function(e){return this.startOf(e).add(e.replace(/s?$/,"s"),1).subtract("ms",1)},isAfter:function(e,n){return n=typeof n!="undefined"?n:"millisecond",+this.clone().startOf(n)>+t(e).startOf(n)},isBefore:function(e,n){return n=typeof n!="undefined"?n:"millisecond",+this.clone().startOf(n)<+t(e).startOf(n)},isSame:function(e,n){return n=typeof n!="undefined"?n:"millisecond",+this.clone().startOf(n)===+t(e).startOf(n)},zone:function(){return this._isUTC?0:this._d.getTimezoneOffset()},daysInMonth:function(){return t.utc([this.year(),this.month()+1,0]).date()},dayOfYear:function(e){var n=r((t(this).startOf("day")-t(this).startOf("year"))/864e5)+1;return e==null?n:this.add("d",e-n)},isoWeek:function(e){var t=tt(this,1,4);return e==null?t:this.add("d",(e-t)*7)},week:function(e){var t=this.lang().week(this);return e==null?t:this.add("d",(e-t)*7)},lang:function(t){return t===e?this._lang:(this._lang=U(t),this)}};for(i=0;i<T.length;i++)rt(T[i].toLowerCase().replace(/s$/,""),T[i]);rt("year","FullYear"),t.fn.days=t.fn.day,t.fn.weeks=t.fn.week,t.fn.isoWeeks=t.fn.isoWeek,t.duration.fn=P.prototype={weeks:function(){return B(this.days()/7)},valueOf:function(){return this._milliseconds+this._days*864e5+this._months*2592e6},humanize:function(e){var t=+this,n=et(t,!e,this.lang());return e&&(n=this.lang().pastFuture(t,n)),this.lang().postformat(n)},lang:t.fn.lang};for(i in N)N.hasOwnProperty(i)&&(st(i,N[i]),it(i.toLowerCase()));st("Weeks",6048e5),t.lang("en",{ordinal:function(e){var t=e%10,n=~~(e%100/10)===1?"th":t===1?"st":t===2?"nd":t===3?"rd":"th";return e+n}}),o&&(module.exports=t),typeof ender=="undefined"&&(this.moment=t),typeof define=="function"&&define.amd&&define("moment",[],function(){return t})}).call(this); \ No newline at end of file
diff --git a/test/unit/extension_module_test.rb b/test/unit/extension_module_test.rb
new file mode 100644
index 0000000..96004e7
--- /dev/null
+++ b/test/unit/extension_module_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class ExtensionModuleTest < ActiveSupport::TestCase
+ def test_should_be_valid
+ assert ExtensionModule.new.valid?
+ end
+end