/[drupal]/contributions/modules/asterisk/asterisk.module
ViewVC logotype

Contents of /contributions/modules/asterisk/asterisk.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.108 - (show annotations) (download) (as text)
Mon Jun 2 08:36:32 2008 UTC (17 months, 3 weeks ago) by thehunmonkgroup
Branch: MAIN
CVS Tags: HEAD
Changes since 1.107: +4 -3 lines
File MIME type: text/x-php
more lax file perms. fixing playback file debugging code.
1 <?php
2 // $Id: asterisk.module,v 1.107 2008/06/02 03:01:33 thehunmonkgroup Exp $
3
4 /**
5 * @file
6 * Enables integratation of Drupal and asterisk
7 */
8
9 define('DEBUG', FALSE);
10
11 /*
12 * Core drupal hooks.
13 */
14
15 /**
16 * Implementation of hook_block
17 */
18 function asterisk_block($op = 'list', $delta = 0, $edit = array()) {
19 if ($op == 'list') {
20 $blocks[0]['info'] = t('Displays a listing of recent audio files. Users can click to hear the file via phone');
21 $blocks[0]['cache'] = BLOCK_CACHE_PER_USER;
22 $blocks[1]['info'] = t('Display a simple text entry to originate calls');
23 $blocks[1]['cache'] = BLOCK_CACHE_PER_USER;
24 return $blocks;
25 }
26 else if ($op == 'configure') {
27 switch ($delta) {
28 case 0:
29 $form['asterisk_audio_block_items'] = array('#type' => 'select',
30 '#title' => t('Number of audio files to show'),
31 '#options' => array('3', '4', '5', '6', '7', '8'),
32 '#default_value' => variable_get('asterisk_audio_block_items', 5),
33 );
34 break;
35 case 1:
36 $form['asterisk_call_number_help'] = array('#type' => 'textarea',
37 '#title' => t('Call number instructions'),
38 '#default_value' => variable_get('asterisk_call_number_help', t('You can dial Free World Dialup numbers as fwd://')),
39 );
40 break;
41 }
42 return $form;
43 }
44 else if ($op == 'save') {
45 switch($delta) {
46 case 0:
47 variable_set('asterisk_audio_block_items', $edit['asterisk_audio_block_items']);
48 break;
49 case 1:
50 variable_set('asterisk_call_number_help', $edit['asterisk_call_number_help']);
51 break;
52 }
53 }
54 else if ($op == 'view') {
55 switch($delta) {
56 case 0:
57 $block['subject'] = t('Recent audio files');
58 $block['content'] = asterisk_audio_file_block();
59 break;
60 case 1:
61 $block['subject'] = t('Make call');
62 $block['content'] = drupal_get_form('asterisk_make_call_block_form');
63 break;
64 }
65 return $block;
66 }
67 }
68
69 /**
70 * Implementation of hook_cron()
71 */
72 function asterisk_cron() {
73 // check for queued messages needs to go here.
74 }
75
76 function asterisk_form_alter(&$form, $form_state, $form_id) {
77
78 switch ($form_id) {
79 // node settings form
80 case 'node_type_form':
81 $type = $form['old_type']['#value'];
82 $form['workflow']['asterisk_node'] = array(
83 '#type' => 'checkbox',
84 '#title' => t('Allow telephone recordings'),
85 '#default_value' => variable_get('asterisk_node_'. $type, 0),
86 '#description' => t('Enabling will allow users with the appropriate permissions to record attachments by telephone.'),
87 );
88 break;
89 }
90 }
91
92 /**
93 * Implementation of hook_help().
94 */
95 function asterisk_help($path, $arg) {
96 switch ($path) {
97 case 'admin/help#asterisk':
98 global $user;
99
100 return t("<p>The Asterisk integration project seeks to add a rich telephony feature set to Drupal. This module maintains a call queue in your local drupal database.</p>
101
102 <p>In order for these calls to be actually made, you must either configure your own asterisk or use an asterisk server that has already been set up to provide drupal services. </p>
103
104 <p>You can view the !site-logs. Each user will have access to their own call logs. This is accessed by clicking the 'My calls' tab while under 'My account'. For example, here are your !user-logs</p>", array('!site-logs' => l(t('site wide call logs'), 'admin/asterisk/calls'), '!user-logs' => l(t('personal call logs'), 'user/'.$user->uid.'/calls')));
105 }
106 }
107
108 /**
109 * Implementation of hook_menu()
110 */
111 function asterisk_menu() {
112
113 global $user;
114
115 $make_calls = array('make calls');
116 $admin_calls = array('administer calls');
117 $admin_asterisk = array('administer asterisk configuration');
118
119 $items = array();
120
121 $items['asterisk/callnumber'] = array(
122 'title' => 'Call number',
123 'page callback' => 'asterisk_page',
124 'access callback' => 'user_access',
125 'access arguments' => $make_calls,
126 'type' => MENU_CALLBACK,
127 );
128 $items['asterisk/calluser'] = array(
129 'title' => 'Call user',
130 'page callback' => 'asterisk_page',
131 'access callback' => 'asterisk_calluser_access',
132 'type' => MENU_CALLBACK,
133 );
134 $items['asterisk/callme'] = array(
135 'title' => 'Call me',
136 'page callback' => 'asterisk_page',
137 'access callback' => 'user_access',
138 'access arguments' => $make_calls,
139 'type' => MENU_CALLBACK,
140 );
141 $items['asterisk/play'] = array(
142 'title' => 'Play me',
143 'page callback' => 'asterisk_page',
144 'access callback' => 'user_access',
145 'access arguments' => $make_calls,
146 'type' => MENU_CALLBACK,
147 );
148 // Admin menu items.
149 $items['admin/asterisk'] = array(
150 'title' => 'Asterisk integration',
151 'description' => 'Manage integration with an Asterisk server.',
152 'page callback' => 'system_admin_menu_block_page',
153 'access callback' => 'user_access',
154 'access arguments' => $admin_asterisk,
155 );
156 $items['admin/asterisk/settings'] = array(
157 'title' => 'Settings',
158 'description' => 'Set up a connection with an Asterisk server. ',
159 'page callback' => 'drupal_get_form',
160 'page arguments' => array('asterisk_settings_form'),
161 'access callback' => 'user_access',
162 'access arguments' => $admin_asterisk,
163 );
164 $items['admin/asterisk/server_test'] = array(
165 'title' => 'Test XML-RPC connection',
166 'page callback' => 'asterisk_server_test',
167 'access callback' => 'user_access',
168 'access arguments' => $admin_asterisk,
169 'type' => MENU_CALLBACK,
170 );
171 $items['admin/asterisk/calls'] = array(
172 'title' => 'Call logs',
173 'description' => 'View logs of all placed calls.',
174 'page callback' => 'asterisk_call_log',
175 'access callback' => 'user_access',
176 'access arguments' => $admin_calls,
177 );
178
179 $items['user/%user/calls'] = array(
180 'title' => 'My calls',
181 'page callback' => 'asterisk_call_log',
182 'page arguments' => array(1),
183 'access callback' => 'asterisk_call_log_access',
184 'access arguments' => array(1),
185 'type' => MENU_LOCAL_TASK,
186 'weight' => 4,
187 );
188
189 return $items;
190 }
191
192 /**
193 * Access callback for asterisk/calluser.
194 */
195 function asterisk_calluser_access() {
196 return user_access('make calls') && user_access('access user profiles');
197 }
198
199 /**
200 * Access callback for user/%user/calls.
201 */
202 function asterisk_call_log_access($account) {
203 global $user;
204 return ($account->uid == $user->uid && user_access('make calls')) || user_access('administer calls');
205 }
206
207 /*
208 * Implementation of hook_nodeapi
209 */
210 function asterisk_nodeapi(&$node, $op) {
211 global $user;
212 switch ($op) {
213 case 'view':
214 if ((($node->uid == $user->uid && user_access('make calls') && user_access('upload recordings')) || user_access('administer calls')) && variable_get('asterisk_node_'. $node->type, 0)) {
215 $node->content['asterisk_record_form'] = array(
216 '#value' => drupal_get_form('asterisk_record_form', $node),
217 '#weight' => 10,
218 );
219 }
220 break;
221 }
222 }
223
224 function asterisk_record_form(&$form_state, $node) {
225 $form = array();
226 $form['asterisk_record'] = array('#type' => 'fieldset',
227 '#title' => t('Record attachment'),
228 '#collapsible' => TRUE,
229 '#collapsed' => TRUE,
230 '#description' => t('Note: once you click \'Record file\', you will receive a call to the telephone number you have listed on your user page. Wait for the beep and record your message. Hang up when finished, and the message will be uploaded to %title shortly thereafter.', array('%title' => $node->title)),
231 );
232 $form['asterisk_record']['asterisk_record_nid'] = array('#type' => 'value',
233 '#value' => $node->nid,
234 );
235 $form['asterisk_record']['asterisk_record_file'] = array('#type' => 'textfield',
236 '#title' => t('Filename'),
237 '#description' => t('A name for your audio file. Please use only letters, numbers, dashes, and underscores--no spaces, and no file extension.'),
238 '#required' => TRUE,
239 );
240 $form['asterisk_record']['asterisk_record_list_file'] = array('#type' => 'checkbox',
241 '#title' => t('List file immediately'),
242 '#description' => t('If checked, the file will be listed for viewing immediately upon upload--otherwise it must specifically enabled for viewing at a later time.'),
243 );
244 /* $form['asterisk_record']['callee_number'] = array('#type' => 'textfield',
245 '#title' => t('Second Party Number'),
246 '#description' => t('If you\'d like to connect to another party first before beginning the recording, enter the party\'s number here. Leave blank if you just want to record yourself. <em>Note: once you are connected to the other party, you can start and stop recording by pressing *1</em>'),
247 );*/
248 $form['asterisk_record']['submit'] = array('#type' => 'submit',
249 '#value' => t('Record file'),
250 );
251 return $form;
252 }
253
254 /**
255 * Implementation of hook_perm()
256 */
257 function asterisk_perm() {
258 return array(
259 'make calls',
260 'upload recordings',
261 'administer calls',
262 'administer asterisk configuration',
263 );
264 }
265
266 /**
267 * Implementation of hook_settings()
268 */
269 function asterisk_settings_form(&$form_state) {
270 global $base_url;
271
272 $form = array();
273 $form['asterisk_phone_number_message'] = array('#type' => 'textarea',
274 '#title' => t('Profile page text'),
275 '#default_value' => variable_get('asterisk_phone_number_message', t('Your phone number of course. You must put your country code, eg 1 for USA. An example would be 13213210123 for a US (NANPA) number')),
276 '#description' => t('Help text for the user profile page. You should state which types of numbers are allowed by your drupal site'),
277 '#cols' => 50,
278 '#rows' => 4,
279 );
280
281 // Asterisk XML-RPC server settings.
282 $form['asterisk_instant_notification'] = array('#type' => 'fieldset',
283 '#title' => t('Asterisk server connection parameters'),
284 '#description' => t('<em>Warning: It is highly recommended that you connect to an Asterisk server that has set up a secure XML-RPC server via SSL, otherwise your credentials and data will be transmitted unencrypted. If another party were to successfully intercept them, they might be able to make calls through the Asterisk server as you!</em>'),
285 );
286 $form['asterisk_instant_notification']['asterisk_server_url'] = array('#type' => 'textfield',
287 '#title' => t('Asterisk XML-RPC URL'),
288 '#default_value' => variable_get('asterisk_server_url', ''),
289 '#maxlength' => 255,
290 '#description' => t('The URL of the XML-RPC server on your Asterisk installation (ex: https://example.com/asterisk_xmlrpc.php). The client end of this module supports both http and https connections.'),
291 );
292 $form['asterisk_instant_notification']['asterisk_server_user'] = array(
293 '#type' => 'item',
294 '#title' => t('Asterisk username: %username', array('%username' => _asterisk_create_username())),
295 '#description' => '<em>'. t('Set automatically by the system.') .'</em>',
296 );
297
298 $form['asterisk_instant_notification']['asterisk_server_pass'] = array('#type' => 'textfield',
299 '#title' => t('Asterisk server password'),
300 '#default_value' => variable_get('asterisk_server_pass', ''),
301 '#size' => 30,
302 '#maxlength' => 30,
303 '#description' => t('The password that you have configured on the Asterisk server for this website.'),
304 );
305 $drupal_url = $base_url .'/xmlrpc.php';
306 $form['asterisk_instant_notification']['asterisk_drupal_url'] = array('#type' => 'textfield',
307 '#title' => t('Drupal XML-RPC URL'),
308 '#default_value' => variable_get('asterisk_drupal_url', $drupal_url),
309 '#maxlength' => 255,
310 '#description' => t('The URL of the XML-RPC server on your Drupal installation. This is most probably %drupal_url', array('%drupal_url' => $drupal_url)),
311 );
312 $form['asterisk_instant_notification']['asterisk_drupal_server_test'] = array('#value' => '<div>'. l(t('Test connection'), 'admin/asterisk/server_test') .' -- use this only after you have saved the settings!</div>');
313
314 return system_settings_form($form);
315 }
316
317 /**
318 * Implementation of hook_user()
319 */
320 function asterisk_user($op, &$edit, &$account) {
321
322 switch($op) {
323 case 'load':
324 asterisk_user_load($account);
325 break;
326 case 'insert':
327 asterisk_user_insert($edit, $account);
328 break;
329 case 'update':
330 asterisk_user_update($edit, $account);
331 break;
332 case 'delete':
333 asterisk_user_delete($edit, $account);
334 break;
335 case 'form':
336 return asterisk_user_form($edit, $account);
337 case 'view':
338
339 /*
340 * Adds a telephone number to the users profile.
341 * Users with telephone numbers can click to call other users
342 */
343 if (user_access('make calls') && isset($account->number) && $account->number != '') {
344 if (_asterisk_is_same_user($account)) {
345 $output = l(t('Call me'), 'asterisk/callme', array('query' => drupal_get_destination()));
346 }
347 else {
348 $output = l(t('Call user'), "asterisk/calluser/$account->uid", array('query' => drupal_get_destination()));
349 }
350
351 if (!isset($account->content['telephone'])) {
352 $account->content['telephone'] = array();
353 }
354 $account->content['telephone'] += array(
355 '#type' => 'user_profile_category',
356 '#attributes' => array('class' => 'user-member'),
357 '#weight' => 10,
358 '#title' => t('Telephone'),
359 );
360 $account->content['telephone']['call_link'] = array(
361 '#type' => 'user_profile_item',
362 '#title' => t('Place calls'),
363 '#value' => $output,
364 );
365 }
366 break;
367 }
368 }
369
370 /**
371 * Implementation of hook_xmlrpc()
372 */
373 function asterisk_xmlrpc() {
374 return array(
375 array(
376 'asterisk.notify.NewMessages',
377 'asterisk_check_messages',
378 array('boolean', 'array'),
379 t('receives a list of new messages from the Asterisk server')
380 ),
381 );
382 }
383
384 /**
385 * @defgroup Asterisk call API
386 * @{
387 * Functions to make calls using Asterisk.
388 *
389 * These functions can connect two users, playback a message to a single user
390 * and record a message over the phone.
391 */
392
393 /**
394 * Builds a call array, adding defaults if necessary
395 *
396 * @param $call
397 * An associative array of call parameters, key = parameter name, value = value.
398 * The following parameters are valid:
399 *
400 * 'caller_number' => Phone number to call first. When answered the caller will
401 * hear ringing until the 'callee' answers. The phone number types supported depend
402 * on the configuration of the Asterisk server. The default configuration
403 * allows calls to SIP, IAX(2), and FWD (http://fwdnet.net) numbers. PSTN
404 * calling can be enabled by configuring a VoIP provider or PSTN connection
405 * with Asterisk.
406 *
407 * Number formats are as follows:
408 * IAX: iax://username user is registered to your asterisk server
409 * SIP: sip://username
410 * FWD: fwd://fwd_number
411 *
412 * Numbers can also be regular Asterisk extensions. These extensions must be
413 * in the context that the call goes to. The default context is drupal. Extensions
414 * are more useful when used as 'callee' numbers.
415 *
416 * 'callee_number' => Phone number to connect the first number to. This is usually an Asterisk
417 * extension. This parameter follows the same rules as 'caller_number'.
418 *
419 * 'module' => The module that originated the call. This is also useful for tracking
420 * purposes.
421 *
422 * 'uid' => The user ID to log as the originator of the call. Useful for tracking. Default is 0.
423 *
424 * 'dispatch_time' => Earliest time that the call should be made (and hopefully not too long
425 * after!). This should be a Unix timestamp. Drupal won't deliver the call
426 * to Asterisk until this time. The time the call will actually be made will
427 * depend on:
428 * 1. How frequently the Drupal site checks for queued calls to send
429 * 2. How long the Asterisk server takes to make the call. This is usually
430 * a fraction of a second, depending on load.
431 * Default is immediately.
432 *
433 * 'caller_id' => Caller ID to set for the call. Not all providers allow setting the caller
434 * ID. Generally VoIP providers will allow this for most calls, digital TDM
435 * (T1, E1, etc.) allows this if you have the feature from your provider,
436 * analog lines do not support this (regular phone lines). Default is no caller ID
437 *
438 * Note: Most providers require this to be a numeric string! VoIP endpoints
439 * SIP, IAX, FWD(uses SIP), etc. will accept strings as well.
440 *
441 * 'vars' => An array of variables to pass to Asterisk. These variables can then be
442 * used in your dialplan or AGI script.
443 *
444 * 'max_retries' => Number of retries before failing (not including the initial attempt, e.g.
445 * 0 = total of 1 attempt to make the call). Default is 2.
446 *
447 * 'retry_time' => Seconds between retries, don't hammer an unavailable phone. Default is 60 seconds
448 *
449 * 'wait_time' => Seconds to wait for an answer. Default is 30 seconds.
450 *
451 * 'context' => Context that the callee call will be made in. Default is 'drupal'.
452 *
453 * 'priority' => The priority of the extension for the call part of the call. Default is 1.
454 *
455 * 'call_status' => Whether the call has been sent to the Asterisk server. This is set to 'dipatched'
456 * by default. If you're not dispatching a call immediately, set to 'uncalled', and set the
457 * 'dispatch_time' parameter appropriately.
458 *
459 * 'drupal_filename' => Name of a file to be recorded. This is only valid if a recording call
460 * is being passed.
461 *
462 * @return
463 * Returns the full calll array with defaults added if necessary.
464 */
465 function asterisk_call($call) {
466
467 // Set defaults if necessary.
468 $call['module'] = isset($call['module']) ? $call['module'] : '';
469 $call['uid'] = isset($call['uid']) ? $call['uid'] : 0;
470 $call['dispatch_time'] = isset($call['dispatch_time']) ? $call['dispatch_time'] : time();
471 $call['vars'] = isset($call['vars']) ? $call['vars'] : array();
472 $call['caller_id'] = isset($call['caller_id']) ? $call['caller_id'] : '';
473 $call['max_retries'] = isset($call['max_retries']) ? $call['max_retries'] : 2;
474 $call['retry_time'] = isset($call['retry_time']) ? $call['retry_time'] : 60;
475 $call['wait_time'] = isset($call['wait_time']) ? $call['wait_time'] : 30;
476 $call['context'] = isset($call['context']) ? $call['context'] : 'drupal';
477 $call['priority'] = isset($call['priority']) ? $call['priority'] : 1;
478 $call['call_status'] = isset($call['call_status']) ? $call['call_status']: 'dispatched';
479
480 return $call;
481 }
482
483 /**
484 * Sends calls to the Asterisk server.
485 *
486 * @param $calls An array of calls, each elment of which is an associative array
487 * describing a call, based on the parameters listed in function asterisk_call.
488 */
489 function asterisk_send_calls($calls) {
490
491 // Get server data.
492 $server = _asterisk_instant_notification();
493
494 // Since we want playback files to be processed before callfiles, we set the
495 // order here.
496 $outgoing = array();
497 $outgoing['playback_file'] = array();
498 $outgoing['callfile'] = array();
499
500 foreach ($calls as $key => $call) {
501
502 // Only allow calls that have both a caller and callee number.
503 if ($call['caller_number'] && $call['callee_number']) {
504 // Prepare the arguments for later insertion into the database.
505 $arguments[$key] = array($call['caller_number'],
506 $call['callee_number'],
507 $call['module'],
508 $call['uid'],
509 $call['dispatch_time'],
510 serialize($call['vars']),
511 $call['caller_id'],
512 $call['max_retries'],
513 $call['retry_time'],
514 $call['wait_time'],
515 $call['context'],
516 $call['priority'],
517 $call['call_status'],
518 time()
519 );
520
521 // calls marked dispatched are sent to the Asterisk server immediately, and only entered in the database
522 // if the XML-RPC call is successful.
523 if ($call['call_status'] == 'dispatched') {
524 $outgoing['callfile'][$key] = asterisk_create_call_file($call);
525 // The call has an uncached playback file associated with it--load it to the call array.
526 if (isset($call['playback_file'])) {
527 $outgoing['playback_file'][$key] = $call['playback_file'];
528 }
529 }
530 // While calls marked uncalled are entered into the db immediately.
531 else {
532 asterisk_add_call_to_db($arguments[$key]);
533 unset($arguments[$key]);
534 unset($calls[$key]);
535 }
536 }
537 }
538
539 // Send the dispatched calls to the Asterisk server, and get back an array of completed call information.
540 // NOTE: A successfully completed call only means that the call was successfully queued to Asterisk's
541 // outgoing call spool.
542 $completed_calls = xmlrpc($server['url'], 'asterisk.send.NewCalls', $server['user'], $server['pass'], $outgoing);
543
544 foreach ($calls as $key => $call) {
545 $message_args = array('%caller_number' => $call['caller_number'], '%callee_number' => $call['callee_number']);
546 // Check for server errors.
547 if (!isset($completed_calls['callfile'][$key]['error']) && !isset($completed_calls['playback_file'][$key]['error'])) {
548 // Mark the call as completed successfully if it was returned in the completed calls array, with an
549 // additional check for a successfully processed playback messagee if one was sent.
550 if (isset($completed_calls['callfile'][$key]) && (!isset($outgoing['playback_file'][$key]) || $completed_calls['playback_file'][$key])) {
551 asterisk_add_call_to_db($arguments[$key]);
552 watchdog('asterisk', 'Queued call to %callee_number, from %caller_number', $message_args, WATCHDOG_NOTICE);
553 drupal_set_message(t('Placed call to %callee_number, from %caller_number', $message_args));
554 }
555 // Call did not complete successfully.
556 else {
557 watchdog('asterisk', 'Call to %callee_number, from %caller_number failed', $message_args, WATCHDOG_ERROR);
558 drupal_set_message(t('Call to %callee_number, from %caller_number failed', $message_args), 'error');
559 }
560 }
561 else {
562 $type = isset($call['playback_file']) ? 'playback_file' : 'callfile';
563 watchdog('asterisk', 'Call to %callee_number, from %caller_number failed: '. $completed_calls[$type][$key]['error'], $message_args, WATCHDOG_ERROR);
564 drupal_set_message(t('Call to %callee_number, from %caller_number failed: '. $completed_calls[$type][$key]['error'], $message_args), 'error');
565 }
566 unset($arguments[$key]);
567 unset($calls[$key]);
568 }
569 }
570
571 /**
572 * Call a number and play a message. The file to be played must be in the
573 * {files} table, and the file must be either in wav or mp3 format.
574 * When the call is dispatched this file is converted to an Asterisk compatible wav file.
575 * (8kHz mono PCM). Files are cached on the Asterisk server, matched by file ID, node ID,
576 * and filename.
577 * @param $fid
578 * The fid identifier for the file to be played back to the callee.
579 * @param $callee_number
580 * The number of the user being called (see asterisk_call()).
581 * @param $module
582 * Module making the call (see asterisk_call()).
583 * @param $uid
584 * uid of user making the call (see asterisk_call()).
585 * @param $dispatch_time
586 * Time that call should occur (see asterisk_call()).
587 * @param $playback_file
588 * A playback file to upload--necessary if the file is not already cached on the
589 * Asterisk server.
590 */
591 function asterisk_playback_message($fid, $callee_number, $module = '', $uid = 0, $dispatch_time = NULL, $playback_file = NULL) {
592 $call = array();
593 $call['caller_number'] = $callee_number;
594 $call['callee_number'] = 'playback';
595 $call['module'] = $module;
596 $call['uid'] = $uid;
597 $call['dispatch_time'] = $dispatch_time;
598 $call['vars'] = array('drupal_fid' => $fid);
599 if (isset($playback_file)) {
600 $call['playback_file'] = $playback_file;
601 }
602 $calls[1] = asterisk_call($call);
603 asterisk_send_calls($calls);
604 }
605
606 /**
607 * Call a number and record a message.
608 * @param $filename
609 * The desired filename of the future recording.
610 * @param $callee_number
611 * The number of the user being called (see asterisk_call()).
612 * @param $nid
613 * Identifier of the node to which the recorded message should be attached.
614 * @param $module
615 * Module making the call (see asterisk_call()).
616 * @param $uid
617 * uid of user making the call (see asterisk_call()).
618 * @param dispatch_time
619 * Time that call should occur (see asterisk_call()).
620 * @param $list
621 * Boolean which determines if the file will be listed in the node as soon as it's created. Default is FALSE.
622 */
623 function asterisk_record_message($filename, $callee_number, $nid = 0, $module = '', $uid = 0, $dispatch_time = NULL, $list = 0) {
624
625 // Get a file ID.
626 $fid = asterisk_get_file_id($uid);
627
628 $call = array();
629 $calls = array();
630 $call['caller_number'] = $callee_number;
631 $call['callee_number'] = 'record';
632 $call['module'] = $module;
633 $call['uid'] = $uid;
634 $call['dispatch_time'] = $dispatch_time;
635 $call['vars'] = array('drupal_fid' => $fid, 'drupal_nid' => $nid, 'drupal_filename' => $filename, 'drupal_list_file' => $list);
636
637 $calls[1] = asterisk_call($call);
638 asterisk_send_calls($calls);
639
640 $node = node_load($nid);
641 drupal_set_message(t('Your message will be uploaded to %title shortly after you hang up.', array('%title' => $node->title)));
642 }
643
644 /**
645 * Gets a file ID for a file recording.
646 *
647 * @param $uid
648 * The user submitting the record request.
649 * @return
650 * The file ID.
651 */
652 function asterisk_get_file_id($uid) {
653 // Enter a temporary file into the files table -- placeholder.
654 $query = "INSERT INTO {files}
655 (uid, filename, filepath, filemime, filesize, status, timestamp)
656 VALUES
657 (%d, '%s', '%s', '%s', %d, %d, %d)";
658
659 $args = array(
660 $uid,
661 '',
662 '',
663 '',
664 0,
665 FILE_STATUS_TEMPORARY,
666 time(),
667 );
668
669 db_query($query, $args);
670 $fid = db_last_insert_id('files', 'fid');
671
672 return $fid;
673 }
674
675 /**
676 * Use the generic call function to call given a user (the callee). The caller is made
677 * the callee so that the user can hear anything that the IVR might play
678 * @param $callee
679 * User object of the user to be called.
680 * @param $number
681 * Number that the callee user will be connected to.
682 * @param $module
683 * Module making the call (see asterisk_call()).
684 * @param $uid
685 * uid of user making the call (see asterisk_call()).
686 * @param dispatch_time
687 * Time that call should occur (see asterisk_call()).
688 */
689 function asterisk_call_number($callee, $number, $module ='', $uid = 0, $dispatch_time = NULL, $call = array()) {
690 $calls = array();
691 $call['caller_number'] = $callee->number;
692 $call['callee_number'] = $number;
693 $call['module'] = $module;
694 $call['uid'] = $uid;
695 $call['dispatch_time'] = $dispatch_time;
696 $calls[1] = asterisk_call($call);
697 asterisk_send_calls($calls);
698 }
699
700 /**
701 * @} End of "defgroup Asterisk call API".
702 */
703
704 /**
705 * Adds a successful call or a successfully queued call to the call queue database table.
706 *
707 * @param $args An array of call data to enter into the database.
708 */
709 function asterisk_add_call_to_db($args) {
710 $query = "INSERT INTO {asterisk_call_queue}
711 (caller_number, callee_number, module, uid, dispatch_time, vars, caller_id, max_retries, retry_time, wait_time, context, priority, call_status, queuetime)
712 VALUES
713 ('%s', '%s', '%s', %d, %d, '%s', '%s', %d, %d, %d, '%s', %d, '%s', %d)
714 ";
715 db_query($query, $args);
716 }
717
718 /**
719 * Checks files for existence on the Asterisk server.
720 *
721 * @param $fids An array of file ID's to check (fid in the files table).
722 *
723 * @return An array of all file ID's that exist on the Asterisk server.
724 */
725 function asterisk_validate_playback_files($fids) {
726
727 $files_array = array();
728 $return = array();
729 $server = _asterisk_instant_notification();
730
731 // Prepare each file for checking on the server--we need to get the file name as it
732 // appears on the server to do that.
733 foreach ($fids as $key => $fid) {
734 $file = asterisk_get_file_info($fid);
735 $files_array['playback_file'][$key] = $file['server_file_name'];
736 }
737
738 // Get the results from the server.
739 $result = xmlrpc($server['url'], 'asterisk.check.Files', $server['user'], $server['pass'], $files_array);
740
741 // Build the array of found files.
742 foreach ($result['playback_file'] as $key => $value) {
743 $return[] = $fids[$key];
744 }
745
746 return $return;
747 }
748
749 /**
750 * Validates the call for recording.
751 */
752 function asterisk_record_form_validate($form, &$form_state) {
753
754 // Check to make sure the current user has a valid callback number.
755 asterisk_user_has_number();
756
757 // Replace all characters that aren't numbers, letters, dashes, or underscores with underscores
758 $filename = preg_replace('/[^\d\w\-]/', '_', $form_state['values']['asterisk_record_file']);
759 form_set_value($form['asterisk_record']['asterisk_record_file'], $filename, $form_state);
760 }
761
762 /**
763 * Submits the call for recording.
764 */
765 function asterisk_record_form_submit($form, &$form_state) {
766 global $user;
767
768 // This call is a bridged call with optional recording
769 if ($callee_number = format_number($form_state['values']['callee_number'])) {
770 // Get a file ID.
771 $fid = asterisk_get_file_id($uid);
772 $call['vars'] = array(
773 'drupal_fid' => $fid,
774 'drupal_nid' => $form_state['values']['asterisk_record_nid'],
775 'drupal_filename' => $form_state['values']['asterisk_record_file'],
776 'drupal_list_file' => $form_state['values']['asterisk_record_list_file'],
777 'drupal_callee_number' => $callee_number,
778 );
779 asterisk_call_number($user, 'bridge', 'asterisk', $user->uid, NULL, $call);
780 }
781 // This is a call just to the user.
782 else {
783 asterisk_record_message($form_state['values']['asterisk_record_file'], $user->number, $form_state['values']['asterisk_record_nid'], 'asterisk', $user->uid, NULL, $form_state['values']['asterisk_record_list_file']);
784 }
785 }
786
787 function asterisk_check_messages($messages) {
788
789 if (!($server = _asterisk_instant_notification())) {
790 return FALSE;
791 }
792 foreach ($messages as $message_key) {
793 $message = xmlrpc($server['url'], 'asterisk.get.Message', $server['user'], $server['pass'], $message_key);
794 asterisk_process_new_message($message);
795 }
796
797 return TRUE;
798 }
799
800 function asterisk_process_new_message($message) {
801
802 switch ($message['type']) {
803 case 'recording':
804 _asterisk_save_file($message);
805 break;
806 }
807 }
808
809 /**
810 * Saves an uploaded file to the files directory, and records it properly
811 * in the files tables.
812 */
813 function _asterisk_save_file($file) {
814
815 $node = node_load($file['nid']);
816 $file['vid'] = $node->vid ? $node->vid : 0;
817 $data = $file['bits'];
818
819 $name = $file['filename'] ? $file['filename'] : t('unknown');
820
821 $t_args = array('%file' => $name, '%title' => $node->title);
822
823
824 if(!$data) {
825 watchdog('asterisk', 'File upload failed. No file sent for %file.', $t_args, WATCHDOG_ERROR);
826 return;
827 }
828 if (!$filepath = file_save_data($data, $name)) {
829 watchdog('asterisk', 'File upload failed. Error storing %file.', $t_args, WATCHDOG_ERROR);
830 return;
831 }
832
833 $query = "UPDATE {files} SET filename = '%s', filepath = '%s', filemime = '%s', filesize = %d, status = %d, timestamp = %d WHERE fid = %d";
834
835 $args = array(
836 $name,
837 $filepath,
838 $file['filemime'],
839 $file['filesize'],
840 FILE_STATUS_PERMANENT,
841 time(),
842 $file['fid'],
843 );
844
845 db_query($query, $args);
846
847 $query = "INSERT INTO {upload}
848 (fid, nid, vid, description, list)
849 VALUES
850 (%d, %d, %d, '%s', %d)";
851
852 $args = array($file['fid'],
853 $file['nid'],
854 $file['vid'],
855 $name,
856 (integer) $file['list']
857 );
858
859 db_query($query, $args);
860 watchdog('asterisk', '%file uploaded successfully to %title.', $t_args);
861 }
862
863 /**
864 * Generates file information about a file to be passed to the Asterisk server.
865 *
866 * @param $fid The file ID.
867 * @return An associative array of information about the file.
868 */
869 function asterisk_get_file_info($fid) {
870 global $base_url;
871
872 $nid = db_result(db_query('SELECT n.nid FROM {upload} u INNER JOIN {node} n ON n.nid = u.nid WHERE n.status = 1 AND n.vid = u.vid AND fid = %d', $fid));
873 $node = node_load($nid);
874
875 // Make sure the file is still active on the node.
876 if ($node) {
877 $file = (array) $node->files[$fid];
878
879 // more unique name for the file on the asterisk server
880 $file['server_file_name'] = $file['fid'] .'_'. $file['nid'] .'_'. $file['filename'];
881
882 if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC) {
883 $file['is_public'] = TRUE;
884 }
885
886 // get rid of this variable--replaced with the channel variable below
887 unset($file['filename']);
888
889 // We use the FILENAME variable in our dialplan. strip the file extension--Asterisk doesn't need it.
890 $filename_stripped_ext = substr($file['server_file_name'], 0, strrpos($file['server_file_name'], '.'));
891 $file['FILENAME'] = $filename_stripped_ext;
892
893 return $file;
894 }
895 else {
896 return FALSE;
897 }
898 }
899
900 /**
901 * Load the user's information
902 */
903 function asterisk_user_load(&$user) {
904 $query = "SELECT * FROM {asterisk_users} WHERE uid = %d";
905 if ($additions = db_fetch_object(db_query($query, $user->uid))) {
906 foreach ($additions as $key => $value) {
907 $user->$key = $value;
908 }
909 }
910 }
911
912 /**
913 * Inserts the user's information
914 */
915 function asterisk_user_insert(&$edit, &$user) {
916 // save the main details to the database
917 $query = "INSERT INTO {asterisk_users} (pin, number, uid) VALUES ('%s', '%s', %d)";
918 db_query($query, $edit['asterisk_pin'], $edit['asterisk_number'], $user->uid);
919
920
921 /* Disabled for now...
922
923 // create the user on the asterisk server
924 $vars = array('op' => 'create-user', 'user' => $user);
925 asterisk_call('system', 'system', 'asterisk', $user->uid, FALSE, $vars);
926
927 */
928
929 $edit['asterisk_number'] = NULL;
930 $edit['asterisk_pin'] = NULL;
931
932 }
933
934 /**
935 * Updates the user's information
936 */
937 function asterisk_user_update(&$edit, &$user) {
938
939 // update the PIN and telephone number information
940 $query = "UPDATE {asterisk_users} SET pin = '%s', number = '%s' WHERE uid = %d";
941 db_query($query, $edit['asterisk_pin'], $edit['asterisk_number'], $user->uid);
942 if (db_affected_rows() == 0) {
943 $query = "INSERT INTO {asterisk_users} (pin, number, uid) VALUES ('%s', '%s', %d)";
944 db_query($query, $edit['asterisk_pin'], $edit['asterisk_number'], $user->uid);
945 }
946
947 /* Disabled for now...
948 // update the user information on the asterisk server
949 $vars = array('op' => 'update-user', 'user' => $user);
950 asterisk_call('system', 'system', 'asterisk', $user->uid, FALSE, $vars);
951 $edit['direct_leap_account_number'] = NULL;
952 */
953
954 $edit['asterisk_number'] = NULL;
955 $edit['asterisk_pin'] = NULL;
956 }
957
958 /**
959 * Deletes the user's information
960 */
961 function asterisk_user_delete(&$edit, &$user) {
962
963 // delete the user's info from our database
964 db_query("DELETE FROM {asterisk_users} WHERE uid = %d", $user->uid);
965
966 /* Disabled for now...
967 // remove the user from our asterisk server as well
968 $vars = array('op' => 'delete-user', 'user' => $user);
969 asterisk_call('system', 'system', 'asterisk', $user->uid, FALSE, $vars);
970 */
971 }
972
973 /**
974 * Generates additions for the user edit form
975 */
976 function asterisk_user_form($edit, $user) {
977
978 $form = array();
979
980 // User asterisk account info
981 $form['asterisk_telephone_information'] = array(
982 '#type' => 'fieldset',
983 '#title' => t('Telephone information'),
984 '#collapsible' => TRUE,
985 '#weight' => 1,
986 );
987 $form['asterisk_telephone_information']['asterisk_number'] = array(
988 '#type' => 'textfield',
989 '#title' => t('Phone number'),
990 '#default_value' => isset($user->number) ?$user->number : '',
991 '#maxlength' => 255,
992 '#description' => variable_get('asterisk_phone_number_message', t('Your phone number of course. You must put your country code, eg 1 for USA. An example would be 13213210123 for a US (NANPA) number')),
993 );
994 $form['asterisk_telephone_information']['asterisk_pin'] = array(
995 '#type' => 'textfield',
996 '#title' => t('Telephone PIN'),
997 '#default_value' => isset($user->pin) ? $user->pin : '',
998 '#size' => 30,
999 '#maxlength' => 30,
1000 '#description' => t('This is your password for telephony features. It should consist of just digits (eg 1542). If you are going to connect your softphone or hardphone to our asterisk servers, this will be the password. Additionally, this PIN will be used to access your voicemail.'),
1001 );
1002
1003 return $form;
1004 }
1005
1006 /**
1007 * Helper function that checks if a user object is the same as that of the user
1008 * currently viewing the page.
1009 */
1010 function _asterisk_is_same_user($callee) {
1011 global $user;
1012 if ($callee->uid == $user->uid) {
1013 return TRUE;
1014 }
1015 else {
1016 return FALSE;
1017 }
1018 }
1019
1020 function asterisk_audio_file_block() {
1021 $query = "SELECT DISTINCT f.fid, f.filename FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid INNER JOIN {node} n ON n.nid = u.nid WHERE n.status = 1 AND n.vid = u.vid AND (f.filemime LIKE '%audio%wav%' OR f.filemime LIKE '%audio%mpeg%') AND u.list = 1 ORDER BY f.fid DESC";
1022 $result = db_query_range($query, 0, variable_get('asterisk_audio_block_items', 5));
1023
1024 $links = array();
1025 while ($file = db_fetch_object($result)) {
1026 $links[] = l($file->filename, 'asterisk/play/'. $file->fid,
1027 array('title' => t('listen to this file by phone'), 'query' => drupal_get_destination()));
1028 }
1029
1030 $output = '';
1031 if (!empty($links)) {
1032 $output .= '<strong>'. t('Click to listen by phone:') .'</strong>';
1033 $output .= theme('item_list', $links);
1034 }
1035
1036 return $output;
1037 }
1038
1039 function asterisk_make_call_block_form(&$form_state) {
1040 $form = array();
1041 $form['asterisk_number'] = array('#type' => 'textfield',
1042 '#title' => t('Number'),
1043 '#size' => 15,
1044 '#maxlength' => 64,
1045 '#description' => variable_get('asterisk_call_number_help', 'You can call Free World Dialup numbers as fwd://username'),
1046 );
1047 $form['submit'] = array('#type' => 'submit',
1048 '#value' => t('Call now'),
1049 );
1050 return $form;
1051 }
1052
1053 /**
1054 * Page callback
1055 */
1056 function asterisk_page() {
1057 global $user;
1058
1059 // Check to make sure the current user has a valid callback number.
1060 asterisk_user_has_number();
1061
1062 $op = arg(1);
1063 switch ($op) {
1064 case 'calluser':
1065 asterisk_call_user_page($user);
1066 break;
1067 case 'callme':
1068 asterisk_call_number($user, 'demo', 'asterisk', $user->uid);
1069 break;
1070 case 'play':
1071 $fid = explode('?', arg(2));
1072 $fid = $fid[0];
1073 // Validate that the user can listen to this file.
1074 $nid = db_result(db_query('SELECT nid FROM {upload} WHERE fid = %d', $fid));
1075 if (!node_access('view', node_load($nid))) {
1076 return drupal_access_denied();;
1077 }
1078 // Check for existence of file on Asterisk server.
1079 $file_on_server = asterisk_validate_playback_files(array($fid));
1080 // File is not on server, so generate a playback_file element.
1081 if (!in_array($fid, $file_on_server)) {
1082 $file = asterisk_get_file_info($fid);
1083 $playback_file = asterisk_generate_playback_file($file);
1084 }
1085 $uids = array($user->uid);
1086 foreach ($uids as $uid) {
1087 $u = user_load(array('uid' => $uid));
1088 if ($u->number) {
1089 // Only send the playfile with the first call--saves bandwidth.
1090 if (isset($playback_file)) {
1091 asterisk_playback_message($fid, $u->number, 'asterisk', $user->uid, NULL, $playback_file);
1092 unset($playback_file);
1093 }
1094 else {
1095 asterisk_playback_message($fid, $u->number, 'asterisk', $user->uid);
1096 }
1097 }
1098 }
1099 break;
1100 }
1101
1102 drupal_goto();
1103 }
1104
1105 /**
1106 * Validates that the current user has a callback number, and displays a warning message
1107 * with an edit link if not.
1108 */
1109 function asterisk_user_has_number() {
1110 global $user;
1111
1112 asterisk_user_load($user);
1113
1114 // We don't have a phone number for this user--send them an appropriate message.
1115 if ($user->number == '') {
1116 drupal_set_message(t('You need to configure your phone number in the \'Telephone information\' section below so that we can connect the call to you'), 'error');
1117 drupal_goto('user/'. $user->uid .'/edit');
1118 }
1119 }
1120
1121 function asterisk_generate_playback_file($file) {
1122
1123 $return = NULL;
1124
1125 if (file_exists($file['filepath'])) {
1126 if ($fh = fopen($file['filepath'], 'r')) {
1127 $playback_file['bits'] = '';
1128 while (!feof($fh)) {
1129 $playback_file['bits'] .= fread($fh, 8192);
1130 }
1131 fclose($fh);
1132
1133 $return['bits'] = xmlrpc_base64($playback_file['bits']);
1134 $return['filemime'] = $file['filemime'];
1135 $return['server_file_name'] = $file['server_file_name'];
1136 }
1137 }
1138
1139 return $return;
1140 }
1141
1142 /**
1143 * Validates the number in the submitted call block.
1144 */
1145 function asterisk_make_call_block_form_validate($form, &$form_state) {
1146
1147 // Check that user has correct permissions
1148 if (!user_access('make calls')) {
1149 form_set_error('asterisk_number', t('You do not have the required permissions to make a call.'));
1150 }
1151 else {
1152 // Check to make sure the current user has a valid callback number.
1153 asterisk_user_has_number();
1154
1155 // Check to make sure the user entered a number in the call block.
1156 if ($form_state['values']['asterisk_number'] == '') {
1157 form_set_error('asterisk_number', t('You need to supply a valid number'));
1158 }
1159 }
1160 }
1161
1162 /**
1163 * Calls the number in the submitted call block.
1164 *
1165 * @param $form_id The form ID.
1166 * @param $form_values An array of submitted form values.
1167 */
1168 function asterisk_make_call_block_form_submit($form, &$form_state) {
1169 global $user;
1170
1171 $call['vars'] = array('drupal_callee_number' => $form_state['values']['asterisk_number']);
1172 asterisk_call_number($user, 'bridge', 'asterisk', $user->uid, NULL, $call);
1173 }
1174
1175 /**
1176 * Processes the call user callback
1177 */
1178 function asterisk_call_user_page($user) {
1179
1180 $callee = user_load(array('uid' => arg(2)));
1181
1182 if ($user->uid == $callee->uid) {
1183 drupal_set_message(t('You cannot call yourself'));
1184 }
1185 elseif ($callee->number == '') {
1186 drupal_set_message(t('You cannot call this user--their phone number has not been configured.'));
1187 }
1188 else {
1189 $call['vars'] = array('drupal_callee_number' => $callee->number);
1190 return asterisk_call_number($user, 'bridge', 'asterisk', $user->uid, NULL, $call);
1191 }
1192
1193 return FALSE;
1194 }
1195
1196 function asterisk_call_log($account = NULL) {
1197 $user_log = is_object($account) && isset($account->uid);
1198
1199 $sql = "SELECT a.*, u.name FROM {asterisk_call_queue} a INNER JOIN {users} u ON u.uid = a.uid WHERE caller_number
1200 != 'system' AND callee_number != 'system'";
1201
1202 $header = array(
1203 array('data' => t('Time'), 'field' => 'dispatch_time', 'sort' => 'desc'),
1204 array('data' => t('Caller'), 'field' => 'caller_number'),
1205 array('data' => t('Callee'), 'field' => 'callee_number')
1206 );
1207
1208 if ($user_log) {
1209 $sql .= " AND u.uid = %d";
1210 $sql .= tablesort_sql($header);<