Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 53d80f2a

Von Moritz Bunkus vor mehr als 10 Jahren hinzugefügt

  • ID 53d80f2a01439ee73cf679c53326ec8fb32ab1ed
  • Vorgänger 1e987ead
  • Nachfolger 754482db

Generische Unterstützung für CTI: Click-to-dial

Unterschiede anzeigen:

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&ouml;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