Location: PHPKode > projects > phlyMail Lite > phlymail/backend/calendar.externalalerts.php
<?php
/**
 * External alerting service, sending alerts for events to external email addresses or SMS recipients
 * @package  phlyMail MessageCenter 4.0.0+
 * @subpackage  Calendar handler
 * @author  Matthias Sommerfeld, <hide@address.com>
 * @copyright 2005-2010, phlyLabs Berlin, http://phlylabs.de
 * @version  4.0.6 2010-06-27
 * @todo  Try to come as close as possible to the actual warning time by sending alerts out in the second
 *   they are scheduled for instead of immediately after fetching the list from the DB
 */

define('LF', "\n");
define('CRLF', "\r\n");
define('_IN_PHM_', true);
declare (ticks = 1);
$interval = 5; // Check interval in minutes
$verbose = false;
$daemonize = false;

if (!isset($argv)) $argv = array();
// Read arguments, if any
foreach ($argv as $v) {
    if (preg_match('!^-(i|-interval)\s?(\d+)!', $v, $found)) {
        $interval = ($found[2] > 0) ? $found[2] : $interval;
    } elseif ('-v' == $v || '--verbose' == $v) {
        $verbose = true;
    } elseif ('-d' == $v || '--daemonize' == $v) {
        $daemonize = true;
    } elseif ('-h' == $v || '--help' == $v) {
        echo 'Alert service daemon for the phlyMail calendar module'.LF
                .'Usage: '.basename(__FILE__).' [Options]'.LF
                ."--interval<int>, -i<int>\tMaximum recheck interval in minutes; Default: 5".LF
                ."--verbose, -v\t\tVerbose mode".LF
                ."--daemonize, -d\t\tBecome a daemon".LF
                ."--help, -h\t\t\tThis help".LF;
        exit;
    }
}

// Signal handling to allow the scripts to be shutdown cleanly via kill
if (function_exists('pcntl_signal')) {
    function sig_handler($signo) { $GLOBALS['caught_signal'] = $signo; }
    pcntl_signal(SIGTERM, 'sig_handler');
    pcntl_signal(SIGHUP, 'sig_handler');
} else {
    vecho('No signal handling installed, only unclean shutdown available');
}
if ($daemonize) {
    vecho('Running with an interval of '.$interval.' minutes');
} else {
    vecho('Running in one shot mode, use -d to make me become a daemon');
}

new calendar_alerter($interval, $daemonize);

/**
* Actual handling class for alerting external recipients
* @version 0.1.4
* @since 0.0.1
*/
class calendar_alerter
{
    private $userdata   = array();
    private $smsactive  = false;
    private $dateformat = array();
    private $subject    = 'phlyMail';

