FOOD FOR THOUGHT Would like to implement email to fax

dicko

Still learning but earning
Joined
Oct 30, 2015
Messages
1,634
Reaction score
846
Absolutely you can fax from outside your pbx, if you use email. then postfix or any MTA will 'encapsulate" the whole shebang, it's how it works ;-)

In bash , perhaps what you mean by 'command line', you escape with \ so to include a " in a quoted by " string you use use

echo " this is a string with \"quotes\" "

conversely

echo ' this is a string with \' single quotes\' '

or even

echo "\"'double'\" 'single' $(ls -l) "

but bash ( your command line hypothetically) will NOT expand system variables or other artifacts like globs inside single quotes, it WILL within double quotes, php has plenty of functions, like addslashes as you have found, BUT if your ultimate need is to pass a string to sendfax, I suggest you just use simple bash conventional "quoting", perhaps that should be 'quoting' ;-)

Also ripmime will easily handle your non ascii characters and any "bizarre" quoting you might find. if you look at the generated txt file by ripmime you will be convinced.
 
Last edited:

AndyInNYC

Active Member
Joined
May 23, 2013
Messages
772
Reaction score
124
OK,

I have a variable $cmdline which is a string that looks like this:
sendfax -r "Nothing in Particular" -U "609.000.1234" -X "My Company, LLC" -d "6095551212" "/root/imap-dump/AA1.pdf"
I used the php command: exec($cmdline);
I get the error:
Code:
sh: sendfax -r "Nothing in Particular" -U "609.000.1234" -X "My Company, LLC" -d "6095551212" "/root/imap-dump/AA1.pdf": No such file or directory

If I type the text exactly as it appears on the command line, the fax goes through. Clearly there is something about exec() I'm doing wrong.

Help?


Andrew
 

billsimon

Well-Known Member
Joined
Jan 2, 2011
Messages
1,540
Reaction score
729
Code:
which sendfax
tells you the full path to sendfax.

Use that in the command you're generating in PHP so you don't have to worry about sendfax being in the executable path.
 

AndyInNYC

Active Member
Joined
May 23, 2013
Messages
772
Reaction score
124
I have my script working from the command line. When I drop it into cron, it executes, but not completely.

my crontab line reads:
*/2 * * * * root /root/ImapFaxer.php
I'm running every 2 minutes because I don't want to wait while debugging. It will normally run every 10 minutes or so.

At the bottom of my script I have the following lines:

Code:
                shell_exec("/usr/bin/" . $cmdline);
                $tmpv = "rm " . $savedir . "*";
//              echo $tmpv;
                shell_exec($tmpv);
}

$cmdline is (basically) 'sendfax -d 18005551212 -(commands and filenames)
$tmpv is supposed to delete the renamed and saved attachments.

The script is running, because the emails gets marked as read. The shell_exec, however, doesn't seem to happen.

What's the best way to fix this so that the script fully executes when called from cron?

Andrew
 

dicko

Still learning but earning
Joined
Oct 30, 2015
Messages
1,634
Reaction score
846
You might want to read up on

/var/www/html/fax/includes/avantfaxcron.php

which is likely in your crontab for root

(you can use "fractional days") a day has 86400 seconds (almost always) so a -t of .001 would be about 1.4 minutes but cron only has a resolution of 1 minute

Be aware that cron runs /bin/sh so absolute paths for everything is recommended

Edit, OK, I remember you decided to do email2fax yourself, so please ignore the solution that works.
 
Last edited:

billsimon

Well-Known Member
Joined
Jan 2, 2011
Messages
1,540
Reaction score
729
The script is running, because the emails gets marked as read. The shell_exec, however, doesn't seem to happen.

What's the best way to fix this so that the script fully executes when called from cron?

Any error output should appear in the cron log (/var/log/cron.log or similar).
 

AndyInNYC

Active Member
Joined
May 23, 2013
Messages
772
Reaction score
124
billsimon - no log entries in cron (other than the job ran)
dicko - avantfaxcron.php is what avantfax uses for clean up of its files. It isn't used to call avantfax with a job (unless I'm reading the code wrong).

