Merge pull request #1251 from moteus/fax_queue

Fax queue
This commit is contained in:
FusionPBX 2015-11-25 07:37:28 -08:00
commit b562c4e5ec
21 changed files with 2222 additions and 265 deletions

View File

@ -277,6 +277,14 @@
$apps[$x]['db'][$y]['fields'][$z]['type'] = "text";
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = "";
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = "fax_send_greeting";
$apps[$x]['db'][$y]['fields'][$z]['type'] = "text";
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = "";
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = "fax_send_channels";
$apps[$x]['db'][$y]['fields'][$z]['type'] = "numeric";
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = "";
$z++;
//$apps[$x]['db'][$y]['fields'][$z]['name']['text'] = "fax_keep_local";
//$apps[$x]['db'][$y]['fields'][$z]['name']['deprecated'] = "fax_local";
//$apps[$x]['db'][$y]['fields'][$z]['type'] = "text";
@ -499,4 +507,75 @@
$apps[$x]['db'][$y]['fields'][$z]['type'] = "numeric";
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = "";
$y = 4; //table array index
$z = 0; //field array index
$apps[$x]['db'][$y]['table'] = 'v_fax_tasks';
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_uuid';
$apps[$x]['db'][$y]['fields'][$z]['type']['pgsql'] = 'uuid';
$apps[$x]['db'][$y]['fields'][$z]['type']['sqlite'] = 'text';
$apps[$x]['db'][$y]['fields'][$z]['type']['mysql'] = 'char(36)';
$apps[$x]['db'][$y]['fields'][$z]['key']['type'] = 'primary';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'fax_uuid';
$apps[$x]['db'][$y]['fields'][$z]['type']['pgsql'] = 'uuid';
$apps[$x]['db'][$y]['fields'][$z]['type']['sqlite'] = 'text';
$apps[$x]['db'][$y]['fields'][$z]['type']['mysql'] = 'char(36)';
$apps[$x]['db'][$y]['fields'][$z]['key']['type'] = 'foreign';
$apps[$x]['db'][$y]['fields'][$z]['key']['reference']['table'] = 'v_fax';
$apps[$x]['db'][$y]['fields'][$z]['key']['reference']['field'] = 'fax_uuid';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = 'FAX server primary key';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_next_time';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'timestamp';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_lock_time';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'timestamp';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_fax_file';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'text';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_wav_file';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'text';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_uri';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'text';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_dial_string';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'text';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_dtmf';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'text';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_interrupted';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'text';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_status';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'numeric';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_no_answer_counter';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'numeric';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_no_answer_retry_counter';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'numeric';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_retry_counter';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'numeric';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
$apps[$x]['db'][$y]['fields'][$z]['name'] = 'task_description';
$apps[$x]['db'][$y]['fields'][$z]['type'] = 'text';
$apps[$x]['db'][$y]['fields'][$z]['description']['en-us'] = '';
$z++;
?>

View File