    public function __construct($interval = 5, $daemonize = false)
    {
        chdir(dirname(__FILE__));
        chdir('../');
        $_PM_ = parse_ini_file('choices.ini.php', true);
        error_reporting(isset($_PM_['core']['debugging_level']) ? $_PM_['core']['debugging_level'] : 0);
        // Global Choices, overloading core settings
        if (file_exists($_PM_['path']['conf'].'/global.choices.ini.php')) {
            $_PM_ = alerts_merge_PM($_PM_, parse_ini_file($_PM_['path']['conf'].'/global.choices.ini.php', true));
        }
        require_once($_PM_['path']['lib'].'/compat.5.x.php');
        require_once($_PM_['path']['lib'].'/functions.php');
        require_once($_PM_['path']['lib'].'/basemethods.php');
        require_once($_PM_['path']['lib'].'/message.encode.php');
        require_once($_PM_['path']['lib'].'/idna_convert.class.php');
        require_once($_PM_['path']['lib'].'/phm_streaming_sendmail.php');
        require_once($_PM_['path']['lib'].'/phm_streaming_smtp.php');
        require_once($_PM_['path']['lib'].'/phm_streaming_mailparser.php');
        require_once($_PM_['path']['lib'].'/fxl_template.inc.php');
        require_once($_PM_['path']['driver'].'/'.$_PM_['core']['database'].'/driver.php');
        require_once($_PM_['path']['handler'].'/calendar/driver.mysql.php');
        $this->_PM_ = $_PM_;
        $this->interval  = $interval;
        $this->daemonize = $daemonize;
        $this->connect();

        // Load SMS driver
        if (isset($_PM_['core']['sms_feature_active']) && $_PM_['core']['sms_feature_active']) {
            $usegwpath = $_PM_['path']['msggw'].'/'.$_PM_['core']['sms_use_gw'];
            $gwcredentials = $_PM_['path']['conf'].'/msggw.'.$_PM_['core']['sms_use_gw'].'.ini.php';
            require_once($usegwpath.'/phm_shortmessage.php');
            $this->GW = new phm_shortmessage($usegwpath, $gwcredentials);
            $this->smsactive = true;
        }

        // Reading in all available messages files to get the relevant date formats
        $d = opendir($_PM_['path']['message']);
        while ($file = readdir($d)) {
            if ('.' == $file) continue;
            if ('..' == $file) continue;
            if (!preg_match('/\.php$/i', trim($file))) continue;
            $lang = preg_replace('/\.php$/i', '', trim($file));
            require_once($_PM_['path']['message'].'/'.$file);
            $langISO = $WP_msg['language'];
            $this->dateformat[$lang] = $WP_msg['dateformat'];
            // Reading in the templates for that language, if not there, try using the English one
            if (file_exists('backend/calendar.alertemail.'.$lang.'.tpl')) {
                $this->mailtpl[$lang] = new fxl_template('backend/calendar.alertemail.'.$lang.'.tpl');
            } elseif (file_exists('backend/calendar.alertemail.en.tpl')) {
                $this->mailtpl[$lang] = new fxl_template('backend/calendar.alertemail.en.tpl');
            } else {
                vecho('No suitable mail template for '.$lang.' found');
                return;
            }
            if ($this->smsactive && file_exists('backend/calendar.alertsms.'.$lang.'.tpl')) {
                $this->smstpl[$lang] = new fxl_template('backend/calendar.alertsms.'.$lang.'.tpl');
            } elseif ($this->smsactive && file_exists('backend/calendar.alertsms.en.tpl')) {
                $this->smstpl[$lang] = new fxl_template('backend/calendar.alertsms.en.tpl');
            } elseif ($this->smsactive) {
                vecho('No suitable SMS template for '.$lang.' found');
                return;
            }
            if (file_exists($_PM_['path']['handler'].'/calendar/lang.'.$lang.'.php')) {
                require_once($_PM_['path']['handler'].'/calendar/lang.'.$lang.'.php');
                $this->msg[$lang] = $WP_msg;
            } elseif (file_exists($_PM_['path']['handler'].'/calendar/lang.'.$langISO.'.php')) {
                require_once($_PM_['path']['handler'].'/calendar/lang.'.$langISO.'.php');
                $this->msg[$lang] = $WP_msg;
            } else {
                require_once($_PM_['path']['handler'].'/calendar/lang.en.php');
                $this->msg[$lang] = $WP_msg;
            }
        }

        if (isset($_PM_['core']['provider_name']) && $_PM_['core']['provider_name'] != '') {
            $this->subject = $_PM_['core']['provider_name'];
        }

        $this->loop();
    }

    private function loop()
    {
        // "Endless" loop for periodic checks
        while (true) {
            $time = time();
            // Check, whether we were given a kill signal (SIGTERM etc.) -> shutdown
            if (isset($GLOBALS['caught_signal'])) {
                vecho('Signal caught: #'.$GLOBALS['caught_signal']);
                break;
            }
            // Make sure, the DB connection doesn't die away
            $this->DB->ping();
            // Are there any events to alert?
            $alerts = $this->cDB->get_alertable_events($this->interval, true, true);
            vecho('Handling '.count($alerts).' events');
            if (!empty($alerts)) $this->handle_alerts($alerts, 'evt');

            // Are there any tasks to alert?
            $alerts = $this->cDB->get_alertable_tasks($this->interval, true, true);
            vecho('Handling '.count($alerts).' tasks');
            if (!empty($alerts)) $this->handle_alerts($alerts, 'tsk');

            if (!$this->daemonize) exit;

            // Task done, now lay asleep for a while
            $sleep = $time + (60 * $this->interval) - time();
            vecho('Going to sleep for '.$sleep.' seconds');
            sleep($sleep);
        }
    }

