<?php
/**
 * PHP Server Monitor
 * Monitor your servers and websites.
 *
 * This file is part of PHP Server Monitor.
 * 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 <http://www.gnu.org/licenses/>.
 *
 * @package     phpservermon
 * @author      Pepijn Over <pep@mailbox.org>
 * @copyright   Copyright (c) 2008-2017 Pepijn Over <pep@mailbox.org>
 * @license     http://www.gnu.org/licenses/gpl.txt GNU GPL v3
 * @version     Release: v3.2.0
 * @link        http://www.phpservermonitor.org/
 **/

###############################################
#
# Language functions
#
###############################################

/**
 * Retrieve language settings from the selected language file
 *
 * @return string
 * @see psm_load_lang()
 */
function psm_get_lang() {
	$args = func_get_args();

	if (empty($args)) return $GLOBALS['sm_lang'];

	$result = null;
	$resultDefault = null;
	$node = null;
	$nodeDefault = null;

	if ($args) {
		$node = '$GLOBALS[\'sm_lang\'][\'' . implode('\'][\'', $args) . '\']';
		$nodeDefault = '$GLOBALS[\'sm_lang_default\'][\'' . implode('\'][\'', $args) . '\']';
	}

	eval('if (isset(' . $node . ')) $result = ' . $node . ';');
	eval('if (isset(' . $nodeDefault . ')) $resultDefault = ' . $nodeDefault . ';');

	if (empty($result)) {
		return $resultDefault;
	} else {
		return $result;
	}
}

/**
 * Load language from the language file to the $GLOBALS['sm_lang'] variable
 *
 * @param string $lang language
 * @see psm_get_lang()
 */
function psm_load_lang($lang) {
	// if not in the language translation must always be available starting translation - English
	$default_lang_file = PSM_PATH_LANG . 'en_US.lang.php';

	if (file_exists($default_lang_file)) {
		require $default_lang_file;

		if (isset($sm_lang)) {
			$GLOBALS['sm_lang_default'] = $sm_lang;
			unset($sm_lang);
		}
	}

	// translated language
	$lang_file = PSM_PATH_LANG . $lang . '.lang.php';

	if (!file_exists($lang_file)) {
		// If the file has been removed, we use the english one
		$en_file = PSM_PATH_LANG . 'en_US.lang.php';
		if (!file_exists($en_file)) {
			// OK, nothing we can do
			die('unable to load language file: ' . $lang_file);
		}
		$lang_file = $en_file;
	}

	require $lang_file;
	if (isset($sm_lang['locale'])) {
		setlocale(LC_TIME, $sm_lang['locale']);
	}

	$GLOBALS['sm_lang'] = $sm_lang;
}

/**
 * Retrieve a list with keys of the available languages
 *
 * @return array
 * @see psm_load_lang()
 */
function psm_get_langs() {
	$fn_ext = '.lang.php';
	$lang_files = glob(PSM_PATH_LANG . '*' . $fn_ext);
	$langs = array();

	foreach($lang_files as $file) {
		$key = str_replace($fn_ext, '', basename($file));
		$sm_lang = array();
		if(file_exists($file)) {
			require $file;
		}
		if(isset($sm_lang['name'])) {
			$name = $sm_lang['name'];
		} else {
			$name = $key;
		}
		$langs[$key] = $name;
		unset($sm_lang);
	}
	ksort($langs);
	return $langs;
}

/**
 * Get a setting from the config.
 *
 * @param string $key
 * @param mixed $alt if not set, return this alternative
 * @return string
 * @see psm_load_conf()
 */
function psm_get_conf($key, $alt = null) {
	if(!isset($GLOBALS['sm_config'])) {
		psm_load_conf();
	}
	$result = (isset($GLOBALS['sm_config'][$key])) ? $GLOBALS['sm_config'][$key] : $alt;

	return $result;
}

/**
 * Load config from the database to the $GLOBALS['sm_config'] variable
 *
 * @global object $db
 * @return boolean
 * @see psm_get_conf()
 */
function psm_load_conf() {
	global $db;

	$GLOBALS['sm_config'] = array();

	if(!defined('PSM_DB_PREFIX') || !$db->status()) {
		return false;
	}
	if(!$db->ifTableExists(PSM_DB_PREFIX.'config')) {
		return false;
	}
	$config_db = $db->select(PSM_DB_PREFIX . 'config', null, array('key', 'value'));

	if(is_array($config_db) && !empty($config_db)) {
		foreach($config_db as $setting) {
			$GLOBALS['sm_config'][$setting['key']] = $setting['value'];
		}
		return true;
	} else {
		return false;
	}
}

/**
 * Update a config setting.
 *
 * If the key does not exist yet it will be created.
 * @global \psm\Service\Database $db
 * @param string $key
 * @param string $value
 */
