From 50fd088dca681f63321aeb795b6598b32da0562f Mon Sep 17 00:00:00 2001 From: Pepijn Over Date: Tue, 7 Jan 2014 20:24:48 +0100 Subject: [PATCH] Initial import of phpservermon v2.0.0 --- classes/mod/modConfig.class.php | 153 ++ classes/mod/modCore.class.php | 45 + classes/mod/modLog.class.php | 137 ++ classes/mod/modServers.class.php | 219 +++ classes/mod/modUsers.class.php | 225 +++ classes/phpmailer.class.php | 1615 ++++++++++++++++++++++ classes/sm/smCore.class.php | 34 + classes/sm/smDatabase.class.php | 365 +++++ classes/sm/smTemplate.class.php | 307 ++++ classes/sm/smUpdaterStatus.class.php | 354 +++++ classes/txtmsg/txtmsgCore.class.php | 61 + classes/txtmsg/txtmsgInetworx.class.php | 142 ++ classes/txtmsg/txtmsgInterface.class.php | 34 + classes/txtmsg/txtmsgMollie.class.php | 96 ++ classes/txtmsg/txtmsgSpryng.class.php | 72 + config.inc.php | 71 + cron/status.cron.php | 70 + docs/CHANGELOG | 25 + docs/COPYING | 674 +++++++++ docs/README | 176 +++ example/.htaccess | 4 + example/.htpasswd | 1 + functions.inc.php | 220 +++ img/delete.png | Bin 0 -> 311 bytes img/edit.png | Bin 0 -> 451 bytes img/off.png | Bin 0 -> 21882 bytes img/on.png | Bin 0 -> 20409 bytes img/opensource.png | Bin 0 -> 14437 bytes inc/monitor.css | 160 +++ inc/monitor.js | 33 + inc/tabs.css | 47 + index.php | 97 ++ install.php | 143 ++ lang/en.lang.php | 132 ++ lang/nl.lang.php | 131 ++ tpl/config.tpl.html | 125 ++ tpl/install.tpl.html | 24 + tpl/log.tpl.html | 42 + tpl/main.tpl.html | 36 + tpl/servers.tpl.html | 108 ++ tpl/users.tpl.html | 69 + 41 files changed, 6247 insertions(+) create mode 100644 classes/mod/modConfig.class.php create mode 100644 classes/mod/modCore.class.php create mode 100644 classes/mod/modLog.class.php create mode 100644 classes/mod/modServers.class.php create mode 100644 classes/mod/modUsers.class.php create mode 100644 classes/phpmailer.class.php create mode 100644 classes/sm/smCore.class.php create mode 100644 classes/sm/smDatabase.class.php create mode 100644 classes/sm/smTemplate.class.php create mode 100644 classes/sm/smUpdaterStatus.class.php create mode 100644 classes/txtmsg/txtmsgCore.class.php create mode 100644 classes/txtmsg/txtmsgInetworx.class.php create mode 100644 classes/txtmsg/txtmsgInterface.class.php create mode 100644 classes/txtmsg/txtmsgMollie.class.php create mode 100644 classes/txtmsg/txtmsgSpryng.class.php create mode 100644 config.inc.php create mode 100644 cron/status.cron.php create mode 100644 docs/CHANGELOG create mode 100644 docs/COPYING create mode 100644 docs/README create mode 100644 example/.htaccess create mode 100644 example/.htpasswd create mode 100644 functions.inc.php create mode 100644 img/delete.png create mode 100644 img/edit.png create mode 100644 img/off.png create mode 100644 img/on.png create mode 100644 img/opensource.png create mode 100644 inc/monitor.css create mode 100644 inc/monitor.js create mode 100644 inc/tabs.css create mode 100644 index.php create mode 100644 install.php create mode 100644 lang/en.lang.php create mode 100644 lang/nl.lang.php create mode 100644 tpl/config.tpl.html create mode 100644 tpl/install.tpl.html create mode 100644 tpl/log.tpl.html create mode 100644 tpl/main.tpl.html create mode 100644 tpl/servers.tpl.html create mode 100644 tpl/users.tpl.html diff --git a/classes/mod/modConfig.class.php b/classes/mod/modConfig.class.php new file mode 100644 index 00000000..1f962b83 --- /dev/null +++ b/classes/mod/modConfig.class.php @@ -0,0 +1,153 @@ +. + */ + +class modConfig extends modCore { + + function __construct() { + parent::__construct(); + + if(!empty($_POST)) { + $this->executeSave(); + } + } + + public function executeSave() { + // save new config + $clean = array( + 'language' => $_POST['language'], + 'show_update' => (isset($_POST['show_update'])) ? '1' : '0', + 'email_status' => (isset($_POST['email_status'])) ? '1' : '0', + 'email_from_name' => $_POST['email_from_name'], + 'email_from_email' => $_POST['email_from_email'], + 'sms_status' => (isset($_POST['sms_status'])) ? '1' : '0', + 'sms_gateway' => $_POST['sms_gateway'], + 'sms_gateway_username' => $_POST['sms_gateway_username'], + 'sms_gateway_password' => $_POST['sms_gateway_password'], + 'sms_from' => $_POST['sms_from'], + 'alert_type' => $_POST['alert_type'], + 'log_status' => (isset($_POST['log_status'])) ? '1' : '0', + 'log_email' => (isset($_POST['log_email'])) ? '1' : '0', + 'log_sms' => (isset($_POST['log_sms'])) ? '1' : '0', + ); + + // save all values to the database + foreach($clean as $key => $value) { + $this->db->save( + SM_DB_PREFIX . 'config', + array('value' => $value), + array('key' => $key) + ); + } + + $this->message = sm_get_lang('config', 'updated'); + } + + public function createHTML() { + $this->tpl_id = 'config'; + $this->tpl->newTemplate($this->tpl_id, 'config.tpl.html'); + + $this->createHTMLUpdate(); + $this->createHTMLLabels(); + + $this->populateFields(); + + return parent::createHTML(); + } + + public function createHTMLUpdate() { + // get latest version number + + } + + public function populateFields() { + $config_db = $this->db->select( + SM_DB_PREFIX . 'config', + null, + array('key', 'value') + ); + + $config = array(); + foreach($config_db as $entry) { + $config[$entry['key']] = $entry['value']; + } + + $this->tpl->addTemplateData( + $this->tpl_id, + array( + 'language_selected_' . $config['language'] => 'selected="selected"', + 'email_status_checked' => ($config['email_status'] == '1') ? 'checked="checked"' : '', + 'email_from_name' => $config['email_from_name'], + 'email_from_email' => $config['email_from_email'], + 'sms_status_checked' => ($config['sms_status'] == '1') ? 'checked="checked"' : '', + 'sms_selected_' . $config['sms_gateway'] => 'selected="selected"', + 'sms_gateway_username' => $config['sms_gateway_username'], + 'sms_gateway_password' => $config['sms_gateway_password'], + 'sms_from' => $config['sms_from'], + 'alert_type_selected_' . $config['alert_type'] => 'selected="selected"', + 'log_status_checked' => ($config['log_status'] == '1') ? 'checked="checked"' : '', + 'log_email_checked' => ($config['log_email'] == '1') ? 'checked="checked"' : '', + 'log_sms_checked' => ($config['log_sms'] == '1') ? 'checked="checked"' : '', + 'show_update_checked' => ($config['show_update'] == '1') ? 'checked="checked"' : '', + ) + ); + } + + public function createHTMLLabels() { + $this->tpl->addTemplateData( + $this->tpl_id, + array( + 'label_settings_email' => sm_get_lang('config', 'settings_email'), + 'label_settings_sms' => sm_get_lang('config', 'settings_sms'), + 'label_settings_notification' => sm_get_lang('config', 'settings_notification'), + 'label_settings_log' => sm_get_lang('config', 'settings_log'), + 'label_general' => sm_get_lang('config', 'general'), + 'label_language' => sm_get_lang('config', 'language'), + 'label_language_english' => sm_get_lang('config', 'english'), + 'label_language_dutch' => sm_get_lang('config', 'dutch'), + 'label_show_update' => sm_get_lang('config', 'show_update'), + 'label_email_status' => sm_get_lang('config', 'email_status'), + 'label_email_from_email' => sm_get_lang('config', 'email_from_email'), + 'label_email_from_name' => sm_get_lang('config', 'email_from_name'), + 'label_sms_status' => sm_get_lang('config', 'sms_status'), + 'label_sms_gateway' => sm_get_lang('config', 'sms_gateway'), + 'label_sms_gateway_mollie' => sm_get_lang('config', 'sms_gateway_mollie'), + 'label_sms_gateway_spryng' => sm_get_lang('config', 'sms_gateway_spryng'), + 'label_sms_gateway_inetworx' => sm_get_lang('config', 'sms_gateway_inetworx'), + 'label_sms_gateway_username' => sm_get_lang('config', 'sms_gateway_username'), + 'label_sms_gateway_password' => sm_get_lang('config', 'sms_gateway_password'), + 'label_sms_from' => sm_get_lang('config', 'sms_from'), + 'label_alert_type' => sm_get_lang('config', 'alert_type'), + 'label_alert_type_status' => sm_get_lang('config', 'alert_type_status'), + 'label_alert_type_offline' => sm_get_lang('config', 'alert_type_offline'), + 'label_alert_type_always' => sm_get_lang('config', 'alert_type_always'), + 'label_log_status' => sm_get_lang('config', 'log_status'), + 'label_log_email' => sm_get_lang('config', 'log_email'), + 'label_log_sms' => sm_get_lang('config', 'log_sms'), + 'message' => ($this->message == '') ? ' ' : $this->message, + ) + ); + } +} + +?> \ No newline at end of file diff --git a/classes/mod/modCore.class.php b/classes/mod/modCore.class.php new file mode 100644 index 00000000..cc225638 --- /dev/null +++ b/classes/mod/modCore.class.php @@ -0,0 +1,45 @@ +. + */ + +abstract class modCore { + public $db; + public $message; + public $mode; + public $tpl; + public $tpl_id; + + function __construct() { + global $db, $tpl; + + $this->db = ($db) ? $db : new smDatabase(); + $this->tpl = ($tpl) ? $tpl : new smTemplate(); + } + + public function createHTML() { + $html = $this->tpl->getTemplate($this->tpl_id); + return $html; + } +} + +?> \ No newline at end of file diff --git a/classes/mod/modLog.class.php b/classes/mod/modLog.class.php new file mode 100644 index 00000000..fc294e20 --- /dev/null +++ b/classes/mod/modLog.class.php @@ -0,0 +1,137 @@ +. + */ + +class modLog extends modCore { + + function __construct() { + parent::__construct(); + } + + public function createHTML() { + $this->createHTMLList(); + + $this->tpl->addCSS('tabs.css', 'main'); + + $this->tpl->addTemplateData( + $this->tpl_id, + array( + 'label_status' => sm_get_lang('log', 'status'), + 'label_email' => sm_get_lang('log', 'email'), + 'label_sms' => sm_get_lang('log', 'sms'), + 'label_title' => sm_get_lang('log', 'title'), + 'label_server' => sm_get_lang('servers', 'server'), + 'label_type' => sm_get_lang('log', 'type'), + 'label_message' => sm_get_lang('system', 'message'), + 'label_date' => sm_get_lang('system', 'date'), + 'label_users' => ucfirst(sm_get_lang('system', 'users')), + ) + ); + + return parent::createHTML(); + } + + public function createHTMLList() { + $this->tpl_id = 'log_list'; + + $this->tpl->newTemplate($this->tpl_id, 'log.tpl.html'); + + $entries = array(); + $entries['status'] = $this->getEntries('status'); + $entries['email'] = $this->getEntries('email'); + $entries['sms'] = $this->getEntries('sms'); + + // get users + $users = $this->db->select(SM_DB_PREFIX.'users', null, array('user_id','name')); + + $users_labels = array(); + foreach ($users as $user) { + $users_labels[$user['user_id']] = $user['name']; + } + + foreach($entries as $key => $records) { + $log_count = count($records); + + for ($x = 0; $x < $log_count; $x++) { + $records[$x]['class'] = ($x & 1) ? 'odd' : 'even'; + $records[$x]['users'] = ''; + $records[$x]['server'] = $records[$x]['label'] . ' (' . $records[$x]['label_adv'] . ')'; + + // fix up user list + if($records[$x]['user_id'] == '') continue; + + $users = explode(',', $records[$x]['user_id']); + foreach($users as $user_id) { + if((int) $user_id == 0 || !isset($users_labels[$user_id])) continue; + + $records[$x]['users'] .= '
'.$users_labels[$user_id]; + } + } + + // add entries to template + $this->tpl->newTemplate('log_entries', 'log.tpl.html'); + $this->tpl->addTemplateDataRepeat('log_entries', 'entries', $records); + $this->tpl->addTemplateData( + 'log_entries', + array( + 'logtitle' => $key, + ) + ); + $this->tpl->addTemplateData( + $this->tpl_id, + array( + 'content_' . $key => $this->tpl->getTemplate('log_entries'), + ) + ); + } + + } + + public function getEntries($type) { + $entries = $this->db->query( + 'SELECT '. + '`servers`.`label`, '. + 'CONCAT_WS('. + '\':\','. + '`servers`.`ip`, '. + '`servers`.`port`'. + ') AS `label_adv`, '. + '`log`.`type`, '. + '`log`.`message`, '. + 'DATE_FORMAT('. + '`log`.`datetime`, '. + '\'%H:%i:%s %d-%m-%y\''. + ') AS `datetime_format`, '. + '`user_id` '. + 'FROM `'.SM_DB_PREFIX.'log` AS `log` '. + 'JOIN `'.SM_DB_PREFIX.'servers` AS `servers` ON (`servers`.`server_id`=`log`.`server_id`) '. + 'WHERE `log`.`type`=\''.$type.'\' '. + 'ORDER BY `datetime` DESC '. + 'LIMIT 0,20' + ); + return $entries; + } + +} + +?> \ No newline at end of file diff --git a/classes/mod/modServers.class.php b/classes/mod/modServers.class.php new file mode 100644 index 00000000..20ee7e04 --- /dev/null +++ b/classes/mod/modServers.class.php @@ -0,0 +1,219 @@ +. + */ + +class modServers extends modCore { + + function __construct() { + parent::__construct(); + + // check mode + if (isset($_GET['edit']) && is_numeric($_GET['edit'])) { + // edit mode or insert mode + $this->mode = 'update'; + } else { + $this->mode = 'list'; + + if(!empty($_POST)) { + $this->executeSave(); + } + if(isset($_GET['delete']) && is_numeric($_GET['delete'])) { + $this->executeDelete(); + } + } + } + + public function createHTML() { + switch($this->mode) { + case 'list': + $this->createHTMLList(); + break; + case 'update': + $this->createHTMLUpdate(); + break; + } + + // add labels + $this->tpl->addTemplateData( + $this->tpl_id, + array( + 'label_label' => sm_get_lang('servers', 'label'), + 'label_domain' => sm_get_lang('servers', 'domain'), + 'label_port' => sm_get_lang('servers', 'port'), + 'label_type' => sm_get_lang('servers', 'type'), + 'label_last_check' => sm_get_lang('servers', 'last_check'), + 'label_rtime' => sm_get_lang('servers', 'rtime'), + 'label_last_online' => sm_get_lang('servers', 'last_online'), + 'label_monitoring' => sm_get_lang('servers', 'monitoring'), + 'label_send_email' => sm_get_lang('servers', 'send_email'), + 'label_send_sms' => sm_get_lang('servers', 'send_sms'), + 'label_action' => sm_get_lang('system', 'action'), + 'label_save' => sm_get_lang('system', 'save'), + 'label_edit' => sm_get_lang('system', 'edit') . ' ' . sm_get_lang('servers', 'server'), + 'label_delete' => sm_get_lang('system', 'delete') . ' ' . sm_get_lang('servers', 'server'), + 'label_yes' => sm_get_lang('system', 'yes'), + 'label_no' => sm_get_lang('system', 'no'), + 'label_add_new' => sm_get_lang('system', 'add_new'), + 'message' => ($this->message == '') ? ' ' : $this->message, + ) + ); + + return parent::createHTML(); + } + + + public function createHTMLUpdate() { + $this->tpl_id = 'servers_update'; + $this->tpl->newTemplate($this->tpl_id, 'servers.tpl.html'); + + $server_id = $_GET['edit']; + + $tpl_data = array(); + + switch((int) $server_id) { + case 0: + // insert mode + $tpl_data['titlemode'] = sm_get_lang('system', 'insert'); + $tpl_data['edit_server_id'] = '0'; + break; + default: + // edit mode + + // get server entry + $edit_server = $this->db->selectRow( + SM_DB_PREFIX.'servers', + array('server_id' => $server_id) + ); + if (empty($edit_server)) { + $this->message = 'Invalid server id'; + return $this->createHTMLList(); + } + + $tpl_data = array_merge($tpl_data, array( + 'titlemode' => sm_get_lang('system', 'edit') . ' ' . $edit_server['label'], + 'edit_server_id' => $edit_server['server_id'], + 'edit_value_label' => $edit_server['label'], + 'edit_value_ip' => $edit_server['ip'], + 'edit_value_port' => $edit_server['port'], + 'edit_type_selected_' . $edit_server['type'] => 'selected="selected"', + 'edit_active_selected_' . $edit_server['active'] => 'selected="selected"', + 'edit_email_selected_' . $edit_server['email'] => 'selected="selected"', + 'edit_sms_selected_' . $edit_server['sms'] => 'selected="selected"', + )); + + break; + } + + $this->tpl->addTemplateData( + $this->tpl_id, + $tpl_data + ); + } + + public function createHTMLList() { + $this->tpl_id = 'servers_list'; + $this->tpl->newTemplate($this->tpl_id, 'servers.tpl.html'); + + // get servers from database + $servers = $this->db->query( + 'SELECT '. + '`server_id`, '. + '`ip`, '. + '`port`, '. + '`type`, '. + '`label`, '. + '`status`, '. + '`error`, '. + '`rtime`, '. + 'IF('. + '`last_check`=\'0000-00-00 00:00:00\', '. + '\'never\', '. + 'DATE_FORMAT(`last_check`, \'%d-%m-%y %H:%i\') '. + ') AS `last_check`, '. + 'IF('. + '`last_online`=\'0000-00-00 00:00:00\', '. + '\'never\', '. + 'DATE_FORMAT(`last_online`, \'%d-%m-%y %H:%i\') '. + ') AS `last_online`, '. + '`active`, '. + '`email`, '. + '`sms` '. + 'FROM `'.SM_DB_PREFIX.'servers` '. + 'ORDER BY `type` ASC, `label` ASC' + ); + + $server_count = count($servers); + + for ($x = 0; $x < $server_count; $x++) { + $servers[$x]['class'] = ($x & 1) ? 'odd' : 'even'; + $servers[$x]['rtime'] = round((float) $servers[$x]['rtime'], 4); + } + // add servers to template + $this->tpl->addTemplateDataRepeat($this->tpl_id, 'servers', $servers); + + } + + public function executeSave() { + // check for add/edit mode + if (isset($_POST['label']) && isset($_POST['ip']) && isset($_POST['port'])) { + $clean = array( + 'label' => $_POST['label'], + 'ip' => $_POST['ip'], + 'port' => $_POST['port'], + 'type' => $_POST['type'], + 'active' => $_POST['active'], + 'email' => $_POST['email'], + 'sms' => $_POST['sms'], + ); + + // check for edit or add + if ((int) $_POST['server_id'] > 0) { + // edit + $this->db->save( + SM_DB_PREFIX.'servers', + $clean, + array('server_id' => $_POST['server_id']) + ); + $this->message = sm_get_lang('servers', 'updated'); + } else { + // add + $clean['status'] = 'on'; + $this->db->save(SM_DB_PREFIX.'servers', $clean); + $this->message = sm_get_lang('servers', 'inserted'); + } + } + } + + public function executeDelete() { + // do delete + $this->db->delete( + SM_DB_PREFIX . 'servers', + array( + 'server_id' => $_GET['delete'] + ) + ); + $this->message = sm_get_lang('system', 'deleted'); + } +} + +?> \ No newline at end of file diff --git a/classes/mod/modUsers.class.php b/classes/mod/modUsers.class.php new file mode 100644 index 00000000..e9494fee --- /dev/null +++ b/classes/mod/modUsers.class.php @@ -0,0 +1,225 @@ +. + */ + +class modUsers extends modCore { + public $servers; + + function __construct() { + parent::__construct(); + + // check mode + if (isset($_GET['edit']) && is_numeric($_GET['edit'])) { + // edit mode or insert mode + $this->mode = 'update'; + } else { + $this->mode = 'list'; + + if(!empty($_POST)) { + $this->executeSave(); + } + if(isset($_GET['delete']) && is_numeric($_GET['delete'])) { + $this->executeDelete(); + } + } + + $this->servers = $this->db->select(SM_DB_PREFIX.'servers', null, array('server_id', 'label')); + } + + public function createHTML() { + switch($this->mode) { + case 'list': + $this->createHTMLList(); + break; + case 'update': + $this->createHTMLUpdate(); + break; + } + + // add labels + $this->tpl->addTemplateData( + $this->tpl_id, + array( + 'label_users' => sm_get_lang('system', 'users'), + 'label_name' => sm_get_lang('users', 'name'), + 'label_mobile' => sm_get_lang('users', 'mobile'), + 'label_email' => sm_get_lang('users', 'email'), + 'label_servers' => sm_get_lang('system', 'servers'), + 'label_action' => sm_get_lang('system', 'action'), + 'label_save' => sm_get_lang('system', 'save'), + 'label_edit' => sm_get_lang('system', 'edit') . ' ' . sm_get_lang('users', 'user'), + 'label_delete' => sm_get_lang('system', 'delete') . ' ' . sm_get_lang('users', 'user'), + 'label_add_new' => sm_get_lang('system', 'add_new'), + 'message' => ($this->message == '') ? ' ' : $this->message, + ) + ); + + return parent::createHTML(); + } + + + public function createHTMLUpdate() { + $this->tpl_id = 'users_update'; + $this->tpl->newTemplate($this->tpl_id, 'users.tpl.html'); + + $user_id = $_GET['edit']; + + $tpl_data = array(); + $servers_count = count($this->servers); + + switch((int) $user_id) { + case 0: + // insert mode + $tpl_data['titlemode'] = sm_get_lang('system', 'insert'); + $tpl_data['edit_user_id'] = '0'; + + // add inactive class to all servers + for ($i = 0; $i < $servers_count; $i++) { + $this->servers[$i]['class'] = 'inactive'; + } + + break; + default: + // edit mode + + // get user entry + $edit_user = $this->db->selectRow( + SM_DB_PREFIX.'users', + array('user_id' => $user_id) + ); + if (empty($edit_user)) { + $this->message = 'Invalid user id'; + return $this->createHTMLList(); + } + + $tpl_data = array_merge($tpl_data, array( + 'titlemode' => sm_get_lang('system', 'edit') . ' ' . $edit_user['name'], + 'edit_user_id' => $edit_user['user_id'], + 'edit_value_name' => $edit_user['name'], + 'edit_value_mobile' => $edit_user['mobile'], + 'edit_value_email' => $edit_user['email'], + )); + + // select servers for this user + $user_servers = explode(',', $edit_user['server_id']); + + for ($h = 0; $h < $servers_count; $h++) { + if(in_array($this->servers[$h]['server_id'], $user_servers)) { + $this->servers[$h]['edit_checked'] = 'checked="checked"'; + $this->servers[$h]['class'] = 'active'; + } + } + + break; + } + + $this->tpl->addTemplateData( + $this->tpl_id, + $tpl_data + ); + // add servers to template for the edit form + $this->tpl->addTemplateDataRepeat('users_update', 'servers', $this->servers); + } + + public function createHTMLList() { + $this->tpl_id = 'users_list'; + $this->tpl->newTemplate($this->tpl_id, 'users.tpl.html'); + + // build label array for the next loop + $servers_labels = array(); + foreach ($this->servers as $server) { + $servers_labels[$server['server_id']] = $server['label']; + } + + // get users from database + $users = $this->db->select( + SM_DB_PREFIX.'users', + null, + null, + null, + array('name') + ); + + $user_count = count($users); + + for ($x = 0; $x < $user_count; $x++) { + $users[$x]['class'] = ($x & 1) ? 'odd' : 'even'; + + $users[$x]['emp_servers'] = ''; + + // fix server list + $user_servers = explode(',', $users[$x]['server_id']); + if (empty($user_servers)) continue; + + foreach ($user_servers as $server) { + if (!isset($servers_labels[$server])) continue; + $users[$x]['emp_servers'] .= $servers_labels[$server] . '
'; + } + $users[$x]['emp_servers'] = substr($users[$x]['emp_servers'], 0, -5); + } + // add servers to template + $this->tpl->addTemplateDataRepeat($this->tpl_id, 'users', $users); + + } + + public function executeSave() { + // check for add/edit mode + + if (isset($_POST['name']) && isset($_POST['mobile']) && isset($_POST['email'])) { + $clean = array( + 'name' => $_POST['name'], + 'mobile' => $_POST['mobile'], + 'email' => $_POST['email'], + 'server_id' => (isset($_POST['server_id'])) ? implode(',', $_POST['server_id']) : '' + ); + + // check for edit or add + if ((int) $_POST['user_id'] > 0) { + // edit + $this->db->save( + SM_DB_PREFIX.'users', + $clean, + array('user_id' => $_POST['user_id']) + ); + $this->message = sm_get_lang('users', 'updated'); + } else { + // add + $this->db->save(SM_DB_PREFIX.'users', $clean); + $this->message = sm_get_lang('users', 'inserted'); + } + } + } + + public function executeDelete() { + // do delete + $this->db->delete( + SM_DB_PREFIX . 'users', + array( + 'user_id' => $_GET['delete'] + ) + ); + $this->message = sm_get_lang('system', 'deleted'); + } +} + +?> \ No newline at end of file diff --git a/classes/phpmailer.class.php b/classes/phpmailer.class.php new file mode 100644 index 00000000..0da0e8c9 --- /dev/null +++ b/classes/phpmailer.class.php @@ -0,0 +1,1615 @@ + +// +// License: LGPL, see LICENSE +//////////////////////////////////////////////////// + +/** + * phpmailer - PHP email transport class + * @author Brent R. Matzelle + */ +class phpmailer +{ + ///////////////////////////////////////////////// + // PUBLIC VARIABLES + ///////////////////////////////////////////////// + + /** + * Email priority (1 = High, 3 = Normal, 5 = low). Default value is 3. + * @public + * @type int + */ + var $Priority = 3; + + /** + * Sets the CharSet of the message. Default value is "iso-8859-1". + * @public + * @type string + */ + var $CharSet = "iso-8859-1"; + + /** + * Sets the Content-type of the message. Default value is "text/plain". + * @public + * @type string + */ + var $ContentType = "text/plain"; + + /** + * Sets the Encoding of the message. Options for this are "8bit" (default), + * "7bit", "binary", "base64", and "quoted-printable". + * @public + * @type string + */ + var $Encoding = "8bit"; + + /** + * Holds the most recent mailer error message. Default value is "". + * @public + * @type string + */ + var $ErrorInfo = ""; + + /** + * Sets the From email address for the message. Default value is "root@localhost". + * @public + * @type string + */ + var $From = "root@localhost"; + + /** + * Sets the From name of the message. Default value is "Root User". + * @public + * @type string + */ + var $FromName = "Root User"; + + /** + * Sets the Sender email of the message. If not empty, will be sent via -f to sendmail + * or as 'MAIL FROM' in smtp mode. Default value is "". + * @public + * @type string + */ + var $Sender = ""; + + /** + * Sets the Subject of the message. Default value is "". + * @public + * @type string + */ + var $Subject = ""; + + /** + * Sets the Body of the message. This can be either an HTML or text body. + * If HTML then run IsHTML(true). Default value is "". + * @public + * @type string + */ + var $Body = ""; + + /** + * Sets the text-only body of the message. This automatically sets the + * email to multipart/alternative. This body can be read by mail + * clients that do not have HTML email capability such as mutt. Clients + * that can read HTML will view the normal Body. + * Default value is "". + * @public + * @type string + */ + var $AltBody = ""; + + /** + * Sets word wrapping on the body of the message to a given number of + * characters. Default value is 0 (off). + * @public + * @type int + */ + var $WordWrap = 0; + + /** + * Method to send mail: ("mail", "sendmail", or "smtp"). + * Default value is "mail". + * @public + * @type string + */ + var $Mailer = "mail"; + + /** + * Sets the path of the sendmail program. Default value is + * "/usr/sbin/sendmail". + * @public + * @type string + */ + var $Sendmail = "/usr/sbin/sendmail"; + + /** + * Turns Microsoft mail client headers on and off. Useful mostly + * for older clients. Default value is false (off). + * @public + * @type bool + */ + var $UseMSMailHeaders = false; + + /** + * Path to phpmailer plugins. This is now only useful if the SMTP class + * is in a different directory than the PHP include path. + * Default is empty (""). + * @public + * @type string + */ + var $PluginDir = ""; + + /** + * Holds phpmailer version. + * @public + * @type string + */ + var $Version = "1.54"; + + /** + * Sets the email address that a reading confirmation will be sent. Default value is "". + * @public + * @type string + */ + var $ConfirmReadingTo = ""; + + /** + * Sets the line endings of the message. Default is "\n"; + * @public + * @type string + */ + var $LE = "\n"; + + + ///////////////////////////////////////////////// + // SMTP VARIABLES + ///////////////////////////////////////////////// + + /** + * Sets the SMTP hosts. All hosts must be separated by a + * semicolon. You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.domain.com:25;smtp2.domain.com"). + * Hosts will be tried in order. + * Default value is "localhost". + * @public + * @type string + */ + var $Host = "localhost"; + + /** + * Sets the default SMTP server port. Default value is 25. + * @public + * @type int + */ + var $Port = 25; + + /** + * Sets the SMTP HELO of the message. + * Default value is "localhost.localdomain". + * @public + * @type string + */ + var $Helo = "localhost.localdomain"; + + /** + * Sets SMTP authentication. Utilizes the Username and Password variables. + * Default value is false (off). + * @public + * @type bool + */ + var $SMTPAuth = false; + + /** + * Sets SMTP username. Default value is "". + * @public + * @type string + */ + var $Username = ""; + + /** + * Sets SMTP password. Default value is "". + * @public + * @type string + */ + var $Password = ""; + + /** + * Sets the SMTP server timeout in seconds. This function will not + * work with the win32 version. Default value is 10. + * @public + * @type int + */ + var $Timeout = 10; + + /** + * Sets SMTP class debugging on or off. Default value is false (off). + * @public + * @type bool + */ + var $SMTPDebug = false; + + + ///////////////////////////////////////////////// + // PRIVATE VARIABLES + ///////////////////////////////////////////////// + + /** + * Holds all "To" addresses. + * @type array + */ + var $to = array(); + + /** + * Holds all "CC" addresses. + * @type array + */ + var $cc = array(); + + /** + * Holds all "BCC" addresses. + * @type array + */ + var $bcc = array(); + + /** + * Holds all "Reply-To" addresses. + * @type array + */ + var $ReplyTo = array(); + + /** + * Holds all string and binary attachments. + * @type array + */ + var $attachment = array(); + + /** + * Holds all custom headers. + * @type array + */ + var $CustomHeader = array(); + + /** + * Holds the type of the message. + * @type string + */ + var $message_type = ""; + + /** + * Holds the message boundaries. + * @type string array + */ + var $boundary = array(); + + ///////////////////////////////////////////////// + // VARIABLE METHODS + ///////////////////////////////////////////////// + + /** + * Sets message type to HTML. Returns void. + * @public + * @returns void + */ + function IsHTML($bool) { + if($bool == true) + $this->ContentType = "text/html"; + else + $this->ContentType = "text/plain"; + } + + /** + * Sets Mailer to send message using SMTP. + * Returns void. + * @public + * @returns void + */ + function IsSMTP() { + $this->Mailer = "smtp"; + } + + /** + * Sets Mailer to send message using PHP mail() function. + * Returns void. + * @public + * @returns void + */ + function IsMail() { + $this->Mailer = "mail"; + } + + /** + * Sets Mailer to send message using the $Sendmail program. + * Returns void. + * @public + * @returns void + */ + function IsSendmail() { + $this->Mailer = "sendmail"; + } + + /** + * Sets Mailer to send message using the qmail MTA. Returns void. + * @public + * @returns void + */ + function IsQmail() { + //$this->Sendmail = "/var/qmail/bin/qmail-inject"; + $this->Sendmail = "/var/qmail/bin/sendmail"; + $this->Mailer = "sendmail"; + } + + + ///////////////////////////////////////////////// + // RECIPIENT METHODS + ///////////////////////////////////////////////// + + /** + * Adds a "To" address. Returns void. + * @public + * @returns void + */ + function AddAddress($address, $name = "") { + $cur = count($this->to); + $this->to[$cur][0] = trim($address); + $this->to[$cur][1] = $name; + } + + /** + * Adds a "Cc" address. Note: this function works + * with the SMTP mailer on win32, not with the "mail" + * mailer. This is a PHP bug that has been submitted + * on http://bugs.php.net. The *NIX version of PHP + * functions correctly. Returns void. + * @public + * @returns void + */ + function AddCC($address, $name = "") { + $cur = count($this->cc); + $this->cc[$cur][0] = trim($address); + $this->cc[$cur][1] = $name; + } + + /** + * Adds a "Bcc" address. Note: this function works + * with the SMTP mailer on win32, not with the "mail" + * mailer. This is a PHP bug that has been submitted + * on http://bugs.php.net. The *NIX version of PHP + * functions correctly. + * Returns void. + * @public + * @returns void + */ + function AddBCC($address, $name = "") { + $cur = count($this->bcc); + $this->bcc[$cur][0] = trim($address); + $this->bcc[$cur][1] = $name; + } + + /** + * Adds a "Reply-to" address. Returns void. + * @public + * @returns void + */ + function AddReplyTo($address, $name = "") { + $cur = count($this->ReplyTo); + $this->ReplyTo[$cur][0] = trim($address); + $this->ReplyTo[$cur][1] = $name; + } + + + ///////////////////////////////////////////////// + // MAIL SENDING METHODS + ///////////////////////////////////////////////// + + /** + * Creates message and assigns Mailer. If the message is + * not sent successfully then it returns false. Use the ErrorInfo + * variable to view description of the error. Returns bool. + * @public + * @returns bool + */ + function Send() { + $header = ""; + $body = ""; + + if((count($this->to) + count($this->cc) + count($this->bcc)) < 1) + { + $this->error_handler("You must provide at least one recipient email address"); + return false; + } + + // Set whether the message is multipart/alternative + if(!empty($this->AltBody)) + $this->ContentType = "multipart/alternative"; + + // Attach sender information & date + $header = $this->received(); + $header .= sprintf("Date: %s%s", $this->rfc_date(), $this->LE); + $header .= $this->create_header(); + + if(!$body = $this->create_body()) + return false; + + //echo "
".$header . $body . "
"; // debugging + + // Choose the mailer + if($this->Mailer == "sendmail") + { + if(!$this->sendmail_send($header, $body)) + return false; + } + elseif($this->Mailer == "mail") + { + if(!$this->mail_send($header, $body)) + return false; + } + elseif($this->Mailer == "smtp") + { + if(!$this->smtp_send($header, $body)) + return false; + } + else + { + $this->error_handler(sprintf("%s mailer is not supported", $this->Mailer)); + return false; + } + + return true; + } + + /** + * Sends mail message to an assigned queue directory. Has an optional + * sendTime argument. This is used when the user wants the + * message to be sent from the queue at a predetermined time. + * The data must be a valid timestamp like that returned from + * the time() or strtotime() functions. Returns false on failure + * or the message file name if success. + * @public + * @returns string + */ + function SendToQueue($queue_path, $send_time = 0) { + $message = array(); + $header = ""; + $body = ""; + + // If invalid or empty just set to the current time + if($send_time == 0) + $send_time = time(); + + if(!is_dir($queue_path)) + { + $this->error_handler("The supplied queue directory does not exist"); + return false; + } + + if((count($this->to) + count($this->cc) + count($this->bcc)) < 1) + { + $this->error_handler("You must provide at least one recipient email address"); + return false; + } + + // Set whether the message is multipart/alternative + if(!empty($this->AltBody)) + $this->ContentType = "multipart/alternative"; + + $header = $this->create_header(); + if(!$body = $this->create_body()) + return false; + + // Seed randomizer + mt_srand(time()); + $msg_id = md5(uniqid(mt_rand())); + + $fp = fopen($queue_path . $msg_id . ".pqm", "wb"); + if(!$fp) + { + $this->error_handler(sprintf("Could not write to %s directory", $queue_path)); + return false; + } + + $message[] = sprintf("----START PQM HEADER----%s", $this->LE); + $message[] = sprintf("SendTime: %s%s", $send_time, $this->LE); + $message[] = sprintf("Mailer: %s%s", $this->Mailer, $this->LE); + + // Choose the mailer + if($this->Mailer == "sendmail") + { + $message[] = sprintf("Sendmail: %s%s", $this->Sendmail, $this->LE); + $message[] = sprintf("Sender: %s%s", $this->Sender, $this->LE); + } + elseif($this->Mailer == "mail") + { + $message[] = sprintf("Sender: %s%s", $this->Sender, $this->LE); + $message[] = sprintf("Subject: %s%s", $this->Subject, $this->LE); + $message[] = sprintf("to: %s%s", $this->addr_list($this->to), $this->LE); + } + elseif($this->Mailer == "smtp") + { + $message[] = sprintf("Host: %s%s", $this->Host, $this->LE); + $message[] = sprintf("Port: %d%s", $this->Port, $this->LE); + $message[] = sprintf("Helo: %s%s", $this->Helo, $this->LE); + $message[] = sprintf("Timeout: %d%s", $this->Timeout, $this->LE); + + if($this->SMTPAuth) + $auth_no = 1; + else + $auth_no = 0; + $message[] = sprintf("SMTPAuth: %d%s", $auth_no, $this->LE); + $message[] = sprintf("Username: %s%s", $this->Username, $this->LE); + $message[] = sprintf("Password: %s%s", $this->Password, $this->LE); + $message[] = sprintf("From: %s%s", $this->From, $this->LE); + + $message[] = sprintf("to: %s%s", $this->addr_list($this->to), $this->LE); + $message[] = sprintf("cc: %s%s", $this->addr_list($this->cc), $this->LE); + $message[] = sprintf("bcc: %s%s", $this->addr_list($this->bcc), $this->LE); + } + else + { + $this->error_handler(sprintf("%s mailer is not supported", $this->Mailer)); + return false; + } + + $message[] = sprintf("----END PQM HEADER----%s", $this->LE); // end of pqm header + $message[] = $header; + $message[] = $body; + + fwrite($fp, join("", $message)); + + return ($msg_id . ".pqm"); + } + + /** + * Sends mail using the $Sendmail program. Returns bool. + * @private + * @returns bool + */ + function sendmail_send($header, $body) { + if ($this->Sender != "") + $sendmail = sprintf("%s -oi -f %s -t", $this->Sendmail, $this->Sender); + else + $sendmail = sprintf("%s -oi -t", $this->Sendmail); + + if(!@$mail = popen($sendmail, "w")) + { + $this->error_handler(sprintf("Could not execute %s", $this->Sendmail)); + return false; + } + + fputs($mail, $header); + fputs($mail, $body); + + $result = pclose($mail) >> 8 & 0xFF; + if($result != 0) + { + $this->error_handler(sprintf("Could not execute %s", $this->Sendmail)); + return false; + } + + return true; + } + + /** + * Sends mail using the PHP mail() function. Returns bool. + * @private + * @returns bool + */ + function mail_send($header, $body) { + //$to = substr($this->addr_append("To", $this->to), 4, -2); + + // Cannot add Bcc's to the $to + $to = $this->to[0][0]; // no extra comma + for($i = 1; $i < count($this->to); $i++) + $to .= sprintf(",%s", $this->to[$i][0]); + + if ($this->Sender != "" && PHP_VERSION >= "4.0") + { + $old_from = ini_get("sendmail_from"); + ini_set("sendmail_from", $this->Sender); + } + + if ($this->Sender != "" && PHP_VERSION >= "4.0.5") + { + // The fifth parameter to mail is only available in PHP >= 4.0.5 + $params = sprintf("-oi -f %s", $this->Sender); + $rt = @mail($to, $this->Subject, $body, $header, $params); + } + else + { + $rt = @mail($to, $this->Subject, $body, $header); + } + + if (isset($old_from)) + ini_set("sendmail_from", $old_from); + + if(!$rt) + { + $this->error_handler("Could not instantiate mail()"); + return false; + } + + return true; + } + + /** + * Sends mail via SMTP using PhpSMTP (Author: + * Chris Ryan). Returns bool. Returns false if there is a + * bad MAIL FROM, RCPT, or DATA input. + * @private + * @returns bool + */ + function smtp_send($header, $body) { + // Include SMTP class code, but not twice + include_once($this->PluginDir . "class.smtp.php"); + + $smtp = new SMTP; + + $smtp->do_debug = $this->SMTPDebug; + + // Try to connect to all SMTP servers + $hosts = explode(";", $this->Host); + $index = 0; + $connection = false; + $smtp_from = ""; + $bad_rcpt = array(); + $e = ""; + + // Retry while there is no connection + while($index < count($hosts) && $connection == false) + { + if(strstr($hosts[$index], ":")) + list($host, $port) = explode(":", $hosts[$index]); + else + { + $host = $hosts[$index]; + $port = $this->Port; + } + + if($smtp->Connect($host, $port, $this->Timeout)) + $connection = true; + //printf("%s host could not connect
", $hosts[$index]); //debug only + $index++; + } + if(!$connection) + { + $this->error_handler("SMTP Error: could not connect to SMTP host server(s)"); + return false; + } + + // Must perform HELO before authentication + $smtp->Hello($this->Helo); + + // If user requests SMTP authentication + if($this->SMTPAuth) + { + if(!$smtp->Authenticate($this->Username, $this->Password)) + { + $this->error_handler("SMTP Error: Could not authenticate"); + return false; + } + } + + if ($this->Sender == "") + $smtp_from = $this->From; + else + $smtp_from = $this->Sender; + + if(!$smtp->Mail(sprintf("<%s>", $smtp_from))) + { + $e = sprintf("SMTP Error: From address [%s] failed", $smtp_from); + $this->error_handler($e); + return false; + } + + // Attempt to send attach all recipients + for($i = 0; $i < count($this->to); $i++) + { + if(!$smtp->Recipient(sprintf("<%s>", $this->to[$i][0]))) + $bad_rcpt[] = $this->to[$i][0]; + } + for($i = 0; $i < count($this->cc); $i++) + { + if(!$smtp->Recipient(sprintf("<%s>", $this->cc[$i][0]))) + $bad_rcpt[] = $this->cc[$i][0]; + } + for($i = 0; $i < count($this->bcc); $i++) + { + if(!$smtp->Recipient(sprintf("<%s>", $this->bcc[$i][0]))) + $bad_rcpt[] = $this->bcc[$i][0]; + } + + // Create error message + if(count($bad_rcpt) > 0) + { + for($i = 0; $i < count($bad_rcpt); $i++) + { + if($i != 0) + $e .= ", "; + $e .= $bad_rcpt[$i]; + } + $e = sprintf("SMTP Error: The following recipients failed [%s]", $e); + $this->error_handler($e); + + return false; + } + + + if(!$smtp->Data(sprintf("%s%s", $header, $body))) + { + $this->error_handler("SMTP Error: Data not accepted"); + return false; + } + $smtp->Quit(); + + return true; + } + + + ///////////////////////////////////////////////// + // MESSAGE CREATION METHODS + ///////////////////////////////////////////////// + + /** + * Creates recipient headers. Returns string. + * @private + * @returns string + */ + function addr_append($type, $addr) { + $addr_str = $type . ": "; + $addr_str .= $this->addr_format($addr[0]); + if(count($addr) > 1) + { + for($i = 1; $i < count($addr); $i++) + { + $addr_str .= sprintf(", %s", $this->addr_format($addr[$i])); + } + $addr_str .= $this->LE; + } + else + $addr_str .= $this->LE; + + return($addr_str); + } + + /** + * Creates a semicolon delimited list for use in pqm files. + * @private + * @returns string + */ + function addr_list($list_array) { + $addr_list = ""; + for($i = 0; $i < count($list_array); $i++) + { + if($i > 0) + $addr_list .= ";"; + $addr_list .= $list_array[$i][0]; + } + + return $addr_list; + } + + /** + * Formats an address correctly. + * @private + * @returns string + */ + function addr_format($addr) { + if(empty($addr[1])) + $formatted = $addr[0]; + else + $formatted = sprintf('"%s" <%s>', addslashes($addr[1]), $addr[0]); + + return $formatted; + } + + /** + * Wraps message for use with mailers that do not + * automatically perform wrapping and for quoted-printable. + * Original written by philippe. Returns string. + * @private + * @returns string + */ + function word_wrap($message, $length, $qp_mode = false) { + if ($qp_mode) + $soft_break = sprintf(" =%s", $this->LE); + else + $soft_break = $this->LE; + + $message = $this->fix_eol($message); + if (substr($message, -1) == $this->LE) + $message = substr($message, 0, -1); + + $line = explode($this->LE, $message); + $message = ""; + for ($i=0 ;$i < count($line); $i++) + { + $line_part = explode(" ", $line[$i]); + $buf = ""; + for ($e = 0; $e $length)) + { + $space_left = $length - strlen($buf) - 1; + if ($e != 0) + { + if ($space_left > 20) + { + $len = $space_left; + if (substr($word, $len - 1, 1) == "=") + $len--; + elseif (substr($word, $len - 2, 1) == "=") + $len -= 2; + $part = substr($word, 0, $len); + $word = substr($word, $len); + $buf .= " " . $part; + $message .= $buf . sprintf("=%s", $this->LE); + } + else + { + $message .= $buf . $soft_break; + } + $buf = ""; + } + while (strlen($word) > 0) + { + $len = $length; + if (substr($word, $len - 1, 1) == "=") + $len--; + elseif (substr($word, $len - 2, 1) == "=") + $len -= 2; + $part = substr($word, 0, $len); + $word = substr($word, $len); + + if (strlen($word) > 0) + $message .= $part . sprintf("=%s", $this->LE); + else + $buf = $part; + } + } + else + { + $buf_o = $buf; + if ($e == 0) + $buf .= $word; + else + $buf .= " " . $word; + if (strlen($buf) > $length and $buf_o != "") + { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + } + $message .= $buf . $this->LE; + } + + return ($message); + } + + /** + * Assembles message header. Returns a string if successful + * or false if unsuccessful. + * @private + * @returns string + */ + function create_header() { + $header = array(); + + // Set the boundaries + $uniq_id = md5(uniqid(time())); + $this->boundary[1] = "b1_" . $uniq_id; + $this->boundary[2] = "b2_" . $uniq_id; + + // To be created automatically by mail() + if(($this->Mailer != "mail") && (count($this->to) > 0)) + $header[] = $this->addr_append("To", $this->to); + + $header[] = sprintf("From: \"%s\" <%s>%s", addslashes($this->FromName), + trim($this->From), $this->LE); + if(count($this->cc) > 0) + $header[] = $this->addr_append("Cc", $this->cc); + + // sendmail and mail() extract Bcc from the header before sending + if((($this->Mailer == "sendmail") || ($this->Mailer == "mail")) && (count($this->bcc) > 0)) + $header[] = $this->addr_append("Bcc", $this->bcc); + + if(count($this->ReplyTo) > 0) + $header[] = $this->addr_append("Reply-to", $this->ReplyTo); + + // mail() sets the subject itself + if($this->Mailer != "mail") + $header[] = sprintf("Subject: %s%s", trim($this->Subject), $this->LE); + + $header[] = sprintf("X-Priority: %d%s", $this->Priority, $this->LE); + $header[] = sprintf("X-Mailer: phpmailer [version %s]%s", $this->Version, $this->LE); + $header[] = sprintf("Return-Path: %s%s", trim($this->From), $this->LE); + + if($this->ConfirmReadingTo != "") + $header[] = sprintf("Disposition-Notification-To: <%s>%s", + trim($this->ConfirmReadingTo), $this->LE); + + // Add custom headers + for($index = 0; $index < count($this->CustomHeader); $index++) + $header[] = sprintf("%s%s", $this->CustomHeader[$index], $this->LE); + + if($this->UseMSMailHeaders) + $header[] = $this->AddMSMailHeaders(); + + $header[] = sprintf("MIME-Version: 1.0%s", $this->LE); + + // Determine what type of message this is + if(count($this->attachment) < 1 && strlen($this->AltBody) < 1) + $this->message_type = "plain"; + else + { + if(count($this->attachment) > 0) + $this->message_type = "attachments"; + if(strlen($this->AltBody) > 0 && count($this->attachment) < 1) + $this->message_type = "alt"; + if(strlen($this->AltBody) > 0 && count($this->attachment) > 0) + $this->message_type = "alt_attachments"; + } + + switch($this->message_type) + { + case "plain": + $header[] = sprintf("Content-Transfer-Encoding: %s%s", + $this->Encoding, $this->LE); + $header[] = sprintf("Content-Type: %s; charset = \"%s\"", + $this->ContentType, $this->CharSet); + break; + case "attachments": + case "alt_attachments": + if($this->EmbeddedImageCount() > 0) + { + $header[] = sprintf("Content-Type: %s;%s\ttype=\"text/html\";%s\tboundary=\"%s\"%s", + "multipart/related", $this->LE, $this->LE, + $this->boundary[1], $this->LE); + } + else + { + $header[] = sprintf("Content-Type: %s;%s", + "multipart/mixed", $this->LE); + $header[] = sprintf("\tboundary=\"%s\"%s", $this->boundary[1], $this->LE); + } + break; + case "alt": + $header[] = sprintf("Content-Type: %s;%s", + "multipart/alternative", $this->LE); + $header[] = sprintf("\tboundary=\"%s\"%s", $this->boundary[1], $this->LE); + break; + } + + // No additional lines when using mail() function + if($this->Mailer != "mail") + $header[] = $this->LE.$this->LE; + + return(join("", $header)); + } + + /** + * Assembles the message body. Returns a string if successful + * or false if unsuccessful. + * @private + * @returns string + */ + function create_body() { + $body = array(); + + // wordwrap the message body if set + if($this->WordWrap > 0) + $this->Body = $this->word_wrap($this->Body, $this->WordWrap); + + switch($this->message_type) + { + case "alt": + // Return text of body + $bndry = new Boundary($this->boundary[1]); + $bndry->CharSet = $this->CharSet; + $bndry->Encoding = $this->Encoding; + $body[] = $bndry->GetSource(); + + $body[] = sprintf("%s%s", $this->AltBody, $this->LE.$this->LE); + + $bndry = new Boundary($this->boundary[1]); + $bndry->CharSet = $this->CharSet; + $bndry->ContentType = "text/html"; + $bndry->Encoding = $this->Encoding; + $body[] = $bndry->GetSource(); + + $body[] = sprintf("%s%s", $this->Body, $this->LE.$this->LE); + + // End the boundary + $body[] = sprintf("%s--%s--%s", $this->LE, + $this->boundary[1], $this->LE.$this->LE); + break; + case "plain": + $body[] = $this->Body; + break; + case "attachments": + $bndry = new Boundary($this->boundary[1]); + $bndry->CharSet = $this->CharSet; + $bndry->ContentType = $this->ContentType; + $bndry->Encoding = $this->Encoding; + $body[] = sprintf("%s%s%s%s", $bndry->GetSource(false), $this->LE, + $this->Body, $this->LE); + + if(!$body[] = $this->attach_all()) + return false; + break; + case "alt_attachments": + $body[] = sprintf("--%s%s", $this->boundary[1], $this->LE); + $body[] = sprintf("Content-Type: %s;%s" . + "\tboundary=\"%s\"%s", + "multipart/alternative", $this->LE, + $this->boundary[2], $this->LE.$this->LE); + + // Create text body + $bndry = new Boundary($this->boundary[2]); + $bndry->CharSet = $this->CharSet; + $bndry->ContentType = "text/plain"; + $bndry->Encoding = $this->Encoding; + $body[] = $bndry->GetSource() . $this->LE; + + $body[] = sprintf("%s%s", $this->AltBody, $this->LE.$this->LE); + + // Create the HTML body + $bndry = new Boundary($this->boundary[2]); + $bndry->CharSet = $this->CharSet; + $bndry->ContentType = "text/html"; + $bndry->Encoding = $this->Encoding; + $body[] = $bndry->GetSource() . $this->LE; + + $body[] = sprintf("%s%s", $this->Body, $this->LE.$this->LE); + + $body[] = sprintf("%s--%s--%s", $this->LE, + $this->boundary[2], $this->LE.$this->LE); + + if(!$body[] = $this->attach_all()) + return false; + break; + } + // Add the encode string code here + $sBody = join("", $body); + $sBody = $this->encode_string($sBody, $this->Encoding); + + return $sBody; + } + + + ///////////////////////////////////////////////// + // ATTACHMENT METHODS + ///////////////////////////////////////////////// + + /** + * Adds an attachment from a path on the filesystem. + * Checks if attachment is valid and then adds + * the attachment to the list. + * Returns false if the file could not be found + * or accessed. + * @public + * @returns bool + */ + function AddAttachment($path, $name = "", $encoding = "base64", $type = "application/octet-stream") { + if(!@is_file($path)) + { + $this->error_handler(sprintf("Could not access [%s] file", $path)); + return false; + } + + $filename = basename($path); + if($name == "") + $name = $filename; + + // Append to $attachment array + $cur = count($this->attachment); + $this->attachment[$cur][0] = $path; + $this->attachment[$cur][1] = $filename; + $this->attachment[$cur][2] = $name; + $this->attachment[$cur][3] = $encoding; + $this->attachment[$cur][4] = $type; + $this->attachment[$cur][5] = false; // isStringAttachment + $this->attachment[$cur][6] = "attachment"; + $this->attachment[$cur][7] = 0; + + return true; + } + + /** + * Attaches all fs, string, and binary attachments to the message. + * Returns a string if successful or false if unsuccessful. + * @private + * @returns string + */ + function attach_all() { + // Return text of body + $mime = array(); + + // Add all attachments + for($i = 0; $i < count($this->attachment); $i++) + { + // Check for string attachment + $isString = $this->attachment[$i][5]; + if ($isString) + { + $string = $this->attachment[$i][0]; + } + else + { + $path = $this->attachment[$i][0]; + } + $filename = $this->attachment[$i][1]; + $name = $this->attachment[$i][2]; + $encoding = $this->attachment[$i][3]; + $type = $this->attachment[$i][4]; + $disposition = $this->attachment[$i][6]; + $cid = $this->attachment[$i][7]; + + $mime[] = sprintf("--%s%s", $this->boundary[1], $this->LE); + $mime[] = sprintf("Content-Type: %s; name=\"%s\"%s", $type, $name, $this->LE); + $mime[] = sprintf("Content-Transfer-Encoding: %s%s", $encoding, $this->LE); + + if($disposition == "inline") + $mime[] = sprintf("Content-ID: <%s>%s", $cid, $this->LE); + else + $mime[] = sprintf("Content-ID: <%s>%s", $name, $this->LE); + + $mime[] = sprintf("Content-Disposition: %s; filename=\"%s\"%s", + $disposition, $name, $this->LE.$this->LE); + + // Encode as string attachment + if($isString) + { + if(!$mime[] = sprintf("%s%s", $this->encode_string($string, $encoding), + $this->LE.$this->LE)) + return false; + } + else + { + if(!$mime[] = sprintf("%s%s", $this->encode_file($path, $encoding), + $this->LE.$this->LE)) + return false; + + $mime[] = sprintf("--%s--%s", $this->boundary[1], $this->LE); + + } + } + + return(join("", $mime)); + } + + /** + * Encodes attachment in requested format. Returns a + * string if successful or false if unsuccessful. + * @private + * @returns string + */ + function encode_file ($path, $encoding = "base64") { + if(!@$fd = fopen($path, "rb")) + { + $this->error_handler(sprintf("File Error: Could not open file %s", $path)); + return false; + } + $file = fread($fd, filesize($path)); + $encoded = $this->encode_string($file, $encoding); + fclose($fd); + + return($encoded); + } + + /** + * Encodes string to requested format. Returns a + * string if successful or false if unsuccessful. + * @private + * @returns string + */ + function encode_string ($str, $encoding = "base64") { + switch(strtolower($encoding)) { + case "base64": + // chunk_split is found in PHP >= 3.0.6 + $encoded = chunk_split(base64_encode($str)); + break; + + case "7bit": + case "8bit": + $encoded = $this->fix_eol($str); + if (substr($encoded, -2) != $this->LE) + $encoded .= $this->LE; + break; + + case "binary": + $encoded = $str; + break; + + case "quoted-printable": + $encoded = $this->encode_qp($str); + break; + + default: + $this->error_handler(sprintf("Unknown encoding: %s", $encoding)); + return false; + } + return($encoded); + } + + /** + * Encode string to quoted-printable. Returns a string. + * @private + * @returns string + */ + function encode_qp ($str) { + $encoded = $this->fix_eol($str); + if (substr($encoded, -2) != $this->LE) + $encoded .= $this->LE; + + // Replace every high ascii, control and = characters + $encoded = preg_replace("/([\001-\010\013\014\016-\037\075\177-\377])/e", + "'='.sprintf('%02X', ord('\\1'))", $encoded); + // Replace every spaces and tabs when it's the last character on a line + $encoded = preg_replace("/([\011\040])".$this->LE."/e", + "'='.sprintf('%02X', ord('\\1')).'".$this->LE."'", $encoded); + + // Maximum line length of 76 characters before CRLF (74 + space + '=') + $encoded = $this->word_wrap($encoded, 74, true); + + return $encoded; + } + + /** + * Adds a string or binary attachment (non-filesystem) to the list. + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * @public + * @returns void + */ + function AddStringAttachment($string, $filename, $encoding = "base64", $type = "application/octet-stream") { + // Append to $attachment array + $cur = count($this->attachment); + $this->attachment[$cur][0] = $string; + $this->attachment[$cur][1] = $filename; + $this->attachment[$cur][2] = $filename; + $this->attachment[$cur][3] = $encoding; + $this->attachment[$cur][4] = $type; + $this->attachment[$cur][5] = true; // isString + $this->attachment[$cur][6] = "attachment"; + $this->attachment[$cur][7] = 0; + } + + /** + * Adds an embedded attachment. This can include images, sounds, and + * just about any other document. + * @param cid this is the Content Id of the attachment. Use this to identify + * the Id for accessing the image in an HTML form. + * @public + * @returns bool + */ + function AddEmbeddedImage($path, $cid, $name = "", $encoding = "base64", $type = "application/octet-stream") { + + if(!@is_file($path)) + { + $this->error_handler(sprintf("Could not access [%s] file", $path)); + return false; + } + + $filename = basename($path); + if($name == "") + $name = $filename; + + // Append to $attachment array + $cur = count($this->attachment); + $this->attachment[$cur][0] = $path; + $this->attachment[$cur][1] = $filename; + $this->attachment[$cur][2] = $name; + $this->attachment[$cur][3] = $encoding; + $this->attachment[$cur][4] = $type; + $this->attachment[$cur][5] = false; // isStringAttachment + $this->attachment[$cur][6] = "inline"; + $this->attachment[$cur][7] = $cid; + + return true; + } + + /** + * Returns the number of embedded images in an email. + * @private + * @returns int + */ + function EmbeddedImageCount() { + $ret = 0; + for($i = 0; $i < count($this->attachment); $i++) + { + if($this->attachment[$i][6] == "inline") + $ret++; + } + + return $ret; + } + + ///////////////////////////////////////////////// + // MESSAGE RESET METHODS + ///////////////////////////////////////////////// + + /** + * Clears all recipients assigned in the TO array. Returns void. + * @public + * @returns void + */ + function ClearAddresses() { + $this->to = array(); + } + + /** + * Clears all recipients assigned in the CC array. Returns void. + * @public + * @returns void + */ + function ClearCCs() { + $this->cc = array(); + } + + /** + * Clears all recipients assigned in the BCC array. Returns void. + * @public + * @returns void + */ + function ClearBCCs() { + $this->bcc = array(); + } + + /** + * Clears all recipients assigned in the ReplyTo array. Returns void. + * @public + * @returns void + */ + function ClearReplyTos() { + $this->ReplyTo = array(); + } + + /** + * Clears all recipients assigned in the TO, CC and BCC + * array. Returns void. + * @public + * @returns void + */ + function ClearAllRecipients() { + $this->to = array(); + $this->cc = array(); + $this->bcc = array(); + } + + /** + * Clears all previously set filesystem, string, and binary + * attachments. Returns void. + * @public + * @returns void + */ + function ClearAttachments() { + $this->attachment = array(); + } + + /** + * Clears all custom headers. Returns void. + * @public + * @returns void + */ + function ClearCustomHeaders() { + $this->CustomHeader = array(); + } + + + ///////////////////////////////////////////////// + // MISCELLANEOUS METHODS + ///////////////////////////////////////////////// + + /** + * Adds the error message to the error container. + * Returns void. + * @private + * @returns void + */ + function error_handler($msg) { + $this->ErrorInfo = $msg; + } + + /** + * Returns the proper RFC 822 formatted date. Returns string. + * @private + * @returns string + */ + function rfc_date() { + $tz = date("Z"); + $tzs = ($tz < 0) ? "-" : "+"; + $tz = abs($tz); + $tz = ($tz/3600)*100 + ($tz%3600)/60; + $date = sprintf("%s %s%04d", date("D, j M Y H:i:s"), $tzs, $tz); + return $date; + } + + /** + * Returns received header for message tracing. Returns string. + * @private + * @returns string + */ + function received() { + // Check for vars because they might not exist. Possibly + // write a small retrieval function (that mailer can use too!) + + $str = sprintf("Received: from phpmailer ([%s]) by %s " . + "with HTTP;%s\t %s%s", + $this->get_server_var("REMOTE_ADDR"), + $this->get_server_var("SERVER_NAME"), + $this->LE, + $this->rfc_date(), + $this->LE); + + return $str; + } + + /** + * Returns the appropriate server variable. Should work with both + * PHP 4.1.0+ as well as older versions. Returns an empty string + * if nothing is found. + * @private + * @returns mixed + */ + function get_server_var($varName) { + global $HTTP_SERVER_VARS; + global $HTTP_ENV_VARS; + + if(!isset($_SERVER)) + { + $_SERVER = $HTTP_SERVER_VARS; + if(!isset($_SERVER["REMOTE_ADDR"])) + $_SERVER = $HTTP_ENV_VARS; // must be Apache + } + + if(isset($_SERVER[$varName])) + return $_SERVER[$varName]; + else + return ""; + } + + /** + * Changes every end of line from CR or LF to CRLF. Returns string. + * @private + * @returns string + */ + function fix_eol($str) { + $str = str_replace("\r\n", "\n", $str); + $str = str_replace("\r", "\n", $str); + $str = str_replace("\n", $this->LE, $str); + return $str; + } + + /** + * Adds a custom header. Returns void. + * @public + * @returns void + */ + function AddCustomHeader($custom_header) { + $this->CustomHeader[] = $custom_header; + } + + /** + * Adds all the Microsoft message headers. Returns string. + * @private + * @returns string + */ + function AddMSMailHeaders() { + $MSHeader = ""; + if($this->Priority == 1) + $MSPriority = "High"; + elseif($this->Priority == 5) + $MSPriority = "Low"; + else + $MSPriority = "Medium"; + + $MSHeader .= sprintf("X-MSMail-Priority: %s%s", $MSPriority, $this->LE); + $MSHeader .= sprintf("Importance: %s%s", $MSPriority, $this->LE); + + return($MSHeader); + } + +} + + +/** + * Boundary - MIME message boundary class + * @author Brent R. Matzelle + */ +class Boundary +{ + /** + * Sets the boundary ID. + * @private + * @type string + */ + var $ID = 0; + + /** + * Sets the boundary Content Type. + * @public + * @type string + */ + var $ContentType = "text/plain"; + + /** + * Sets the Encoding. + * @public + * @type string + */ + var $Encoding = ""; + + /** + * Sets an attachment disposition. + * @public + * @type string + */ + var $Disposition = ""; + + /** + * Sets an attachment file name. + * @public + * @type string + */ + var $FileName = ""; + + /** + * Sets the Char set. + * @public + * @type string + */ + var $CharSet = ""; + + /** + * Sets the line endings of the message. Default is "\n"; + * @public + * @type string + */ + var $LE = "\n"; + + /** + * Main constructor. + */ + function Boundary($boundary_id) { + $this->ID = $boundary_id; + } + + /** + * Returns the source of the boundary. + * @public + * @returns string + */ + function GetSource($bLineEnding = true) { + $ret = array(); + $mime[] = sprintf("--%s%s", $this->ID, $this->LE); + $mime[] = sprintf("Content-Type: %s; charset = \"%s\"%s", + $this->ContentType, $this->CharSet, $this->LE); + //$mime[] = sprintf("Content-Transfer-Encoding: %s%s", $this->Encoding, + // $this->LE); + + if(strlen($this->Disposition) > 0) + { + $mime[] = sprintf("Content-Disposition: %s;"); + if(strlen($this->FileName) > 0) + $mime[] = sprinf("filename=\"%s\"", $this->$this->FileName); + } + + if($bLineEnding) + $mime[] = $this->LE; + + return join("", $mime); + } +} + +?> \ No newline at end of file diff --git a/classes/sm/smCore.class.php b/classes/sm/smCore.class.php new file mode 100644 index 00000000..71809ae6 --- /dev/null +++ b/classes/sm/smCore.class.php @@ -0,0 +1,34 @@ +. + */ + +abstract class smCore { + public $db; + + function __construct() { + // add database handler + $this->db = $GLOBALS['db']; + } +} + +?> \ No newline at end of file diff --git a/classes/sm/smDatabase.class.php b/classes/sm/smDatabase.class.php new file mode 100644 index 00000000..cc668403 --- /dev/null +++ b/classes/sm/smDatabase.class.php @@ -0,0 +1,365 @@ +. + */ + +class smDatabase { + + protected $debug = array(); + protected $last_inserted_id; + protected $link; + protected $num_rows_found; + protected $num_rows_returned; + + function __construct() { + // Initizale connection + $this->link = mysql_connect(SM_DB_HOST, SM_DB_USER, SM_DB_PASS); + + if (!mysql_select_db(SM_DB_NAME, $this->link)) { + trigger_error(mysql_errno() . ": " . mysql_error()); + } + + // Setting the utf collection + mysql_query("SET NAMES utf8;", $this->getLink()); + mysql_query("SET CHARACTER SET 'utf8';", $this->getLink()); + } + + /** + * Executes a query + * + * @param $sql string MySQL query + * @return resource mysql resource + */ + + public function executeQuery($sql) { + + $result = mysql_query($sql, $this->getLink()); + + if (mysql_error($this->getLink())) { + trigger_error(mysql_errno($this->getLink()) . ': ' . mysql_error($this->getLink())); + return false; + } + + if (is_resource($result) && mysql_num_rows($result) > 0) { + // Rows returned + $this->num_rows_returned = mysql_num_rows($result); + + // Rows found + $result_num_rows_found = $this->fetchResults(mysql_query('SELECT FOUND_ROWS();')); + $this->num_rows_found = $result_num_rows_found[0]['FOUND_ROWS()']; + } + + if (substr(strtolower(trim($sql)), 0, 6) == 'insert') { + // we have an insert + $this->last_inserted_id = mysql_insert_id($this->getLink()); + $result = $this->last_inserted_id; + } + + return $result; + } + + /** + * Exectues query and fetches result + * + * @param $query string MySQL query + * @return $result array + */ + public function query($query) { + + // Execute query and process results + $result_resource = $this->executeQuery($query); + $result = $this->fetchResults($result_resource); + + return $result; + } + + /** + * Fetch results from a query + * + * @param resource $result result from a mysql query + * @return array $array with results (multi-dimimensial) for more than one rows + */ + + public function fetchResults($result_query){ + + if (!is_resource($result_query)) { + return array(); + } + + $num_rows = mysql_num_rows($result_query); + + $result = array(); + while($record = mysql_fetch_assoc($result_query)) { + $result[] = $record; + } + + return $result; + } + + /** + * Performs a select on the given table and returns an multi dimensional associative array with results + * + * @param string $table tablename + * @param mixed $where string or array with where data + * @param array $fields array with fields to be retrieved. if empty all fields will be retrieved + * @param string $limit limit. for example: 0,30 + * @param array $orderby fields for the orderby clause + * @param string $direction ASC or DESC. Defaults to ASC + * @return array multi dimensional array with results + */ + + public function select($table, $where = null, $fields = null, $limit = '', $orderby = null, $direction = 'ASC'){ + // build query + $query_parts = array(); + $query_parts[] = 'SELECT SQL_CALC_FOUND_ROWS'; + + // Fields + if ($fields !== null && !empty($fields)) { + $query_parts[] = "`".implode('`,`', $fields)."`"; + } else { + $query_parts[] = ' * '; + } + + // From + $query_parts[] = "FROM `{$table}`"; + + // Where clause + $query_parts[] = $this->buildWhereClause($table, $where); + + // Order by + if ($orderby !== null && !empty($orderby)) { + $orderby_clause = 'ORDER BY '; + + foreach($orderby as $field) { + $orderby_clause .= "`{$field}`, "; + } + $query_parts[] = substr($orderby_clause, 0, -2) . ' ' . $direction; + } + + // Limit + if ($limit != '') { + $query_parts[] = 'LIMIT ' . $limit; + } + + $query = implode(' ', $query_parts); + + // Get results + $result = $this->query($query); + + return $result; + } + + public function selectRow($table, $where = null, $fields = null, $limit = '', $orderby = null, $direction = 'ASC') { + $result = $this->select($table, $where, $fields, $limit, $orderby, $direction); + + if ($this->getNumRowsReturned() == '1') { + $result = $result[0]; + } + return $result; + } + + /** + * Remove a record from database + * + * @param string $table tablename + * @param mixed $where Where clause array or primary Id (string) or where clause (string) + * @return boolean + */ + public function delete($table, $where = null){ + + if ($table != '') { + + $sql = 'DELETE FROM `'.$table.'` ' . $this->buildWhereClause($table, $where); + + $this->query($sql); + } + } + + /** + * Insert or update data to the database + * + * @param array $table table name + * @param array $data data to save or insert + * @param mixed $where either string ('user_id=2' or just '2' (works only with primary field)) or array with where clause (only when updating) + */ + public function save($table, $data, $where = null) { + + if ($where === null) { + // insert mode + $query = "INSERT INTO "; + } else { + $query = "UPDATE "; + } + + $query .= "`{$table}` SET "; + + foreach($data as $field => $value) { + $value = $this->escapeValue($value); + $query .= "`{$table}`.`{$field}`='{$value}', "; + } + + $query = substr($query, 0, -2) . ' ' . $this->buildWhereClause($table, $where); + + return $this->query($query); + } + + /** + * Build WHERE clause for query + * + * @param string $table table name + * @param mixed $where can be primary id (eg '2'), can be string (eg 'name=pepe') or can be array + * @return string sql where clause + */ + public function buildWhereClause($table, $where = null) { + + $query = ''; + + if ($where !== null) { + if (is_array($where)) { + $query .= " WHERE "; + + foreach($where as $field => $value) { + $value = $this->escapeValue($value); + $query .= "`{$table}`.`{$field}`='{$value}' AND "; + } + $query = substr($query, 0, -5); + } else { + if (strpos($where, '=') === false) { + // no field given, use primary field + $structure = $this->getTableStructure($table); + $where = $this->escapeValue($where); + $query .= " WHERE `{$table}`.`{$structure['primary']}`='{$where}'"; + } elseif (strpos(strtolower(trim($where)), 'where') === false) { + $query .= " WHERE {$where}"; + } else { + $query .= ' '.$where; + } + } + } + return $query; + } + + /** + * Get table structure and primary key + * + * @param string $table table name + * @return array primary key and database structure + */ + public function getTableStructure($table) { + if ($table == '') return false; + + $structure = $this->query("DESCRIBE `{$table}`"); + + if (empty($structure)) return false; + + // use arrray search function to get primary key + $search_needle = array( + 'key' => 'Key', + 'value' => 'PRI' + ); + $primary = pep_array_search_key_value( + $structure, + array( + 'key' => 'Key', + 'value' => 'PRI' + ) + ); + + $primary_field = $structure[$primary[0]['path'][0]]['Field']; + return array( + 'primary' => $primary_field, + 'fields' => $structure + ); + } + + /** + * Get information about a field from the database + * + * @param string $table + * @param string $field + * @return array mysql field information + */ + public function getFieldInfo($table, $field) { + if ($table == '' || $field == '') return array(); + + $db_structure = $this->getTableStructure($table); + + $field_info = pep_array_search_key_value( + $db_structure, + array( + 'key' => 'Field', + 'value' => $field + ) + ); + + if (empty($field_info)) { + return array(); + } + + // return field info + return $field_info[0]['result']; + } + + /** + * Formats the value for the SQL query to secure against injections + * + * @param string $value + * @return string + */ + public function escapeValue($value) { + if(get_magic_quotes_gpc()) { + $value = stripslashes($value); + } + $value = mysql_real_escape_string($value, $this->link); + + return $value; + } + + /** + * Get number of rows found + * + * @return int number of rows found + */ + public function getNumRowsFound() { + return $this->num_rows_found; + } + + /** + * Get number of rows returned + * + * @return int number of rows returned + */ + public function getNumRowsReturned() { + return $this->num_rows_returned; + } + + /** + * Get the database connection identifier + * + * @return object db connection + */ + public function getLink() { + return $this->link; + } +} + +?> diff --git a/classes/sm/smTemplate.class.php b/classes/sm/smTemplate.class.php new file mode 100644 index 00000000..332f0c7d --- /dev/null +++ b/classes/sm/smTemplate.class.php @@ -0,0 +1,307 @@ +. + */ + +class smTemplate { + protected $css_files = array(); + protected $js_files = array(); + protected $output; + protected $templates = array(); + + function __construct() { + // add the main template + $this->newTemplate('main', 'main.tpl.html'); + } + + /** + * Add template + * + * @param string $id template id used in the tpl file (html) + * @param string $filename path to template file, or if file is located in the default template dir just the filename + * @return mixed false if template cannot be found, html code on success + */ + public function newTemplate($id, $filename) { + if (file_exists($filename)) { + $this->templates[$id] = $this->parseFile($filename); + } elseif (file_exists(SM_PATH_TPL.$filename)) { + $this->templates[$id] = $this->parseFile(SM_PATH_TPL.$filename); + } else { + // file does not exist + trigger_error('Template not found with id: '.$id.' and filename: '.$filename); + return false; + } + $use_tpl = null; + + // only get data from the file that's between the tpl id tags with this id + // find "tpl_{$row_id}" in the current template + //preg_match_all('{(.*?)}is', $this->templates[$id], $matches); + preg_match_all("{(.*?)}is", $this->templates[$id], $matches); + + // no repeat tpl found? skip to next one + if (empty($matches)) return false; + + // check if the row_id is in one of the matches (aka whether the supplied row id actually has a template in this file) + if (isset($matches[1][0])) { + $use_tpl = $matches[1][0]; + } + + // no template with the given id found.. + if ($use_tpl === null) return false; + + // remove tpl code tags from original template so it won't be in the source + $this->templates[$id] = preg_replace("{(.*?)}is", "", $use_tpl); + + return $this->templates[$id]; + } + + /** + * Add data to the template + * + * @param string $id template_id used by add_template() + * @param array $data + * @return string new template + */ + public function addTemplateData($id, $data) { + // does the template exist? + if (!isset($this->templates[$id])) { + // file does not exist + trigger_error('Template not found with id: '.$id); + return false; + } + + foreach($data as $key => $value) { + // check if $value is a file + $value = (file_exists($value)) ? $this->parseFile($value) : $value; + + $this->templates[$id] = str_replace('{'.$key.'}', $value, $this->templates[$id]); + } + return $this->templates[$id]; + } + + /** + * Add repeat rows to template + * + * @param string $id template id used by add_template() + * @param string $repeat_id ID used in template file for the repeat template: html + * @param array $data + * @return mixed false if repeat template cannot be found, html code on success + */ + public function addTemplateDataRepeat($id, $repeat_id, $data) { + // does the template exist? + if (!isset($this->templates[$id])) { + // file does not exist + trigger_error('Template not found with id: '.$id); + return false; + } + + $use_tpl = null; + + // find "tpl_repeat_{$repeat_id}_" in the current template + //preg_match_all('{(.*?)}is', $this->templates[$id], $matches); + preg_match_all("{(.*?)}is", $this->templates[$id], $matches); + + // no repeat tpl found? skip to next one + if (empty($matches)) return false; + + // check if the row_id is in one of the matches (aka whether the supplied row id actually has a repeat template in this file) + if (isset($matches[1][0])) { + $use_tpl = $matches[1][0]; + } + + // if we didn't find a repeat template for the row_id supplied, skip the rest.. + if ($use_tpl === null) return false; + + // remove repeat tpl code from original template so it won't be in the source + $this->templates[$id] = preg_replace("{(.*?)}is", "", $this->templates[$id]); + + // now lets go through all the records supplied and put them in the HTML repeat code we just found + $result = ''; + + foreach($data as $record) { + $tmp_string = $use_tpl; + foreach($record as $k => $v) { + $tmp_string = str_replace('{'.$k.'}', $v, $tmp_string); + } + $result .= $tmp_string."\n"; + } + + // add to main template.. + return $this->addTemplateData($id, array($repeat_id => $result)); + } + + public function display($id) { + // check if there are any unused tpl_repeat templates, and if there are remove them + $result = preg_replace('{(.*?)}is', '', $this->templates[$id]); + + // check for tpl variables that have not been replaced. ie: {name}. ignore literal stuff, though. ie: {{name}} is {name} and should not be removed + preg_match_all('~{?{(.+?)}}?~', $result, $matches); + + // add css and javascript files to header + $result = $this->addHeaderFiles($id, $result); + + foreach($matches[0] as $match) { + if (substr($match, 0, 2) == '{{') { + // literal! remove only first and last bracket! + $result = str_replace($match, substr($match, 1, -1), $result); + } else { + // unused variable, remove completely + $result = str_replace($match, '', $result); + } + } + return $result; + } + + /** + * Adds a css file to the list which will be added to the template when display() is called + * + * @param string $template_id + * @param string $filename + * @param string $path uses default set in config if non specified + */ + public function addCSS($filename, $template_id = 'main', $path = SM_PATH_CSS) { + if (!isset($this->css_files[$template_id])) { + $this->css_files[$template_id] = array(); + } + + // if file doesn't exist we assume it's inline + $type = (file_exists($path.$filename)) ? 'file' : 'inline'; + + $this->css_files[$template_id][$filename] = array( + 'file' => ($type == 'file') ? $path.$filename : $filename, + 'type' => $type + ); + } + + /** + * Adds a javascript file to the list which will be added to the template when display() is called + * + * @param string $template_id + * @param string $filename path to file or CSS code to be added inline + * @param string $path uses default set in config if non specified + */ + public function addJS($filename, $template_id = 'main', $path = SM_PATH_JS) { + if (!isset($this->js_files[$template_id])) { + $this->js_files[$template_id] = array(); + } + + // if file doesn't exist we assume it's inline + $type = (file_exists($path.$filename)) ? 'file' : 'inline'; + + $this->js_files[$template_id][$filename] = array( + 'file' => ($type == 'file') ? $path.$filename : $filename, + 'type' => $type + ); + } + + /** + * Get html code for a template, or if no template id given get all templates + * + * @param string $template_id + * @return mixed string when ID given, else array + */ + public function getTemplate($template_id = null) { + if ($template_id === null) { + return $this->templates; + } elseif (isset($this->templates[$template_id])) { + return $this->templates[$template_id]; + } else { + return false; + } + } + + /** + * Adds the CSS and JavaScript files to the header html. + * + * @param string $template_id + * @param string $html + * @return string new html code + */ + protected function addHeaderFiles($template_id, $html) { + // get code between tags + preg_match_all("{(.*?)<\/head>}is", $html, $matches); + + if (isset($matches[1][0]) && $matches[1][0] != '') { + $header = $matches[1][0]; + + if (isset($this->css_files[$template_id]) && !empty($this->css_files[$template_id])) { + $header .= "\t\n"; + + foreach($this->css_files[$template_id] as $filename => $info) { + switch($info['type']) { + case 'file': + $header .= "\t\n"; + break; + case 'inline': + $header .= + "\t\n"; + break; + } + } + } + + if (isset($this->js_files[$template_id]) && !empty($this->js_files[$template_id])) { + $header .= "\t\n"; + + foreach($this->js_files[$template_id] as $filename => $info) { + switch($info['type']) { + case 'file': + $header .= "\t\n"; + break; + case 'inline': + $header .= + "\t\n"; + break; + } + } + } + // add new header to html + $html = preg_replace('{'.$matches[1][0].'}is', $header, $html); + } + + return $html; + } + + /** + * + * Get file content + * + * @param string $filename filename + * @return string file contents + */ + protected function parseFile($filename) { + if (!file_exists($filename)) return false; + + ob_start(); + include($filename); + $file_content = ob_get_contents(); + ob_end_clean(); + + return $file_content; + } +} + +?> \ No newline at end of file diff --git a/classes/sm/smUpdaterStatus.class.php b/classes/sm/smUpdaterStatus.class.php new file mode 100644 index 00000000..d1a15589 --- /dev/null +++ b/classes/sm/smUpdaterStatus.class.php @@ -0,0 +1,354 @@ +. + */ + +class smUpdaterStatus extends smCore { + public $error; + public $notify; + public $rtime = 0; + public $server; + public $status_org = false; + public $status_new = false; + + /** + * Set a new server + * + * @param array $server + * @param string $status_org either 'on' or 'off' + */ + public function setServer($server, $status_org) { + $this->clearResults(); + + $this->server = $server; + $this->status_org = $status_org; + } + + /** + * Get the new status of the selected server. If the update has not been performed yet it will do so first + * + * @return string + */ + public function getStatus() { + if(!$this->server) { + return false; + } + if(!$this->status_new) { + $this->update(); + } + return $this->status_new; + } + + /** + * The function its all about. This one checks whether the given ip and port are up and running! + * If the server check fails it will try one more time + * + * @param int $max_runs how many times should the script recheck the server if unavailable. default is 2 + * @return string new status + */ + public function update($max_runs = 2) { + switch($this->server['type']) { + case 'service': + $result = $this->updateService($max_runs); + break; + case 'website': + $result = $this->updateWebsite($max_runs); + break; + } + return $result; + + } + + protected function updateService($max_runs, $run = 1) { + // save response time + $time = explode(' ', microtime()); + $starttime = $time[1] + $time[0]; + + @$fp = fsockopen ($this->server['ip'], $this->server['port'], $errno, $errstr, 10); + + $time = explode(" ", microtime()); + $endtime = $time[1] + $time[0]; + $this->rtime = ($endtime - $starttime); + + + $this->status_new = ($fp === false) ? 'off' : 'on'; + $this->error = $errstr; + // add the error to the server array for when parsing the messages + $this->server['error'] = $this->error; + + @fclose($fp); + + // check if server is available and rerun if asked. + if($this->status_new == 'off' && $run < $max_runs) { + return $this->updateService($max_runs, $run + 1); + } + + return $this->status_new; + } + + protected function updateWebsite($max_runs, $run = 1) { + // save response time + $time = explode(' ', microtime()); + $starttime = $time[1] + $time[0]; + + $ch = curl_init(); + curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt ($ch, CURLOPT_URL, $this->server['ip']); + curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt ($ch, CURLOPT_TIMEOUT, 10); + curl_setopt ($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11'); + + // We're only interested in the header, because that should tell us plenty! + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD'); + + $headers = curl_exec ($ch); + curl_close ($ch); + + $time = explode(" ", microtime()); + $endtime = $time[1] + $time[0]; + $this->rtime = ($endtime - $starttime); + + // the first line would be the status code.. + $status_code = strtok($headers, "\r\n"); + // keep it general + // $code[1][0] = status code + // $code[2][0] = name of status code + preg_match_all("/[A-Z]{2,5}\/\d\.\d\s(\d{3})\s(.*)/", $status_code, $code_matches); + + if(empty($code_matches[0])) { + // somehow we dont have a proper response. + $this->server['error'] = $this->error = 'no response from server'; + $this->status_new = 'off'; + } else { + $code = $code_matches[1][0]; + $msg = $code_matches[2][0]; + + // All status codes starting with a 4 mean trouble! + if(substr($code, 0, 1) == '4') { + $this->server['error'] = $this->error = $code . ' ' . $msg; + $this->status_new = 'off'; + } else { + $this->status_new = 'on'; + } + } + + // check if server is available and rerun if asked. + if($this->status_new == 'off' && $run < $max_runs) { + return $this->updateWebsite($max_runs, $run + 1); + } + + return $this->status_new; + } + + /** + * This function initializes the sending (text msg & email) and logging + * + */ + public function notify() { + if(sm_get_conf('email_status') == false && sm_get_conf('sms_status') == false && sm_get_conf('log_status') == false) { + // seems like we have nothing to do. skip the rest + return false; + } + $notify = false; + + // check which type of alert the user wants + switch(sm_get_conf('alert_type')) { + case 'always': + if($this->status_new == 'off') { + // server is offline. we are in error state. + $notify = true; + } + break; + case 'offline': + // only send a notification if the server goes down for the first time! + if($this->status_new == 'off' && $this->status_org == 'on') { + $notify = true; + } + break; + case 'status': + if($this->status_new != $this->status_org) { + // status has been changed! + $notify = true; + } + break; + } + + if(!$notify) { + return false; + } + + // first add to log (we use the same text as the SMS message because its short..) + if(sm_get_conf('log_status')) { + sm_add_log( + $this->server['server_id'], + 'status', + sm_parse_msg($this->status_new, 'sms', $this->server) + ); + } + + // check if email is enabled for this server + if(sm_get_conf('email_status') && $this->server['email'] == 'yes') { + // send email + $this->notifyByEmail(); + } + // check if sms is enabled for this server + if(sm_get_conf('sms_status') && $this->server['sms'] == 'yes') { + // yay lets wake those nerds up! + $this->notifyByTxtMsg(); + } + return true; + } + + /** + * This functions performs the email notifications + * + * @return boolean + */ + protected function notifyByEmail() { + $userlist = array(); + + // find all the users with this server listed + $users = $this->db->select( + SM_DB_PREFIX . 'users', + 'FIND_IN_SET(\''.$this->server['server_id'].'\', `server_id`) AND `email` != \'\'', + array('user_id', 'name', 'email') + ); + + if (empty($users)) { + return false; + } + + // build mail object with some default values + $mail = new phpmailer(); + + $mail->From = sm_get_conf('email_from_email'); + $mail->FromName = sm_get_conf('email_from_name'); + $mail->Subject = sm_parse_msg($this->status_new, 'email_subject', $this->server); + $mail->Priority = 1; + + $body = sm_parse_msg($this->status_new, 'email_body', $this->server); + $mail->Body = $body; + $mail->AltBody = str_replace('
', "\n", $body); + + // go through empl + foreach ($users as $user) { + // we sent a seperate email to every single user. + $userlist[] = $user['user_id']; + $mail->AddAddress($user['email'], $user['name']); + $mail->Send(); + $mail->ClearAddresses(); + } + + if(sm_get_conf('log_email')) { + // save to log + sm_add_log($this->server['server_id'], 'email', $body, implode(',', $userlist)); + } + } + + /** + * This functions performs the text message notifications + * + * @return unknown + */ + protected function notifyByTxtMsg() { + // send sms to all users for this server using defined gateway + $users = $this->db->select( + SM_DB_PREFIX . 'users', + 'FIND_IN_SET(\''.$this->server['server_id'].'\', `server_id`) AND `mobile` != \'\'', + array('user_id', 'name', 'mobile') + ); + + if (empty($users)) { + return false; + } + + // we have to build an userlist for the log table.. + $userlist = array(); + + // open the right class + // not making this any more dynamic, because perhaps some gateways need custom settings (like Mollie) + switch(strtolower(sm_get_conf('sms_gateway'))) { + case 'inetworx': + $sms = new txtmsgInetworx(); + break; + case 'mollie': + $sms = new txtmsgMollie(); + $sms->setGateway(1); + break; + case 'spryng': + $sms = new txtmsgSpryng(); + break; + } + + // copy login information from the config file + $sms->setLogin(sm_get_conf('sms_gateway_username'), sm_get_conf('sms_gateway_password')); + $sms->setOriginator(sm_get_conf('sms_from')); + + // add all users to the recipients list + foreach ($users as $user) { + $userlist[] = $user['user_id']; + $sms->addRecipients($user['mobile']); + } + + $message = sm_parse_msg($this->status_new, 'sms', $this->server); + + // Send sms + $result = $sms->sendSMS($message); + + if(sm_get_conf('log_sms')) { + // save to log + sm_add_log($this->server['server_id'], 'sms', $message, implode(',', $userlist)); + } + return $result; + } + + /** + * Get the error returned by the update function + * + * @return string + */ + public function getError() { + return $this->error; + } + + /** + * Get the response time of the server + * + * @return string + */ + public function getRtime() { + return $this->rtime; + } + + /** + * Clear all the results that are left from the previous run + * + */ + protected function clearResults() { + $this->error = ''; + $this->status_org = false; + $this->status_new = false; + } +} + +?> \ No newline at end of file diff --git a/classes/txtmsg/txtmsgCore.class.php b/classes/txtmsg/txtmsgCore.class.php new file mode 100644 index 00000000..2bc09edd --- /dev/null +++ b/classes/txtmsg/txtmsgCore.class.php @@ -0,0 +1,61 @@ +. + */ + +abstract class txtmsgCore implements txtmsgInterface { + protected $originator; + protected $password; + protected $recipients = array(); + protected $username; + + /** + * Define login information for the gateway + * + * @param string $username + * @param string $password + */ + public function setLogin($username, $password) { + $this->username = $username; + $this->password = $password; + } + + /** + * Set the mobile number the text message will be send from + * + * @param string $originator + */ + public function setOriginator($originator) { + $this->originator = $originator; + } + + /** + * Add new recipient to the list + * + * @param unknown_type $recipient + */ + public function addRecipients($recipient) { + array_push($this->recipients, $recipient); + } +} + +?> diff --git a/classes/txtmsg/txtmsgInetworx.class.php b/classes/txtmsg/txtmsgInetworx.class.php new file mode 100644 index 00000000..fd922b11 --- /dev/null +++ b/classes/txtmsg/txtmsgInetworx.class.php @@ -0,0 +1,142 @@ +. + */ + +class txtmsgInetworx extends txtmsgCore { + // ========================================================================= + // [ Fields ] + // ========================================================================= + + // ========================================================================= + // [ Methods ] + // ========================================================================= + + /** + * Send a text message to one or more recipients + * + * @param string $subject + * @param string $body + * @return boolean + */ + public function sendSMS($message) { + + if(empty($this->recipients)) { + return false; + } + + $errors = 0; + + foreach($this->recipients as $receiver) { + if(!$this->executeSend($message, $receiver, $this->originator)) { + $errors++; + } + } + $this->recipients = array(); + + return ($errors > 0) ? false : true; + } + + /** + * Performs the actual send operation + * + * @param string $subject + * @param string $body + * @param string $receiver + * @param string $sender + * @return unknown + */ + protected function executeSend($message, $receiver, $sender) { + $con_https[0] = 'sms.inetworx.ch'; + $con_https[1] = '443'; + $con_https[2] = 'inetworxag:conn2smsapp'; + $posturl = "/smsapp/sendsms.php"; + + if(!empty($receiver)) { + $postvars = 'user=' . urlencode($this->username) . + '&pass=' . urlencode($this->password) . + '&sender=' . urldecode($sender) . + '&rcpt=' . urlencode($receiver) . + '&msgbody=' . urlencode($message) + ; + //if enabled, it sends a flash-message (not stored in Inbox!!) + //$postvars .= "&mclass=1"; + + $rtnval = $this->_auth_https_post(array($con_https[0], $con_https[1], $con_https[2], $posturl, $postvars)); + + return $rtnval; + //echo "SMS-Send-Result: $rtnval"; + } else { + return false; + } + } + + protected function _auth_https_post($inarray) { + // AUTH_HTTPS_POST: Request POST URL using basic authentication and SSL. + // Input: inarray[0]: host name + // inarray[1]: service port + // inarray[2]: user/password + // inarray[3]: URL request + // inarray[4]: POST variables + // Output: Message returned by server. + + // Build the header. + $header = "POST ".$inarray[3]." HTTP/1.0\r\n"; + $header .= "Authorization: Basic ".base64_encode("$inarray[2]")."\r\n"; + $header .= "Host: ".$inarray[0]."\r\n"; + $header .= "Content-type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-length: ".strlen($inarray[4])."\r\n\r\n"; + // Connect to the server. + $connection = fsockopen("ssl://".$inarray[0], $inarray[1], &$errnum, &$errdesc, 10); + $msg = ""; + if (! $connection){ + $msg = $errdesc." (".$errnum.")"; + } + else + { + socket_set_blocking($connection,false); + fputs($connection,$header.$inarray[4]); + while (! feof($connection)) + { + $newline = fgets($connection,128); + switch ($newline) + { + // Skip http headers. + case (strstr($newline, 'Content-')): break; + case (strstr($newline, 'HTTP/1')): break; + case (strstr($newline, 'Date:')): break; + case (strstr($newline, 'Server:')): break; + case (strstr($newline, 'X-Powered-By:')): break; + case (strstr($newline, 'Connection:')): break; + case "": break; + case "\r\n": break; + default: $msg .= $newline; + } //end switch + } //end while + fclose($connection); + } //end else + return $msg; + } //end function auth_https_post + +} + +?> \ No newline at end of file diff --git a/classes/txtmsg/txtmsgInterface.class.php b/classes/txtmsg/txtmsgInterface.class.php new file mode 100644 index 00000000..6c51e8e8 --- /dev/null +++ b/classes/txtmsg/txtmsgInterface.class.php @@ -0,0 +1,34 @@ +. + */ + +interface txtmsgInterface { + + public function setLogin($username, $password); + public function setOriginator($originator); + public function addRecipients($recipient); + public function sendSMS($message); + +} + +?> \ No newline at end of file diff --git a/classes/txtmsg/txtmsgMollie.class.php b/classes/txtmsg/txtmsgMollie.class.php new file mode 100644 index 00000000..47ebdc37 --- /dev/null +++ b/classes/txtmsg/txtmsgMollie.class.php @@ -0,0 +1,96 @@ +. + */ + +class txtmsgMollie extends txtmsgCore { + // ========================================================================= + // [ Fields ] + // ========================================================================= + public $gateway = 1; + public $success = false; + public $reference = ''; + + // ========================================================================= + // [ Methods ] + // ========================================================================= + /** + * Select the gateway to use + * + * @param unknown_type $gateway + */ + public function setGateway($gateway) { + $this->gateway = $gateway; + } + + public function setReference ($reference) { + $this->reference = $reference; + } + + /** + * Send a text message to one or more recipients + * + * @param string $subject + * @param string $body + * @return boolean + */ + public function sendSMS($message) { + $recipients = implode(',', $this->recipients); + + $result = $this->_auth_https_post('www.mollie.nl', '/xml/sms/', + 'gateway='.urlencode($this->gateway). + '&username='.urlencode($this->username). + '&password='.urlencode($this->password). + '&originator='.urlencode($this->originator). + '&recipients='.urlencode($recipients). + '&message='.urlencode($message) . + (($this->reference != '') ? '&reference='.$this->reference : '') + ); + + $this->recipients = array(); + + list($headers, $xml) = preg_split("/(\r?\n){2}/", $result, 2); + $data = simplexml_load_string($xml); + + $this->success = ($data->item->success == 'true'); + return $this->success; + } + + protected function _auth_https_post($host, $path, $data) { + $fp = @fsockopen($host,80); + $buf = ''; + if ($fp) { + @fputs($fp, "POST $path HTTP/1.0\n"); + @fputs($fp, "Host: $host\n"); + @fputs($fp, "Content-type: application/x-www-form-urlencoded\n"); + @fputs($fp, "Content-length: " . strlen($data) . "\n"); + @fputs($fp, "Connection: close\n\n"); + @fputs($fp, $data); + while (!feof($fp)) { + $buf .= fgets($fp,128); + } + fclose($fp); + } + return $buf; + } +} +?> \ No newline at end of file diff --git a/classes/txtmsg/txtmsgSpryng.class.php b/classes/txtmsg/txtmsgSpryng.class.php new file mode 100644 index 00000000..f0fd03a6 --- /dev/null +++ b/classes/txtmsg/txtmsgSpryng.class.php @@ -0,0 +1,72 @@ +. + */ + +class txtmsgSpryng extends txtmsgCore { + // ========================================================================= + // [ Fields ] + // ========================================================================= + public $gateway = 1; + public $resultcode = null; + public $resultmessage = null; + public $success = false; + public $successcount = 0; + + // ========================================================================= + // [ Methods ] + // ========================================================================= + public function setGateway($gateway) { + $this->gateway = $gateway; + } + + public function sendSMS($message) { + $recipients = implode(',', $this->recipients); + + $result = $this->_auth_https_post('http://www.spryng.nl', '/SyncTextService', + '?OPERATION=send' . + '&USERNAME=' . $this->username . + '&PASSWORD=' . $this->password . + '&DESTINATION=' . $recipients . + '&SENDER=' . $this->originator . + '&BODY=' . $message . + '&SMSTYPE=' . 'BUSINESS' + ); + return $result; + } + + protected function _auth_https_post($host, $path, $data) { + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $host . $path . $data); + //curl_setopt($ch, CURLOPT_HEADER, 1); + //curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + $data = curl_exec($ch); + curl_close($ch); + return $data; + } +} + +?> \ No newline at end of file diff --git a/config.inc.php b/config.inc.php new file mode 100644 index 00000000..6bafc901 --- /dev/null +++ b/config.inc.php @@ -0,0 +1,71 @@ +. + */ + +######################################## +# +# START SERVER MONITOR CONFIGURATION +# +######################################## + +// Database information +// Prefix used for tables +define('SM_DB_PREFIX', 'monitor_'); +// Database username +define('SM_DB_USER', 'db_user'); +// Database password +define('SM_DB_PASS', 'db_pass'); +// Database name +define('SM_DB_NAME', 'db_name'); +// Database host +define('SM_DB_HOST', 'localhost'); + +######################################## +# +# END SERVER MONITOR CONFIGURATION +# +######################################## + +// Include paths +// Tell the script where to find the templates, css files and javascript files. +// If you haven't changed anything to the structure you should leave these unchanged +define('SM_PATH_TPL', 'tpl/'); +define('SM_PATH_CSS', 'inc/'); +define('SM_PATH_JS', 'inc/'); + +error_reporting(0); +ini_set('display_errors', '0'); + +require 'functions.inc.php'; +$db = new smDatabase(); + +sm_load_conf(); + +$lang = sm_get_conf('language'); + +if(!$lang) { + $lang = 'en'; +} +sm_load_lang($lang); + +?> \ No newline at end of file diff --git a/cron/status.cron.php b/cron/status.cron.php new file mode 100644 index 00000000..8b930fd1 --- /dev/null +++ b/cron/status.cron.php @@ -0,0 +1,70 @@ +. + */ + +// include main configuration and functionality +require_once dirname(__FILE__) . '/../config.inc.php'; + +// get the active servers from database +$servers = $db->select( + SM_DB_PREFIX.'servers', + array('active' => 'yes'), + array('server_id', 'ip', 'port', 'label', 'type', 'status', 'active', 'email', 'sms') +); + +$updater = new smUpdaterStatus(); + +foreach ($servers as $server) { + $status_org = $server['status']; + // remove the old status from the array to avoid confusion between the new and old status + unset($server['status']); + + $updater->setServer($server, $status_org); + + // check server status + // it returns the new status, and performs the update check automatically. + $status_new = $updater->getStatus(); + // notify the nerds if applicable + $updater->notify(); + + // update server status + $save = array( + 'last_check' => date('Y-m-d H:i:s'), + 'status' => $status_new, + 'error' => $updater->getError(), + 'rtime' => $updater->getRtime(), + ); + + // if the server is on, add the last_online value + if($save['status'] == 'on') { + $save['last_online'] = date('Y-m-d H:i:s'); + } + + $db->save( + SM_DB_PREFIX . 'servers', + $save, + array('server_id' => $server['server_id']) + ); +} + +?> \ No newline at end of file diff --git a/docs/CHANGELOG b/docs/CHANGELOG new file mode 100644 index 00000000..6c84ce5b --- /dev/null +++ b/docs/CHANGELOG @@ -0,0 +1,25 @@ +######################### +# +# Version 2.0.0 +# October 19, 2009 +# +######################### +- server type ("service" or "website") +- different types of notification +- new text message gateways +- code rewrite +- new layout +- check for updates function + +######################### +# +# Version 1.0.1 +# September 18, 2008 +# +######################### + + +- log.php + tpl/log.tpl.html + select order by clause used datetime field after DATE_FORMAT had been performed, + resulting in a wrong list of log entries shown \ No newline at end of file diff --git a/docs/COPYING b/docs/COPYING new file mode 100644 index 00000000..20d40b6b --- /dev/null +++ b/docs/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/docs/README b/docs/README new file mode 100644 index 00000000..c75dea11 --- /dev/null +++ b/docs/README @@ -0,0 +1,176 @@ + PHP Server Monitor v2.0.0 + http://phpservermon.sourceforge.net + Copyright (c) 2008-2009 Pepijn Over + + PHP Server Monitor is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + PHP Server Monitor is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with PHP Server Monitor. If not, see . + +################# +# # +# SUMMARY # +# # +################# + +PHP Server Monitor is a script that checks whether the servers on your list are up +and running on the selected ports. +It comes with a web based user interface where you can add and remove servers or websites from the MySQL database, +and you can manage users for each server with a mobile number and email address. + +With version 2.0 there's the support for websites as well. On the "Add server" page, you can choose +whether it's a "service" or a "website": +* service + A connection will be made to the entered ip or domain, on the given port. This way you can check if certain + services on your machine are still running. To check your IMAP service for example, enter port 143. + +* website + The previous version only tried to establish a connection to the server on port 80. If you are running multiple + websites on 1 machine, there was no proper way to check each website for possible errors. Also it was impossible to make + sure your site was really up and running, all you knew was that the server was still online. + This function takes care of that. + You can enter a link to a website (for example http://sourceforge.net/index.php), it will use cURL to open the website and + check the HTTP status code (see http://en.wikipedia.org/wiki/List_of_HTTP_status_codes for details). + If the HTTP status code is in the 4xx range, it means an error occured and the website is not accesible to the public. + In that case the script will return a "status offline", and will start sending out notifications. + +Each server has it's own settings regarding notification. +You can choose for email notification or text message (SMS). As of version 2.0, there are 3 gateways +available: +* Mollie - http://www.mollie.nl +* Spryng - http://www.spryng.nl +* Inetworx - http://www.inetworx.ch +For these gateways you need an account with sufficient credits. + +If logging is enabled in the configuration, it will log any connection errors, emails and text messages sent. +The latest log records will be displayed on your web interface. +The cron/status.cron.php can be added as a cronjob which will keep the server status up to date. + +I'd appreciate any feedback you might have regarding this script. Please leave it on the sourceforge +project page (tracker), or send me an email (see top of file for link). + +################# +# # +# DOWNLOAD # +# # +################# + +The latest version can be found at http://phpservermon.sourceforge.net + + +################# +# # +# REQUIREMENTS # +# # +################# + + 1. php 5 + 2. MySQL Database + 3. FTP access + + +################# +# # +# INSTALL # +# # +################# + +By default the PHP Server Monitor does not come with any security methods. After uploading these files to +your public html folder these will be visible to everyone on the web. It is recommended to put a password +on this folder to prevent other people from playing with your tool. An example .htaccess login script can +be found in the example/ dir. To create your own username and password for the .htpasswd file, see +http://www.htaccesstools.com/htpasswd-generator/ + + 1. Configuration + Open the config.php file with a plain text editor such as Notepad. + The first thing to do now in order to get started, is to get your database login information + right. The information is stored using php's define() function. To change these values correctly, only + update the second parameter of the function. + For example: + define('SM_DB_USER', 'db_user'); + To change your username you should ONLY change the 'db_user' part. Do NOT remove the quotes around + your username as that will result in an error. + + 2. Upload files + The next step is to get your files onto your webserver where you can reach them. You can rename the + folder of the server monitor without trouble, but if you change the structure please make sure + to update the settings in the config.php file + + 3. Run install.php + Once your database login information is correct, you can run the install.php script located in the + root dir. This script will create all the database tables you will need. + After running the install.php script you can remove it. + + 4. Configure your installation + Open the main page of the server monitor, by simply calling index.php. In the menu on the top find "config", + it'll open a page where you can change the necessary information for your tool. + + 5. [optional] Add a cronjob + In order to keep the server monitor up to date, the monitor.php file has to run regulary. If you're running + this on a linux machine, the easiest way is to add a cronjob. If it's your own server or you have + shell access and permission to open the crontab, locate the "crontab" file + (usually in /etc/crontab, but depends on distro). Open the file (vi /etc/crontab), and add the following + (change the paths to match your installation directories): + + #server monitor every 15 min + */15 * * * * root /usr/bin/php /var/www/html/phpservermon/cron/status.cron.php + + As you can see, this line will run the status.cron.php script every 15 minutes. Change the line to suit your + needs. If you do not have shell access, ask your webhosting to set it up for you. + + 6. Voila! + + +################# +# # +# CUSTOMIZING # +# # +################# + + 1. Language + The server monitor uses language files. That means that any regular text you see on the screen can easily be + changed without having to digg through the code. These language files are stored in the directory "lang". + The language that's being used by the monitor is defined in the config table. If you like + you can make changes to the language file or even add a new one. + + 1.1 Changing the email or text message + Open the language file that corresponds to the selected language + (default is English ("en.lang.php")). Scroll all the way to the bottom until you spot this line: + + 'notifications' => array( + + After that you'll see the lines that hold the notification messages. For example: + + 'off_sms' => 'Server \'%LABEL%\' is DOWN: ip=%IP%, port=%PORT%. Error=%ERROR%', + + The first part of this line, 'off_sms', is the name of the notification. You should not change this. The + second part is the actual message. There are a few variables you can use in your message: + - %LABEL% The name of the server + - %IP% The ip of the server + - %PORT% The port of the server + - %ERROR% This one only works for the off_* messages and contains the error returned by the + monitor + + 1.2 Adding a new language + It's not the easiest thing to add a new language to the monitor, but if you can spare a few minutes of your time + to send in a translation, it can be added to a future release. + - Create a new file in the directory "lang" named "mylanguage.lang.php". + - Copy the contents of the file "en.lang.php" to your new file. + - Translate the English stuff to your own language. + - Send a copy to ipdope[at]users.sourceforge.net so I can add it to the next release :) + +################# +# # +# CREDITS # +# # +################# + +1. classes/phpmailer.class.php - Brent R. Matzelle \ No newline at end of file diff --git a/example/.htaccess b/example/.htaccess new file mode 100644 index 00000000..93aa72b4 --- /dev/null +++ b/example/.htaccess @@ -0,0 +1,4 @@ +AuthUserFile ".htpasswd" +AuthType Basic +AuthName "Server Monitor" +require valid-user diff --git a/example/.htpasswd b/example/.htpasswd new file mode 100644 index 00000000..d37bda04 --- /dev/null +++ b/example/.htpasswd @@ -0,0 +1 @@ +admin:dGRkPurkuWmW2 \ No newline at end of file diff --git a/functions.inc.php b/functions.inc.php new file mode 100644 index 00000000..fc9662f9 --- /dev/null +++ b/functions.inc.php @@ -0,0 +1,220 @@ +. + */ + +/** + * + * Autoload + * + */ +function __autoload($class) { + // first check if a subdir exists for the class + // it splits using uppercase chars + preg_match_all("/(\P{Lu}+)|(\p{Lu}+\P{Lu}*)/", $class, $subdir_matches); + + if(!empty($subdir_matches) && count($subdir_matches[0]) > 1) { + // okay we have some upper case, lets see if a dir exists + $dir = dirname(__FILE__) . '/classes/' . trim($subdir_matches[0][0]); + $file = $dir . '/' . trim($class) . '.class.php'; + + if(is_dir($dir) && file_exists($file)) { + require $file; + return $file; + } + } else { + $file = dirname(__FILE__).'/classes/'.trim(strtolower($class)).'.class.php'; + + if(file_exists($file)){ + require $file; + return $file; + } + } + + trigger_error("KERNEL_ERR : Unable to find file:\n\t\t[$file]\n\t associated with class:\n\t\t$class", E_USER_ERROR); + return false; +} + +############################################### +# +# Language functions +# +############################################### + +/** + * Retrieve language settings from the selected language file + * + * @return unknown + */ +function sm_get_lang() { + $args = func_get_args(); + + if(empty($args)) return $GLOBALS['sm_lang']; + + $result = null; + $node = null; + + if($args) { + $node = '$GLOBALS[\'sm_lang\'][\'' . implode('\'][\'', $args) . '\']'; + } + eval('if (isset('.$node.')) $result = '.$node.';'); + + return $result; +} + +function sm_load_lang($lang) { + $lang_file = dirname(__FILE__) . '/lang/' . $lang . '.lang.php'; + + if(!file_exists($lang_file)) { + die('unable to load language file: ' . $lang_file); + } + + require $lang_file; + + $GLOBALS['sm_lang'] = $sm_lang; +} + +function sm_get_conf($key) { + $result = (isset($GLOBALS['sm_config'][$key])) ? $GLOBALS['sm_config'][$key] : null; + + return $result; +} + +function sm_load_conf() { + global $db; + + // load config from database into global scope + $GLOBALS['sm_config'] = array(); + $config_db = $db->select(SM_DB_PREFIX . 'config', null, array('key', 'value')); + foreach($config_db as $setting) { + $GLOBALS['sm_config'][$setting['key']] = $setting['value']; + } + + if(empty($GLOBALS['sm_config']) && basename($_SERVER['SCRIPT_NAME']) != 'install.php') { + // no config found, go to install page + die('Failed to load config table. Please run the install.php file'); + } +} + + +############################################### +# +# Miscellaneous functions +# +############################################### + +/** + * This function merely adds the message to the log table. It does not perform any checks, + * everything should have been handled when calling this function + * + * @param string $server_id + * @param string $message + */ +function sm_add_log($server_id, $type, $message, $user_id = null) { + global $db; + + $db->save( + SM_DB_PREFIX.'log', + array( + 'server_id' => $server_id, + 'type' => $type, + 'message' => $message, + 'user_id' => ($user_id === null) ? '' : $user_id, + ) + ); +} + +/** + * Parses a string from the language file with the correct variables replaced in the message + * + * @param string $status is either 'on' or 'off' + * @param string $type is either 'sms' or 'email' + * @param array $server information about the server which may be placed in a message: %KEY% will be replaced by your value + * @return string parsed message + */ +function sm_parse_msg($status, $type, $vars) { + $message = ''; + + $message = sm_get_lang('notifications', $status . '_' . $type); + + if(!$message) { + return $message; + } + $vars['date'] = date('Y-m-d H:i:s'); + + foreach($vars as $k => $v) { + $message = str_replace('%' . strtoupper($k) . '%', $v, $message); + } + + return $message; +} + +function sm_curl_get($href) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + curl_setopt($ch, CURLOPT_URL, $href); + $result = curl_exec($ch); + curl_close($ch); + + return $result; +} + +function sm_check_updates() { + $latest = sm_curl_get('http://phpservermon.neanderthal-technology.com/version'); + $current = sm_get_conf('version'); + + if((int) $current < (int) $latest) { + // new update available + return true; + } + return false; +} + +############################################### +# +# Debug functions +# +############################################### + +/** + * Only used for debugging and testing + * + * @param mixed $arr + */ +function pre($arr = null) { + echo "
";
+	if ($arr === null) debug_print_backtrace();
+	print_r($arr);
+	echo "
"; +} + +function sm_no_cache() { + header("Expires: Mon, 20 Dec 1998 01:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-cache, must-revalidate"); + header("Pragma: no-cache"); +} + +?> \ No newline at end of file diff --git a/img/delete.png b/img/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc4d3b2030ecf772fae856411c7b0968d99005b GIT binary patch literal 311 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLli5Jei0#Y0$L4LviA%Njt^WI31l&6bhNX4zB1b1cz#v=#L9XQ1BeB*}A z%q#-T*4)b4(o?t>F*0#zoIb=Q$oTmtkhx|Ks|wRYkOCL`0G3nf>>LU!8g&ecZvf@i zF{v>LIB*y*Py(9O5Wuep(t6;`5vIViTuKZ~9gTA(-x+M+aA06$eNbb-z@e}}OvNsM m(}BTo!EZ5z1_KrbCLV@_U2LV?JKcpq4)=8Rb6Mw<&;$Tia9maZ literal 0 HcmV?d00001 diff --git a/img/edit.png b/img/edit.png new file mode 100644 index 0000000000000000000000000000000000000000..05711a0948ed445c97dd21645f42a22a403735c9 GIT binary patch literal 451 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl>+{$oq9z#bv`|(K8|7RH1EM%;!+W@rl%$ff) zXP){0|Noy*%+B9=lNdxR0lGTCPZRLGK012lzcvQJL|i0{bqbP0l+XkKJS4LG literal 0 HcmV?d00001 diff --git a/img/off.png b/img/off.png new file mode 100644 index 0000000000000000000000000000000000000000..87789faa8b5f8a238910506ca210d5e172a485f5 GIT binary patch literal 21882 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 literal 0 HcmV?d00001 diff --git a/img/on.png b/img/on.png new file mode 100644 index 0000000000000000000000000000000000000000..34c6512a7165de4e9f7abff06642b4e7b0098ec0 GIT binary patch literal 20409 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 eo$BN#Tv_f|$b*fB2KXyM2pI`Q@fxw8f&U9. + */ + +require_once 'config.inc.php'; + +sm_no_cache(); + +if(isset($_GET['action']) && $_GET['action'] == 'check') { + require 'cron/status.cron.php'; + header('Location: index.php'); +} + +$tpl = new smTemplate(); + +$tpl->addJS('monitor.js'); +$tpl->addCSS('monitor.css'); + +// check for updates? +if(sm_get_conf('show_update')) { + $last_update = sm_get_conf('last_update_check'); + + if((time() - (7 * 24 * 60 * 60)) > $last_update) { + // been more than a week since update, lets go + $need_upd = sm_check_updates(); + // update "update-date" + $db->save(SM_DB_PREFIX . 'config', array('value' => time()), array('key' => 'last_update_check')); + + if($need_upd) { + // yay, new update available =D + $tpl->addTemplateData( + 'main', + array( + 'update_available' => '
'.sm_get_lang('system', 'update_available').'
', + ) + ); + } + } +} + + +$type = (!isset($_GET['type'])) ? 'servers' : $_GET['type']; +$allowed_types = array('servers', 'users', 'log', 'config'); + +// add some default vars +$tpl->addTemplateData( + 'main', + array( + 'title' => strtoupper(sm_get_lang('system', 'title')), + 'subtitle' => sm_get_lang('system', $type), + 'label_servers' => sm_get_lang('system', 'servers'), + 'label_users' => sm_get_lang('system', 'users'), + 'label_log' => sm_get_lang('system', 'log'), + 'label_config' => sm_get_lang('system', 'config'), + 'label_update' => sm_get_lang('system', 'update'), + 'label_help' => sm_get_lang('system', 'help'), + 'active_' . $type => 'active', + ) +); + +// make sure user selected a valid type. if so, include the file and add to template +if(in_array($type, $allowed_types)) { + eval('$mod = new mod'.ucfirst($type).'();'); + + $html = $mod->createHTML(); + + $tpl->addTemplatedata( + 'main', + array( + 'content' => $html, + ) + ); +} + +echo $tpl->display('main'); + +?> \ No newline at end of file diff --git a/install.php b/install.php new file mode 100644 index 00000000..14a9394c --- /dev/null +++ b/install.php @@ -0,0 +1,143 @@ +. + */ + +// this script creates all the database tables required for server monitor +error_reporting(0x0ffffff); + +require 'config.inc.php'; +$tpl = new smTemplate(); +$tpl->addCSS('monitor.css', 'install'); + +$tpl->newTemplate('install', 'install.tpl.html'); + + +if(!is_resource($db->getLink())) { + // no valid db info + $tpl->addTemplatedata( + 'install', + array('error' => 'Couldn\'t connect to database!') + ); + echo $tpl->display('install'); + die(); +} + +$tables = array( + 'users' => + array( + 0 => "CREATE TABLE `" . SM_DB_PREFIX . "users` ( + `user_id` int(11) NOT NULL auto_increment, + `server_id` varchar(255) NOT NULL, + `name` varchar(255) NOT NULL, + `mobile` varchar(15) NOT NULL, + `email` varchar(255) NOT NULL, + PRIMARY KEY (`user_id`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8;", + 1 => "INSERT INTO `" . SM_DB_PREFIX . "users` (`server_id`, `name`, `mobile`, `email`) VALUES ('1,2', 'example_user', '0123456789', 'user@example.com')" + ), + 'log' => + array( + 0 => "CREATE TABLE `" . SM_DB_PREFIX . "log` ( + `log_id` int(11) NOT NULL auto_increment, + `server_id` int(11) NOT NULL, + `type` enum('status','email','sms') NOT NULL, + `message` varchar(255) NOT NULL, + `datetime` timestamp NOT NULL default CURRENT_TIMESTAMP, + `user_id` varchar(255) NOT NULL, + PRIMARY KEY (`log_id`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8;", + ), + 'servers' => + array( + 0 => "CREATE TABLE `" . SM_DB_PREFIX . "servers` ( + `server_id` int(11) NOT NULL auto_increment, + `ip` varchar(100) NOT NULL, + `port` int(5) NOT NULL, + `label` varchar(255) NOT NULL, + `type` enum('service','website') NOT NULL default 'service', + `status` enum('on','off') NOT NULL default 'on', + `error` varchar(255) NOT NULL, + `rtime` FLOAT(9, 7) NOT NULL, + `last_online` datetime NOT NULL, + `last_check` datetime NOT NULL, + `active` enum('yes','no') NOT NULL default 'yes', + `email` enum('yes','no') NOT NULL default 'yes', + `sms` enum('yes','no') NOT NULL default 'no', + PRIMARY KEY (`server_id`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8;", + 1 => "INSERT INTO `" . SM_DB_PREFIX . "servers` (`ip`, `port`, `label`, `type`, `status`, `error`, `rtime`, `last_online`, `last_check`, `active`, `email`, `sms`) VALUES ('http://sourceforge.net/index.php', 80, 'SourceForge', 'website', 'on', '', '', '0000-00-00 00:00:00', '0000-00-00 00:00:00', 'yes', 'yes', 'yes'), ('smtp.gmail.com', 465, 'Gmail SMTP', 'service', 'on', '', '', '0000-00-00 00:00:00', '0000-00-00 00:00:00', 'yes', 'yes', 'yes')", + ), + 'config' => + array( + 0 => "CREATE TABLE `" . SM_DB_PREFIX . "config` ( + `config_id` int(11) NOT NULL AUTO_INCREMENT, + `key` varchar(255) NOT NULL, + `value` varchar(255) NOT NULL, + PRIMARY KEY (`config_id`), + KEY `key` (`key`(50)) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8;", + 1 => "INSERT INTO `" . SM_DB_PREFIX . "config` (`config_id`, `key`, `value`) VALUES + (null, 'language', 'en'), + (null, 'email_status', '1'), + (null, 'email_from_email', 'monitor@example.org'), + (null, 'email_from_name', 'Server Monitor'), + (null, 'sms_status', '1'), + (null, 'sms_gateway', 'mollie'), + (null, 'sms_gateway_username', 'username'), + (null, 'sms_gateway_password', 'password'), + (null, 'sms_from', '1234567890'), + (null, 'alert_type', 'status'), + (null, 'log_status', '1'), + (null, 'log_email', '1'), + (null, 'log_sms', '1'), + (null, 'version', '200'), + (null, 'show_update', '1'), + (null, 'last_update_check', '0');", + ) +); + +$result = array(); + +foreach($tables as $name => $queries) { + $if_table_exists = $db->query('SHOW TABLES LIKE \'' . SM_DB_PREFIX . $name.'\''); + + if(!empty($if_table_exists)) { + $message = 'Table ' . SM_DB_PREFIX . $name . ' already exists in your database!'; + } else { + $message = ''; + + foreach($queries as $query) { + $message .= 'Executing ' . $query . '

'; + $db->query($query); + } + } + + $result[] = array( + 'name' => $name, + 'result' => $message, + ); +} +$tpl->addTemplateDataRepeat('install', 'tables', $result); + +echo $tpl->display('install'); +?> \ No newline at end of file diff --git a/lang/en.lang.php b/lang/en.lang.php new file mode 100644 index 00000000..98ebb31a --- /dev/null +++ b/lang/en.lang.php @@ -0,0 +1,132 @@ +. + */ + +$sm_lang = array( + 'system' => array( + 'title' => 'Server Monitor', + 'servers' => 'servers', + 'users' => 'users', + 'log' => 'log', + 'update' => 'update', + 'config' => 'config', + 'help' => 'help', + 'action' => 'Action', + 'save' => 'Save', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'deleted' => 'Record has been deleted', + 'date' => 'Date', + 'message' => 'Message', + 'yes' => 'Yes', + 'no' => 'No', + 'edit' => 'Edit', + 'insert' => 'Insert', + 'add_new' => 'Add new?', + 'update_available' => 'A new update is available from
http://phpservermon.sourceforge.net.', + ), + 'users' => array( + 'user' => 'user', + 'name' => 'Name', + 'mobile' => 'Mobile', + 'email' => 'Email', + 'updated' => 'User updated.', + 'inserted' => 'User added.', + ), + 'log' => array( + 'title' => 'Log entries', + 'type' => 'Type', + 'status' => 'status', + 'email' => 'email', + 'sms' => 'sms', + ), + 'servers' => array( + 'server' => 'Server', + 'label' => 'Label', + 'domain' => 'Domain/IP', + 'port' => 'Port', + 'type' => 'Type', + 'last_check' => 'Last check', + 'last_online' => 'Last online', + 'monitoring' => 'Monitoring', + 'send_email' => 'Send Email', + 'send_sms' => 'Send SMS', + 'updated' => 'Server updated.', + 'inserted' => 'Server added.', + 'rtime' => 'Response time', + ), + 'config' => array( + 'general' => 'General', + 'language' => 'Language', + 'show_update' => 'Check for new updates weekly?', + 'english' => 'English', + 'dutch' => 'Dutch', + 'email_status' => 'Allow sending email?', + 'email_from_email' => 'Email from address', + 'email_from_name' => 'Email from name', + 'sms_status' => 'Allow sending text messages?', + 'sms_gateway' => 'Gateway to use for sending messages', + 'sms_gateway_mollie' => 'Mollie', + 'sms_gateway_spryng' => 'Spryng', + 'sms_gateway_inetworx' => 'Inetworx', + 'sms_gateway_username' => 'Gateway username', + 'sms_gateway_password' => 'Gateway password', + 'sms_from' => 'Sender\'s phone number', + 'alert_type' => + 'Select when you\'d like to be notified.
'. + '
'. + '1) Status change
'. + 'You will receive a notifcation when a server has a change in status. So from online -> offline or offline -> online.
'. + '2) Offline
'. + 'You will receive a notification when a server goes offline for the *FIRST TIME ONLY*. For example, '. + 'your cronjob is every 15 mins and your server goes down at 1 am and stays down till 6 am. '. + 'You will get 1 notification at 1 am and thats it.
'. + '3) Always
'. + 'You will receive a notification every time the script runs and a site is down, even if the site has been '. + 'offline for hours.'. + '
', + + 'alert_type_status' => 'Status change', + 'alert_type_offline' => 'Offline', + 'alert_type_always' => 'Always', + 'log_status' => 'Log status
If log status is set to TRUE, the monitor will log the event whenever the Notification settings are passed
', + 'log_email' => 'Log emails sent by the script?', + 'log_sms' => 'Log text messages sent by the script?', + 'updated' => 'The configuration has been updated.', + 'settings_email' => 'Email settings', + 'settings_sms' => 'Text message settings', + 'settings_notification' => 'Notification settings', + 'settings_log' => 'Log settings', + ), + // for newlines in the email messages use
+ 'notifications' => array( + 'off_sms' => 'Server \'%LABEL%\' is DOWN: ip=%IP%, port=%PORT%. Error=%ERROR%', + 'off_email_subject' => 'IMPORTANT: Server \'%LABEL%\' is DOWN', + 'off_email_body' => "Failed to connect to the following server:

Server: %LABEL%
IP: %IP%
Port: %PORT%
Error: %ERROR%
Date: %DATE%", + 'on_sms' => 'Server \'%LABEL%\' is RUNNING: ip=%IP%, port=%PORT%', + 'on_email_subject' => 'IMPORTANT: Server \'%LABEL%\' is RUNNING', + 'on_email_body' => "Server '%LABEL%' is running again:

Server: %LABEL%
IP: %IP%
Port: %PORT%
Date: %DATE%", + ), +); + +?> \ No newline at end of file diff --git a/lang/nl.lang.php b/lang/nl.lang.php new file mode 100644 index 00000000..f38fcb26 --- /dev/null +++ b/lang/nl.lang.php @@ -0,0 +1,131 @@ +. + */ + +$sm_lang = array( + 'system' => array( + 'title' => 'Server Monitor', + 'servers' => 'servers', + 'users' => 'gebruikers', + 'log' => 'log', + 'update' => 'update', + 'config' => 'config', + 'help' => 'help', + 'action' => 'Actie', + 'save' => 'Opslaan', + 'edit' => 'Wijzig', + 'delete' => 'Verwijder', + 'deleted' => 'Record is verwijderd', + 'date' => 'Datum', + 'message' => 'Bericht', + 'yes' => 'Ja', + 'no' => 'Nee', + 'edit' => 'Wijzig', + 'insert' => 'Voeg toe', + 'add_new' => 'Voeg toe?', + 'update_available' => 'Een nieuwe update is beschikbaar op http://phpservermon.sourceforge.net.', + ), + 'users' => array( + 'user' => 'gebruiker', + 'name' => 'Naam', + 'mobile' => 'Mobiel', + 'email' => 'Email', + 'updated' => 'Gebruiker gewijzigd.', + 'inserted' => 'Gebruiker toegevoegd.', + ), + 'log' => array( + 'title' => 'Log entries', + 'type' => 'Type', + 'status' => 'status', + 'email' => 'email', + 'sms' => 'sms', + ), + 'servers' => array( + 'server' => 'Server', + 'label' => 'Label', + 'domain' => 'Domein/IP', + 'port' => 'Poort', + 'type' => 'Type', + 'last_check' => 'Laatst gecontroleerd', + 'last_online' => 'Laatst online', + 'monitoring' => 'Monitoring', + 'send_email' => 'Stuur email', + 'send_sms' => 'Stuur SMS', + 'updated' => 'Server gewijzigd.', + 'inserted' => 'Server toegevoegd.', + 'rtime' => 'Response tijd', + ), + 'config' => array( + 'general' => 'Algemeen', + 'language' => 'Taal', + 'show_update' => 'Check for new updates weekly?', + 'english' => 'Engels', + 'dutch' => 'Nederlands', + 'email_status' => 'Sta email berichten toe?', + 'email_from_email' => 'Email van adres', + 'email_from_name' => 'Email van naam', + 'sms_status' => 'Sta SMS berichten toe?', + 'sms_gateway' => 'Gateway voor het sturen van SMS', + 'sms_gateway_mollie' => 'Mollie', + 'sms_gateway_spryng' => 'Spryng', + 'sms_gateway_inetworx' => 'Inetworx', + 'sms_gateway_username' => 'Gateway gebruikersnaam', + 'sms_gateway_password' => 'Gateway wachtwoord', + 'sms_from' => 'Telefoonnummer afzender', + 'alert_type' => + 'Selecteer wanneer je een notificatie wilt.
'. + '
'. + '1) Status verandering
'. + 'Je ontvangt alleen bericht wanneer een server van status verandert. Dus van online -> offline of offline -> online.
'. + '2) Offline
'. + 'Je ontvangt bericht wanneer een server offline gaat voor de *EERSTE KEER*. Bijvoorbeeld, '. + 'je cronjob draait iedere 15 min en je server gaat down om 01:00 en blijft offline tot 06:00. '. + 'Je krijgt 1 bericht om 01:00 en dat is het.
'. + '3) Altijd
'. + 'Je krijgt een bericht elke keer dat het script draait en een website is down, ook al is de site al een paar uur offline.'. + '
', + + 'alert_type_status' => 'Status verandering', + 'alert_type_offline' => 'Offline', + 'alert_type_always' => 'Altijd', + 'log_status' => 'Log status
Als de log status op TRUE staat, zal de monitor een log aanmaken elke keer dat hij door de notificatie instellingen komt
', + 'log_email' => 'Log emails verstuurd bij het script?', + 'log_sms' => 'Log sms berichten verstuurd bij het script?', + 'updated' => 'De configuratie is gewijzigd.', + 'settings_email' => 'Email instellingen', + 'settings_sms' => 'SMS instellingen', + 'settings_notification' => 'Notificatie instellingen', + 'settings_log' => 'Log instellingen', + ), + // for newlines in the email messages use
+ 'notifications' => array( + 'off_sms' => 'Server %LABEL% is DOWN: ip=%IP%, poort=%PORT%. Fout=%ERROR%', + 'off_email_subject' => 'BELANGRIJK: Server %LABEL% is DOWN', + 'off_email_body' => "De server kon niet worden bereikt:

Server: %LABEL%
IP: %IP%
Poort: %PORT%
Fout: %ERROR%
Datum: %DATE%", + 'on_sms' => 'Server %LABEL% is RUNNING: ip=%IP%, poort=%PORT%', + 'on_email_subject' => 'BELANGRIJK: Server %LABEL% is RUNNING', + 'on_email_body' => "Server %LABEL% is weer online:

Server: %LABEL%
IP: %IP%
Poort: %PORT%
Datum: %DATE%", + ), +); + +?> \ No newline at end of file diff --git a/tpl/config.tpl.html b/tpl/config.tpl.html new file mode 100644 index 00000000..e07da956 --- /dev/null +++ b/tpl/config.tpl.html @@ -0,0 +1,125 @@ + +{config_update} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {message} +
+ {label_general} +
{label_language} + +
{label_show_update}
+ {label_settings_email} +
{label_email_status}
{label_email_from_name}
{label_email_from_email}
+ {label_settings_sms} +
{label_sms_status}
{label_sms_gateway} + +
{label_sms_gateway_username}
{label_sms_gateway_password}
{label_sms_from}
+ {label_settings_notification} +
{label_alert_type} + +
+ {label_settings_log} +
{label_log_status}
{label_log_email}
{label_log_sms}
  + +
+
+ + + +
+ Please update! +
+ \ No newline at end of file diff --git a/tpl/install.tpl.html b/tpl/install.tpl.html new file mode 100644 index 00000000..98d8adc9 --- /dev/null +++ b/tpl/install.tpl.html @@ -0,0 +1,24 @@ + + + + + + PHP Server Monitor Installation script + + +

This script will create all the database tables required for PHP Server Monitor.

+

{error}

+ + +
+ Creating table {name}...

+ {result} +
+ + {tables} +
+ The installation is complete. Please check above if errors have occured.
+ If no errors have occured, you can remove this file and click here to go to your index + + + \ No newline at end of file diff --git a/tpl/log.tpl.html b/tpl/log.tpl.html new file mode 100644 index 00000000..0b887f6d --- /dev/null +++ b/tpl/log.tpl.html @@ -0,0 +1,42 @@ + + +
+{content_status} +
+ + + + + + + + + + + + + + + + + + + + + + + + + {entries} +
{logtitle}
{label_server}{label_message}{label_date}{label_users}
{server}{message}{datetime_format}{users}
+ \ No newline at end of file diff --git a/tpl/main.tpl.html b/tpl/main.tpl.html new file mode 100644 index 00000000..bf7c0f3b --- /dev/null +++ b/tpl/main.tpl.html @@ -0,0 +1,36 @@ + + + + + + PHP Server Monitor + + +
+ +
+

{subtitle}

+ {content} +
+ {update_available} + +
+ + + \ No newline at end of file diff --git a/tpl/servers.tpl.html b/tpl/servers.tpl.html new file mode 100644 index 00000000..8f5bcd05 --- /dev/null +++ b/tpl/servers.tpl.html @@ -0,0 +1,108 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {servers} +
 {label_label}{label_domain}{label_port}{label_type}{label_last_check}{label_rtime}{label_last_online}{label_monitoring}{label_send_email}{label_send_sms}{label_action}
{message}
{status}{label}{ip}{port}{type}{last_check}{rtime} s{last_online}{active}{email}{sms} + edit +   + delete +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

{titlemode}

{label_label}
{label_domain}
{label_port}
{label_type} + +
{label_monitoring} + +
{label_send_email} + +
{label_send_sms} + +
+ + + +
+
+ \ No newline at end of file diff --git a/tpl/users.tpl.html b/tpl/users.tpl.html new file mode 100644 index 00000000..7ce20f79 --- /dev/null +++ b/tpl/users.tpl.html @@ -0,0 +1,69 @@ + +
+ + + + + + + + + + + + + + + + + + + + + {users} +
{label_name}{label_mobile}{label_email}{label_servers}{label_action}
{message}
{name}{mobile}{email}{emp_servers} + edit +   + delete +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + +

{titlemode}

{label_name}
{label_mobile}
{label_email}
{label_servers} +
+ + + + {servers} +
+
+ + + +
+
+ \ No newline at end of file