    public function __destruct()
    {
        vecho('Closing DB connections');
        unset($this->cDB);
        unset($GLOBALS['DB']);
        vecho('Done');
    }

    private function connect()
    {
        $_PM_ = &$this->_PM_;
        $GLOBALS['DB'] = new driver($_PM_['path']['conf'].'/driver.'.$_PM_['core']['database'].'.ini.php');
        $cDB = new calendar_driver(0);
        if ($cDB && is_object($cDB) && $GLOBALS['DB'] && is_object($GLOBALS['DB'])) {
            vecho('Connected to DB');
            $this->DB = &$GLOBALS['DB'];
            $this->cDB = $cDB;
        } else {
            vecho('No DB connection');
            $this->__destruct();
        }
    }


    /**
    * Takes an array of events to alert
    * @param  array  List of events to process
    * @return void
    */
    private function handle_alerts($alerts, $type)
    {
        foreach ($alerts as $id => $data) {
            if (!isset($this->userdata[$data['uid']])) {
                $this->userdata[$data['uid']] = $this->DB->get_usr_choices($data['uid']);
            }
            $lang = $this->userdata[$data['uid']]['core']['language'];
            if ($data['mailto']) {
                $from = (isset($this->_PM_['core']['systememail']) && $this->_PM_['core']['systememail'])
                        ? $this->_PM_['core']['systememail']
                        : $data['mailto'];
                $subject = isset($this->userdata[$data['uid']]['core']['provider_name']) && $this->userdata[$data['uid']]['core']['provider_name']
                        ? $this->userdata[$data['uid']]['core']['provider_name']
                        : $this->subject;
                $head_reminder = $type == 'evt' ? $this->msg[$lang]['CalEvtReminder'] : $this->msg[$lang]['CalTskReminder'];
                $subject .= ' '.$head_reminder;
                $this->send_email(array
                        ('uid' => $data['uid']
                        ,'lang' => $lang
                        ,'from' => $from
                        ,'to' => $data['mailto']
                        ,'msgid' => rtrim(create_msgid($from, true))
                        ,'subject' => rtrim(encode_1522_line_q($subject, 'g', 'UTF-8'))
                        ,'subject_html' => $subject
                        ,'title' => $data['title']
                        ,'location' => $data['location']
                        ,'reminder' => $data['reminder']
                        ,'reminder_html' => nl2br($data['reminder'])
                        ,'desc' => $data['description']
                        ,'desc_html' => nl2br($data['description'])
                        ,'start' => date($this->dateformat[$lang], $data['starts'])
                        ,'end' => date($this->dateformat[$lang], $data['ends'])
                        ,'time' => time()
                        ));
            }
            if ($this->smsactive && $data['smsto']) {
                // Is the user allowed to send out SMS?
                $nochfrei = modsms_nochfrei
                        (isset($this->_PM_['core']['sms_maxmonthly']) ? $this->_PM_['core']['sms_maxmonthly'] : 0
                        ,$this->DB->get_user_accounting('sms', date('Ym'), $data['uid'])
                        ,$this->DB->get_sms_global_deposit()
                        ,isset($this->_PM_['core']['sms_allowover']) ? $this->_PM_['core']['sms_allowover'] : false
                        );
                $active = (isset($this->userdata[$data['uid']]['core']) && $this->userdata[$data['uid']]['core']) ? 1 : 0;
                if (!$nochfrei || !$active) continue;

                // Use the appropriate charset for sending
                $send_enc = (isset($this->_PM_['core']['sms_send_encoding']))
                        ? $this->_PM_['core']['sms_send_encoding']
                        : null;
                $subject_dec = decode_utf8($this->subject, $send_enc, false);
                $title_dec = decode_utf8($data['title'], $send_enc, false);
                $location_dec = decode_utf8($data['location'], $send_enc, false);
                $reminder_dec = decode_utf8($data['reminder'], $send_enc, false);
                $this->send_sms(array
                        ('uid' => $data['uid']
                        ,'lang' => $lang
                        ,'from' => (isset($this->userdata[$data['uid']]['core']['sms_sender']) && $this->userdata[$data['uid']]['core']['sms_sender'])
                                ? $this->userdata[$data['uid']]['core']['sms_sender']
                                : $data['smsto']
                        ,'to' => $data['smsto']
                        ,'subject' => ($subject_dec) ? $subject_dec : $this->subject
                        ,'title' => ($title_dec) ? $title_dec :$data['title']
                        ,'location' => ($location_dec) ? $location_dec : $data['location']
                        ,'reminder' => ($reminder_dec) ? $reminder_dec : $data['reminder']
                        ,'start' => date($this->dateformat[$lang], $data['starts'])
                        ,'end' => date($this->dateformat[$lang], $data['ends'])
                        ,'head_reminder' => $head_reminder
                        ));
            }
            $this->cDB->discard_event_alert($data['reminder_id']);
        }
    }

