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
1
package SL::CTI;
2

  
3
use strict;
4

  
5
use String::ShellQuote;
6

  
7
use SL::MoreCommon qw(uri_encode);
8

  
9
sub call {
10
  my ($class, %params) = @_;
11

  
12
  my $config           = $::lx_office_conf{cti}  || {};
13
  my $command          = $config->{dial_command} || die $::locale->text('Dial command missing in kivitendo configuration\'s [cti] section');
14
  my $external_prefix  = $params{internal} ? '' : ($config->{external_prefix} // '');
15

  
16
  my %command_args     = (
17
    phone_extension    => $::myconfig{phone_extension} || die($::locale->text('Phone extension missing in user configuration')),
18
    phone_password     => $::myconfig{phone_password}  || die($::locale->text('Phone password missing in user configuration')),
19
    number             => $external_prefix . $class->sanitize_number(%params),
20
  );
21

  
22
  foreach my $key (keys %command_args) {
23
    my $value = shell_quote($command_args{$key});
24
    $command  =~ s{<\% ${key} \%>}{$value}gx;
25
  }
26

  
27
  return `$command`;
28
}
29

  
30
sub call_link {
31
  my ($class, %params) = @_;
32

  
33
  return "controller.pl?action=CTI/call&number=" . uri_encode($class->sanitize_number(number => $params{number})) . ($params{internal} ? '&internal=1' : '');
34
}
35

  
36
sub sanitize_number {
37
  my ($class, %params) = @_;
38

  
39
  my $config           = $::lx_office_conf{cti} || {};
40
  my $idp              = $config->{international_dialing_prefix} // '00';
41

  
42
  my $number           = $params{number} // '';
43
  $number              =~ s/[^0-9+\.-]//g;                                     # delete unsupported characters
44
  my $countrycode      = $number =~ s/^(?: $idp | \+ ) ( \d{2} )//x ? $1 : ''; # TODO: countrycodes can have more or less than 2 digits
45
  $number              =~ s/^0//x if $countrycode;                             # kill non standard optional zero after global identifier
46

  
47
  return '' unless $number;
48

  
49
  return ($countrycode ? $idp . $countrycode : '') . $number;
50
}
51

  
52
1;
SL/Controller/CTI.pm
1
package SL::Controller::CTI;
2

  
3
use strict;
4

  
5
use SL::CTI;
6
use SL::DB::AuthUserConfig;
7
use SL::Helper::Flash;
8
use SL::Locale::String;
9

  
10
use parent qw(SL::Controller::Base);
11

  
12
use Rose::Object::MakeMethods::Generic (
13
  'scalar --get_set_init' => [ qw(internal_extensions) ],
14
);
15

  
16
sub action_call {
17
  my ($self) = @_;
18

  
19
  eval {
20
    my $res = SL::CTI->call(number => $::form->{number}, internal => $::form->{internal});
21
    flash('info', t8('Calling #1 now', $::form->{number}));
22
    1;
23
  } or do {
24
    flash('error', $@);
25
  };
26

  
27
  $self->render('cti/calling');
28
}
29

  
30
sub action_list_internal_extensions {
31
  my ($self) = @_;
32

  
33
  $self->render('cti/list_internal_extensions', title => t8('Internal Phone List'));
34
}
35

  
36
#
37
# filters
38
#
39

  
40
sub init_internal_extensions {
41
  my ($self) = @_;
42

  
43
  my $user_configs = SL::DB::Manager::AuthUserConfig->get_all(
44
    where => [
45
      cfg_key      => 'phone_extension',
46
      '!cfg_value' => undef,
47
      '!cfg_value' => '',
48
    ],
49
    with_objects => [ qw(user) ],
50
  );
51

  
52
  my %users;
53
  foreach my $config (@{ $user_configs }) {
54
    $users{$config->user_id} ||= {
55
      name            => $config->user->get_config_value('name') || $config->user->login,
56
      phone_extension => $config->cfg_value,
57
      call_link       => SL::CTI->call_link(number => $config->cfg_value, internal => 1),
58
    };
59
  }
60

  
61
  return [
62
    sort { lc($a->{name}) cmp lc($b->{name}) } values %users
63
  ];
64
}
65

  
66
1;
bin/mozilla/ct.pl
48 48
use POSIX qw(strftime);
49 49

  
50 50
use SL::CT;
51
use SL::CTI;
51 52
use SL::CVar;
52 53
use SL::Request qw(flatten);
53 54
use SL::DB::Business;
......
268 269
    my $column                = $ref->{formtype} eq 'invoice' ? 'invnumber' : $ref->{formtype} eq 'order' ? 'ordnumber' : 'quonumber';
269 270
    $row->{$column}->{data}   = $ref->{$column};
270 271

  
272
    if (my $number = SL::CTI->sanitize_number(number => $ref->{phone})) {
273
      $row->{phone}->{link}       = SL::CTI->call_link(number => $number);
274
      $row->{phone}->{link_class} = 'cti_call_action';
275
    }
276

  
271 277
    $report->add_data($row);
272 278
  }