function psm_update_conf($key, $value) {
	global $db;

	// check if key exists
	$exists = psm_get_conf($key, false);
	if($exists === false) {
		// add new config record
		$db->save(
			PSM_DB_PREFIX . 'config',
			array(
				'key' => $key,
				'value' => $value,
			)
		);
	} else {
		$db->save(
			PSM_DB_PREFIX.'config',
			array('value' => $value),
			array('key' => $key)
		);
	}
	$GLOBALS['sm_config'][$key] = $value;
}

###############################################
#
# 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 $type
 * @param string $message
 *
 * @return int log_id
 */
function psm_add_log($server_id, $type, $message) {
	global $db;

	return $db->save(
		PSM_DB_PREFIX.'log',
		array(
			'server_id' => $server_id,
			'type' => $type,
			'message' => $message,
		)
	);
}

/**
 * This function just adds a user to the log_users table.
 *
 * @param $log_id
 * @param $user_id
 */
function psm_add_log_user($log_id, $user_id) {
	global $db;

    $db->save(
        PSM_DB_PREFIX . 'log_users',
        array(
            'log_id'  => $log_id,
            'user_id' => $user_id,
        )
    );
}

/**
 * This function adds the result of a check to the uptime table for logging purposes.
 *
 * @param int $server_id
 * @param int $status
 * @param string $latency
 */
function psm_log_uptime($server_id, $status, $latency) {
	global $db;

	$db->save(
		PSM_DB_PREFIX.'servers_uptime',
		array(
			'server_id' => $server_id,
			'date' => date('Y-m-d H:i:s'),
			'status' => $status,
			'latency' => $latency,
		)
	);
}

/**
 * Parses a string from the language file with the correct variables replaced in the message
 *
 * @param boolean $status
 * @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 psm_parse_msg($status, $type, $vars) {
	$status = ($status == true) ? 'on' : 'off';

	$message = psm_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;
}

/**
 * Shortcut to curl_init(), curl_exec and curl_close()
 *
 * @param string $href
 * @param boolean $header return headers?
 * @param boolean $body return body?
 * @param int $timeout connection timeout in seconds. defaults to PSM_CURL_TIMEOUT (10 secs).
 * @param boolean $add_agent add user agent?
 * @param string|bool $website_username Username website
 * @param string|bool $website_password Password website
 * @return string cURL result
 */
function psm_curl_get($href, $header = false, $body = true, $timeout = null, $add_agent = true, $website_username = false, $website_password = false) {
	$timeout = $timeout == null ? PSM_CURL_TIMEOUT : intval($timeout);

	$ch = curl_init();
	curl_setopt($ch, CURLOPT_HEADER, $header);
	curl_setopt($ch, CURLOPT_NOBODY, (!$body));
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
	curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
	curl_setopt($ch, CURLOPT_ENCODING, '');

    if($website_username !== false && $website_password !== false && !empty($website_username) && !empty($website_password)) {
        curl_setopt($ch, CURLOPT_USERPWD, $website_username . ":" . $website_password);
    }

	curl_setopt($ch, CURLOPT_URL, $href);

	$proxy_url = psm_get_conf('proxy_url','');
	if (psm_get_conf('proxy','0') === '1') {
		curl_setopt($ch, CURLOPT_PROXY, $proxy_url);
		$proxy_user = psm_get_conf('proxy_user','');
		$proxy_password = psm_get_conf('proxy_password','');
		if (!empty($proxy_user) && !empty($proxy_password)) {
			curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy_user . ':' . $proxy_password);
		}
	}

	if($add_agent) {
		curl_setopt ($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; phpservermon/'.PSM_VERSION.'; +http://www.phpservermonitor.org)');
	}

	$result = curl_exec($ch);
	curl_close($ch);

	return $result;
}

/**
 * Get a "nice" timespan message
 *
 * Source: http://www.interactivetools.com/forum/forum-posts.php?postNum=2208966
 * @param string $time
 * @return string
 */
function psm_timespan($time) {
	if(empty($time) || $time == '0000-00-00 00:00:00') {
		return psm_get_lang('system', 'never');
	}
	if ($time !== intval($time)) { $time = strtotime($time); }
	if ($time < strtotime(date('Y-m-d 00:00:00')) - 60*60*24*3) {
		$format = psm_get_lang('system', (date('Y') !== date('Y', $time)) ? 'long_day_format' : 'short_day_format');
		// Check for Windows to find and replace the %e
		// modifier correctly
		if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
			$format = preg_replace('#(?<!%)((?:%%)*)%e#', '\1%#d', $format);
		}
		return strftime($format, $time);
	}
	$d = time() - $time;
	if ($d >= 60*60*24) {
		$format = psm_get_lang('system', (date('l', time() - 60*60*24) == date('l', $time)) ? 'yesterday_format' : 'other_day_format');
		return strftime($format, $time);
	}
	if ($d >= 60*60*2) { return sprintf(psm_get_lang('system', 'hours_ago'), intval($d / (60*60))); }
	if ($d >= 60*60) { return psm_get_lang('system', 'an_hour_ago'); }
	if ($d >= 60*2) { return sprintf(psm_get_lang('system', 'minutes_ago'), intval($d / 60)); }
	if ($d >= 60) { return psm_get_lang('system', 'a_minute_ago'); }
	if ($d >= 2) { return sprintf(psm_get_lang('system', 'seconds_ago'), intval($d));intval($d); }

	return psm_get_lang('system', 'a_second_ago');
}