    /**
    * Used to send an email based on the data passed and depending on the system's settings
    * At the moment emails are sent in UTF-8, so no decoding is necessary
    * @param  array  All payload necessary
    * - lang  string  Language selected by the user (full name, like de_Du) for using the right template file
    * - from  string From address (usually the system's email address)
    * - to  string  Receiver address as stated in the event record
    * - subject  string  Subject addon (usually the provider name of the installation)
    * - title  string  Title of the event
    * - location  string  Location of the event
    * - start  datetime  Start of the event
    * - end  datetime  End of the event
    * - desc string  Descriptive text entered with tthe event
    * - uid  int  ID of the user
    *
    * @return  bool  whether sending was successfully done
    * @since 0.0.1
    */
    private function send_email($data)
    {
        $_PM_ = &$this->_PM_;
        $tpl = $this->mailtpl[$data['lang']];
        $data['to'] = mailparser::parse_email_address($data['to'], 0, true, true);
        $data['from'] = mailparser::parse_email_address($data['from'], 0, true, true);

        if ($data['reminder']) $tpl->fill_block('reminder', 'reminder', $data['reminder']);
        if ($data['reminder_html']) $tpl->fill_block('reminder_html', 'reminder_html', $data['reminder_html']);

        foreach (array('from', 'to', 'subject', 'subject_html', 'title', 'location', 'start', 'end'
                ,'desc', 'desc_html', 'msgid', 'time') as $token) {
            $tpl->assign($token, (isset($data[$token]) ? $data[$token] : ''));
        }

        if ($_PM_['core']['send_method'] == 'sendmail') {
            $sendmail = str_replace('$1', $data['from'], trim($_PM_['core']['sendmail']));
            $sm = new phm_streaming_sendmail($sendmail);
            $moep = $sm->get_last_error();
            if ($moep) {
                vecho($moep);
                $sm = false;
            }
        } elseif($_PM_['core']['send_method'] == 'smtp') {
            $sm = new phm_streaming_smtp
                    ($_PM_['core']['fix_smtp_host']
                    ,$_PM_['core']['fix_smtp_port']
                    ,$_PM_['core']['fix_smtp_user']
                    ,$_PM_['core']['fix_smtp_pass']);
            $server_open = $sm->open_server($data['from'], array($data['to']));
            if (!$server_open) {
                vecho(str_replace('<br />', '\n', str_replace(LF, '', $sm->get_last_error())).'\n');
                $sm = false;
            }
        }
        if (!$sm) return false;
        $sm->put_data_to_stream($tpl->get_content());
        $sm->finish_transfer();
        if ($_PM_['core']['send_method'] == 'sendmail') {
            if (!$sm->close()) {
                vecho('No mail sent ('.$sm->get_last_error().')\n');
                $success = false;
            } else {
                $success = true;
            }
        }
        if ($_PM_['core']['send_method'] == 'smtp') {
            if ($sm->check_success()) {
                $success = true;
            } else {
                vecho('No mail sent ('.$sm->get_last_error().')\n');
                $success = false;
            }
            $sm->close();
        }
        if ($success) vecho('Sent mail... '.print_r($data, true));
        return $success;
    }