273 279

  
......
392 398
      $row->{$_}->{link} = 'mailto:' . E($ref->{$_}) if $ref->{$_};
393 399
    }
394 400

  
401
    for (qw(cp_phone1 cp_phone2 cp_mobile1)) {
402
      next unless my $number = SL::CTI->sanitize_number(number => $ref->{$_});
403

  
404
      $row->{$_}->{link}       = SL::CTI->call_link(number => $number);
405
      $row->{$_}->{link_class} = 'cti_call_action';
406
    }
407

  
395 408
    $report->add_data($row);
396 409
  }
397 410

  
config/kivitendo.conf.default
314 314

  
315 315
# If set to 1 each exception will include a full stack backtrace.
316 316
backtrace_on_die = 0
317

  
318
[cti]
319
# If you want phone numbers to be clickable then this must be set to a
320
# command that does the actually dialing. Within this command three
321
# variables are replaced before it is executed:
322
#
323
# 1. <%phone_extension%> and <%phone_password%> are taken from the user
324
#    configuration (changeable in the admin interface).
325
# 2. <%number%> is the number to dial. It has already been sanitized
326
#    and formatted correctly regarding e.g. the international dialing
327
#    prefix.
328
#
329
# The following is an example that works with the OpenUC telephony
330
# server:
331
# dial_command = curl --insecure -X PUT https://<%phone_extension%>:<%phone_password%>@IP.AD.DR.ESS:8443/sipxconfig/rest/my/call/<%number%>
332
dial_command =
333
# If you need to dial something before the actual number then set
334
# external_prefix to it.
335
external_prefix = 0
336
# The prefix for international calls (numbers starting with +).
337
international_dialing_prefix = 00
css/kivitendo/main.css
443 443
span.toggle_selected {
444 444
  font-weight: bold;
445 445
}
446

  
447
/* CTI */
448
a.cti_call_action {
449
  display: inline-block;
450
  padding-left: 18px;
451
  height: 16px;
452
  position: relative;
453
  top: 2px;
454
  vertical-align: center;
455
  background-image: url(../../image/icons/16x16/phone.png);
456
  background-repeat: no-repeat;
457
}
css/lx-office-erp/main.css
494 494
span.toggle_selected {
495 495
  font-weight: bold;
496 496
}
497

  
498
/* CTI */
499
a.cti_call_action {
500
  display: inline-block;
501
  padding-left: 18px;
502
  height: 16px;
503
  position: relative;
504
  top: 2px;
505
  vertical-align: center;
506
  background-image: url(../../image/icons/16x16/phone.png);
507
  background-repeat: no-repeat;
508
}
image/icons/16x16/Program--Internal Phone List.png
1
phone.png
js/kivi.CustomerVendor.js
160 160
    var url = "common.pl?INPUT_ENCODING=UTF-8&action=show_history&longdescription=&input_name="+ encodeURIComponent(id);
161 161
    window.open(url, "_new_generic", parm);
162 162
  };
163

  
164
  this.update_dial_action = function($input) {
165
    var $action = $('#' + $input.prop('id') + '-dial-action');
166

  
167
    if (!$action)
168
      return true;
169

  
170
    var number = $input.val().replace(/\s+/g, '');
171
    if (number == '')
172
      $action.hide();
173
    else
174
      $action.prop('href', 'controller.pl?action=CTI/call&number=' + encodeURIComponent(number)).show();
175

  
176
    return true;
177
  };
178

  
179
  this.init_dial_action = function(input) {
180
    if ($('#_cti_enabled').val() != 1)
181
      return false;
182

  
183
    var $input    = $(input);
184
    var action_id = $input.prop('id') + '-dial-action';
185

  
186
    if (!$('#' + action_id).size()) {
187
      var $action = $('<a href="" id="' + action_id + '" class="cti_call_action" target="_blank" tabindex="-1"></a>');
188
      $input.wrap('<span nobr></span>').after($action);
189

  
190
      $input.change(function() { kivi.CustomerVendor.update_dial_action($input); });
191
    }
192

  
193
    kivi.CustomerVendor.update_dial_action($input);
194

  
195
    return true;
196
  };
163 197
});
198

  
199
function local_reinit_widgets() {
200
  $('#cv_phone,#shipto_shiptophone,#contact_cp_phone1,#contact_cp_phone2,#contact_cp_mobile1,#contact_cp_mobile2').each(function(idx, elt) {
201
    kivi.CustomerVendor.init_dial_action($(elt));
202
  });
203
}
locale/de/all
408 408
  'CSV import: parts and services' => 'CSV-Import: Waren und Dienstleistungen',
