Revision 53d80f2a
Von Moritz Bunkus vor mehr als 10 Jahren hinzugefügt
SL/CTI.pm | ||
---|---|---|
package SL::CTI;
|
||
|
||
use strict;
|
||
|
||
use String::ShellQuote;
|
||
|
||
use SL::MoreCommon qw(uri_encode);
|
||
|
||
sub call {
|
||
my ($class, %params) = @_;
|
||
|
||
my $config = $::lx_office_conf{cti} || {};
|
||
my $command = $config->{dial_command} || die $::locale->text('Dial command missing in kivitendo configuration\'s [cti] section');
|
||
my $external_prefix = $params{internal} ? '' : ($config->{external_prefix} // '');
|
||
|
||
my %command_args = (
|
||
phone_extension => $::myconfig{phone_extension} || die($::locale->text('Phone extension missing in user configuration')),
|
||
phone_password => $::myconfig{phone_password} || die($::locale->text('Phone password missing in user configuration')),
|
||
number => $external_prefix . $class->sanitize_number(%params),
|
||
);
|
||
|
||
foreach my $key (keys %command_args) {
|
||
my $value = shell_quote($command_args{$key});
|
||
$command =~ s{<\% ${key} \%>}{$value}gx;
|
||
}
|
||
|
||
return `$command`;
|
||
}
|
||
|
||
sub call_link {
|
||
my ($class, %params) = @_;
|
||
|
||
return "controller.pl?action=CTI/call&number=" . uri_encode($class->sanitize_number(number => $params{number})) . ($params{internal} ? '&internal=1' : '');
|
||
}
|
||
|
||
sub sanitize_number {
|
||
my ($class, %params) = @_;
|
||
|
||
my $config = $::lx_office_conf{cti} || {};
|
||
my $idp = $config->{international_dialing_prefix} // '00';
|
||
|
||
my $number = $params{number} // '';
|
||
$number =~ s/[^0-9+\.-]//g; # delete unsupported characters
|
||
my $countrycode = $number =~ s/^(?: $idp | \+ ) ( \d{2} )//x ? $1 : ''; # TODO: countrycodes can have more or less than 2 digits
|
||
$number =~ s/^0//x if $countrycode; # kill non standard optional zero after global identifier
|
||
|
||
return '' unless $number;
|
||
|
||
return ($countrycode ? $idp . $countrycode : '') . $number;
|
||
}
|
||
|
||
1;
|
SL/Controller/CTI.pm | ||
---|---|---|
package SL::Controller::CTI;
|
||
|
||
use strict;
|
||
|
||
use SL::CTI;
|
||
use SL::DB::AuthUserConfig;
|
||
use SL::Helper::Flash;
|
||
use SL::Locale::String;
|
||
|
||
use parent qw(SL::Controller::Base);
|
||
|
||
use Rose::Object::MakeMethods::Generic (
|
||
'scalar --get_set_init' => [ qw(internal_extensions) ],
|
||
);
|
||
|
||
sub action_call {
|
||
my ($self) = @_;
|
||
|
||
eval {
|
||
my $res = SL::CTI->call(number => $::form->{number}, internal => $::form->{internal});
|
||
flash('info', t8('Calling #1 now', $::form->{number}));
|
||
1;
|
||
} or do {
|
||
flash('error', $@);
|
||
};
|
||
|
||
$self->render('cti/calling');
|
||
}
|
||
|
||
sub action_list_internal_extensions {
|
||
my ($self) = @_;
|
||
|
||
$self->render('cti/list_internal_extensions', title => t8('Internal Phone List'));
|
||
}
|
||
|
||
#
|
||
# filters
|
||
#
|
||
|
||
sub init_internal_extensions {
|
||
my ($self) = @_;
|
||
|
||
my $user_configs = SL::DB::Manager::AuthUserConfig->get_all(
|
||
where => [
|
||
cfg_key => 'phone_extension',
|
||
'!cfg_value' => undef,
|
||
'!cfg_value' => '',
|
||
],
|
||
with_objects => [ qw(user) ],
|
||
);
|
||
|
||
my %users;
|
||
foreach my $config (@{ $user_configs }) {
|
||
$users{$config->user_id} ||= {
|
||
name => $config->user->get_config_value('name') || $config->user->login,
|
||
phone_extension => $config->cfg_value,
|
||
call_link => SL::CTI->call_link(number => $config->cfg_value, internal => 1),
|
||
};
|
||
}
|
||
|
||
return [
|
||
sort { lc($a->{name}) cmp lc($b->{name}) } values %users
|
||
];
|
||
}
|
||
|
||
1;
|
bin/mozilla/ct.pl | ||
---|---|---|
use POSIX qw(strftime);
|
||
|
||
use SL::CT;
|
||
use SL::CTI;
|
||
use SL::CVar;
|
||
use SL::Request qw(flatten);
|
||
use SL::DB::Business;
|
||
... | ... | |
my $column = $ref->{formtype} eq 'invoice' ? 'invnumber' : $ref->{formtype} eq 'order' ? 'ordnumber' : 'quonumber';
|
||
$row->{$column}->{data} = $ref->{$column};
|
||
|
||
if (my $number = SL::CTI->sanitize_number(number => $ref->{phone})) {
|
||
$row->{phone}->{link} = SL::CTI->call_link(number => $number);
|
||
$row->{phone}->{link_class} = 'cti_call_action';
|
||
}
|
||
|
||
$report->add_data($row);
|
||
}
|
||
|
||
... | ... | |
$row->{$_}->{link} = 'mailto:' . E($ref->{$_}) if $ref->{$_};
|
||
}
|
||
|
||
for (qw(cp_phone1 cp_phone2 cp_mobile1)) {
|
||
next unless my $number = SL::CTI->sanitize_number(number => $ref->{$_});
|
||
|
||
$row->{$_}->{link} = SL::CTI->call_link(number => $number);
|
||
$row->{$_}->{link_class} = 'cti_call_action';
|
||
}
|
||
|
||
$report->add_data($row);
|
||
}
|
||
|
config/kivitendo.conf.default | ||
---|---|---|
|
||
# If set to 1 each exception will include a full stack backtrace.
|
||
backtrace_on_die = 0
|
||
|
||
[cti]
|
||
# If you want phone numbers to be clickable then this must be set to a
|
||
# command that does the actually dialing. Within this command three
|
||
# variables are replaced before it is executed:
|
||
#
|
||
# 1. <%phone_extension%> and <%phone_password%> are taken from the user
|
||
# configuration (changeable in the admin interface).
|
||
# 2. <%number%> is the number to dial. It has already been sanitized
|
||
# and formatted correctly regarding e.g. the international dialing
|
||
# prefix.
|
||
#
|
||
# The following is an example that works with the OpenUC telephony
|
||
# server:
|
||
# dial_command = curl --insecure -X PUT https://<%phone_extension%>:<%phone_password%>@IP.AD.DR.ESS:8443/sipxconfig/rest/my/call/<%number%>
|
||
dial_command =
|
||
# If you need to dial something before the actual number then set
|
||
# external_prefix to it.
|
||
external_prefix = 0
|
||
# The prefix for international calls (numbers starting with +).
|
||
international_dialing_prefix = 00
|
css/kivitendo/main.css | ||
---|---|---|
span.toggle_selected {
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* CTI */
|
||
a.cti_call_action {
|
||
display: inline-block;
|
||
padding-left: 18px;
|
||
height: 16px;
|
||
position: relative;
|
||
top: 2px;
|
||
vertical-align: center;
|
||
background-image: url(../../image/icons/16x16/phone.png);
|
||
background-repeat: no-repeat;
|
||
}
|
css/lx-office-erp/main.css | ||
---|---|---|
span.toggle_selected {
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* CTI */
|
||
a.cti_call_action {
|
||
display: inline-block;
|
||
padding-left: 18px;
|
||
height: 16px;
|
||
position: relative;
|
||
top: 2px;
|
||
vertical-align: center;
|
||
background-image: url(../../image/icons/16x16/phone.png);
|
||
background-repeat: no-repeat;
|
||
}
|
image/icons/16x16/Program--Internal Phone List.png | ||
---|---|---|
phone.png
|
js/kivi.CustomerVendor.js | ||
---|---|---|
var url = "common.pl?INPUT_ENCODING=UTF-8&action=show_history&longdescription=&input_name="+ encodeURIComponent(id);
|
||
window.open(url, "_new_generic", parm);
|
||
};
|
||
|
||
this.update_dial_action = function($input) {
|
||
var $action = $('#' + $input.prop('id') + '-dial-action');
|
||
|
||
if (!$action)
|
||
return true;
|
||
|
||
var number = $input.val().replace(/\s+/g, '');
|
||
if (number == '')
|
||
$action.hide();
|
||
else
|
||
$action.prop('href', 'controller.pl?action=CTI/call&number=' + encodeURIComponent(number)).show();
|
||
|
||
return true;
|
||
};
|
||
|
||
this.init_dial_action = function(input) {
|
||
if ($('#_cti_enabled').val() != 1)
|
||
return false;
|
||
|
||
var $input = $(input);
|
||
var action_id = $input.prop('id') + '-dial-action';
|
||
|
||
if (!$('#' + action_id).size()) {
|
||
var $action = $('<a href="" id="' + action_id + '" class="cti_call_action" target="_blank" tabindex="-1"></a>');
|
||
$input.wrap('<span nobr></span>').after($action);
|
||
|
||
$input.change(function() { kivi.CustomerVendor.update_dial_action($input); });
|
||
}
|
||
|
||
kivi.CustomerVendor.update_dial_action($input);
|
||
|
||
return true;
|
||
};
|
||
});
|
||
|
||
function local_reinit_widgets() {
|
||
$('#cv_phone,#shipto_shiptophone,#contact_cp_phone1,#contact_cp_phone2,#contact_cp_mobile1,#contact_cp_mobile2').each(function(idx, elt) {
|
||
kivi.CustomerVendor.init_dial_action($(elt));
|
||
});
|
||
}
|
locale/de/all | ||
---|---|---|
'CSV import: parts and services' => 'CSV-Import: Waren und Dienstleistungen',
|
||
'CSV import: projects' => 'CSV-Import: Projekte',
|
||
'CSV import: shipping addresses' => 'CSV-Import: Lieferadressen',
|
||
'CTI settings' => 'CTI-Einstellungen',
|
||
'Calculate' => 'Berechnen',
|
||
'Calling #1 now' => 'Wähle jetzt #1',
|
||
'Can not create that quantity with current stock' => 'Diese Anzahl kann mit dem gegenwärtigen Lagerbestand nicht hergestellt werden.',
|
||
'Cancel' => 'Abbrechen',
|
||
'Cancel Accounts Payables Transaction' => 'Kreditorenbuchung stornieren',
|
||
... | ... | |
'Destination warehouse and bin' => 'Ziellager und -lagerplatz',
|
||
'Detail view' => 'Detailanzeige',
|
||
'Details (one letter abbreviation)' => 'D',
|
||
'Dial command missing in kivitendo configuration\'s [cti] section' => 'Wählbefehl fehlt im Abschnitt [cti] der kivitendo-Konfiguration',
|
||
'Difference' => 'Differenz',
|
||
'Dimensions' => 'Abmessungen',
|
||
'Directory' => 'Verzeichnis',
|
||
... | ... | |
'Interest' => 'Zinsen',
|
||
'Interest Rate' => 'Zinssatz',
|
||
'Internal Notes' => 'Interne Bemerkungen',
|
||
'Internal Phone List' => 'Interne Telefonliste',
|
||
'Internal comment' => 'Interne Bemerkungen',
|
||
'Internet' => 'Internet',
|
||
'Introduction of clients' => 'Einführung von Mandanten',
|
||
... | ... | |
'No file has been uploaded yet.' => 'Es wurde noch keine Datei hochgeladen.',
|
||
'No function blocks have been created yet.' => 'Es wurden noch keine Funktionsblöcke angelegt.',
|
||
'No groups have been created yet.' => 'Es wurden noch keine Gruppen angelegt.',
|
||
'No internal phone extensions have been configured yet.' => 'Es wurden noch keine internen Durchwahlen konfiguriert.',
|
||
'No or an unknown authenticantion module specified in "config/kivitendo.conf".' => 'Es wurde kein oder ein unbekanntes Authentifizierungsmodul in "config/kivitendo.conf" angegeben.',
|
||
'No part was found matching the search parameters.' => 'Es wurde kein Artikel gefunden, auf den die Suchparameter zutreffen.',
|
||
'No payment term has been created yet.' => 'Es wurden noch keine Zahlungsbedingungen angelegt.',
|
||
... | ... | |
'Person' => 'Person',
|
||
'Personal settings' => 'Persönliche Einstellungen',
|
||
'Phone' => 'Telefon',
|
||
'Phone extension' => 'Durchwahl',
|
||
'Phone extension missing in user configuration' => 'Durchwahl fehlt in der Benutzerkonfiguration',
|
||
'Phone password' => 'Telefonpasswort',
|
||
'Phone password missing in user configuration' => 'Telefonpasswort fehlt in der Benutzerkonfiguration',
|
||
'Phone1' => 'Telefon 1 ',
|
||
'Phone2' => 'Telefon 2',
|
||
'Pick List' => 'Sammelliste',
|
menus/erp.ini | ||
---|---|---|
module=am.pl
|
||
action=config
|
||
|
||
[Program--Internal Phone List]
|
||
module=controller.pl
|
||
action=CTI/list_internal_extensions
|
||
|
||
[Program--Version]
|
||
module=login.pl
|
||
action=company_logo
|
t/cti/call_link.t | ||
---|---|---|
use Test::More tests => 9;
|
||
|
||
use strict;
|
||
use lib 't';
|
||
use utf8;
|
||
|
||
use_ok 'SL::CTI';
|
||
|
||
{
|
||
no warnings 'once';
|
||
$::lx_office_conf{cti}->{international_dialing_prefix} = '00';
|
||
}
|
||
|
||
is SL::CTI->call_link(number => '0371 5347 620'), 'controller.pl?action=CTI/call&number=03715347620';
|
||
is SL::CTI->call_link(number => '0049(0)421-22232 22'), 'controller.pl?action=CTI/call&number=0049421-2223222';
|
||
is SL::CTI->call_link(number => '+49(0)421-22232 22'), 'controller.pl?action=CTI/call&number=0049421-2223222';
|
||
is SL::CTI->call_link(number => 'Tel: +49 40 809064 0'), 'controller.pl?action=CTI/call&number=0049408090640';
|
||
|
||
is SL::CTI->call_link(number => '0371 5347 620', internal => 1), 'controller.pl?action=CTI/call&number=03715347620&internal=1';
|
||
is SL::CTI->call_link(number => '0049(0)421-22232 22', internal => 1), 'controller.pl?action=CTI/call&number=0049421-2223222&internal=1';
|
||
is SL::CTI->call_link(number => '+49(0)421-22232 22', internal => 1), 'controller.pl?action=CTI/call&number=0049421-2223222&internal=1';
|
||
is SL::CTI->call_link(number => 'Tel: +49 40 809064 0', internal => 1), 'controller.pl?action=CTI/call&number=0049408090640&internal=1';
|
t/cti/sanitize_number.t | ||
---|---|---|
use Test::More tests => 5;
|
||
|
||
use strict;
|
||
use lib 't';
|
||
use utf8;
|
||
|
||
use_ok 'SL::CTI';
|
||
|
||
{
|
||
no warnings 'once';
|
||
$::lx_office_conf{cti}->{international_dialing_prefix} = '00';
|
||
}
|
||
|
||
is SL::CTI->sanitize_number(number => '0371 5347 620'), '03715347620';
|
||
is SL::CTI->sanitize_number(number => '0049(0)421-22232 22'), '0049421-2223222';
|
||
is SL::CTI->sanitize_number(number => '+49(0)421-22232 22'), '0049421-2223222';
|
||
is SL::CTI->sanitize_number(number => 'Tel: +49 40 809064 0'), '0049408090640';
|
templates/webpages/admin/edit_user.html | ||
---|---|---|
</tr>
|
||
</table>
|
||
|
||
<h2>[%- LxERP.t8("CTI settings") %]</h2>
|
||
|
||
<table>
|
||
<tr>
|
||
<th align="right">[% LxERP.t8("Phone extension") %]</th>
|
||
<td>[% L.input_tag("user.config_values.phone_extension", props.phone_extension) %]</td>
|
||
</tr>
|
||
|
||
<tr>
|
||
<th align="right">[% LxERP.t8("Phone password") %]</th>
|
||
<td>[% L.input_tag("user.config_values.phone_password", props.phone_password) %]</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<h2>[%- LxERP.t8("Access to clients") %]</h2>
|
||
|
||
[% IF SELF.all_clients.size %]
|
templates/webpages/cti/calling.html | ||
---|---|---|
<body>
|
||
[% PROCESS 'common/flash.html' %]
|
||
</body>
|
||
</html>
|
templates/webpages/cti/list_internal_extensions.html | ||
---|---|---|
[%- USE HTML %][%- USE LxERP -%]
|
||
<body>
|
||
|
||
<h1>[% HTML.escape(title) %]</h1>
|
||
|
||
[% IF !SELF.internal_extensions.size %]
|
||
<p>[% LxERP.t8("No internal phone extensions have been configured yet.") %]</p>
|
||
|
||
[% ELSE %]
|
||
<table>
|
||
<tr class="listheading">
|
||
<th>[% LxERP.t8("Name") %]</th>
|
||
<th>[% LxERP.t8("Phone extension") %]</th>
|
||
</tr>
|
||
|
||
[%- FOREACH extension = SELF.internal_extensions %]
|
||
<tr class="listrow">
|
||
<td>[% HTML.escape(extension.name) %]</td>
|
||
<td><a href="[% HTML.escape(extension.call_link) %]" class="cti_call_action">[% HTML.escape(extension.phone_extension) %]</a></td>
|
||
</tr>
|
||
[%- END %]
|
||
</table>
|
||
[% END %]
|
||
</body>
|
||
</html>
|
templates/webpages/customer_vendor/form.html | ||
---|---|---|
[%- USE LxERP %]
|
||
[%- USE L %]
|
||
|
||
[% L.hidden_tag('_cti_enabled', !!LXCONFIG.cti.dial_command) %]
|
||
|
||
[% cv_cvars = SELF.cv.cvars_by_config %]
|
||
|
||
<form method="post" action="controller.pl">
|
templates/webpages/customer_vendor/tabs/contacts.html | ||
---|---|---|
empty_title = LxERP.t8('New contact'),
|
||
value_key = 'cp_id',
|
||
title_key = 'full_name',
|
||
onchange = "kivi.CustomerVendor.selectContact({onFormSet: function(){contactsMapWidget.testInputs();}});",
|
||
onchange = "kivi.CustomerVendor.selectContact({onFormSet: function(){ contactsMapWidget.testInputs(); local_reinit_widgets(); }});",
|
||
)
|
||
%]
|
||
</td>
|
templates/webpages/customer_vendor/tabs/shipto.html | ||
---|---|---|
title_key = 'displayable_id',
|
||
with_empty = 1,
|
||
empty_title = LxERP.t8('New shipto'),
|
||
onchange = "kivi.CustomerVendor.selectShipto({onFormSet: function(){shiptoMapWidget.testInputs();}});",
|
||
onchange = "kivi.CustomerVendor.selectShipto({onFormSet: function(){ shiptoMapWidget.testInputs(); local_reinit_widgets(); }});",
|
||
)
|
||
%]
|
||
</td>
|
Auch abrufbar als: Unified diff
Generische Unterstützung für CTI: Click-to-dial