    /**
    * Used to send an SMS based on the data passed
    * @param  array  All payload necessary
    * - lang  string  Language selected by the user (full name, like de_Du)
    * - from  string From address (usually the system's email address)
    * - to  string  Receiver address as stated in the event record
    * - title  string  Title of the event
    * - location  string  Location of the event
    * - start  datetime  Start of the event
    * - end  datetime  End of the event
    * - uid  int  ID of the user
    *
    * @return  bool  whether sending was successfully done
    * @since 0.0.1
    */
    private function send_sms($data)
    {
        $tpl = $this->smstpl[$data['lang']];
        if ($data['reminder']) $tpl->fill_block('reminder', 'reminder', $data['reminder']);
        foreach (array('from', 'to', 'subject', 'title', 'location', 'desc', 'start', 'end', 'head_reminder') as $token) {
            $tpl->assign($token, (isset($data[$token]) ? $data[$token] : ''));
        }
        // Receiver and sender - numbers, text, type get "washed"
        $Washed = $this->GW->wash_input(array
                ('from' => $data['from']
                ,'to' => $data['to']
                ,'text' => $tpl->get_content()
                ));
        if (!is_array($Washed)) {
            vecho('Could not send SMS ('.$this->GW->get_last_error().')');
            return false;
        } else {
            // Und weg damit
            $return = $this->GW->send_sms($Washed);
            switch ($return[0]) {
            case 101:
            case 100:
                $sms_sent = (isset($return[2])) ? $return[2] : 1;
                $this->DB->decrease_sms_global_deposit($sms_sent);
                $this->DB->set_user_accounting('sms', date('Ym'), $data['uid'], $sms_sent);
                $this->DB->log_sms_sent(array
                        ('uid' => $data['uid']
                        ,'when' => time()
                        ,'receiver' => substr($Washed['to'], 0, -3) . 'xxx'
                        ,'size' => strlen($Washed['text'])
                        ,'type' => 0
                        ));
                vecho('Sent SMS... '.print_r($data, true));
                return true;
                break;
            default:
                vecho('Could not send SMS ('.$return[1].')');
                return false;
                break;
            }
        }
    }
}

// Since array_merge can only merge flat arrays and array_merge_recursive appends doublettes
// to the father element we have to do the merge "manually"
function alerts_merge_PM($_PM_, $import)
{
    foreach ($import as $k => $v) {
        if (is_array($v)) {
            foreach ($v as $k2 => $v2) $_PM_[$k][$k2] = $v2;
        } else {
            $_PM_[$k] = $v;
        }
    }
    return $_PM_;
}

function vecho ($text)
{
    if (!$GLOBALS['verbose']) return;
    echo $text.LF;
}

// Returns number of SMS still possible, if user may still send at least one SMS, else false
function modsms_nochfrei($maxmonthly, $getsmssent, $globalfree, $allowover)
{
    if ($maxmonthly) {
        $nochfrei = $maxmonthly - ($getsmssent+0);
        return ($nochfrei > 0) ? $nochfrei : false;
    } elseif ($allowover) {
        return 1000; // Dumme hohe Zahl, damit das Senden dann auch geht...
    }
    return $globalfree;
}
?>
Return current item: phlyMail Lite