I have a 'sendfax job' which is properly formatted. If I copy and paste it from the command line, it works. Submitted via cron using shell_exec it doesn't work.

The script is running (the 'new' email in the account shows as read). It just appears that the shell_exec function doesn't run or doesn't run correctly.

I think I'm going to create a script which calls shell_exec to send an email. If the email goes through, then i still won't know. If the email doesn't go through, it's a problem with shell_exec from cron.

Andrew
 

AndyInNYC

Active Member
Joined
May 23, 2013
Messages
772
Reaction score
124
Well, emails get through.

Any thoughts on why I have a failure?

Andrew
 

AndyInNYC

Active Member
Joined
May 23, 2013
Messages
772
Reaction score
124
OK, it doesn't run from cron - perhaps someone can help here.

This is the Class which my routines use. Without the class I was getting 0 byte files and other errors. I modified the Class slightly since it was failing if there were no UNSEEN emails

Code:
<?php
/**
 * imap-attachment.php
 *
 * @author hakre <hakre.wordpress.com>
 * @link http://stackoverflow.com/questions/9974334/how-to-download-mails-attachment-to-a-specific-folder-using-imap-and-php
 */

/**
 * Utility Class
 */
class IMAP
{
    /**
     *
     * =?x-unknown?B?
     * =?iso-8859-1?Q?
     * =?windows-1252?B?
     *
     * @param string $stringQP
     * @param string $base (optional) charset (IANA, lowercase)
     * @return string UTF-8
     */
    public static function decodeToUTF8($stringQP, $base = 'windows-1252')
    {
        $pairs = array(
            '?x-unknown?' => "?$base?"
        );
        $stringQP = strtr($stringQP, $pairs);
        return imap_utf8($stringQP);
    }
}

class IMAPMailbox implements IteratorAggregate, Countable
{
    private $stream;

    public function __construct($hostname, $username, $password)
    {
        $stream = imap_open($hostname, $username, $password);
        if (FALSE === $stream) {
            throw new Exception('Connect failed: ' . imap_last_error());
        }
        $this->stream = $stream;
    }

    public function getStream()
    {
        return $this->stream;
    }

    /**
     * @return stdClass
     */
    public function check()
    {
        $info = imap_check($this->stream);
        if (FALSE === $info) {
            throw new Exception('Check failed: ' . imap_last_error());
        }
        return $info;
    }

    /**
     * @param string $criteria
     * @param int $options
     * @param int $charset
     * @return IMAPMessage[]
     * @throws Exception
     */
    public function search($criteria, $options = NULL, $charset = NULL)
    {
        $emails = imap_search($this->stream, $criteria, $options, $charset);
        if (FALSE === $emails)
        {
        //    return $emails;
        } else
        {
        foreach ($emails as &$email) {
            $email = $this->getMessageByNumber($email);
        }
        return $emails;
        }
    }
    /**
     * @param int $number
     * @return IMAPMessage
     */
    public function getMessageByNumber($number)
    {
        return new IMAPMessage($this, $number);
    }

    public function getOverview($sequence = NULL)
    {
        if (NULL === $sequence) {
            $sequence = sprintf('1:%d', count($this));
        }
        return new IMAPOverview($this, $sequence);
    }

    /**
     * Retrieve an external iterator
     * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
     * @return Traversable An instance of an object implementing Iterator or
     * Traversable
     */
    public function getIterator()
    {
        return $this->getOverview()->getIterator();
    }

    /**
     * @return int
     */
    public function count()
    {
        return $this->check()->Nmsgs;
    }
}

class IMAPOverview extends ArrayObject
{
    private $mailbox;

