<?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;
}
?>