409 409
  'CSV import: projects'        => 'CSV-Import: Projekte',
410 410
  'CSV import: shipping addresses' => 'CSV-Import: Lieferadressen',
411
  'CTI settings'                => 'CTI-Einstellungen',
411 412
  'Calculate'                   => 'Berechnen',
413
  'Calling #1 now'              => 'Wähle jetzt #1',
412 414
  'Can not create that quantity with current stock' => 'Diese Anzahl kann mit dem gegenwärtigen Lagerbestand nicht hergestellt werden.',
413 415
  'Cancel'                      => 'Abbrechen',
414 416
  'Cancel Accounts Payables Transaction' => 'Kreditorenbuchung stornieren',
......
803 805
  'Destination warehouse and bin' => 'Ziellager und -lagerplatz',
804 806
  'Detail view'                 => 'Detailanzeige',
805 807
  'Details (one letter abbreviation)' => 'D',
808
  'Dial command missing in kivitendo configuration\'s [cti] section' => 'Wählbefehl fehlt im Abschnitt [cti] der kivitendo-Konfiguration',
806 809
  'Difference'                  => 'Differenz',
807 810
  'Dimensions'                  => 'Abmessungen',
808 811
  'Directory'                   => 'Verzeichnis',
......
1265 1268
  'Interest'                    => 'Zinsen',
1266 1269
  'Interest Rate'               => 'Zinssatz',
1267 1270
  'Internal Notes'              => 'Interne Bemerkungen',
1271
  'Internal Phone List'         => 'Interne Telefonliste',
1268 1272
  'Internal comment'            => 'Interne Bemerkungen',
1269 1273
  'Internet'                    => 'Internet',
1270 1274
  'Introduction of clients'     => 'Einführung von Mandanten',
......
1535 1539
  'No file has been uploaded yet.' => 'Es wurde noch keine Datei hochgeladen.',
1536 1540
  'No function blocks have been created yet.' => 'Es wurden noch keine Funktionsblöcke angelegt.',
1537 1541
  'No groups have been created yet.' => 'Es wurden noch keine Gruppen angelegt.',
1542
  'No internal phone extensions have been configured yet.' => 'Es wurden noch keine internen Durchwahlen konfiguriert.',
1538 1543
  'No or an unknown authenticantion module specified in "config/kivitendo.conf".' => 'Es wurde kein oder ein unbekanntes Authentifizierungsmodul in "config/kivitendo.conf" angegeben.',
1539 1544
  'No part was found matching the search parameters.' => 'Es wurde kein Artikel gefunden, auf den die Suchparameter zutreffen.',
1540 1545
  'No payment term has been created yet.' => 'Es wurden noch keine Zahlungsbedingungen angelegt.',
......
1733 1738
  'Person'                      => 'Person',
1734 1739
  'Personal settings'           => 'Pers&ouml;nliche Einstellungen',
1735 1740
  'Phone'                       => 'Telefon',
1741
  'Phone extension'             => 'Durchwahl',
1742
  'Phone extension missing in user configuration' => 'Durchwahl fehlt in der Benutzerkonfiguration',
1743
  'Phone password'              => 'Telefonpasswort',
1744
  'Phone password missing in user configuration' => 'Telefonpasswort fehlt in der Benutzerkonfiguration',
1736 1745
  'Phone1'                      => 'Telefon 1 ',
1737 1746
  'Phone2'                      => 'Telefon 2',
1738 1747
  'Pick List'                   => 'Sammelliste',