    public function __construct(IMAPMailbox $mailbox, $sequence)
    {
        $result = imap_fetch_overview($mailbox->getStream(), $sequence);
        if (FALSE === $result) {
            throw new Exception('Overview failed: ' . imap_last_error());
        }
        $this->mailbox = $mailbox;
        foreach ($result as $overview)
        {
            if (!isset($overview->subject)) {
                $overview->subject = '';
            } else {
                $overview->subject = IMAP::decodeToUTF8($overview->subject);
            }
        }
        parent::__construct($result);
    }

    /**
     * @return IMAPMailbox
     */
    public function getMailbox()
    {
        return $this->mailbox;
    }
}

class IMAPMessage
{
    private $mailbox;
    private $number;
    private $stream;

    public function __construct(IMAPMailbox $mailbox, $number)
    {
        $this->mailbox = $mailbox;
        $this->number = $number;
        $this->stream = $mailbox->getStream();
    }

    public function getNumber()
    {
        return $this->number;
    }

    /**
     * @param int $number
     * @return string
     */
    public function fetchBody($number)
    {
        return imap_fetchbody($this->stream, $this->number, $number);
    }

    /**
     * @return stdClass
     * @throws Exception
     */
    public function fetchOverview()
    {
        $result = imap_fetch_overview($this->stream, $this->number);
        if (FALSE === $result) {
            throw new Exception('FetchOverview failed: ' . imap_last_error());
        }
        list($result) = $result;
        foreach ($result as &$prop) {
            $prop = imap_utf8($prop);
        }
        return $result;
    }

    public function fetchStructure()
    {
        $structure = imap_fetchstructure($this->stream, $this->number);
        if (FALSE === $structure) {
            throw new Exception('FetchStructure failed: ' . imap_last_error());
        }
        return $structure;
    }

    /**
     * @return IMAPAttachments
     */
    public function getAttachments()
    {
        return new IMAPAttachments($this);
    }

    public function __toString()
    {
        return (string)$this->number;
    }
}

class IMAPAttachment
{
    private $attachment;
    private $message;

    public function __construct(IMAPMessage $message, $attachment)
    {
        $this->message = $message;
        $this->attachment = $attachment;
    }

    /**
     * @return string;
     */
    public function getBody()
    {
        return $this->message->fetchBody($this->attachment->number);
    }

    /**
     * @return int
     */
    public function getSize()
    {
        return (int)$this->attachment->bytes;
    }

    /**
     * @return string
     */
    public function getExtension()
    {
        return pathinfo($this->getFilename(), PATHINFO_EXTENSION);
    }

    public function getFilename()
    {
        $filename = $this->attachment->filename;
        NULL === $filename && $filename = $this->attachment->name;
        return $filename;
    }

    public function __toString()
    {
        $encoding = $this->attachment->encoding;
        switch ($encoding) {
            case 0: // 7BIT
            case 1: // 8BIT
            case 2: // BINARY
                return $this->getBody();

            case 3: // BASE-64
                return base64_decode($this->getBody());

            case 4: // QUOTED-PRINTABLE
                return imap_qprint($this->getBody());
        }
        throw new Exception(sprintf('Encoding failed: Unknown encoding %s (5: OTHER).', $encoding));
    }
}

class IMAPAttachments extends ArrayObject
{
    private $message;

    public function __construct(IMAPMessage $message)
    {
        $array = $this->setMessage($message);
        parent::__construct($array);
    }

    private function setMessage(IMAPMessage $message)
    {
        $this->message = $message;
        return $this->parseStructure($message->fetchStructure());
    }

    private function parseStructure($structure)
    {
        $attachments = array();
        if (!isset($structure->parts)) {
            return $attachments;
        }

        foreach ($structure->parts as $index => $part)
        {
            if (!$part->ifdisposition) continue;
            $attachment = new stdClass;
            $attachment->isAttachment = FALSE;
            $attachment->number = $index + 1;
            $attachment->bytes = $part->bytes;
            $attachment->encoding = $part->encoding;
            $attachment->filename = NULL;
            $attachment->name = NULL;
            $part->ifdparameters
                && ($attachment->filename = $this->getAttribute($part->dparameters, 'filename'))
                && $attachment->isAttachment = TRUE;
            $part->ifparameters
                && ($attachment->name = $this->getAttribute($part->parameters, 'name'))
                && $attachment->isAttachment = TRUE;

            $attachment->isAttachment
                && $attachments[] = new IMAPAttachment($this->message, $attachment);
        }
        return $attachments;
    }