/**
 * Get a localised date from MySQL date format
 * @param string $time
 * @return string
 */
function psm_date($time) {
	if(empty($time) || $time == '0000-00-00 00:00:00') {
		return psm_get_lang('system', 'never');
	}
	return strftime('%x %X', strtotime($time));
}

/**
 * Check if an update is available for PHP Server Monitor.
 *
 * Will only check for new version if user turned updates on in config.
 * @return boolean
 */
function psm_update_available() {
	if(!psm_get_conf('show_update')) {
		// user does not want updates, fair enough.
		return false;
	}

	$last_update = psm_get_conf('last_update_check');

	if((time() - PSM_UPDATE_INTERVAL) > $last_update) {
		// been more than a week since update, lets go
		// update last check date
		psm_update_conf('last_update_check', time());
		$latest = psm_curl_get(PSM_UPDATE_URL);
		// add latest version to database
		if($latest !== false && strlen($latest) < 15) {
			psm_update_conf('version_update_check', $latest);
		}
	} else {
		$latest = psm_get_conf('version_update_check');
	}

	if($latest != false) {
		$current = psm_get_conf('version');
		return version_compare($latest, $current, '>');
	} else {
		return false;
	}
}

/**
 * Prepare a new phpmailer instance.
 *
 * If the from name and email are left blank they will be prefilled from the config.
 * @param string $from_name
 * @param string $from_email
 * @return \PHPMailer
 */
function psm_build_mail($from_name = null, $from_email = null) {
	$phpmailer = new \PHPMailer();
	$phpmailer->Encoding = "base64";
	$phpmailer->CharSet = 'UTF-8';
	$phpmailer->SMTPDebug = false;

	if(psm_get_conf('email_smtp') == '1') {
		$phpmailer->IsSMTP();
		$phpmailer->Host = psm_get_conf('email_smtp_host');
		$phpmailer->Port = psm_get_conf('email_smtp_port');
		$phpmailer->SMTPSecure = psm_get_conf('email_smtp_security');

		$smtp_user = psm_get_conf('email_smtp_username');
		$smtp_pass = psm_get_conf('email_smtp_password');

		if($smtp_user != '' && $smtp_pass != '') {
			$phpmailer->SMTPAuth = true;
			$phpmailer->Username = $smtp_user;
			$phpmailer->Password = $smtp_pass;
		}
	} else {
		$phpmailer->IsMail();
	}
	if($from_name == null) {
		$from_name = psm_get_conf('email_from_name');
	}
	if($from_email == null) {
		$from_email = psm_get_conf('email_from_email');
	}
	$phpmailer->SetFrom($from_email, $from_name);

	return $phpmailer;
}

/**
 * Prepare a new Pushover util.
 *
 * @return \Pushover
 */
function psm_build_pushover() {
	$pushover = new \Pushover();
	$pushover->setToken(psm_get_conf('pushover_api_token'));

	return $pushover;
}

/**
 * Prepare a new SMS util.
 *
 * @return \psm\Txtmsg\TxtmsgInterface
 */
function psm_build_sms() {
	$sms = null;

	// open the right class
	// not making this any more dynamic, because perhaps some gateways need custom settings (like Mollie)
	switch(strtolower(psm_get_conf('sms_gateway'))) {
		case 'mosms':
			$sms = new \psm\Txtmsg\Mosms();
			break;
		case 'smsit':
			$sms = new \psm\Txtmsg\Smsit();
			break;
		case 'inetworx':
			$sms = new \psm\Txtmsg\Inetworx();
			break;
		case 'mollie':
			$sms = new \psm\Txtmsg\Mollie();
			$sms->setGateway(1);
			break;
		case 'spryng':
			$sms = new \psm\Txtmsg\Spryng();
			break;
		case 'clickatell':
			$sms = new \psm\Txtmsg\Clickatell();
			break;
		case 'textmarketer':
			$sms = new \psm\Txtmsg\Textmarketer();
			break;
		case 'smsglobal':
			$sms = new \psm\Txtmsg\Smsglobal();
			break;
		case 'freevoipdeal':
			$sms = new \psm\Txtmsg\FreeVoipDeal();
			break;
		case 'nexmo':
			$sms = new \psm\Txtmsg\Nexmo();
			break;
		case 'freemobilesms':
			$sms = new \psm\Txtmsg\FreeMobileSMS();
			break;
		case 'clicksend':
			$sms = new \psm\Txtmsg\ClickSend();
			break;
		case 'octopush':
			$sms = new \psm\Txtmsg\Octopush();
			break;
		case 'smsgw':
			$sms = new \psm\Txtmsg\Smsgw();
			break;
	}

	// copy login information from the config file
	if($sms) {
		$sms->setLogin(psm_get_conf('sms_gateway_username'), psm_get_conf('sms_gateway_password'));
		$sms->setOriginator(psm_get_conf('sms_from'));
	}

	return $sms;
}