@ -12,6 +12,13 @@ if ($domains_processed == 1) {
$array[$x]['default_setting_description'] = 'Path to image/logo file displayed in the header of the cover sheet.';
$x++;
$array[$x]['default_setting_category'] = 'fax';
$array[$x]['default_setting_subcategory'] = 'cover_font';
$array[$x]['default_setting_name'] = 'text';
$array[$x]['default_setting_value'] = 'times';
$array[$x]['default_setting_enabled'] = 'false';
$array[$x]['default_setting_description'] = 'Font used to generate cover page. Can be full path to .ttf file or font name alredy installed.';
$x++;
$array[$x]['default_setting_category'] = 'fax';
$array[$x]['default_setting_subcategory'] = 'cover_footer';
$array[$x]['default_setting_name'] = 'text';
$array[$x]['default_setting_value'] = "The information contained in this facsimile is intended for the sole confidential use of the recipient(s) designated above, and may contain confidential and legally privileged information. If you are not the intended recipient, you are hereby notified that the review, disclosure, dissemination, distribution, copying, duplication in any form, and taking of any action in regards to the contents of this document - except with respect to its direct delivery to the intended recipient - is strictly prohibited. Please notify the sender immediately and destroy this cover sheet and all attachments. If stored or viewed electronically, please permanently delete it from your system.";
@ -60,6 +67,55 @@ if ($domains_processed == 1) {
$array[$x]['default_setting_enabled'] = 'true';
$array[$x]['default_setting_description'] = 'Keep the file after sending or receiving the fax.';
$x++;
$array[$x]['default_setting_category'] = 'fax';
$array[$x]['default_setting_subcategory'] = 'send_mode';
$array[$x]['default_setting_name'] = 'text';
$array[$x]['default_setting_value'] = 'queue';
$array[$x]['default_setting_enabled'] = 'false';
$array[$x]['default_setting_description'] = '';
$x++;
$array[$x]['default_setting_category'] = 'fax';
$array[$x]['default_setting_subcategory'] = 'send_retry_limit';
$array[$x]['default_setting_name'] = 'numeric';
$array[$x]['default_setting_value'] = '5';
$array[$x]['default_setting_enabled'] = 'true';
$array[$x]['default_setting_description'] = 'Number of attempts to send fax (count only calls with answer)';
$x++;
$array[$x]['default_setting_category'] = 'fax';
$array[$x]['default_setting_subcategory'] = 'send_retry_interval';
$array[$x]['default_setting_name'] = 'numeric';
$array[$x]['default_setting_value'] = '15';
$array[$x]['default_setting_enabled'] = 'true';
$array[$x]['default_setting_description'] = 'Delay before we make next call after answered call';
$x++;
$array[$x]['default_setting_category'] = 'fax';
$array[$x]['default_setting_subcategory'] = 'send_no_answer_retry_limit';
$array[$x]['default_setting_name'] = 'numeric';
$array[$x]['default_setting_value'] = '3';
$array[$x]['default_setting_enabled'] = 'true';
$array[$x]['default_setting_description'] = 'Number of unanswered attempts in sequence';
$x++;
$array[$x]['default_setting_category'] = 'fax';
$array[$x]['default_setting_subcategory'] = 'send_no_answer_retry_interval';
$array[$x]['default_setting_name'] = 'numeric';
$array[$x]['default_setting_value'] = '30';
$array[$x]['default_setting_enabled'] = 'true';
$array[$x]['default_setting_description'] = 'Delay before we make next call after no answered call';
$x++;
$array[$x]['default_setting_category'] = 'fax';
$array[$x]['default_setting_subcategory'] = 'send_no_answer_limit';
$array[$x]['default_setting_name'] = 'numeric';
$array[$x]['default_setting_value'] = '3';
$array[$x]['default_setting_enabled'] = 'true';
$array[$x]['default_setting_description'] = 'Giveup reach the destination after this number of sequences';
$x++;
$array[$x]['default_setting_category'] = 'fax';
$array[$x]['default_setting_subcategory'] = 'send_no_answer_interval';
$array[$x]['default_setting_name'] = 'numeric';
$array[$x]['default_setting_value'] = '300';
$array[$x]['default_setting_enabled'] = 'true';
$array[$x]['default_setting_description'] = 'Delay before next call sequence';
$x++;
//get an array of the default settings
$sql = "select * from v_default_settings ";
$prep_statement = $db->prepare($sql);

View File

@ -1111,6 +1111,10 @@ $text['label-accountcode']['de-at'] = "Account Code";
$text['label-accountcode']['ro'] = "Cod cont";
$text['label-accountcode']['he'] = "קוד חשבון";
$text['label-fax_send_greeting']['en-us'] = "Greeting";
$text['label-fax_send_channels']['en-us'] = "Number of channels";
$text['header-sent']['en-us'] = "Sent Faxes";
$text['header-sent']['es-cl'] = "Los Faxes Enviados";
$text['header-sent']['pt-pt'] = "Faxes Enviados";

View File

@ -135,8 +135,10 @@ else {
} else {
$forward_prefix = $forward_prefix.$fax_forward_number.'#'; //found
}
$fax_local = check_str($_POST["fax_local"]);
$fax_local = check_str($_POST["fax_local"]); //! @todo check in database
$fax_description = check_str($_POST["fax_description"]);
$fax_send_greeting = check_str($_POST["fax_send_greeting"]);
$fax_send_channels = check_str($_POST["fax_send_channels"]);
}
//delete the user from the fax users
@ -274,6 +276,8 @@ if (count($_POST)>0 && strlen($_POST["persistformvar"]) == 0) {
if (strlen($fax_forward_number) > 0) {
$sql .= "fax_forward_number, ";
}
$sql .= "fax_send_greeting,";
$sql .= "fax_send_channels,";
$sql .= "fax_description ";
$sql .= ")";
$sql .= "values ";
@ -305,6 +309,9 @@ if (count($_POST)>0 && strlen($_POST["persistformvar"]) == 0) {
if (strlen($fax_forward_number) > 0) {
$sql .= "'$fax_forward_number', ";
}
$sql .= (strlen($fax_send_greeting)==0?'NULL':"'$fax_send_greeting'") . ",";
$sql .= (strlen($fax_send_channels)==0?'NULL':"'$fax_send_channels'") . ",";
$sql .= "'$fax_description' ";
$sql .= ")";
$db->exec(check_sql($sql));
@ -345,9 +352,16 @@ if (count($_POST)>0 && strlen($_POST["persistformvar"]) == 0) {
else {
$sql .= "fax_forward_number = null, ";
}
$tmp = strlen($fax_send_greeting)==0?'NULL':"'$fax_send_greeting'";
$sql .= "fax_send_greeting = $tmp,";
$tmp = strlen($fax_send_channels)==0?'NULL':"'$fax_send_channels'";
$sql .= "fax_send_channels = $tmp,";
$sql .= "fax_description = '$fax_description' ";
$sql .= "where domain_uuid = '".$_SESSION['domain_uuid']."' ";
$sql .= "and fax_uuid = '$fax_uuid' ";
$db->exec(check_sql($sql));
unset($sql);
}
@ -426,9 +440,14 @@ if (count($_POST)>0 && strlen($_POST["persistformvar"]) == 0) {
$fax_caller_id_number = $row["fax_caller_id_number"];
$fax_forward_number = $row["fax_forward_number"];
$fax_description = $row["fax_description"];
$fax_send_greeting = $row["fax_send_greeting"];
$fax_send_channels = $row["fax_send_channels"];
}
unset ($prep_statement);
}
else{
$fax_send_channels = 10;
}
//replace the dash with a space
$fax_name = str_replace("-", " ", $fax_name);
@ -667,6 +686,28 @@ if (count($_POST)>0 && strlen($_POST["persistformvar"]) == 0) {
}
}
echo "<tr>\n";
echo "<td class='vncell' valign='top' align='left' nowrap='nowrap'>\n";
echo " ".$text['label-fax_send_greeting']."\n";
echo "</td>\n";
echo "<td class='vtable' align='left'>\n";
echo " <input class='formfld' type='text' name='fax_send_greeting' maxlength='255' value=\"$fax_send_greeting\">\n";
echo "<br />\n";
echo " ".$text['description-fax_send_greeting']."\n";
echo "</td>\n";
echo "</tr>\n";
echo "<tr>\n";
echo "<td class='vncell' valign='top' align='left' nowrap='nowrap'>\n";
echo " ".$text['label-fax_send_channels']."\n";
echo "</td>\n";
echo "<td class='vtable' align='left'>\n";
echo " <input class='formfld' type='text' name='fax_send_channels' maxlength='255' value=\"$fax_send_channels\">\n";
echo "<br />\n";
echo " ".$text['description-fax_send_channels']."\n";
echo "</td>\n";
echo "</tr>\n";
echo "<tr>\n";
echo "<td class='vncell' valign='top' align='left' nowrap='nowrap'>\n";
echo " ".$text['label-description']."\n";
@ -677,7 +718,6 @@ if (count($_POST)>0 && strlen($_POST["persistformvar"]) == 0) {
echo "".$text['description-info']."\n";
echo "</td>\n";
echo "</tr>\n";
}
echo " <tr>\n";

View File

@ -29,6 +29,7 @@ include "root.php";
require_once "resources/require.php";
require_once "resources/functions/object_to_array.php";
require_once "resources/functions/parse_attachments.php";
require_once "resources/functions/parse_message.php";
require_once "resources/classes/text.php";
//get accounts to monitor
@ -55,6 +56,12 @@ if (sizeof($result) != 0) {
$event_socket['password'] = $record['event_socket_password'];
unset($sql, $prep_statement, $record);
$fax_send_mode_default = $_SESSION['fax']['send_mode']['text'];
if(strlen($fax_send_mode_default) == 0){
$fax_send_mode_default = 'direct';
}
$fax_cover_font_default = $_SESSION['fax']['cover_font']['text'];
foreach ($result as $row) {
//get fax server and account connection details
$fax_uuid = $row["fax_uuid"];
@ -74,12 +81,23 @@ if (sizeof($result) != 0) {
$fax_email_connection_mailbox = $row["fax_email_connection_mailbox"];
$fax_email_outbound_subject_tag = $row["fax_email_outbound_subject_tag"];
$fax_email_outbound_authorized_senders = $row["fax_email_outbound_authorized_senders"];
$fax_send_greeting = $row["fax_send_greeting"];
//load default settings, then domain settings over top
unset($_SESSION);
$_SESSION = $default_settings;
load_domain_settings($domain_uuid);
$fax_send_mode = $_SESSION['fax']['send_mode']['text'];
if(strlen($fax_send_mode) == 0){
$fax_send_mode = $fax_send_mode_default;
}
$fax_cover_font = $_SESSION['fax']['cover_font']['text'];
if(strlen($fax_cover_font) == 0){
$fax_cover_font = $fax_cover_font_default;
}
//load event socket connection parameters
$_SESSION['event_socket_ip_address'] = $event_socket['ip_address'];
$_SESSION['event_socket_port'] = $event_socket['port'];
@ -159,22 +177,14 @@ if (sizeof($result) != 0) {
else {
$fax_numbers[] = $tmp;
}
foreach ($fax_numbers as $index => $fax_number) {
$fax_numbers[$index] = preg_replace("~[^0-9]~", "", $fax_number);
if ($fax_numbers[$index] == '') { unset($fax_numbers[$index]); }
}
unset($fax_subject); //clear so not on cover page
//get email body (if any) for cover page
$fax_message = imap_fetchbody($connection, $email_id, '1.1', FT_UID);
$fax_message = strip_tags($fax_message);
$fax_message = trim($fax_message);
$fax_message = parse_message($connection, $email_id, FT_UID);
if ($fax_message == '') {
$fax_message = imap_fetchbody($connection, $email_id, '1', FT_UID);
$fax_message = strip_tags($fax_message);
$fax_message = trim($fax_message);
$fax_message = str_replace("\r\n\r\n","\r\n", $fax_message);
}
$fax_message = str_replace("\r\n\r\n","\r\n", $fax_message);
// set fax directory (used for pdf creation - cover and/or attachments)
$fax_dir = $_SESSION['switch']['storage']['dir'].'/fax'.(($domain_name != '') ? '/'.$domain_name : null);
@ -200,8 +210,12 @@ if (sizeof($result) != 0) {
}
//send fax
$cwd = getcwd();
$included = true;
require("fax_send.php");
if($cwd){
chdir($cwd);
}
//reset variables
unset($fax_numbers);

View File

@ -84,21 +84,29 @@ if (!$included) {
}
foreach ($result as &$row) {
//set database fields as variables
$fax_uuid = $row["fax_uuid"];
$fax_extension = $row["fax_extension"];
$fax_caller_id_name = $row["fax_caller_id_name"];
$fax_caller_id_number = $row["fax_caller_id_number"];
$fax_accountcode = $row["accountcode"];
$fax_send_greeting = $row["fax_send_greeting"];
//limit to one row
break;
}
unset ($prep_statement);
$fax_send_mode = $_SESSION['fax']['send_mode']['text'];
if(strlen($fax_send_mode) == 0){
$fax_send_mode = 'direct';
}
}
//set the fax directory
$fax_dir = $_SESSION['switch']['storage']['dir'].'/fax'.((count($_SESSION["domains"]) > 1) ? '/'.$_SESSION['domain_name'] : null);
// set fax cover font to generate pdf
$fax_cover_font = $_SESSION['fax']['cover_font']['text'];
}
else {
else{
require_once "resources/classes/EventSocket.php";
}
@ -122,6 +130,68 @@ if(!function_exists('gs_cmd')) {
}
}
if(!function_exists('fax_enqueue')) {
function fax_enqueue($fax_uuid, $fax_file, $wav_file, $fax_uri, $fax_dtmf, $dial_string){
global $db, $db_type;
$task_uuid = uuid();
$dial_string .= "task_uuid='" . $task_uuid . "',";
$description = ''; //! @todo add description
if ($db_type == "pgsql") {
$date_utc_now_sql = "NOW() at time zone 'utc'";
}
if ($db_type == "mysql") {
$date_utc_now_sql = "UTC_TIMESTAMP()";
}
if ($db_type == "sqlite") {
$date_utc_now_sql = "datetime('now')";
}
$sql = <<<HERE
INSERT INTO v_fax_tasks( task_uuid, fax_uuid,
task_next_time, task_lock_time,
task_fax_file, task_wav_file, task_uri, task_dial_string, task_dtmf,
task_interrupted, task_status, task_no_answer_counter, task_no_answer_retry_counter, task_retry_counter,
task_description)
VALUES (?, ?,
$date_utc_now_sql, NULL,
?, ?, ?, ?, ?,
'false', 0, 0, 0, 0,
?);
HERE;
$stmt = $db->prepare($sql);
$i = 0;
$stmt->bindValue(++$i, $task_uuid);
$stmt->bindValue(++$i, $fax_uuid);
$stmt->bindValue(++$i, $fax_file);
$stmt->bindValue(++$i, $wav_file);
$stmt->bindValue(++$i, $fax_uri);
$stmt->bindValue(++$i, $dial_string);
$stmt->bindValue(++$i, $fax_dtmf);
$stmt->bindValue(++$i, $description);
if ($stmt->execute()) {
$response = 'Enqueued';
}
else{
//! @todo log error
$response = 'Fail enqueue';
var_dump($db->errorInfo());
}
unset($stmt);
return $response;
}
}
if(!function_exists('fax_split_dtmf')) {
function fax_split_dtmf(&$fax_number, &$fax_dtmf){
$tmp = array();
$fax_dtmf = '';
if(preg_match('/^\s*(.*?)\s*\((.*)\)\s*$/', $fax_number, $tmp)){
$fax_number = $tmp[1];
$fax_dtmf = $tmp[2];
}
}
}
//get the fax extension
if (strlen($fax_extension) > 0) {
//set the fax directories. example /usr/local/freeswitch/storage/fax/329/inbox
@ -171,14 +241,6 @@ if(!function_exists('gs_cmd')) {
if (($_POST['action'] == "send")) {
$fax_numbers = $_POST['fax_numbers'];
if (sizeof($fax_numbers) > 0) {
foreach ($fax_numbers as $index => $fax_number) {
$fax_numbers[$index] = preg_replace("~[^0-9]~", "", $fax_number);
if ($fax_numbers[$index] == '') { unset($fax_numbers[$index]); }
}
sort($fax_numbers);
}
$fax_uuid = check_str($_POST["id"]);
$fax_caller_id_name = check_str($_POST['fax_caller_id_name']);
$fax_caller_id_number = check_str($_POST['fax_caller_id_number']);
@ -200,6 +262,23 @@ if(!function_exists('gs_cmd')) {
$continue = true;
}
// cleanup numbers
if (isset($fax_numbers)) {
foreach ($fax_numbers as $index => $fax_number) {
fax_split_dtmf($fax_number, $fax_dtmf);
$fax_number = preg_replace("~[^0-9]~", "", $fax_number);
$fax_dtmf = preg_replace("~[^0-9Pp*#]~", "", $fax_dtmf);
if ($fax_number != ''){
if ($fax_dtmf != '') {$fax_number .= " (" . $fax_dtmf . ")";}
$fax_numbers[$index] = $fax_number;
}
else{
unset($fax_numbers[$index]);
}
}
sort($fax_numbers);
}
if ($continue) {
//determine page size
switch ($fax_page_size) {
@ -322,6 +401,19 @@ if(!function_exists('gs_cmd')) {
$pdf -> setPrintFooter(false);
$pdf -> SetMargins(0, 0, 0, true);
if(strlen($fax_cover_font) > 0){
if(substr($fax_cover_font, -4) == '.ttf'){
$pdf_font = TCPDF_FONTS::addTTFfont($fax_cover_font);
}
else{
$pdf_font = $fax_cover_font;
}
}
if(!$pdf_font){
$pdf_font = 'times';
}
//add blank page
$pdf -> AddPage('P', array($page_width, $page_height));
@ -329,9 +421,6 @@ if(!function_exists('gs_cmd')) {
$x = 0;
$y = 0;
// output grid
//showgrid($pdf);
//logo
$display_logo = false;
if (!isset($_SESSION['fax']['cover_logo']['text'])) {
@ -375,23 +464,23 @@ if(!function_exists('gs_cmd')) {
//header
if ($fax_header != '') {
$pdf -> SetLeftMargin(0.5);
$pdf -> SetFont("times", "", 10);
$pdf -> SetFont($pdf_font, "", 10);
$pdf -> Write(0.3, $fax_header);
}
//fax, cover sheet
$pdf -> SetTextColor(0,0,0);
$pdf -> SetFont("times", "B", 55);
$pdf -> SetFont($pdf_font, "B", 55);
$pdf -> SetXY($x + 4.55, $y + 0.25);
$pdf -> Cell($x + 3.50, $y + 0.4, $text['label-fax-fax'], 0, 0, 'R', false, null, 0, false, 'T', 'T');
$pdf -> SetFont("times", "", 12);
$pdf -> SetFont($pdf_font, "", 12);
$pdf -> SetFontSpacing(0.0425);
$pdf -> SetXY($x + 4.55, $y + 1.0);
$pdf -> Cell($x + 3.50, $y + 0.4, $text['label-fax-cover-sheet'], 0, 0, 'R', false, null, 0, false, 'T', 'T');
$pdf -> SetFontSpacing(0);
//field labels
$pdf -> SetFont("times", "B", 12);
$pdf -> SetFont($pdf_font, "B", 12);
if ($fax_recipient != '' || sizeof($fax_numbers) > 0) {
$pdf -> Text($x + 0.5, $y + 2.0, strtoupper($text['label-fax-recipient']).":");
}
@ -406,7 +495,7 @@ if(!function_exists('gs_cmd')) {
}
//field values
$pdf -> SetFont("times", "", 12);
$pdf -> SetFont($pdf_font, "", 12);
$pdf -> SetXY($x + 2.0, $y + 1.95);
if ($fax_recipient != '') {
$pdf -> Write(0.3, $fax_recipient);
@ -446,7 +535,7 @@ if(!function_exists('gs_cmd')) {
//message
$pdf -> Rect($x + 0.5, $y + 3.4, 7.5, 6.25, 'D');
if ($fax_message != '') {
$pdf -> SetFont("times", "", 12);
$pdf -> SetFont($pdf_font, "", 12);
$pdf -> SetXY($x + 0.75, $y + 3.65);
$pdf -> MultiCell(7, 5.75, $fax_message, 0, 'L', false);
}
@ -534,7 +623,7 @@ if(!function_exists('gs_cmd')) {
}
//preview, if requested
if ($_REQUEST['submit'] == $text['button-preview']) {
if (($_REQUEST['submit'] != '') && ($_REQUEST['submit'] == $text['button-preview'])) {
unset($file_type);
if (file_exists($dir_fax_temp.'/'.$fax_instance_uuid.'.pdf')) {
$file_type = 'pdf';
@ -599,29 +688,64 @@ if(!function_exists('gs_cmd')) {
}
//send the fax
$fax_file = $dir_fax_temp."/".$fax_instance_uuid.".tif";
$common_dial_string = "for_fax=1,";
$common_dial_string .= "accountcode='" . $fax_accountcode . "',";
$common_dial_string .= "sip_h_X-accountcode='" . $fax_accountcode . "',";
$common_dial_string .= "domain_uuid=" . $_SESSION["domain_uuid"] . ",";
$common_dial_string .= "domain_name=" . $_SESSION["domain_name"] . ",";
$common_dial_string .= "mailto_address='" . $mailto_address . "',";
$common_dial_string .= "mailfrom_address='" . $mailfrom_address . "',";
$common_dial_string .= "origination_caller_id_name='" . $fax_caller_id_name . "',";
$common_dial_string .= "origination_caller_id_number='" . $fax_caller_id_number . "',";
$common_dial_string .= "fax_ident='" . $fax_caller_id_number . "',";
$common_dial_string .= "fax_header='" . $fax_caller_id_name . "',";
$common_dial_string .= "fax_file='" . $fax_file . "',";
foreach ($fax_numbers as $fax_number) {
$fp = event_socket_create($_SESSION['event_socket_ip_address'], $_SESSION['event_socket_port'], $_SESSION['event_socket_password']);
if ($fp) {
//prepare the fax command
$route_array = outbound_route_to_bridge($_SESSION['domain_uuid'], $fax_prefix.$fax_number);
$fax_file = $dir_fax_temp."/".$fax_instance_uuid.".tif";
if (count($route_array) == 0) {
//send the internal call to the registered extension
$fax_uri = "user/".$fax_number."@".$_SESSION['domain_name'];
$t38 = "";
$dial_string = $common_dial_string;
fax_split_dtmf($fax_number, $fax_dtmf);
//prepare the fax command
$route_array = outbound_route_to_bridge($_SESSION['domain_uuid'], $fax_prefix . $fax_number);
if (count($route_array) == 0) {
//send the internal call to the registered extension
$fax_uri = "user/".$fax_number."@".$_SESSION['domain_name'];
$t38 = "";
}
else {
//send the external call
$fax_uri = $route_array[0];
$t38 = "fax_enable_t38=true,fax_enable_t38_request=true,";
}
if ($fax_send_mode != 'queue') {
$dial_string .= $t38;
$dial_string .= "fax_uri=" . $fax_uri . ",";
$dial_string .= "fax_retry_attempts=1" . ",";
$dial_string .= "fax_retry_limit=20" . ",";
$dial_string .= "fax_retry_sleep=180" . ",";
$dial_string .= "fax_verbose=true" . ",";
$dial_string .= "fax_use_ecm=off" . ",";
$dial_string .= "api_hangup_hook='lua fax_retry.lua'";
$dial_string = "{" . $dial_string . "}" . $fax_uri." &txfax('".$fax_file."')";
$fp = event_socket_create($_SESSION['event_socket_ip_address'], $_SESSION['event_socket_port'], $_SESSION['event_socket_password']);
if ($fp) {
$cmd = "api originate " . $dial_string;
// echo($cmd . "<br/>\n");
//send the command to event socket
$response = event_socket_request($fp, $cmd);
$response = str_replace("\n", "", $response);
$uuid = str_replace("+OK ", "", $response);
}
else {
//send the external call
$fax_uri = $route_array[0];
$t38 = "fax_enable_t38=true,fax_enable_t38_request=true,";
}
$cmd = "api originate {for_fax=1,accountcode='".$fax_accountcode."',sip_h_X-accountcode='".$fax_accountcode."',domain_uuid=".$_SESSION["domain_uuid"].",domain_name=".$_SESSION["domain_name"].",mailto_address='".$mailto_address."',mailfrom_address='".$mailfrom_address."',origination_caller_id_name='".$fax_caller_id_name."',origination_caller_id_number='".$fax_caller_id_number."',fax_ident='".$fax_caller_id_number."',fax_header='".$fax_caller_id_name."',fax_uri=".$fax_uri.",fax_file='".$fax_file."',fax_retry_attempts=1,fax_retry_limit=20,fax_retry_sleep=180,fax_verbose=true,fax_use_ecm=off,".$t38."api_hangup_hook='lua fax_retry.lua'}".$fax_uri." &txfax('".$fax_file."')";
//send the command to event socket
$response = event_socket_request($fp, $cmd);
$response = str_replace("\n", "", $response);
$uuid = str_replace("+OK ", "", $response);
fclose($fp);
}
else{ // enqueue
$wav_file = ''; //! @todo add custom message
$response = fax_enqueue($fax_uuid, $fax_file, $wav_file, $fax_uri, $fax_dtmf, $dial_string);
}
}
//wait for a few seconds

View File

@ -0,0 +1,38 @@
<?php
function parse_message($connection, $message_number, $option = '', $to_charset = 'UTF-8') {
$structure = imap_fetchstructure($connection, $message_number, $option);
if(isset($structure->parts) && count($structure->parts)) {
for($i = 0; $i < count($structure->parts); $i++) {
$msg = '';
$part = $structure->parts[$i];
if($part->type == TYPETEXT){
$msg = imap_fetchbody($connection, $message_number, $i+1, $option);
if($part->encoding == ENCBASE64){
$msg = base64_decode($msg);
}
else if($part->encoding == ENCQUOTEDPRINTABLE){
$msg = quoted_printable_decode($msg);
}
if($msg && $to_charset){
$charset = '';
if(isset($part->parameters) && count($part->parameters)) {
foreach($part->parameters as &$parameter){
if($parameter->attribute == 'CHARSET') {
$charset = $parameter->value;
break;
}
}
}
if($charset){
$msg = mb_convert_encoding($msg, $to_charset, $charset);
}
}
if($msg){
return $msg;
}
}
}
}
}

View File

@ -0,0 +1,181 @@
-- @usage without queue
-- api: originate {fax_file='',wav_file='',fax_dtmf=''}user/108@domain.local &lua(fax_queue/exec.lua)
-- @usage with queue task
-- api: originate {task_uuid=''}user/108@domain.local &lua(fax_queue/exec.lua)
-- @fax_dtmf
-- 0-9*# - dtmf symbols
-- @200 - dtmf duration in ms
-- p - pause 500 ms
-- P - pause 1000 ms
--
-- example: pause 5 sec dial 008 pause 2 sec paly greeting
-- PPPPP008@300PP
--
require "resources.functions.config"
local log = require "resources.functions.log".fax_task
-- If we handle queue task
local task_uuid = session:getVariable('task_uuid')
local task if task_uuid then
local Tasks = require "fax_queue.tasks"
task = Tasks.select_task(task_uuid)
if not task then
log.warningf("Can not found fax task: %q", tostring(task_uuid))
return
end
end
if task then
local str = 'Queue task :'
for k, v in pairs(task) do
str = str .. ('\n %q = %q'):format(k, v)
end
log.info(str)
else
log.info('Not queued task')
end
local function empty(t) return (not t) or (#t == 0) end
local function not_empty(t) if not empty(t) then return t end end
local dtmf, wav_file, fax_file
if task then
dtmf = not_empty(task.dtmf)
wav_file = not_empty(task.wav_file) or not_empty(task.greeting)
fax_file = not_empty(task.fax_file)
else
dtmf = not_empty(session:getVariable('fax_dtmf'))
wav_file = not_empty(session:getVariable('wav_file'))
fax_file = not_empty(session:getVariable('fax_file'))
end
if not (wav_file or fax_file) then
log.warning("No fax or wav file")
return
end
local function decode_dtmf(dtmf)
local r, sleep, seq = {}
dtmf:gsub('P', 'pp'):gsub('.', function(ch)
if ch == ';' or ch == ',' then
r[#r + 1] = sleep or seq
sleep, seq = nil
elseif ch == 'p' then
r[#r + 1] = seq
sleep = (sleep or 0) + 500
seq = nil
else
r[#r + 1] = sleep
seq = (seq or '') .. ch
sleep = nil
end
end)
r[#r + 1] = sleep or seq
return r
end
local function send_fax()
session:execute("txfax", fax_file)
end
local function start_fax_detect(detect_duration)
if not tone_detect_cb then
function tone_detect_cb(s, type, obj, arg)
if type == "event" then
if obj:getHeader('Event-Name') == 'DETECTED_TONE' then
return "false"
end
end
end
end
log.notice("Start detecting fax")
detect_duration = detect_duration or 60000
session:setInputCallback("tone_detect_cb")
session:execute("tone_detect", "txfax 2100 r +" .. tostring(detect_duration) .. " set remote_fax_detected=txfax")
session:execute("tone_detect", "rxfax 1100 r +" .. tostring(detect_duration) .. " set remote_fax_detected=rxfax")
session:setVariable("sip_api_on_image", "uuid_break " .. session:getVariable("uuid") .. " all")
end
local function stop_fax_detect()
session:unsetInputCallback()
session:execute("stop_tone_detect")
session:setVariable("sip_api_on_image", "")
end
local function fax_deteced()
if session:getVariable('has_t38') == 'true' then
log.noticef('Detected t38')
session:setVariable('remote_fax_detected', 'txfax')
end
if fax_file and session:getVariable('remote_fax_detected') then
log.noticef("Detected %s", session:getVariable('remote_fax_detected'))
if session:getVariable('remote_fax_detected') == 'txfax' then
send_fax()
else
log.warning('Remote fax try send fax')
end
return true
end
end
local function check()
if not session:ready() then return false end
if fax_deteced() then return false end
return true
end
local function task()
session:waitForAnswer(session)
while not session:answered() do
if not session:ready() then return end
session:sleep(500)
end
if not (session:ready() and session:answered()) then return end
if fax_file and wav_file then
start_fax_detect()
end
if dtmf then
dtmf = decode_dtmf(dtmf)
for _, element in ipairs(dtmf) do
if type(element) == 'number' then
session:streamFile("silence_stream://" .. tostring(element))
else
session:execute("send_dtmf", element)
end
if not check() then return end
end
end
if wav_file then
session:streamFile(wav_file)
if not check() then return end
end
if fax_file then
if wav_file then
stop_fax_detect()
end
send_fax()
end
end
log.noticef("START TASK")
log.notice("Fax:" .. tostring(fax_file))
log.notice("Wav:" .. tostring(wav_file))
task()
log.noticef("STOP TASK")
log.notice("Ready: " .. tostring(session:ready()))
log.notice("Answered: " .. tostring(session:answered()))

View File

@ -0,0 +1,69 @@
require "resources.functions.config"
require "resources.functions.sleep"
local log = require "resources.functions.log".next_fax_task
local Tasks = require "fax_queue.tasks"
local Esl = require "resources.functions.esl"
local FAX_OPTIONS = {
"fax_use_ecm=false,fax_enable_t38=true,fax_enable_t38_request=true,fax_disable_v17=default";
"fax_use_ecm=true,fax_enable_t38=true,fax_enable_t38_request=true,fax_disable_v17=false";
"fax_use_ecm=true,fax_enable_t38=false,fax_enable_t38_request=false,fax_disable_v17=false";
"fax_use_ecm=true,fax_enable_t38=true,fax_enable_t38_request=true,fax_disable_v17=true";
"fax_use_ecm=false,fax_enable_t38=false,fax_enable_t38_request=false,fax_disable_v17=false";
}
local function next_task()
local task, err = Tasks.next_task()
if not task then
if err then
log.noticef('Can not select next task: %s', tostring(err))
else
log.notice("No task")
end
return
end
local esl
local ok, err = pcall(function()
for k, v in pairs(task) do
print(string.format(" `%s` => `%s`", tostring(k), tostring(v)))
end
local mode = (task.retry_counter % #FAX_OPTIONS) + 1
local dial_string = '{' ..
task.dial_string .. "api_hangup_hook='lua fax_queue/retry.lua'," ..
FAX_OPTIONS[mode] ..
'}' .. task.uri
local originate = 'originate ' .. dial_string .. ' &lua(fax_queue/exec.lua)'
log.notice(originate)
esl = assert(Esl.new())
local ok, err = esl:api(originate)
log.notice(ok or err)
end)
if esl then esl:close() end
if not ok then
Tasks.release_task(task)
log.noticef("Error execute task: %s", tostring(err))
end
return true
end
local function poll_once()
Tasks.cleanup_tasks()
while next_task() do
sleep(5000)
end
Tasks.release_db()
end
return {
poll_once = poll_once;
}

View File

@ -0,0 +1,346 @@
-- include libraries
require "resources.functions.config";
require "resources.functions.explode";
require "resources.functions.split";
require "resources.functions.count";
local log = require "resources.functions.log".fax_retry
local Database = require "resources.functions.database"
local Settings = require "resources.functions.lazy_settings"
local Tasks = require "fax_queue.tasks"
local task_uuid = env:getHeader('task_uuid')
local task = Tasks.select_task(task_uuid)
if not task then
log.warningf("Can not find fax task: %q", tostring(task_uuid))
return
end
-- show all channel variables
if debug["fax_serialize"] then
log.noticef("info:\n%s", env:serialize())
end
local dbh = Database.new('system')
-- Channel/FusionPBX variables
local uuid = env:getHeader("uuid")
local domain_uuid = env:getHeader("domain_uuid") or task.domain_uuid
local domain_name = env:getHeader("domain_name") or task.domain_name
local origination_caller_id_name = env:getHeader("origination_caller_id_name") or '000000000000000'
local origination_caller_id_number = env:getHeader("origination_caller_id_number") or '000000000000000'
local accountcode = env:getHeader("accountcode")
local duration = tonumber(env:getHeader("billmsec")) or 0
local sip_to_user = env:getHeader("sip_to_user")
local bridge_hangup_cause = env:getHeader("bridge_hangup_cause")
local hangup_cause_q850 = tonumber(env:getHeader("hangup_cause_q850"))
local answered = duration > 0
-- fax variables
local fax_success = env:getHeader('fax_success')
local has_t38 = env:getHeader('has_t38') or 'false'
local t38_broken_boolean = env:getHeader('t38_broken_boolean') or ''
local fax_result_code = tonumber(env:getHeader('fax_result_code')) or 2
local fax_result_text = env:getHeader('fax_result_text') or 'FS_NOT_SET'
local fax_ecm_used = env:getHeader('fax_ecm_used') or ''
local fax_local_station_id = env:getHeader('fax_local_station_id') or ''
local fax_document_transferred_pages = env:getHeader('fax_document_transferred_pages') or nil
local fax_document_total_pages = env:getHeader('fax_document_total_pages') or nil
local fax_image_resolution = env:getHeader('fax_image_resolution') or ''
local fax_image_size = env:getHeader('fax_image_size') or nil
local fax_bad_rows = env:getHeader('fax_bad_rows') or nil
local fax_transfer_rate = env:getHeader('fax_transfer_rate') or nil
local fax_v17_disabled = env:getHeader('fax_v17_disabled') or ''
local fax_ecm_requested = env:getHeader('fax_ecm_requested') or ''
local fax_remote_station_id = env:getHeader('fax_remote_station_id') or ''
local fax_options = ("fax_use_ecm=%s,fax_enable_t38=%s,fax_enable_t38_request=%s,fax_disable_v17=%s"):format(
env:getHeader('fax_use_ecm') or '',
env:getHeader('fax_enable_t38') or '',
env:getHeader('fax_enable_t38_request') or '',
env:getHeader('fax_disable_v17') or ''
)
-- Fax task params
local fax_uri = env:getHeader("fax_uri") or task.uri
local fax_file = env:getHeader("fax_file") or task.fax_file
local wav_file = env:getHeader("wav_file") or task.wav_file
local fax_uuid = task.fax_uuid
-- Email variables
local email_address = env:getHeader("mailto_address")
local from_address = env:getHeader("mailfrom_address") or email_address
local number_dialed = fax_uri:match("/([^/]-)%s*$")
local email_message_fail = "We are sorry the fax failed to go through. It has been attached. Please check the number "..number_dialed..", and if it was correct you might consider emailing it instead."
local email_message_success = "We are happy to report the fax was sent successfully. It has been attached for your records."
log.noticef([[<<< CALL RESULT >>>
uuid: = '%s'
answered: = '%s'
fax_file: = '%s'
wav_file: = '%s'
fax_uri: = '%s'
sip_to_user: = '%s'
accountcode: = '%s'
origination_caller_id_name: = '%s'
origination_caller_id_number: = '%s'
mailfrom_address: = '%s'
mailto_address: = '%s'
hangup_cause_q850: = '%s'
fax_options = '%s'
]],
tostring(uuid) ,
tostring(answered) ,
tostring(fax_file) ,
tostring(wav_file) ,
tostring(fax_uri) ,
tostring(sip_to_user) ,
tostring(accountcode) ,
tostring(origination_caller_id_name) ,
tostring(origination_caller_id_number) ,
tostring(from_address) ,
tostring(email_address) ,
tostring(hangup_cause_q850) ,
fax_options
)
if fax_success then
log.noticef([[<<< FAX RESULT >>>
fax_success = '%s'
has_t38 = '%s'
t38_broken_boolean = '%s'
fax_result_code = '%s'
fax_result_text = '%s'
fax_ecm_used = '%s'
fax_local_station_id = '%s'
fax_document_transferred_pages = '%s'
fax_document_total_pages = '%s'
fax_image_resolution = '%s'
fax_image_size = '%s'
fax_bad_rows = '%s'
fax_transfer_rate = '%s'
fax_v17_disabled = '%s'
fax_ecm_requested = '%s'
fax_remote_station_id = '%s'
'%s'
]],
fax_success ,
has_t38 ,
t38_broken_boolean ,
fax_result_code ,
fax_result_text ,
fax_ecm_used ,
fax_local_station_id ,
fax_document_transferred_pages ,
fax_document_total_pages ,
fax_image_resolution ,
fax_image_size ,
fax_bad_rows ,
fax_transfer_rate ,
fax_v17_disabled ,
fax_ecm_requested ,
fax_remote_station_id ,
'---------------------------------'
)
end
--get the values from the fax file
if not (fax_uuid and domain_name) then
local array = split(fax_file, "[\\/]+")
domain_name = domain_name or array[#array - 3]
local fax_extension = fax_extension or array[#array - 2]
if not fax_uuid then
local sql = "SELECT fax_uuid FROM v_fax "
sql = sql .. "WHERE domain_uuid = '" .. domain_uuid .. "' "
sql = sql .. "AND fax_extension = '" .. fax_extension .. "' "
fax_uuid = dbh:first_value(sql);
end
end
--get the domain_uuid using the domain name required for multi-tenant
if domain_name and not domain_uuid then
local sql = "SELECT domain_uuid FROM v_domains ";
sql = sql .. "WHERE domain_name = '" .. domain_name .. "' "
domain_uuid = dbh:first_value(sql)
end
assert(domain_name and domain_uuid)
--settings
local settings = Settings.new(dbh, domain_name, domain_uuid)
local keep_local = settings:get('fax', 'keep_local','boolean')
local storage_type = (keep_local == "false") and "" or settings:get('fax', 'storage_type', 'text')
--be sure accountcode is not empty
if (accountcode == nil) then
accountcode = domain_name
end
local function opt(v, default)
if v then return "'" .. v .. "'" end
return default or 'NULL'
end
local function now_sql()
return (database["type"] == "sqlite") and "'"..os.date("%Y-%m-%d %X").."'" or "now()";
end
--add to fax logs
do
local fields = {
"fax_log_uuid";
"domain_uuid";
"fax_uuid";
"fax_success";
"fax_result_code";
"fax_result_text";
"fax_file";
"fax_ecm_used";
"fax_local_station_id";
"fax_document_transferred_pages";
"fax_document_total_pages";
"fax_image_resolution";
"fax_image_size";
"fax_bad_rows";
"fax_transfer_rate";
"fax_retry_attempts";
"fax_retry_limit";
"fax_retry_sleep";
"fax_uri";
"fax_date";
"fax_epoch";
}
local values = {
"'"..uuid .. "'";
"'"..domain_uuid .. "'";
opt(fax_uuid);
opt(fax_success);
opt(fax_result_code);
opt(fax_result_text);
opt(fax_file);
opt(fax_ecm_used);
opt(fax_local_station_id);
opt(fax_document_transferred_pages, "'0'");
opt(fax_document_total_pages, "'0'");
opt(fax_image_resolution);
opt(fax_image_size);
opt(fax_bad_rows);
opt(fax_transfer_rate);
opt(fax_retry_attempts);
opt(fax_retry_limit);
opt(fax_retry_sleep);
opt(fax_uri);
now_sql();
"'"..os.time().."' ";
}
local sql = "insert into v_fax_logs(" .. table.concat(fields, ",") .. ")" ..
"values(" .. table.concat(values, ",") .. ")"
if (debug["sql"]) then
log.noticef("SQL: %s", sql);
end
dbh:query(sql);
end
-- add the fax files
if fax_success == "1" then
if storage_type == "base64" then
--include the base64 function
require "resources.functions.base64";
--base64 encode the file
local f = io.open(fax_file, "rb");
if not f then
log.waitng("Can not find file %s", fax_file)
storage_type = nil
else
local file_content = f:read("*all");
f:close()
fax_base64 = base64.encode(file_content)
end
end
-- build SQL
local sql do
sql = {
"insert into v_fax_files(";
"fax_file_uuid"; ",";
"fax_uuid"; ",";
"fax_mode"; ",";
"fax_destination"; ",";
"fax_file_type"; ",";
"fax_file_path"; ",";
"fax_caller_id_name"; ",";
"fax_caller_id_number"; ",";
"fax_date"; ",";
"fax_epoch"; ",";
"fax_base64"; ",";
"domain_uuid"; " ";
") values (";
opt(uuid); ",";
opt(fax_uuid); ",";
"'tx'"; ",";
opt(sip_to_user); ",";
"'tif'"; ",";
opt(fax_file); ",";
opt(origination_caller_id_name); ",";
opt(origination_caller_id_number); ",";
now_sql(); ",";
"'" .. os.time() .. "'"; ",";
opt(fax_base64); ",";
opt(domain_uuid); " ";
")"
}
sql = table.concat(sql, "\n");
if (debug["sql"]) then
log.noticef("SQL: %s", sql);
end
end
if storage_type == "base64" then
local db_type, db_cnn = split_first(database["system"], "://", true)
local luasql = require ("luasql." .. db_type);
local env = assert (luasql[db_type]());
local dbh = env:connect(db_cnn);
dbh:execute(sql)
dbh:close()
env:close()
else
result = dbh:query(sql)
end
end
if fax_success == "1" then
--Success
log.infof("RETRY STATS SUCCESS: GATEWAY[%s]", fax_options);
if keep_local == "false" then
os.remove(fax_file);
end
Tasks.remove_task(task)
end
if fax_success ~= "1" then
if not answered then
log.noticef("no answer: %d", hangup_cause_q850)
else
if not fax_success then
log.noticef("Fax not detected: %s", fax_options)
else
log.noticef("fax fail %s", fax_options)
end
end
Tasks.wait_task(task, answered, hangup_cause_q850)
if task.status ~= 0 then
Tasks.remove_task(task)
end
end

View File

@ -0,0 +1,251 @@
local Database = require "resources.functions.database"
local Settings = require "resources.functions.lazy_settings"
local db
local date_utc_now_sql
local now_add_sec_sql
if database.type == 'pgsql' then
date_utc_now_sql = "NOW() at time zone 'utc'"
now_add_sec_sql = "NOW() at time zone 'utc' + interval '%s second'"
elseif database.type == 'mysql' then
date_utc_now_sql = "UTC_TIMESTAMP()"
now_add_sec_sql = "DATE_ADD(UTC_TIMESTAMP(), INTERVAL %s SECOND)"
elseif database.type == 'sqlite' then
date_utc_now_sql = "datetime('now')"
now_add_sec_sql = "datetime('now', '%s seconds')"
else
error("unsupported database type: " .. database.type)
end
-- Broken on FS 1.4 with native postgresql
-- Fixed on 1.6.0
-- Also works with ODBC
local ignore_affected_rows = true
if dbh_affected_rows_broken ~= nil then
ignore_affected_rows = dbh_affected_rows_broken
end
local Q850_TIMEOUT = {
[17] = 60;
}
local select_task_common_sql = [[
select
t1.task_uuid as uuid,
t1.fax_uuid as fax_uuid,
t3.domain_name,
t3.domain_uuid,
t1.task_status as status,
t1.task_uri as uri,
t1.task_dial_string as dial_string,
t1.task_dtmf as dtmf,
t1.task_fax_file as fax_file,
t1.task_wav_file as wav_file,
t1.task_no_answer_counter as no_answer_counter,
t1.task_no_answer_retry_counter as no_answer_retry_counter,
t1.task_retry_counter as retry_counter,
t2.fax_send_greeting as greeting
from v_fax_tasks t1
inner join v_fax t2 on t2.fax_uuid = t1.fax_uuid
inner join v_domains t3 on t2.domain_uuid = t3.domain_uuid
where t1.task_interrupted <> 'true'
]]
local next_task_sql = select_task_common_sql .. [[
and t1.task_status = 0 and t1.task_next_time < ]] .. date_utc_now_sql .. [[
and t2.fax_send_channels > (select count(*) from v_fax_tasks as tasks
where tasks.fax_uuid = t1.fax_uuid and
tasks.task_status > 0 and tasks.task_status <= 2
)
order by t1.task_next_time
]]
local select_task_sql = select_task_common_sql .. "and t1.task_uuid='%s'"
local aquire_task_sql = [[
update v_fax_tasks set task_status = 1, task_lock_time = ]] .. date_utc_now_sql .. [[
where task_uuid = '%s' and task_status = 0
]]
local wait_task_sql = [[
update v_fax_tasks
set task_status = %s,
task_lock_time = NULL,
task_no_answer_counter = %s,
task_no_answer_retry_counter = %s,
task_retry_counter = %s,
task_next_time = ]] .. now_add_sec_sql .. [[
where task_uuid = '%s'
]]
local remove_task_task_sql = [[
delete from v_fax_tasks
where task_uuid = '%s'
]]
local release_task_sql = [[
update v_fax_tasks
set task_status = 0, task_lock_time = NULL,
task_next_time = ]] .. now_add_sec_sql .. [[
where task_uuid = '%s'
]]
local release_stuck_tasks_sql = [[
update v_fax_tasks
set task_status = 0, task_lock_time = NULL,
task_next_time = ]] .. date_utc_now_sql .. [[
where task_lock_time < ]] .. now_add_sec_sql:format('-3600') .. [[
]]
local remove_finished_tasks_sql = [[
delete from v_fax_tasks where task_status > 3
]]
local function get_db()
if not db then
db = assert(Database.new('system'))
end
return db
end
local function next_task()
local db = get_db()
while true do
local task, err = db:first_row(next_task_sql)
if not task then return nil, err end
local ok, err = db:query( aquire_task_sql:format(task.uuid) )
if not ok then return nil, err end
local rows = db:affected_rows()
if ignore_affected_rows then
rows = 1
end
if rows == 1 then
task.no_answer_counter = tonumber(task.no_answer_counter)
task.no_answer_retry_counter = tonumber(task.no_answer_retry_counter)
task.retry_counter = tonumber(task.retry_counter)
return task
end
end
end
local function select_task(task_uuid)
local db = get_db()
local task, err = db:first_row(select_task_sql:format(task_uuid))
if not task then return nil, err end
task.no_answer_counter = tonumber(task.no_answer_counter)
task.no_answer_retry_counter = tonumber(task.no_answer_retry_counter)
task.retry_counter = tonumber(task.retry_counter)
return task
end
local function wait_task(task, answered, q850)
local db = get_db()
local interval = 30
local settings = Settings.new(db, task.domain_name, task.domain_uuid)
task.status = 0
if not answered then
interval = Q850_TIMEOUT[q850 or 17] or interval
end
if not answered then
local fax_send_no_answer_retry_limit = tonumber(settings:get('fax', 'send_no_answer_retry_limit', 'numeric')) or 0
task.no_answer_retry_counter = task.no_answer_retry_counter + 1
if task.no_answer_retry_counter >= fax_send_no_answer_retry_limit then
task.no_answer_retry_counter = 0
task.no_answer_counter = task.no_answer_counter + 1
local fax_send_no_answer_limit = tonumber(settings:get('fax', 'send_no_answer_limit', 'numeric')) or 0
if task.no_answer_counter >= fax_send_no_answer_limit then
task.status = 4
else
interval = tonumber(settings:get('fax', 'send_no_answer_interval', 'numeric')) or interval
end
else
interval = tonumber(settings:get('fax', 'send_no_answer_retry_interval', 'numeric')) or interval
end
else
task.retry_counter = task.retry_counter + 1
local fax_send_retry_limit = tonumber(settings:get('fax', 'send_retry_limit', 'numeric')) or 0
if task.retry_counter >= fax_send_retry_limit then
task.status = 4
else
interval = tonumber(settings:get('fax', 'send_retry_interval', 'numeric')) or interval
task.task_seq_call_counter = 0
end
end
local sql = wait_task_sql:format(
tostring(task.status),
tostring(task.no_answer_counter),
tostring(task.no_answer_retry_counter),
tostring(task.retry_counter),
tostring(interval),
task.uuid
)
print(sql)
local ok, err = db:query( sql )
if not ok then return nil, err end
return task
end
local function remove_task(task)
local db = get_db()
local sql = remove_task_task_sql:format(task.uuid)
local ok, err = db:query( sql )
if not ok then return nil, err end
return db:affected_rows()
end
local function release_task(task)
local db = get_db()
local interval = 30
local sql = release_task_sql:format(
tostring(interval),
task.uuid
)
local ok, err = db:query( sql )
if not ok then return nil, err end
return task
end
local function cleanup_tasks()
local db = get_db()
db:query(release_stuck_tasks_sql)
db:query(remove_finished_tasks_sql)
end
return {
release_db = function()
if db then
db:release()
db = nil
end
end;
next_task = next_task;
wait_task = wait_task;
select_task = select_task;
remove_task = remove_task;
release_task = release_task;
cleanup_tasks = cleanup_tasks;
}

View File

@ -0,0 +1,42 @@
local sleep_interval = 60;
--include config.lua
require "resources.functions.config";
--general functions
require "resources.functions.file_exists";
require "resources.functions.mkdir";
require "resources.functions.sleep";
local log = require "resources.functions.log".fax_queue_monitor
local Next = require "fax_queue.next"
mkdir(scripts_dir .. "/run");
--define the run file
local run_file = scripts_dir .. "/run/fax_queue.tmp";
--used to stop the lua service
local file = assert(io.open(run_file, "w"));
file:write("remove this file to stop the script");
file:close()
log.notice("Start")
while true do
local ok, err = pcall(function()
Next.poll_once()
end)
if not ok then
log.errf("fail poll queue: %s", tostring(err))
end
if not file_exists(run_file) then
break;
end
sleep(sleep_interval * 1000)
end
log.notice("Stop")

View File

@ -0,0 +1 @@
require "fax_queue.next".poll_once()

View File

@ -8,7 +8,13 @@
require "resources.functions.trim";
local api = api or freeswitch.API();
local api = api
if (not api) and freeswitch then api = freeswitch.API() else
api = {}
function api:execute()
return '-ERR UNSUPPORTTED'
end
end
local function send_event(action, key)
local event = freeswitch.Event("MEMCACHE", action);

View File

@ -1,115 +1,190 @@
require 'resources.functions.config'
require 'resources.functions.file_exists'
require 'resources.functions.database_handle'
local unpack = unpack or table.unpack
-----------------------------------------------------------
local OdbcDatabase = {} if not freeswitch then
OdbcDatabase.__index = OdbcDatabase
local Database = {} do
local odbc = require "odbc.dba"
Database.__index = Database
function OdbcDatabase.new(name)
local self = setmetatable({}, OdbcDatabase)
function Database.new(name)
local dbh = assert(name)
if type(name) == 'string' then
if name == 'switch' and file_exists(database_dir.."/core.db") then
dbh = freeswitch.Dbh("sqlite://"..database_dir.."/core.db")
else
dbh = database_handle(name)
end
end
assert(dbh:connected())
local connection_string = assert(database[name])
local self = setmetatable({
_dbh = dbh;
}, Database)
local typ, dsn, user, password = connection_string:match("^(.-)://(.-):(.-):(.-)$")
assert(typ == 'odbc', "unsupported connection string:" .. connection_string)
return self
self._dbh = odbc.Connect(dsn, user, password)
return self
end
function Database:query(sql, fn)
if (fn == nil) then
return self._dbh:query(sql)
else
return self._dbh:query(sql, fn)
end
function OdbcDatabase:query(sql, fn)
self._rows_affected = nil
if fn then
return self._dbh:neach(sql, function(row)
local o = {}
for k, v in pairs(row) do
if v == odbc.NULL then
o[k] = nil
else
o[k] = tostring(v)
end
end
return fn(o)
end)
end
local ok, err = self._dbh:exec(sql)
if not ok then return nil, err end
self._rows_affected = ok
return self._rows_affected
end
function OdbcDatabase:affected_rows()
return self._rows_affected;
end
function OdbcDatabase:release()
if self._dbh then
self._dbh:destroy()
self._dbh = nil
end
end
function OdbcDatabase:connected()
return self._dbh and self._dbh:connected()
end
end
-----------------------------------------------------------
-----------------------------------------------------------
local FsDatabase = {} if freeswitch then
require "resources.functions.file_exists"
require "resources.functions.database_handle"
FsDatabase.__index = FsDatabase
function FsDatabase.new(name)
local dbh = assert(name)
if type(name) == 'string' then
if name == 'switch' and file_exists(database_dir.."/core.db") then
dbh = freeswitch.Dbh("sqlite://"..database_dir.."/core.db")
else
dbh = database_handle(name)
end
end
assert(dbh:connected())
local self = setmetatable({
_dbh = dbh;
}, FsDatabase)
return self
end
function FsDatabase:query(sql, fn)
if fn then
return self._dbh:query(sql, fn)
end
return self._dbh:query(sql)
end
function FsDatabase:affected_rows()
if self._dbh then
return self._dbh:affected_rows()
end
end
function FsDatabase:release()
if self._dbh then
self._dbh:release()
self._dbh = nil
end
end
function FsDatabase:connected()
return self._dbh and self._dbh:connected()
end
end
-----------------------------------------------------------
-----------------------------------------------------------
local Database = {} do
Database.__index = Database
Database.__base = freeswitch and FsDatabase or OdbcDatabase
Database = setmetatable(Database, Database.__base)
function Database.new(...)
local self = Database.__base.new(...)
setmetatable(self, Database)
return self
end
function Database:first_row(sql)
local result
local ok, err = self:query(sql, function(row)
result = row
return 1
end)
if not ok then return nil, err end
return result
local result
local ok, err = self:query(sql, function(row)
result = row
return 1
end)
if not ok then return nil, err end
return result
end
function Database:first_value(sql)
local result, err = self:first_row(sql)
if not result then return nil, err end
local k, v = next(result)
return v
local result, err = self:first_row(sql)
if not result then return nil, err end
local k, v = next(result)
return v
end
function Database:first(sql, ...)
local result, err = self:first_row(sql)
if not result then return nil, err end
local t, n = {}, select('#', ...)
for i = 1, n do
t[i] = result[(select(i, ...))]
end
return unpack(t, 1, n)
local result, err = self:first_row(sql)
if not result then return nil, err end
local t, n = {}, select('#', ...)
for i = 1, n do
t[i] = result[(select(i, ...))]
end
return unpack(t, 1, n)
end
function Database:fetch_all(sql)
local result = {}
local ok, err = self:query(sql, function(row)
result[#result + 1] = row
end)
if not ok then return nil, err end
return result
local result = {}
local ok, err = self:query(sql, function(row)
result[#result + 1] = row
end)
if (not ok) and err then return nil, err end
return result
end
function Database:release(sql)
if self._dbh then
self._dbh:release()
self._dbh = nil
end
end
function Database.__self_test__(...)
local db = Database.new(...)
assert(db:connected())
function Database:connected(sql)
return self._dbh and self._dbh:connected()
end
assert("1" == db:first_value("select 1 as v union all select 2 as v"))
function Database.__self_test__(name)
local db = Database.new(name or 'system')
assert(db:connected())
local t = assert(db:first_row("select '1' as v union all select '2' as v"))
assert(t.v == "1")
assert("1" == db:first_value("select 1 as v union all select 2 as v"))
t = assert(db:fetch_all("select '1' as v union all select '2' as v"))
assert(#t == 2)
assert(t[1].v == "1")
assert(t[2].v == "2")
local t = assert(db:first_row("select 1 as v union all select 2 as v"))
assert(t.v == "1")
local a, b = assert(db:first("select '1' as b, '2' as a", 'a', 'b'))
assert(a == "2")
assert(b == "1")
t = assert(db:fetch_all("select 1 as v union all select 2 as v"))
assert(#t == 2)
assert(t[1].v == "1")
assert(t[2].v == "2")
-- assert(nil == db:first_value("some non sql query"))
local a, b = assert(db:first("select 1 as b, 2 as a", 'a', 'b'))
assert(a == "2")
assert(b == "1")
-- assert(nil == db:first_value("some non sql query"))
db:release()
assert(not db:connected())
print(" * databse - OK!")
db:release()
assert(not db:connected())
print(" * databse - OK!")
end
end
-- if debug.self_test then
-- Database.__self_test__()
-- end
-----------------------------------------------------------
return Database

View File

@ -0,0 +1,159 @@
local function class(base)
local t = base and setmetatable({}, base) or {}
t.__index = t
t.__class = t
t.__base = base
function t.new(...)
local o = setmetatable({}, t)
if o.__init then
if t == ... then -- we call as Class:new()
return o:__init(select(2, ...))
else -- we call as Class.new()
return o:__init(...)
end
end
return o
end
return t
end
local EventSocket = class() do
if not freeswitch then
local socket = require "socket"
local ESLParser = require "lluv.esl".ESLParser
local split_status = require "lluv.esl.utils".split_status
local Database = require "resources.functions.database"
local EOL = '\n'
local host, port, auth
function EventSocket:__init()
if not host then
local db = Database.new('system')
local settings, err = db:first_row("select event_socket_ip_address, event_socket_port, event_socket_password from v_settings")
if not settings then return nil, err end
host, port, auth = settings.event_socket_ip_address, settings.event_socket_port, settings.event_socket_password
end
return self:_connect(host, port, auth)
end
function EventSocket:_connect(host, port, password)
local err
self._cnn, err = socket.connect(host, port)
if not self._cnn then return nil, err end
self._cnn:settimeout(1)
self._parser = ESLParser.new()
local auth
while true do
local event
event, err = self:_recv_event()
if not event then break end
local ct = event:getHeader('Content-Type')
if ct == 'auth/request' then
self._cnn:send('auth ' .. password .. EOL .. EOL)
elseif ct == 'command/reply' then
local reply = event:getHeader('Reply-Text')
if reply then
local ok, status, msg = split_status(reply)
if ok then auth = true else err = msg end
else
err = 'invalid response'
end
break
end
end
if not auth then
self._cnn:close()
self._cnn = nil
return nil, err
end
return self
end
function EventSocket:_recv_event()
local event, err = self._parser:next_event()
while event == true do
local str, rst
str, err, rst = self._cnn:receive("*l")
if str then self._parser:append(str):append(EOL) end
if rst then self._parser:append(rst) end
if err and err ~= 'timeout' then
break
end
event = self._parser:next_event()
end
if (not event) or (event == true) then
return nil, err
end
return event
end
function EventSocket:_request(cmd)
if not self._cnn then return nil, 'closed' end
for str in (cmd .. '\n'):gmatch("(.-)\n") do
self._cnn:send(str .. EOL)
end
self._cnn:send(EOL)
return self:_recv_event()
end
function EventSocket:api(cmd)
local event, err = self:_request('api ' .. cmd)
if not event then return nil, err end
local body = event:getBody()
if body then return body end
return event:getReply()
end
function EventSocket:close()
if self._cnn then
self._cnn:close()
self._cnn = nil
end
end
end
if freeswitch then
local api
function EventSocket:__init()
self._api = api or freeswitch.API()
api = self._api
return self
end
function EventSocket:api(cmd)
local result = self._api:executeString(cmd)
if result and result:sub(1, 4) == '-ERR' then
return nil, result:sub(5)
end
return result
end
function EventSocket:close()
self._api = nil
end
end
end
return EventSocket

View File

@ -0,0 +1,172 @@
-- -- Global settings
-- local settings = Settings.new('system')
-- print(settings:get('switch', 'base', 'dir'))
--
-- Domain settings (to `fax_retry.lua`)
-- local Settings = require "resources.functions.settings"
-- local settings = Settings.new(dbh, domain_name, domain_uuid)
-- storage_type = settings:get('fax', 'storage_type', 'text') or ''
-- storage_path = settings:get('fax', 'storage_path', 'text') or ''
-- storage_path = storage_path
-- :gsub("${domain_name}", domain_name)
-- :gsub("${voicemail_id}", voicemail_id)
-- :gsub("${voicemail_dir}", voicemail_dir)
local Database = require "resources.functions.database"
local cache = require "resources.functions.cache"
require "resources.functions.split"
-----------------------------------------------------------
local Settings = {} do
Settings.__index = Settings
local NONE = '15783958-912c-4893-8866-4ccd1ca73c6e'
local function append(t, v)
t[#t+1] = v
return t
end
local function append_setting(array, category, subcategory, name, value)
--add the category array
if not array[category] then
array[category] = {}
end
--add the subcategory array
if not array[category][subcategory] then
array[category][subcategory] = {}
end
--set the name and value
if (name == "array") then
if not array[category][subcategory][name] then
array[category][subcategory][name] = {}
end
append(array[category][subcategory][name], value);
elseif value ~= nil then
array[category][subcategory][name] = value;
end
end
function Settings.new(db, domain_name, domain_uuid)
local self = setmetatable({}, Settings)
self._array = {}
self._db = db
self._domain_name = domain_name
self._domain_uuid = domain_uuid
return self
end
function Settings:_cache_key(category, subcategory, name)
return 'setting:' .. (self._domain_name or '') .. ':' .. category .. ':' .. subcategory .. ':' .. name
end
function Settings:set(category, subcategory, name, value)
append_setting(self._array, category, subcategory, name, value)
return self
end
function Settings:get(category, subcategory, name)
local a = self._array
local v = a[category] and a[category][subcategory] and a[category][subcategory][name]
if v == NONE then return nil end
if v ~= nil then return v end
local key = self:_cache_key(category, subcategory, name)
v = cache.get(key)
if v then
if v ~= NONE and name == 'array' then
v = split(v, '/+/', true)
end
self:set(category, subcategory, name, v)
if v == NONE then return nil end
return v
end
return self:_load(category, subcategory, name)
end
function Settings:_load(category, subcategory, name)
local domain_uuid = self._domain_uuid
local db = self._db
if type(self._db) == 'string' then
db = Database.new(self._db)
end
local found = false
--get the domain settings
if domain_uuid then
sql = "SELECT domain_setting_uuid,domain_setting_category,domain_setting_subcategory,domain_setting_name,domain_setting_value "
sql = sql .. "FROM v_domain_settings ";
sql = sql .. "WHERE domain_uuid = '" .. domain_uuid .. "'";
sql = sql .. "AND domain_setting_enabled = 'true' ";
sql = sql .. "AND domain_setting_category = '" .. category .."'";
sql = sql .. "AND domain_setting_subcategory = '" .. subcategory .. "'";
sql = sql .. "AND domain_setting_name = '" .. name .. "'";
sql = sql .. "AND domain_setting_value is not null ";
sql = sql .. "ORDER BY domain_setting_category, domain_setting_subcategory ASC ";
db:query(sql, function(row)
found = true;
self:set(
row.domain_setting_category,
row.domain_setting_subcategory,
row.domain_setting_name,
row.domain_setting_value
)
end)
end
if not found then
local sql = "SELECT default_setting_uuid,default_setting_category,default_setting_subcategory,default_setting_name,default_setting_value "
sql = sql .. "FROM v_default_settings ";
sql = sql .. "WHERE default_setting_enabled = 'true' ";
sql = sql .. "AND default_setting_category = '" .. category .."'";
sql = sql .. "AND default_setting_subcategory = '" .. subcategory .. "'";
sql = sql .. "AND default_setting_name = '" .. name .. "'";
sql = sql .. "AND default_setting_value is not null ";
sql = sql .. "ORDER BY default_setting_category, default_setting_subcategory ASC";
db:query(sql, function(row)
found = true;
self:set(
row.default_setting_category,
row.default_setting_subcategory,
row.default_setting_name,
row.default_setting_value
)
end)
end
if not found then
self:set(category, subcategory, name, NONE)
end
local a = self._array
local v = a[category] and a[category][subcategory] and a[category][subcategory][name]
if cache.support() then
local key = self:_cache_key(category, subcategory, name)
local value = v
if v ~= NONE and name == 'array' then
value = table.concat(v, '/+/')
end
local exp = expire and expire["settings"] or 3600
cache.set(key, value, exp)
end
if type(self._db) == 'string' then
db:release()
end
if v == NONE then return nil end
return v
end
end
-----------------------------------------------------------
return Settings

View File

@ -3,9 +3,14 @@
-- log.noticef("%s %s", "hello", "world")
-- -- log if debug.SQL or debug.xml_handler.SQL then
-- log.tracef("SQL", "SQL is %s", sql)
local function log(name, level, msg)
freeswitch.consoleLog(level, "[" .. name .. "] " .. msg .. "\n")
local log if freeswitch then
log = function (name, level, msg)
freeswitch.consoleLog(level, "[" .. name .. "] " .. msg .. "\n")
end
else
log = function (name, level, msg)
print(os.date("%Y-%m-%d %X") .. '[' .. level:upper() .. '] [' .. name .. '] ' .. msg)
end
end
local function logf(name, level, ...)

View File

@ -0,0 +1,15 @@
if freeswitch then
function sleep(ms)
freeswitch.msleep(ms)
end
else
local socket = require "socket"
function sleep(ms)
socket.sleep(ms/1000)
end
end

View File

@ -0,0 +1,27 @@
function split(str, sep, plain)
local b, res = 1, {}
while b <= #str do
local e, e2 = string.find(str, sep, b, plain)
if e then
res[#res + 1] = string.sub(str, b, e-1)
b = e2 + 1
else
res[#res + 1] = string.sub(str, b)
break
end
end
return res
end
function split_first(str, sep, plain)
local e, e2 = string.find(str, sep, nil, plain)
if e then
return string.sub(str, 1, e - 1), string.sub(str, e2 + 1)
end
return str
end
local unpack = unpack or table.unpack
function usplit(...) return unpack(split(...)) end

View File

@ -37,6 +37,266 @@ if (defined('STDIN')) {
//echo "$document_root is document_root\n";
}
if (stristr(PHP_OS, 'WIN')) { $IS_WINDOWS = true; } else { $IS_WINDOWS = false; }
if(!function_exists('exec_in_dir')) {
function exec_in_dir($dir, $cmd, &$ok){
$args = func_get_args();
$cwd = getcwd();
chdir($dir);
$output = array();
$ret = 0;
$result = exec($cmd, $output, $ret);
if($cwd)
chdir($cwd);
$ok = ($ret == 0);
return join($output, "\n");
}
}
if(!function_exists('correct_path')) {
function correct_path($p) {
global $IS_WINDOWS;
if ($IS_WINDOWS) {
return str_replace('/', '\\', $p);
}
return $p;
}
}
if(!function_exists('path_join')) {
function path_join() {
$args = func_get_args();
$paths = array();
foreach ($args as $arg) {
$paths = array_merge($paths, (array)$arg);
}
$prefix = null;
foreach($paths as &$path) {
if($prefix === null && strlen($path) > 0) {
if(substr($path, 0, 1) == '/') $prefix = '/';
else $prefix = '';
}
$path = trim( $path, '/' );
}
if($prefix === null){
return '';
}
$paths = array_filter($paths);
return $prefix . join('/', $paths);
}
}
if(!function_exists('tiff2pdf')) {
function tiff2pdf($tiff_file_name){
//convert the tif to a pdf
//Ubuntu: apt-get install libtiff-tools
global $IS_WINDOWS;
if(!file_exists($tiff_file_name)){
echo "tiff file does not exists";
return false; // "tiff file does not exists";
}
$GS = $IS_WINDOWS ? 'gswin32c' : 'gs';
$tiff_file = pathinfo($tiff_file_name);
$dir_fax = $tiff_file['dirname'];
$fax_file_name = $tiff_file['filename'];
$pdf_file_name = path_join( $dir_fax, $fax_file_name . '.pdf' );
if(file_exists($pdf_file_name))
return $pdf_file_name;
$dir_fax_temp = $_SESSION['server']['temp']['dir'];
if(!$dir_fax_temp){
$dir_fax_temp = path_join(dirname($dir_fax), 'temp');
}
if(!file_exists($dir_fax_temp)){
echo"can not create temporary directory";
return false; //
}
$cmd = "tiffinfo " . correct_path($tiff_file_name) . ' | grep "Resolution:"';
$ok = false;
$resp = exec_in_dir($dir_fax, $cmd, $ok);
if(!$ok){
echo"can not find fax resoulution";
return false; // "can not find fax resoulution"
}
$ppi_w = 0;
$ppi_h = 0;
$tmp = array();
if(preg_match('/Resolution.*?(\d+).*?(\d+)/', $resp, $tmp)){
$ppi_w = $tmp[1];
$ppi_h = $tmp[2];
}
$cmd = "tiffinfo " . $tiff_file_name . ' | grep "Image Width:"';
$resp = exec_in_dir($dir_fax, $cmd, $ok);
if(!$ok){
echo"can not find fax size";
return false; // "can not find fax size"
}
$pix_w = 0;
$pix_h = 0;
$tmp = array();
if(preg_match('/Width.*?(\d+).*?Length.*?(\d+)/', $resp, $tmp)){
$pix_w = $tmp[1];
$pix_h = $tmp[2];
}
$page_width = $pix_w / $ppi_w;
$page_height = $pix_h / $ppi_h;
$page_size = 'a4';
if (($page_width > 8.4) && ($page_height > 13)) {
$page_width = 8.5;
$page_height = 14;
$page_size = 'legal';
}
elseif (($page_width > 8.4) && ($page_height < 12)) {
$page_width = 8.5;
$page_height = 11;
$page_size = 'letter';
}
elseif (($page_width < 8.4) && ($page_height > 11)) {
$page_width = 8.3;
$page_height = 11.7;
$page_size = 'a4';
}
$page_width = sprintf('%.4f', $page_width);
$page_height = sprintf('%.4f', $page_height);
$cmd = join(array('tiff2pdf',
'-i -u i',
'-p', $page_size,
'-w', $page_width,
'-l', $page_height,
'-f',
'-o', correct_path(path_join($dir_fax_temp, $fax_file_name . '.pdf')),
correct_path($tiff_file_name),
), ' ');
$resp = exec_in_dir($dir_fax, $cmd, $ok);
if(!file_exists(path_join($dir_fax_temp, $fax_file_name . '.pdf'))){
echo "can not create temporary pdf: $resp";
return false;
}
$cmd = join(array($GS,
'-q -sDEVICE=tiffg3',
'-r' . $ppi_w . 'x' . $ppi_h,
'-g' . $pix_w . 'x' . $pix_h,
'-dNOPAUSE',
'-sOutputFile=' . $fax_file_name . '_temp.tif',
'--',
$fax_file_name . '.pdf',
'-c quit',
), ' ');
$resp = exec_in_dir($dir_fax_temp, $cmd, $ok);
unlink(path_join($dir_fax_temp, $fax_file_name . '.pdf'));
if(!file_exists(path_join($dir_fax_temp, $fax_file_name . '_temp.tif'))){
echo "can not temporary tiff: $resp";
return false;
}
$cmd = join(array('tiff2pdf',
'-i -u i',
'-p', $page_size,
'-w', $page_width,
'-l', $page_height,
'-f',
'-o', correct_path($pdf_file_name),
correct_path(path_join($dir_fax_temp, $fax_file_name . '_temp.tif')),
), ' ');
$resp = exec_in_dir($dir_fax, $cmd, $ok);
unlink(path_join($dir_fax_temp, $fax_file_name . '_temp.tif'));
if(!file_exists($pdf_file_name)){
echo "can not create pdf: $resp";
return false;
}
return $pdf_file_name;
}
}
if(!function_exists('fax_enqueue')) {
function fax_enqueue($fax_uuid, $fax_file, $wav_file, $fax_uri, $fax_dtmf, $dial_string){
global $db, $db_type;
$task_uuid = uuid();
$dial_string .= "task_uuid='" . $task_uuid . "',";
$description = ''; //! @todo add description
if ($db_type == "pgsql") {
$date_utc_now_sql = "NOW() at time zone 'utc'";
}
if ($db_type == "mysql") {
$date_utc_now_sql = "UTC_TIMESTAMP()";
}
if ($db_type == "sqlite") {
$date_utc_now_sql = "datetime('now')";
}
$sql = <<<HERE
INSERT INTO v_fax_tasks( task_uuid, fax_uuid,
task_next_time, task_lock_time,
task_fax_file, task_wav_file, task_uri, task_dial_string, task_dtmf,
task_interrupted, task_status, task_no_answer_counter, task_no_answer_retry_counter, task_retry_counter,
task_description)
VALUES (?, ?,
$date_utc_now_sql, NULL,
?, ?, ?, ?, ?,
'false', 0, 0, 0, 0,
?);
HERE;
$stmt = $db->prepare($sql);
$i = 0;
$stmt->bindValue(++$i, $task_uuid);
$stmt->bindValue(++$i, $fax_uuid);
$stmt->bindValue(++$i, $fax_file);
$stmt->bindValue(++$i, $wav_file);
$stmt->bindValue(++$i, $fax_uri);
$stmt->bindValue(++$i, $dial_string);
$stmt->bindValue(++$i, $fax_dtmf);
$stmt->bindValue(++$i, $description);
if ($stmt->execute()) {
$response = 'Enqueued';
}
else{
//! @todo log error
$response = 'Fail enqueue';
var_dump($db->errorInfo());
}
unset($stmt);
return $response;
}
}
if(!function_exists('fax_split_dtmf')) {
function fax_split_dtmf(&$fax_number, &$fax_dtmf){
$tmp = array();
$fax_dtmf = '';
if(preg_match('/^\s*(.*?)\s*\((.*)\)\s*$/', $fax_number, $tmp)){
$fax_number = $tmp[1];
$fax_dtmf = $tmp[2];
}
}
}
//includes
if (!defined('STDIN')) { include "root.php"; }
require_once "resources/require.php";
@ -111,19 +371,12 @@ if (defined('STDIN')) {
}
$mailto_address = $fax_email;
echo "mailto_adress is ".$mailto_address."\n";
echo "fax_email is ".$fax_email."\n";
//get the fax file name (only) if a full path
$array = explode("/", $fax_file);
$fax_file_only = $array[count($array)-1];
$fax_file_name = pathinfo($fax_file_only, PATHINFO_FILENAME);
unset($array);
//used for debug
echo "fax_email $fax_email\n";
echo "fax_extension $fax_extension\n";
echo "fax_name $fax_file_only\n";
$fax_path = pathinfo($fax_file);
$fax_file_only = $fax_path['basename'];
$fax_file_name = $fax_path['filename'];
$dir_fax = $fax_path['dirname'];
//get the domain_uuid from the database
$sql = "select * from v_domains ";
@ -153,6 +406,7 @@ if (defined('STDIN')) {
foreach ($result as &$row) {
//set database fields as variables
//$fax_email = $row["fax_email"];
$fax_uuid = $row["fax_uuid"];
$fax_accountcode = $row["fax_accountcode"];
$fax_pin_number = $row["fax_pin_number"];
$fax_caller_id_name = $row["fax_caller_id_name"];
@ -164,129 +418,128 @@ if (defined('STDIN')) {
unset ($prep_statement);
//set the fax directory
$dir_fax = $_SESSION['switch']['storage']['dir'].'/fax/'.$domain_name.'/'.$fax_extension.'/inbox';
echo "dir_fax is $dir_fax\n";
if (!file_exists($dir_fax)) {
$dir_fax = $_SESSION['switch']['storage']['dir'].'/fax/'.$fax_extension.'/inbox';
}
//convert the tif to a pdf
//Ubuntu: apt-get install libtiff-tools
$fax_file_warning = "";
if (file_exists($dir_fax.'/'.$fax_file_name.".tif")) {
if (!file_exists($dir_fax.'/'.$fax_file_name.".pdf")) {
//define temp directory
$dir_fax_temp = str_replace('/inbox', '/temp', $dir_fax);
if (!is_dir($dir_fax_temp)) {
mkdir($dir_fax_temp,0774,true);
chmod($dir_fax_temp,0774);
}
//enter fax directory
chdir($dir_fax);
//get fax resolution (ppi, W & H)
$resp = exec("tiffinfo ".$fax_file_name.".tif | grep 'Resolution:'");
$resp_array = explode(' ', trim($resp));
$ppi_w = (int) $resp_array[1];
$ppi_h = (int) $resp_array[2];
unset($resp_array);
$gs_r = $ppi_w.'x'.$ppi_h; //used by ghostscript
//get page dimensions/size (pixels/inches, W & H)
$resp = exec("tiffinfo ".$fax_file_name.".tif | grep 'Image Width:'");
$resp_array = explode(' ', trim($resp));
$pix_w = $resp_array[2];
$pix_h = $resp_array[5];
unset($resp_array);
$gs_g = $pix_w.'x'.$pix_h; //used by ghostscript
$page_width = $pix_w / $ppi_w;
$page_height = $pix_h / $ppi_h;
if ($page_width > 8.4 && $page_height > 13) {
$page_width = 8.5;
$page_height = 14;
$page_size = 'legal';
}
else if ($page_width > 8.4 && $page_height < 12) {
$page_width = 8.5;
$page_height = 11;
$page_size = 'letter';
}
else if ($page_width < 8.4 && $page_height > 11) {
$page_width = 8.3;
$page_height = 11.7;
$page_size = 'a4';
}
//generate pdf (a work around, as tiff2pdf improperly inverts the colors)
$cmd_tif2pdf = "tiff2pdf -i -u i -p ".$page_size." -w ".$page_width." -l ".$page_height." -f -o ".$dir_fax_temp.'/'.$fax_file_name.".pdf ".$dir_fax.'/'.$fax_file_name.".tif";
exec($cmd_tif2pdf);
chdir($dir_fax_temp);
$cmd_pdf2tif = "gs -q -sDEVICE=tiffg3 -r".$gs_r." -g".$gs_g." -dNOPAUSE -sOutputFile=".$fax_file_name."_temp.tif -- ".$fax_file_name.".pdf -c quit";
exec($cmd_pdf2tif); //convert pdf to tif
@unlink($dir_fax_temp.'/'.$fax_file_name.".pdf");
$cmd_tif2pdf = "tiff2pdf -i -u i -p ".$page_size." -w ".$page_width." -l ".$page_height." -f -o ".$dir_fax.'/'.$fax_file_name.".pdf ".$dir_fax_temp.'/'.$fax_file_name."_temp.tif";
exec($cmd_tif2pdf);
@unlink($dir_fax_temp.'/'.$fax_file_name."_temp.tif");
if (!file_exists($dir_fax) || !file_exists(path_join($dir_fax, $fax_file_only))) {
$dir_fax = $_SESSION['switch']['storage']['dir'].'/fax/'.$domain_name.'/'.$fax_extension.'/inbox';
if (!file_exists($dir_fax) || !file_exists(path_join($dir_fax, $fax_file_only))) {
$dir_fax = $_SESSION['switch']['storage']['dir'].'/fax/'.$fax_extension.'/inbox';
}
}
else {
$fax_file_warning = " Fax image not available on server.";
echo $fax_file_warning."<br>";
$fax_file = path_join($dir_fax, $fax_file_only);
//used for debug
echo "mailto_adress is $mailto_address\n";
echo "fax_email is $fax_email\n";
echo "fax_extension is $fax_extension\n";
echo "fax_name is $fax_file_only\n";
echo "dir_fax is $dir_fax\n";
echo "full_path is $fax_file\n";
$pdf_file = tiff2pdf($fax_file);
if(!$pdf_file){
$fax_file_warning = ' Fax image not available on server.';
}
else{
$fax_file_warning = '';
}
//used for debug
echo "pdf file is $pdf_file\n";
//forward the fax
if (strpos($fax_file_name,'#') !== false) {
$tmp = explode("#",$fax_file_name);
$fax_forward_number = $tmp[0];
}
if(file_exists($fax_file)) {
if (strpos($fax_file_name,'#') !== false) {
$tmp = explode("#",$fax_file_name);
$fax_forward_number = $tmp[0];
}
echo "fax_forward_number is $fax_forward_number\n";
if (strlen($fax_forward_number) > 0) {
if (file_exists($dir_fax."/".$fax_file_name.".tif")) {
//get the event socket information
$sql = "select * from v_settings ";
$prep_statement = $db->prepare(check_sql($sql));
$prep_statement->execute();
$result = $prep_statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($result as &$row) {
$event_socket_ip_address = $row["event_socket_ip_address"];
$event_socket_port = $row["event_socket_port"];
$event_socket_password = $row["event_socket_password"];
break;
}
//create the event socket connection
$fp = event_socket_create($event_socket_ip_address, $event_socket_port, $event_socket_password);
//send the command with event socket
if ($fp) {
//prepare the fax originate command
$route_array = outbound_route_to_bridge($_SESSION['domain_uuid'], $fax_forward_number);
$fax_file = $dir_fax."/".$fax_file_name.".tif";
if (count($route_array) == 0) {
//send the internal call to the registered extension
$fax_uri = "user/".$fax_forward_number."@".$domain_name;
$t38 = "";
}
else {
//send the external call
$fax_uri = $route_array[0];
$t38 = "fax_enable_t38=true,fax_enable_t38_request=true";
}
$cmd = "api originate {absolute_codec_string='PCMU,PCMA',accountcode='".$fax_accountcode."',sip_h_X-accountcode='".$fax_accountcode."',domain_uuid=".$_SESSION["domain_uuid"].",domain_name=".$_SESSION["domain_name"].",mailto_address='".$mailto_address."',mailfrom_address='".$mailfrom_address."',origination_caller_id_name='".$fax_caller_id_name."',origination_caller_id_number=".$fax_caller_id_number.",fax_uri=".$fax_uri.",fax_file='".$fax_file."',fax_retry_attempts=1,fax_retry_limit=20,fax_retry_sleep=180,fax_verbose=true,fax_use_ecm=off,".$t38.",api_hangup_hook='lua fax_retry.lua'}".$fax_uri." &txfax('".$fax_file."')";
//send info to the log
echo "fax forward\n";
echo $cmd."\n";
//send the command to event socket
$response = event_socket_request($fp, $cmd);
$response = str_replace("\n", "", $response);
//send info to the log
echo "response: ".$response."\n";
//get the uuid
$uuid = str_replace("+OK ", "", $response);
//close event socket
fclose($fp);
}
echo "fax_forward_number is $fax_forward_number\n";
if (strlen($fax_forward_number) > 0) {
fax_split_dtmf($fax_forward_number, $fax_dtmf);
$fax_send_mode = $_SESSION['fax']['send_mode']['text'];
if(strlen($fax_send_mode) == 0){
$fax_send_mode = 'direct';
}
$route_array = outbound_route_to_bridge($_SESSION['domain_uuid'], $fax_forward_number);
if (count($route_array) == 0) {
//send the internal call to the registered extension
$fax_uri = "user/".$fax_forward_number."@".$domain_name;
$t38 = "";
}
else {
//send the external call
$fax_uri = $route_array[0];
$t38 = "fax_enable_t38=true,fax_enable_t38_request=true";
}
$common_dial_string = "absolute_codec_string='PCMU,PCMA',";
$common_dial_string .= "accountcode='" . $fax_accountcode . "',";
$common_dial_string .= "sip_h_X-accountcode='" . $fax_accountcode . "',";
$common_dial_string .= "domain_uuid=" . $_SESSION["domain_uuid"] . ",";
$common_dial_string .= "domain_name=" . $_SESSION["domain_name"] . ",";
$common_dial_string .= "mailto_address='" . $mailto_address . "',";
$common_dial_string .= "mailfrom_address='" . $mailfrom_address . "',";
$common_dial_string .= "origination_caller_id_name='" . $fax_caller_id_name . "',";
$common_dial_string .= "origination_caller_id_number='" . $fax_caller_id_number . "',";
$common_dial_string .= "fax_ident='" . $fax_caller_id_number . "',";
$common_dial_string .= "fax_header='" . $fax_caller_id_name . "',";
$common_dial_string .= "fax_file='" . $fax_file . "',";
if ($fax_send_mode != 'queue') {
$dial_string .= $t38;
$dial_string .= "fax_uri=" . $fax_uri . ",";
$dial_string .= "fax_retry_attempts=1" . ",";
$dial_string .= "fax_retry_limit=20" . ",";
$dial_string .= "fax_retry_sleep=180" . ",";
$dial_string .= "fax_verbose=true" . ",";
$dial_string .= "fax_use_ecm=off" . ",";
$dial_string .= "api_hangup_hook='lua fax_retry.lua'";
$dial_string = "{" . $dial_string . "}" . $fax_uri." &txfax('".$fax_file."')";
//get the event socket information
$sql = "select * from v_settings ";
$prep_statement = $db->prepare(check_sql($sql));
$prep_statement->execute();
$result = $prep_statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($result as &$row) {
$event_socket_ip_address = $row["event_socket_ip_address"];
$event_socket_port = $row["event_socket_port"];
$event_socket_password = $row["event_socket_password"];
break;
}
//create the event socket connection
$fp = event_socket_create($event_socket_ip_address, $event_socket_port, $event_socket_password);
//send the command with event socket
if ($fp) {
//prepare the fax originate command
$cmd = "api originate " . $dial_string;
//send info to the log
echo "fax forward\n";
echo $cmd."\n";
//send the command to event socket
$response = event_socket_request($fp, $cmd);
$response = str_replace("\n", "", $response);
//send info to the log
echo "response: ".$response."\n";
//get the uuid
$uuid = str_replace("+OK ", "", $response);
//close event socket
fclose($fp);
}
}
else{
$wav_file = '';
$response = fax_enqueue($fax_uuid, $fax_file, $wav_file, $fax_uri, $fax_dtmf, $dial_string);
}
}
}
//send the email
if (strlen($fax_email) > 0 && file_exists($dir_fax."/".$fax_file_name.".tif")) {
if (strlen($fax_email) > 0 && file_exists($fax_file)) {
//prepare the message
$tmp_subject = (($fax_email_inbound_subject_tag != '') ? "[".$fax_email_inbound_subject_tag."]" : "Fax Received").": ".$fax_file_name;
@ -340,11 +593,11 @@ if (defined('STDIN')) {
//add the attachments
if (strlen($fax_file_name) > 0) {
if (file_exists($dir_fax.'/'.$fax_file_name.".pdf")) {
$mail->AddAttachment($dir_fax.'/'.$fax_file_name.'.pdf'); // pdf attachment
if ($pdf_file && file_exists($pdf_file)) {
$mail->AddAttachment($pdf_file); // pdf attachment
}
else {
$mail->AddAttachment($dir_fax.'/'.$fax_file_name.'.tif'); // tif attachment
$mail->AddAttachment($fax_file); // tif attachment
}
//$filename='fax.tif'; $encoding = "base64"; $type = "image/tif";
//$mail->AddStringAttachment(base64_decode($strfax),$filename,$encoding,$type);
@ -373,7 +626,7 @@ if (defined('STDIN')) {
// failed_fax_emails.sh - this is created when we have a email we need to re-send. At the time it is created, an at job is created to execute it in 3 minutes time,
// this allows us to try sending the email again at that time. If the file exists but there is no at job this is because there are no longer any emails queued
// as we have successfully sent them all.
if (strlen($fax_email) > 0 && file_exists($dir_fax."/".$fax_file_name.".tif")) {
if (strlen($fax_email) > 0 && file_exists($fax_file)) {
if (stristr(PHP_OS, 'WIN')) {
//not compatible with windows
}
@ -387,7 +640,7 @@ if (defined('STDIN')) {
} else {
// create an instruction log to email messages once the connection to the mail server has been restored
$fp = fopen($fax_to_email_queue_dir."/failed_fax_emails.log", "a");
fwrite($fp, PHP_BINDIR."/php ".$_SERVER["DOCUMENT_ROOT"].PROJECT_PATH."/secure/fax_to_email.php email='".$fax_email."' extension=".$fax_extension." name='".$dir_fax.'/'.$fax_file_only."' messages='".$fax_messages."' domain=".$domain_name." caller_id_name='".$caller_id_name."' caller_id_number=".$caller_id_number." retry=true\n");
fwrite($fp, PHP_BINDIR."/php ".$_SERVER["DOCUMENT_ROOT"].PROJECT_PATH."/secure/fax_to_email.php email='".$fax_email."' extension=".$fax_extension." name='".$fax_file."' messages='".$fax_messages."' domain=".$domain_name." caller_id_name='".$caller_id_name."' caller_id_number=".$caller_id_number." retry=true\n");
fclose($fp);
// create a script to do the delayed mailing
$fp = fopen($_SESSION['server']['temp']['dir']."/failed_fax_emails.sh", "w");
@ -413,4 +666,4 @@ if (defined('STDIN')) {
fwrite($fp, $content);
fclose($fp);
?>
?>