    private function getAttribute($params, $name)
    {
        foreach ($params as $object)
        {
            if ($object->attribute == $name) {
                return IMAP::decodeToUTF8($object->value);
            }
        }
        return NULL;
    }
}
?>
My code to follow
 

AndyInNYC

Active Member
Joined
May 23, 2013
Messages
772
Reaction score
124
This is my code. I prefilled a few of the sendfax variables.

an email should have lines like
#!#-d Send to Me@8005551214
#!#-c This is a comment
#!#-G



Code:
#!/usr/bin/php
<?php

require_once("/root/ImapClass2.php");


$savedir = __DIR__ . '/imap-dump/';

$hostname ='{my.mailserver.com:993/imap/ssl/novalidate-cert}';
$username = ''myemailaddress';
$password = 'mypassword';

$myparams = array(
    //    Param, TakesParam, Used, Variable,SurroundwithQuotes
    array("-b",1,0,"",0),
    array("-B",1,0,"",0),
    array("-c",1,0,"",1),
    array("-C",1,1,"/var/www/html/avantfax/images/cover.ps",1),
    array("-E",1,0,"",0),
    array("-f",1,1,'"' . 'Andrew B' . '"',1),
    array("-l",0,0,"",0),
    array("-G",0,1,"",0),
    array("-m",0,0,"",0),
    array("-n",0,0,"",0),
    array("-N",0,0,"",0),
    array("-R",0,0,"",0),
    array("-r",1,0,"",1),
    array("-S",1,0,"",1),
    array("-U",1,1,'"' . '(609) 000-0000' . '"',1),
    array("-V",1,0,"",1),
    array("-W",1,0,"",1),
    array("-x",1,0,"",1),
    array("-X",1,1,"My C",1),
    array("-y",1,0,"",1),
    array("-Y",1,1,'"' . 'My Town' . '"',1),
    array("-d",1,0,"",1),
    );

$inbox = new IMAPMailbox($hostname, $username, $password);
$emails = $inbox->search('UNSEEN');
if ($emails) {
    rsort($emails);
    foreach ($emails as $email) {
    // Give me an array to hold the file names
    $names = Array();
    // Get the Message Body (text)
    $msgtxt =  $email->fetchBody(1);
    // Convert the text to a usable format
    $msgtxt = imap_qprint($msgtxt);
    // Get the number of parameters we are going to search for
    $iterations = count($myparams);

    // Loop through params and build the command line text entries
    for ($loop=0;$loop<$iterations;$loop++)
        {
        $tmp = $myparams[$loop][0];
        $tsearch = "/^#!#" . $tmp . " (.*)$/m";
        if (preg_match($tsearch,$msgtxt,$matches))
            {
            $thetxt = $matches[1];
            $myparams[$loop][2]=1;
            // we have matched text - see if we have an argument/if we care if we have an argument
            if ($myparams[$loop][1] == 1)
                {
                if ($myparams[$loop][4]==1)
                    {
                    // save with quotes around
                    $thetxt = rtrim($thetxt);
                    $thetxt = addslashes($thetxt);
                    $myparams[$loop][3] = '"' . $thetxt . '"';
                    } else
                    {
                    $thetxt = rtrim($thetxt);
                    $thetxt = addslashes($thetxt);
                    $myparams[$loop][3]=$thetxt;
                    }
                }
            }
        }
    // Loop through any and all attachments
    // save them to the temp directory
    // add the name to the names() array to add to the fax attachment
    $afilename = "AA";
    $attachnum = 0;
        foreach ($email->getAttachments() as $attachment)
        {
        $attachnum = $attachnum +1;
        $attachnum_string = (string) $attachnum;
        $savepath = $savedir . $afilename . $attachnum_string . ".pdf";
            file_put_contents($savepath, $attachment);
            $names[] = $savepath;
              } // close of getting each attachment

    // time to build the command line - variables first and files second
    // phone number must be last in list (-d) so it is last in the array

    $cmdline = "sendfax";
    for  ($loop=0;$loop<$iterations;$loop++)
        {
        // Check if we have set it to USED for this Fax
        if ($myparams[$loop][2] == 1)
            {
            // we used it, check if it has text
            if ($myparams[$loop][1] == 1)
                {
                $txt = $myparams[$loop][0];
                $cmdline .=  " " . $txt . " ";
                $txt = $myparams[$loop][3];
                $cmdline .= $txt;
                } else
                {
                // no text, just the - param
                $txt = $myparams[$loop][0];
                $cmdline .= " " . $txt;
                }
            }
        }
        $num_elements = sizeof($names);
        for ($loop=0; $loop < $num_elements; $loop++)
            {
            $txt = $names[$loop];
            $cmdline  .= ' "'  . $txt . '"' ;
            }
}
//        echo print_r($cmdline) . PHP_EOL;
        shell_exec("/usr/bin/" . $cmdline);
        $tmpv = "rm " . $savedir . "*";
//        echo $tmpv;
        shell_exec($tmpv);
}