/**
 * Generate a new link to the current monitor
 * @param array|string $params key value pairs or pre-formatted string
 * @param boolean $urlencode urlencode all params?
 * @param boolean $htmlentities use entities in url?
 * @return string
 */
function psm_build_url($params = array(), $urlencode = true, $htmlentities = true) {
	if(defined('PSM_BASE_URL') && PSM_BASE_URL !== null) {
		$url = PSM_BASE_URL;
	} else {
		$url = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443 ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
		// on Windows, dirname() adds both back- and forward slashes (http://php.net/dirname).
		// for urls, we only want the forward slashes.
		$url .= dirname($_SERVER['SCRIPT_NAME']);
		$url = str_replace('\\', '', $url);
	}
	$url = rtrim($url, '/') . '/';

	if($params != null) {
		$url .= '?';
		if(is_array($params)) {
			$delim = ($htmlentities) ? '&amp;' : '&';

			foreach($params as $k => $v) {
				if($urlencode) {
					$v = urlencode($v);
				}
				$url .= $delim . $k . '=' . $v;
			}
		} else {
			$url .= $params;
		}
	}

	return $url;
}

/**
 * Try existence of a GET var, if not return the alternative
 * @param string $key
 * @param string $alt
 * @return mixed
 */
function psm_GET($key, $alt = null) {
	if(isset($_GET[$key])) {
		return $_GET[$key];
	} else {
		return $alt;
	}
}

/**
 * Try existence of a POST var, if not return the alternative
 * @param string $key
 * @param string $alt
 * @return mixed
 */
function psm_POST($key, $alt = null) {
	if(isset($_POST[$key])) {
		return $_POST[$key];
	} else {
		return $alt;
	}
}

/**
 * Check if we are in CLI mode
 *
 * Note, php_sapi cannot be used because cgi-fcgi returns both for web and cli.
 * @return boolean
 */
function psm_is_cli() {
	return (!isset($_SERVER['SERVER_SOFTWARE']) || php_sapi_name() == 'cli');
}

###############################################
#
# Debug functions
#
###############################################

/**
 * Only used for debugging and testing
 *
 * @param mixed $arr
 */
function pre($arr = null) {
	echo "<pre>";
	if ($arr === null) {
		debug_print_backtrace();
	}
	print_r($arr);
	echo "</pre>";
}

/**
 * Send headers to the browser to avoid caching
 */
function psm_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");
}

/**
 * Encrypts the password for storage in the database
 *
 * @param string $key
 * @param string $password
 * @return string
 * @author Pavel Laupe Dvorak <pavel@pavel-dvorak.cz>
 */
function psm_password_encrypt($key, $password)
{
    if(empty($password))
        return '';

    if (empty($key))
        throw new \InvalidArgumentException('invalid_encryption_key');

    $iv = mcrypt_create_iv(
		mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC),
		MCRYPT_DEV_URANDOM
	);

	$encrypted = base64_encode(
		$iv .
		mcrypt_encrypt(
			MCRYPT_RIJNDAEL_128,
			hash('sha256',  $key, true),
			$password,
			MCRYPT_MODE_CBC,
			$iv
		)
	);

	return $encrypted;
}

/**
 * Decrypts password stored in the database for future use
 *
 * @param string $key
 * @param string $encryptedString
 * @return string
 * @author Pavel Laupe Dvorak <pavel@pavel-dvorak.cz>
 */
function psm_password_decrypt($key, $encryptedString)
{
	if(empty($encryptedString))
		return '';

	if (empty($key))
         throw new \InvalidArgumentException('invalid_encryption_key');
	
	$data = base64_decode($encryptedString);
	$iv = substr($data, 0, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));

	$decrypted = rtrim(
		mcrypt_decrypt(
			MCRYPT_RIJNDAEL_128,
			hash('sha256',  $key, true),
			substr($data, mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC)),
			MCRYPT_MODE_CBC,
			$iv
		),
		"\0"
	);

	return $decrypted;
}