menus/erp.ini
787 787
module=am.pl
788 788
action=config
789 789

  
790
[Program--Internal Phone List]
791
module=controller.pl
792
action=CTI/list_internal_extensions
793

  
790 794
[Program--Version]
791 795
module=login.pl
792 796
action=company_logo
t/cti/call_link.t
1
use Test::More tests => 9;
2

  
3
use strict;
4
use lib 't';
5
use utf8;
6

  
7
use_ok 'SL::CTI';
8

  
9
{
10
  no warnings 'once';
11
  $::lx_office_conf{cti}->{international_dialing_prefix} = '00';
12
}
13

  
14
is SL::CTI->call_link(number => '0371 5347 620'),        'controller.pl?action=CTI/call&number=03715347620';
15
is SL::CTI->call_link(number => '0049(0)421-22232 22'),  'controller.pl?action=CTI/call&number=0049421-2223222';
16
is SL::CTI->call_link(number => '+49(0)421-22232 22'),   'controller.pl?action=CTI/call&number=0049421-2223222';
17
is SL::CTI->call_link(number => 'Tel: +49 40 809064 0'), 'controller.pl?action=CTI/call&number=0049408090640';
18

  
19
is SL::CTI->call_link(number => '0371 5347 620',        internal => 1), 'controller.pl?action=CTI/call&number=03715347620&internal=1';
20
is SL::CTI->call_link(number => '0049(0)421-22232 22',  internal => 1), 'controller.pl?action=CTI/call&number=0049421-2223222&internal=1';
21
is SL::CTI->call_link(number => '+49(0)421-22232 22',   internal => 1), 'controller.pl?action=CTI/call&number=0049421-2223222&internal=1';
22
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
1
use Test::More tests => 5;
2

  
3
use strict;
4
use lib 't';
5
use utf8;
6

  
7
use_ok 'SL::CTI';
8

  
9
{
10
  no warnings 'once';
11
  $::lx_office_conf{cti}->{international_dialing_prefix} = '00';
12
}
13

  
14
is SL::CTI->sanitize_number(number => '0371 5347 620'),        '03715347620';
15
is SL::CTI->sanitize_number(number => '0049(0)421-22232 22'),  '0049421-2223222';
16
is SL::CTI->sanitize_number(number => '+49(0)421-22232 22'),   '0049421-2223222';
17
is SL::CTI->sanitize_number(number => 'Tel: +49 40 809064 0'), '0049408090640';
templates/webpages/admin/edit_user.html
99 99
  </tr>
100 100
 </table>
101 101

  
102
 <h2>[%- LxERP.t8("CTI settings") %]</h2>
103

  
104
 <table>
105
  <tr>
106
   <th align="right">[% LxERP.t8("Phone extension") %]</th>
107
   <td>[% L.input_tag("user.config_values.phone_extension", props.phone_extension) %]</td>
108
  </tr>
109

  
110
  <tr>
111
   <th align="right">[% LxERP.t8("Phone password") %]</th>
112
   <td>[% L.input_tag("user.config_values.phone_password", props.phone_password) %]</td>
113
  </tr>
114
 </table>
115

  
102 116
 <h2>[%- LxERP.t8("Access to clients") %]</h2>
103 117

  
104 118
[% IF SELF.all_clients.size %]
templates/webpages/cti/calling.html
1
<body>
2
[% PROCESS 'common/flash.html' %]
3
</body>
4
</html>
templates/webpages/cti/list_internal_extensions.html
1
[%- USE HTML %][%- USE LxERP -%]
2
<body>
3

  
4
<h1>[% HTML.escape(title) %]</h1>
5

  
6
[% IF !SELF.internal_extensions.size %]
7
 <p>[% LxERP.t8("No internal phone extensions have been configured yet.") %]</p>
8

  
9
[% ELSE %]
10
 <table>
11
  <tr class="listheading">
12
   <th>[% LxERP.t8("Name") %]</th>
13
   <th>[% LxERP.t8("Phone extension") %]</th>
14
  </tr>
15

  
16
  [%- FOREACH extension = SELF.internal_extensions %]
17
   <tr class="listrow">
18
    <td>[% HTML.escape(extension.name) %]</td>
19
    <td><a href="[% HTML.escape(extension.call_link) %]" class="cti_call_action">[% HTML.escape(extension.phone_extension) %]</a></td>
20
   </tr>
21
  [%- END %]
22
 </table>
23
[% END %]
24
</body>
25
</html>
templates/webpages/customer_vendor/form.html
2 2
[%- USE LxERP %]
3 3
[%- USE L %]
4 4

  
5
[% L.hidden_tag('_cti_enabled', !!LXCONFIG.cti.dial_command) %]
6

  
5 7
[% cv_cvars = SELF.cv.cvars_by_config %]
6 8

  
7 9
<form method="post" action="controller.pl">
templates/webpages/customer_vendor/tabs/contacts.html
18 18
            empty_title = LxERP.t8('New contact'),
19 19
            value_key = 'cp_id',
20 20
            title_key = 'full_name',
21
            onchange = "kivi.CustomerVendor.selectContact({onFormSet: function(){contactsMapWidget.testInputs();}});",
21
            onchange = "kivi.CustomerVendor.selectContact({onFormSet: function(){ contactsMapWidget.testInputs(); local_reinit_widgets(); }});",
22 22
          )
23 23
        %]
24 24
      </td>
templates/webpages/customer_vendor/tabs/shipto.html
16 16
             title_key = 'displayable_id',
17 17
             with_empty = 1,
18 18
             empty_title = LxERP.t8('New shipto'),
19
             onchange = "kivi.CustomerVendor.selectShipto({onFormSet: function(){shiptoMapWidget.testInputs();}});",
19
             onchange = "kivi.CustomerVendor.selectShipto({onFormSet: function(){ shiptoMapWidget.testInputs(); local_reinit_widgets(); }});",
20 20
           )
21 21
        %]
22 22
      </td>

Auch abrufbar als: Unified diff