?>

Again, for some reason, this doesn't run when called from cron - perhaps someone here will see what the issue is.
I didn't attempt any optimization - it's my first php script and I wanted to be able to 'look' at all my intermediate steps (and figure out why sh*t wasn't working). Given the volume it will be called with (ie only when there's a new email) it doesn't matter if it's slow (at least to me).

You'll need to create a subdirectory called 'imap-dump' under where you put this, and you may need to check any hardcoded directories I used (again, trying to figure out why I'm failing).

Andrew[/Code]
 
Last edited:

billsimon

Well-Known Member
Joined
Jan 2, 2011
Messages
1,540
Reaction score
729
Also check /var/log/syslog or /var/log/messages for error output, and root's mailbox.
 

AndyInNYC

Active Member
Joined
May 23, 2013
Messages
772
Reaction score
124
billsimon,

Thanks. The solution was found in root's mail.
My 'prefilled' variables in the array weren't getting/keeping double quotes surrounding them.

I changed the array to have '"' . 'The preselection' . '"' as the variable. If I were sending the parameter in the email, I think (and will check later) that the sendfax line would have been correct.

The faxes are now sent. I still have an error reported to root's mail:

Code:
PHP Notice:  Unknown: Invalid quoted-printable sequence: =_=-_OpenGroupware_org_NGMime-605-1512400693.329147-0------
Content-Type: text/p (errflg=3) in Unknown on line 0

I think this is due to the format of the incoming/parsed email and how I'm handling it. Faxing now works, so unless the answer is obvious to someone, I'm inclined to leave it.

Andrew
 

billsimon

Well-Known Member
Joined
Jan 2, 2011
Messages
1,540
Reaction score
729
It means you are trying to parse the MIME header as quoted-printable. You probably grabbed the entire e-mail body into the variable you are processing, rather than just the plain text part of the body.
 

AndyInNYC

Active Member
Joined
May 23, 2013
Messages
772
Reaction score
124
More than likely - I'm using imap_qprint on the results of the fetchbody routine. I call fetchbody with a '1' as the variable.

Andrew
 

ostridge

Guru
Joined
Jan 22, 2015
Messages
1,636
Reaction score
523
Any thoughts on how I can get IncrediblePBX to see my patients and write my notes?
With phone appointments, and Lenny; and with a fair bit of dialplan changes and some new audio messages that should work. :devil2:
 
Get 3CX - Absolutely Free!

Link up your team and customers Phone System Live Chat Video Conferencing

Hosted or Self-managed. Up to 10 users free forever. No credit card. Try risk free.

3CX
A 3CX Account with that email already exists. You will be redirected to the Customer Portal to sign in or reset your password if you've forgotten it.
Top