<?php
// $Header: /cvsroot/phptsmadmin/phpTSMadmin/lib/functions.php,v 1.37 2009/04/19 04:03:05 wurley Exp $

/**
 * A collection of common generic functions used throughout the application.
 *
 * @author Deon George
 * @package phpTSMadmin
 */

/**
 * @package phpTSMadmin
 * @subpackage Functions
 */

define('HTDOCDIR',sprintf('%s/',realpath(LIBDIR.'../htdocs/')));
define('LANGDIR',sprintf('%s/',realpath(LIBDIR.'../locale/')));
define('CONFDIR',sprintf('%s/',realpath(LIBDIR.'../config')));
define('HOOKSDIR',sprintf('%s/',realpath(LIBDIR.'../hooks/')));
define('CSSDIR','css/');
define('IMGDIR','images/');
define('JSDIR','js/');

/**
 * Supplimental functions
 * This list is a list of supplimental functions that are used throughout
 * this application. The order here IS important - so that files that refer to
 * functions defined in other files need to be listed after those files.
 */
$app['function_files'] = array(
	LIBDIR.'session_functions.php',
	# Datasource functions
	LIBDIR.'ds.php',
	LIBDIR.'tsm_classes.php',
	# Functions for rendering the page
	LIBDIR.'page.php'
);

if (file_exists(LIBDIR.'functions.custom.php'))
	array_push($app['function_files'],LIBDIR.'functions.custom.php');

/**
 * Loads class definition
 */
function __autoload($className) {
	if (defined('DEBUG_ENABLED') && DEBUG_ENABLED)
		debug_log('Call to autoload (%s)',1,__FILE__,__LINE__,__METHOD__,$className);

	if (file_exists(HOOKSDIR."classes/$className.php"))
		require_once(HOOKSDIR."classes/$className.php");
	elseif (file_exists(LIBDIR."$className.php"))
		require_once(LIBDIR."$className.php");
	elseif (file_exists(LIBDIR."ds_$className.php"))
		require_once(LIBDIR."ds_$className.php");
	else
		system_message(array(
			'title'=>_('Generic Error'),
			'body'=>sprintf('%s: %s [%s]',
				__METHOD__,_('Called to load a class that cant be found'),$className),
			'type'=>'error'));
}

/**
 * Strips all slashes from the specified array in place (pass by ref).
 * @param Array The array to strip slashes from, typically one of
 *                     $_GET, $_POST, or $_COOKIE.
 */
function array_stripslashes(&$array) {
	if (DEBUG_ENABLED)
		debug_log('Entered with (%s)',1,__FILE__,__LINE__,__METHOD__,$array);

	if (is_array($array))
		while (list($key) = each($array))
			if (is_array($array[$key]) && $key != $array)
				array_stripslashes($array[$key]);
			else
				$array[$key] = stripslashes($array[$key]);
}

/**
 * Compatibility Functions
 * These functions exist, so that a standard function can be used in new applications, and they
 * map to already defined functions in older applications.
 */

/**
 * If gettext is not available in PHP, then this will provide compatibility for it.
 */
if (! function_exists('_')) {
	function _($msg) {
		return $msg;
	}
}

/**
 * Generic Utility Functions
 */

/**
 * Custom error handling function.
 * When a PHP error occurs, PHP will call this function rather than printing
 * the typical PHP error string. This provides the application the ability to
 * format an error message so that it looks better.
 * Optionally, it can present a link so that a user can search/submit bugs.
 * This function is not to be called directly. It is exclusively for the use of
 * PHP internally. If this function is called by PHP from within a context
 * where error handling has been disabled (ie, from within a function called
 * with "@" prepended), then this function does nothing.
 *
 * @param int The PHP error number that occurred (ie, E_ERROR, E_WARNING, E_PARSE, etc).
 * @param string The PHP error string provided (ie, "Warning index "foo" is undefined)
 * @param string The file in which the PHP error ocurred.
 * @param int The line number on which the PHP error ocurred
 * @see set_error_handler
 */
function app_error_handler($errno,$errstr,$file,$lineno) {
	if (defined('DEBUG_ENABLED') && DEBUG_ENABLED)
		debug_log('Entered with (%s,%s,%s,%s)',1,__FILE__,__LINE__,__METHOD__,
			$errno,$errstr,$file,$lineno);

	/**
	 * error_reporting will be 0 if the error context occurred
	 * within a function call with '@' preprended (ie, @ldap_bind() );
	 * So, don't report errors if the caller has specifically
	 * disabled them with '@'
	 */
	if (ini_get('error_reporting') == 0 || error_reporting() == 0)
		return;

	$file = basename($file);
	$caller = basename($_SERVER['PHP_SELF']);
	$errtype = '';

	switch ($errno) {
		case E_STRICT: $errtype = 'E_STRICT'; break;
		case E_ERROR: $errtype = 'E_ERROR'; break;
		case E_WARNING: $errtype = 'E_WARNING'; break;
		case E_PARSE: $errtype = 'E_PARSE'; break;
		case E_NOTICE: $errtype = 'E_NOTICE'; break;
		case E_CORE_ERROR: $errtype = 'E_CORE_ERROR'; break;
		case E_CORE_WARNING: $errtype = 'E_CORE_WARNING'; break;
		case E_COMPILE_ERROR: $errtype = 'E_COMPILE_ERROR'; break;
		case E_COMPILE_WARNING: $errtype = 'E_COMPILE_WARNING'; break;
		case E_USER_ERROR: $errtype = 'E_USER_ERROR'; break;
		case E_USER_WARNING: $errtype = 'E_USER_WARNING'; break;
		case E_USER_NOTICE: $errtype = 'E_USER_NOTICE'; break;
		case E_ALL: $errtype = 'E_ALL'; break;

		default: $errtype = sprintf('%s: %s',_('Unrecognized error number'),$errno);
	}

	# Take out extra spaces in error strings.
	$errstr = preg_replace('/\s+/',' ',$errstr);

	if ($errno == E_NOTICE) {
		$body = '<table class="notice">';
		$body .= sprintf('<tr><td>%s:</td><td><b>%s</b> (<b>%s</b>)</td></tr>',_('Error'),$errstr,$errtype);
		$body .= sprintf('<tr><td>%s:</td><td><b>%s</b> %s <b>%s</b>, %s <b>%s</b></td></tr>',
			_('File'),$file,_('line'),$lineno,_('caller'),$caller);
		$body .= sprintf('<tr><td>Versions:</td><td>APP: <b>%s</b>, PHP: <b>%s</b>, SAPI: <b>%s</b></td></tr>',
			app_version(),phpversion(),php_sapi_name());
		$body .= sprintf('<tr><td>Web server:</td><td><b>%s</b></td></tr>',isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : 'SCRIPT');

		if (function_exists('get_href'))
			$body .= sprintf('<tr><td colspan="2"><a href="%s" target="_blank"><center>%s.</center></a></td></tr>',
				get_href('search_bug',"&summary_keyword=".rawurlencode($errstr)),
				_('Please check and see if this bug has been reported'));
		$body .= '</table>';

		system_message(array(
			'title'=>_('You found a non-fatal application bug!'),
			'body'=>$body,
			'type'=>'error'));

		return;
	}

	# If this is a more serious error, call the error call.
	error(sprintf('%s: %s',$errtype,$errstr),'error',null,true,true);
}

/**
 * Returns the application name.
 */
function app_name() {
	if (isset($_SESSION[APPCONFIG]))
		return $_SESSION[APPCONFIG]->getValue('app','name',false);
	else
		return 'Application name NOT set';
}

/**
 * Returns the application version currently running. The version
 * is read from the file named VERSION.
 *
 * @return string The current version as read from the VERSION file.
 */
function app_version() {
	static $CACHE = null;

	if ($CACHE)
		return $CACHE;

	$version_file = realpath(LIBDIR.'../VERSION');
	if (! file_exists($version_file))
		$CACHE = 'UNKNOWN';

	else {
		$f = fopen($version_file,'r');
		$version = trim(fread($f, filesize($version_file)));
		fclose($f);

		# We use cvs_prefix, because CVS will translate this on checkout otherwise.
		$cvs_prefix = '\$Name:';

		$CACHE = preg_replace('/^'.$cvs_prefix.' RELEASE-([0-9_]+)\s*\$$/','$1',$version);
		$CACHE = preg_replace('/_/','.',$CACHE);

		# Check if we are a CVS copy.
		if (preg_match('/^'.$cvs_prefix.'?\s*\$$/',$CACHE))
			$CACHE = 'CVS';

		# Check if we are special CVS branch
		elseif (preg_match('/^'.$cvs_prefix.'?\s*([a-zA-Z]+)?\s*\$$/',$CACHE,$match))
			$CACHE = $match[1];

		# If return is still the same as version, then the tag is not one we expect.
		elseif ($CACHE == $version)
			$CACHE = 'UNKNOWN';
	}

	if (defined('DEBUG_ENABLED') && DEBUG_ENABLED)
		debug_log('Entered with (), Returning (%s)',1,__FILE__,__LINE__,__METHOD__,$CACHE);

	return $CACHE;
}

/**
 * This function will convert the browser two character language into the
 * default 5 character language, where the country portion should NOT be
 * assumed to be upper case characters of the first two characters.
 */
function auto_lang($lang) {
	switch ($lang) {
		case 'ja': return 'ja_JP';
		case 'cs': return 'cs_CZ';
		default: return sprintf('%s_%s',$lang,strtoupper($lang));
	}
}

/**
 * Makes sure that the config file is properly setup.
 */
function check_config($config_file) {
	# Read in config_default.php
	require_once LIBDIR.'config_default.php';

	# Make sure their PHP version is current enough
	if (strcmp(phpversion(),REQUIRED_PHP_VERSION) < 0)
		system_message(array(
			'title'=>_('Incorrect version of PHP'),
			'body'=>sprintf('This application requires PHP version %s or greater.<br /><small>(You are using %s)</small>',
				REQUIRED_PHP_VERSION,phpversion()),
			'type'=>'error'));

	$config = new Config;

	if (file_exists(LIBDIR.'config_custom.php') && is_readable(LIBDIR.'config_custom.php'))
		include LIBDIR.'config_custom.php';

	ob_start();
	require $config_file;
	$str = '';
	if (ob_get_level()) {
		$str = ob_get_contents();
		ob_end_clean();
	}

	if ($str) {
		$str = strip_tags($str);
		$matches = array();
		preg_match('/(.*):\s+(.*):.*\s+on line (\d+)/',$str,$matches);

		if (isset($matches[1]) && isset($matches[2]) && isset($matches[3])) {
			$error_type = $matches[1];
			$error = $matches[2];
			$line_num = $matches[3];

			$file = file($config_file);

			$body = '<h3 class="title">Config file ERROR</h3>';
			$body .= sprintf('<h3 class="subtitle">%s (%s) on line %s</h3>',$error_type,$error,$line_num);

			$body .= '<center>';
			$body .= sprintf('Looks like your config file has an ERROR on line %s.<br />',$line_num);
			$body .= 'Here is a snippet around that line <br />';
			$body .= '<br />'."\n";

			$body .= '<div style="text-align: left; font-family: monospace; margin-left: 80px; margin-right: 80px; border: 1px solid black; padding: 10px;">';

			for ($i = $line_num-9; $i<$line_num+5; $i++) {
				if ($i+1 == $line_num)
					$body .= '<div style="color:red;background:#fdd">';

				if ($i < 0)
					continue;

				$body .= sprintf('<b>%s</b>: %s<br />',$i+1,$file[$i]);

				if ($i+1 == $line_num)
					$body .= '</div>';
			}

			$body .= '</div>';
			$body .= '<br />';
			$body .= 'Hint: Sometimes these errors are caused by lines <b>preceding</b> the line reported.';
			$body .= '</center>';

			$block = new block();
			$block->SetBody($body);
			$www['page'] = new page();
			$www['page']->block_add('body',$block);
			$www['page']->display();

			die();
		}
	}

	# Process our friendly attributes.
	if (isset($friendly_attrs))
		$config->getFriendlyAttrs($friendly_attrs);

	# Check for server definitions.
	if (! isset($servers) || count($servers->GetServerList()) == 0)
		error(_('Your config.php is missing Server Definitions. Please see the sample file config/config.php.example.'),'error','index.php',true);

	$config->setServers($servers);

	# Check the memory limit parameter.
	if (ini_get('memory_limit') < $config->getValue('session','memorylimit'))
		system_message(array(
			'title'=>_('Memory Limit low.'),
			'body'=> sprintf('Your php memory limit is low - currently %s, you should increase it to atleast %s. This is normally controlled in /etc/php.ini.',ini_get('memory_limit'),$config->getValue('session','memorylimit')),
			'type'=>'error'));

	return $config;
}

/**
 * Commands available in the control_panel of the page
 *
 * @return array
 */
function cmd_control_pane($type) {
	switch ($type) {
		case 'main' :
			return array(
				'home'=>array(
					'title'=>_('Home'),
					'link'=>sprintf('href="index.php" title="%s"',_('Home')),
					'image'=>sprintf('<img src="%s/home-big.png" alt="%s" />',IMGDIR,_('Home'))),

				'purge'=>array(
					'title'=>_('Purge caches'),
					'link'=>sprintf('href="cmd.php?cmd=purge_cache" onclick="return displayAJ(\'BODY\',\'cmd=purge_cache\',\'%s\');" title="%s"',
						_('Clearing cache'),_('Purge caches')),
					'image'=>sprintf('<img src="%s/trash-big.png" alt="%s" />',IMGDIR,_('Purge caches'))),

				'appearance:hide_debug_info'=>array(
					'title'=>_('Show Cache'),
					'link'=>sprintf('href="cmd.php?cmd=show_cache" onclick="return displayAJ(\'BODY\',\'cmd=show_cache\',\'%s\');" title="%s"',
						_('Loading'),_('Show Cache'),_('Show Cache')),
					'image'=>sprintf('<img src="%s/debug-cache.png" alt="%s" />',IMGDIR,_('Show Cache'))),
			);
			break;

		case 'top' :
			return array();
			break;
	}
}

/**
 * This function dumps the $variable for debugging purposes
 *
 * @param string|array Variable to dump
 * @param boolean Whether to stop execution or not.
 */
function debug_dump($variable,$die=false,$onlydebugaddr=false) {
	if ($onlydebugaddr &&
		isset($_SESSION[APPCONFIG]) && $_SESSION[APPCONFIG]->getValue('debug','addr') &&
		$_SERVER['HTTP_X_FORWARDED_FOR'] != $_SESSION[APPCONFIG]->getValue('debug','addr') &&
		$_SERVER['REMOTE_ADDR'] != $_SESSION[APPCONFIG]->getValue('debug','addr'))
		return;

	$backtrace = debug_backtrace();
	$caller['class'] = isset($backtrace[0]['class']) ? $backtrace[0]['class'] : 'N/A';
	$caller['function'] = isset($backtrace[0]['function']) ? $backtrace[0]['function'] : 'N/A';
	$caller['file'] = isset($backtrace[0]['file']) ? $backtrace[0]['file'] : 'N/A';
	$caller['line'] = isset($backtrace[0]['line']) ? $backtrace[0]['line'] : 'N/A';
	$caller['debug'] = $variable;

	print '<PRE>';
	print_r($caller);
	print '</PRE>';

	if ($die)
		die();
}

/**
 * This function generates a backtrace
 *
 * @param boolean Whether to stop execution or not.
 */
function debug_dump_backtrace($msg='Calling BackTrace',$die=false) {
	error($msg,'note',null,$die,true);
}

/**
 * Send a debug as a sys message
 */
function debug_sysmsg($msg) {
	system_message(array('title'=>_('Debug'),'body'=>$msg,'type'=>'debug'));
}

/**
 * Debug Logging to Syslog
 *
 * The global debug level is turned on in your configuration file by setting:
 * <code>
 *	$config->custom->debug['level'] = 255;
 * </code>
 * together with atleast one output direction (currently file and syslog are supported).
 * <code>
 *	$config->custom->debug['file'] = '/tmp/app_debug.log';
 *	$config->custom->debug['syslog'] = true;
 * </code>
 *
 * The debug level is turned into binary, then if the message levels bit is on
 * the message will be sent to the debug log. (Thus setting your debug level to 255,
 * all bits on, will results in all messages being printed.)
 *
 * The message level bits are defined here.
 *  0(  1) = Entry/Return results from function calls.
 *  1(  2) = Configuration Processing
 *  2(  4) = Template Processing
 *  3(  8) = Schema Processing
 *  4( 16) = Server Communication
 *  5( 32) = Menu Processing
 *  7( 64) = Other non generic messages
 *  8(128) = Page Processing
 * @param string Message to send to syslog
 * @param int Log bit number for this message.
 * @see syslog.php
 */
function debug_log($msg,$level=0) {
	global $debug_file;

	# Temporary, to catch when these are not set in the function arguments.
	$file = __FILE__;
	$line = __LINE__;
	$method = __METHOD__;

	# In case we are called before we are fully initialised or if debugging is not set.
	if (! isset($_SESSION[APPCONFIG]) || ! ($_SESSION[APPCONFIG]->getValue('debug','file')
		|| $_SESSION[APPCONFIG]->getValue('debug','syslog')))
		return false;

	$debug_level = $_SESSION[APPCONFIG]->getValue('debug','level');
	if (! $debug_level || (! ($level & $debug_level)))
		return;

	$debugaddr = false;
	if ($_SESSION[APPCONFIG]->getValue('debug','addr')) {
		if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] == $_SESSION[APPCONFIG]->getValue('debug','addr'))
			$debugaddr = true;

		elseif ($_SERVER['REMOTE_ADDR'] == $_SESSION[APPCONFIG]->getValue('debug','addr'))
			$debugaddr = true;
	} else $debugaddr = true;

	if (! $debugaddr)
		return;

	# If we are limiting debug to a browser, then check that
	$caller = basename( $_SERVER['PHP_SELF'] );

	if (func_num_args() > 2) {
		$args = func_get_args();
		array_shift($args);
		array_shift($args);

		# This is temporary, until we change all the debug_log statements.
		if (is_string($args[0]) && preg_match('/.php$/',$args[0])) {
			$file = array_shift($args);
			$line = array_shift($args);
			$method = array_shift($args);
		}

		$fargs = array();
		foreach ($args as $key) {
			if (is_array($key) || is_object($key))
				array_push($fargs,serialize($key));
			else
				array_push($fargs,$key);
		}
		$msg = vsprintf($msg, array_values($fargs));
	}

	if (function_exists('stopwatch'))
		$timer = stopwatch();
	else
		$timer = null;

	$debug_message = sprintf('[%2.3f] %3s-%s(%04s): %s: %s',$timer,$level,basename($file),$line,$method,substr($msg,0,200));

	if ($debug_file || $_SESSION[APPCONFIG]->getValue('debug','file')) {
		if (! $debug_file)
			$debug_file = fopen($_SESSION[APPCONFIG]->getValue('debug','file'),
				$_SESSION[APPCONFIG]->getValue('debug','append') ? 'a' : 'w');

		fwrite($debug_file,$debug_message."\n");
	}

	if ($_SESSION[APPCONFIG]->getValue('debug','syslog'))
		syslog_notice($debug_message);

	return syslog_notice( sprintf('%s(%s): %s',$caller,$level,$msg) );
}

/**
 * Display an error message in the system message panel of the page.
 */
function error($msg,$type='note',$redirect=null,$fatal=false,$backtrace=false) {
	global $www;
	static $counter;

	# Just a check to see that we are called right.
	if (! isset($www['page']) && ! $fatal)
		die("Function error called incorrectly [$msg]");

	# If the error is fatal, we'll need to stop here.
	if (! isset($www['page']))
		$www['page'] = new page();

	if ($fatal)
		$www['page']->setsysmsg(array('title'=>_('Error'),'body'=>$msg,'type'=>$type));
	else
		system_message(array('title'=>_('Error'),'body'=>$msg,'type'=>$type),$redirect);

	# Spin loop detection
	if ($counter++ > 20) {
		debug_dump('Spin loop detection.');
		debug_dump(array('msg'=>$msg,'session'=>$_SESSION['sysmsg'],'www'=>$www),1);
	}

	# Do we have a backtrace to display?
	if ($backtrace) {
		$backtraceblock = new block();
		$backtraceblock->SetTitle('PHP Debug Backtrace');

		$body = '<table class="result_table">';
		$body .= "\n";

		foreach (debug_backtrace() as $error => $line) {
			$_SESSION['backtrace'][$error]['file'] = isset($line['file']) ? $line['file'] : 'unknown';
			$_SESSION['backtrace'][$error]['line'] = isset($line['line']) ? $line['line'] : 'unknown';
			$body .= sprintf('<tr class="hightlight"><td colspan="2"><b><small>%s</small></b></td><td>%s (%s)</td></tr>',
				_('File'),isset($line['file']) ? $line['file'] : $last['file'],isset($line['line']) ? $line['line'] : '');

			$_SESSION['backtrace'][$error]['function'] = $line['function'];
			$body .= sprintf('<tr><td>&nbsp;</td><td><b><small>%s</small></b></td><td><small>%s',
				_('Function'),$line['function']);

			if (isset($line['args'])) {
				$display = strlen(serialize($line['args'])) < 50 ? serialize($line['args']) : substr(serialize($line['args']),0,50).'...<TRUNCATED>';
				$_SESSION['backtrace'][$error]['args'] = $line['args'];
				if (file_exists(LIBDIR.'../tools/unserialize.php'))
					$body .= sprintf('&nbsp;(<a href="%s?index=%s" target="backtrace">%s</a>)',
						'../tools/unserialize.php',$error,$display);
				else
					$body .= sprintf('&nbsp;(%s)',$display);
			}
			$body .= '</small></td></tr>';
			$body .= "\n";

			if (isset($line['file']))
				$last['file'] = $line['file'];
		}

		$body .= '</table>';
		$body .= "\n";
		$backtraceblock->SetBody($body);

		$www['page']->block_add('body',$backtraceblock);
	}

	if ($fatal) {
		$www['page']->display(array('menu'=>false));
		die();
	}
}

/**
 * Return the result of a form variable, with optional default
 *
 * @return The form GET/REQUEST/SESSION/POST variable value or its default
 */
function get_request($attr,$type='POST',$die=false,$default=null) {
	switch($type) {
		case 'GET':
			$value = isset($_GET[$attr]) ? (is_array($_GET[$attr]) ? $_GET[$attr] : rawurldecode($_GET[$attr])) : $default;
			break;

		case 'REQUEST':
			$value = isset($_REQUEST[$attr]) ? (is_array($_REQUEST[$attr]) ? $_REQUEST[$attr] : rawurldecode($_REQUEST[$attr])) : $default;
			break;

		case 'SESSION':
			$value = isset($_SESSION[$attr]) ? (is_array($_SESSION[$attr]) ? $_SESSION[$attr] : rawurldecode($_SESSION[$attr])) : $default;
			break;

		case 'POST':
		default:
			$value = isset($_POST[$attr]) ? (is_array($_POST[$attr]) ? $_POST[$attr] : rawurldecode($_POST[$attr])) : $default;
			break;
	}

	if ($die && is_null($value))
		system_message(array(
			'title'=>_('Generic Error'),
			'body'=>sprintf('%s: Called "%s" without "%s" using "%s"',
				basename($_SERVER['PHP_SELF']),get_request('cmd','REQUEST'),$attr,$type),
			'type'=>'error'),
			'index.php');

	return $value;
}

/**
 * Record a system message.
 * This function can be used as an alternative to generate a system message, if page hasnt yet been defined.
 */
function system_message($msg,$redirect=null) {
	if (! is_array($msg))
		return null;

	if (! isset($msg['title']) && ! isset($msg['body']))
		return null;

	if (! isset($msg['type']))
		$msg['type'] = 'info';

	if (! isset($_SESSION['sysmsg']) || ! is_array($_SESSION['sysmsg']))
		$_SESSION['sysmsg'] = array();

	# Try and detect if we are in a redirect loop
	if (get_request('redirect','GET') && $msg['type'] != 'debug') {
		foreach ($_SESSION['sysmsg'] as $detail) {
			if ($msg == $detail && ! isset($detail['special'])) {
				debug_dump($_SESSION['sysmsg']);
				debug_dump_backtrace('Redirect Loop Detected',true);
			}
		}
	}

	array_push($_SESSION['sysmsg'],$msg);

	if ($redirect) {
		if (preg_match('/\?/',$redirect))
			$redirect .= '&';
		else
			$redirect .= '?';
		$redirect .= 'redirect=true';

		# Check if we were an ajax request, and only render the ajax message
		if (get_request('meth','REQUEST') == 'ajax')
			$redirect .= '&meth=ajax';

		header("Location: $redirect");
		die();
	}
}

/**
 * Other Functions
 */

/**
 * Encryption using blowfish algorithm
 *
 * @param string Original data
 * @param string The secret
 * @return string The encrypted result
 * @author lem9 (taken from the phpMyAdmin source)
 */
function blowfish_encrypt($data,$secret=null) {
	if (DEBUG_ENABLED)
		debug_log('Entered with (%s,%s)',1,__FILE__,__LINE__,__METHOD__,
			$data,$secret);

	# If our secret is null or blank, get the default.
	if ($secret === null || ! trim($secret))
		$secret = $_SESSION[APPCONFIG]->getValue('session','blowfish');

	# If the secret isnt set, then just return the data.
	if (! trim($secret))
		return $data;

	if (file_exists(LIBDIR.'blowfish.php'))
		require_once LIBDIR.'blowfish.php';
	else
		return $data;

	$pma_cipher = new Horde_Cipher_blowfish;
	$encrypt = '';

	for ($i=0; $i<strlen($data); $i+=8) {
		$block = substr($data, $i, 8);

		if (strlen($block) < 8)
			$block = full_str_pad($block,8,"\0", 1);

		$encrypt .= $pma_cipher->encryptBlock($block, $secret);
	}
	return base64_encode($encrypt);
}

/**
 * Decryption using blowfish algorithm
 *
 * @param string Encrypted data
 * @param string The secret
 * @return string Original data
 * @author lem9 (taken from the phpMyAdmin source)
 */
function blowfish_decrypt($encdata,$secret=null) {
	if (DEBUG_ENABLED)
		debug_log('Entered with (%s,%s)',1,__FILE__,__LINE__,__METHOD__,
			$encdata,$secret);

	# This cache gives major speed up for stupid callers :)
	static $CACHE = array();

	if (isset($CACHE[$encdata]))
		return $CACHE[$encdata];

	# If our secret is null or blank, get the default.
	if ($secret === null || ! trim($secret))
		$secret = $_SESSION[APPCONFIG]->getValue('session','blowfish');

	# If the secret isnt set, then just return the data.
	if (! trim($secret))
		return $encdata;

	if (file_exists(LIBDIR.'blowfish.php'))
		require_once LIBDIR.'blowfish.php';
	else
		return $data;

	$pma_cipher = new Horde_Cipher_blowfish;
	$decrypt = '';
	$data = base64_decode($encdata);

	for ($i=0; $i<strlen($data); $i+=8)
		$decrypt .= $pma_cipher->decryptBlock(substr($data, $i, 8), $secret);

	$return = trim($decrypt);
	$CACHE[$encdata] = $return;
	return $return;
}

/**
 * String padding
 *
 * @param string Input string
 * @param integer Length of the result
 * @param string The filling string
 * @param integer Padding mode
 * @return string The padded string
 */
function full_str_pad($input,$pad_length,$pad_string='',$pad_type=0) {
	if (DEBUG_ENABLED)
		debug_log('Entered with (%s,%s,%s,%s)',1,__FILE__,__LINE__,__METHOD__,
			$input,$pad_length,$pad_string,$pad_type);

	$str = '';
	$length = $pad_length - strlen($input);

	if ($length > 0) { // str_repeat doesn't like negatives
		if ($pad_type == STR_PAD_RIGHT) { // STR_PAD_RIGHT == 1
			$str = $input.str_repeat($pad_string, $length);
		} elseif ($pad_type == STR_PAD_BOTH) { // STR_PAD_BOTH == 2
			$str = str_repeat($pad_string, floor($length/2));
			$str .= $input;
			$str .= str_repeat($pad_string, ceil($length/2));
		} else { // defaults to STR_PAD_LEFT == 0
			$str = str_repeat($pad_string, $length).$input;
		}

	} else { // if $length is negative or zero we don't need to do anything
		$str = $input;
	}
	return $str;
}

/**
 * Returns the cached array of application server resources.
 *
 * Note that internally, this function utilizes a two-layer cache,
 * one in memory using a static variable for multiple calls within
 * the same page load, and one in a session for multiple calls within
 * the same user session (spanning multiple page loads).
 *
 * @return Returns the cached attributed requested,
 *         or null if there is nothing cached..
 */
function get_cached_item($index,$item,$subitem='null') {
	# Set default return
	$return = null;

	# Check config to make sure session-based caching is enabled.
	if ($_SESSION[APPCONFIG]->getValue('cache',$item)) {

		global $CACHE;
		if (isset($CACHE[$index][$item][$subitem])) {
			if (DEBUG_ENABLED)
				debug_log('Returning MEMORY cached [%s] (%s)',1,__FILE__,__LINE__,__METHOD__,
					$item,$subitem);

			$return = $CACHE[$index][$item][$subitem];

		} elseif (isset($_SESSION['cache'][$index][$item][$subitem])) {
			if (DEBUG_ENABLED)
				debug_log('Returning SESSION cached [%s] (%s)',1,__FILE__,__LINE__,__METHOD__,
					$item,$subitem);

			$return = $_SESSION['cache'][$index][$item][$subitem];
			$CACHE[$index][$item][$subitem] = $return;
		}
	}

	if (DEBUG_ENABLED)
		debug_log('Entered with (%s,%s,%s), Returning (%s)',1,__FILE__,__LINE__,__METHOD__,
			$index,$item,$subitem,$return);

	return $return;
}

/**
 * Caches the specified $item for the specified $index.
 *
 * Returns true on success of false on failure.
 */
function set_cached_item($index,$item,$subitem='null',$data) {
	if (DEBUG_ENABLED)
		debug_log('Entered with (%s,%s,%s,%s)',1,__FILE__,__LINE__,__METHOD__,
			$index,$item,$subitem,$data);

	# Check config to make sure session-based caching is enabled.
	if ($_SESSION[APPCONFIG]->getValue('cache',$item)) {
		global $CACHE;

		$CACHE[$index][$item][$subitem] = $data;
		$_SESSION['cache'][$index][$item][$subitem] = $data;
		return true;

	} else
		return false;
}

/**
 * Deletes the cache for a specified $item for the specified $index
 */
function del_cached_item($index,$item,$subitem='null') {
	global $CACHE;

	if (DEBUG_ENABLED)
		debug_log('Entered with (%s,%s,%s)',1,__FILE__,__LINE__,__METHOD__,
			$index,$item,$subitem);

	# Check config to make sure session-based caching is enabled.
	if (isset($_SESSION['cache'][$index][$item][$subitem]))
		unset($_SESSION['cache'][$index][$item][$subitem]);

	if (isset($CACHE[$index][$item][$subitem]))
		unset($CACHE[$index][$item][$subitem]);
}

/**
 * Utility wrapper for setting cookies, which takes into consideration
 * application configuration values. On success, true is returned. On
 * failure, false is returned.
 *
 * @param string The name of the cookie to set.
 * @param string The value of the cookie to set.
 * @param int (optional) The duration in seconds of this cookie. If unspecified, $cookie_time is used from config.php
 * @param string (optional) The directory value of this cookie (see php.net/setcookie)
 * @return boolean
 */
function set_cookie($name,$val,$expire=null,$dir=null) {
	# Set default return
	$return = false;

	if ($expire == null) {
		$cookie_time = $_SESSION[APPCONFIG]->getValue('session','cookie_time');
		$expire = $cookie_time == 0 ? null : time() + $cookie_time;
	}

	if ($dir == null)
		$dir = dirname($_SERVER['PHP_SELF']);

	if (@setcookie($name,$val,$expire,$dir)) {
		$_COOKIE[$name] = $val;
		$return = true;
	}

	if (DEBUG_ENABLED)
		debug_log('Entered with (%s,%s,%s,%s), Returning (%s)',1,__FILE__,__LINE__,__METHOD__,
			$name,$val,$expire,$dir,$return);

	return $return;
}

/**
 * Get a customized file for a server
 * We don't need any caching, because it's done by PHP
 *
 * @param int The ID of the server
 * @param string The requested filename
 *
 * @return string The customized filename, if exists, or the standard one
 */
function get_custom_file($index,$filename,$path) {
	# Set default return
	$return = $path.$filename;
	$server = $_SESSION[APPCONFIG]->getServer($index);

	$custom = $server->getValue('custom','pages_prefix');
	if (! is_null($custom) && is_file(realpath($path.$custom.$filename)))
		$return = $path.$custom.$filename;

	if (DEBUG_ENABLED)
		debug_log('Entered with (%s,%s,%s), Returning (%s)',1,__FILE__,__LINE__,__METHOD__,
			$index,$filename,$path,$return);

	return $return;
}

/**
 * Sort a multi dimensional array.
 *
 * @param array Multi demension array passed by reference
 * @param string Comma delimited string of sort keys.
 * @param boolean Whether to reverse sort.
 * @return array Sorted multi demension array.
 */
function masort(&$data,$sortby,$rev=0) {
	if (DEBUG_ENABLED)
		debug_log('Entered with (%s,%s,%s)',1,__FILE__,__LINE__,__METHOD__,
			$data,$sortby,$rev);

	# if the array to sort is null or empty
	if (! $data) return;

	static $CACHE = array();

	if (empty($CACHE[$sortby])) {
		$code = "\$c=0;\n";
		foreach (split(',',$sortby) as $key) {
			$code .= "if (is_object(\$a) || is_object(\$b)) {\n";
			$code .= "	if (\$a->$key != \$b->$key)\n";

			if ($rev)
				$code .= "	return (\$a->$key < \$b->$key ? 1 : -1);\n";
			else
				$code .= "	return (\$a->$key > \$b->$key ? 1 : -1);\n";

			$code .= "} else {\n";

			$code .= "if ((! isset(\$a['$key'])) && (! isset(\$b['$key']))) return 0;\n";
			$code .= "if ((! isset(\$a['$key'])) && isset(\$b['$key'])) return -1;\n";
			$code .= "if (isset(\$a['$key']) && (! isset(\$b['$key']))) return 1;\n";


			$code .= "if (is_numeric(\$a['$key']) && is_numeric(\$b['$key'])) {\n";

			$code .= "	if (\$a['$key'] != \$b['$key'])\n";
			if ($rev)
				$code .= "	return (\$a['$key'] < \$b['$key'] ? 1 : -1);\n";
			else
				$code .= "	return (\$a['$key'] > \$b['$key'] ? 1 : -1);\n";

			$code .= "} else {\n";

			if ($rev)
				$code .= "	if ( (\$c = strcasecmp(\$b['$key'],\$a['$key'])) != 0 ) return \$c;\n";
			else
				$code .= "	if ( (\$c = strcasecmp(\$a['$key'],\$b['$key'])) != 0 ) return \$c;\n";
			$code .= "}}\n";
		}
		$code .= 'return $c;';
		$CACHE[$sortby] = create_function('$a, $b',$code);
	}

	uasort($data,$CACHE[$sortby]);
}

/**
 * Is compression enabled for output
 */
function isCompress() {
	return (isset($_SESSION[APPCONFIG]) && $_SESSION[APPCONFIG]->getValue('appearance','compress')
		&& ! ini_get('zlib.output_compression')
		&& eregi('gzip',$_SERVER['HTTP_ACCEPT_ENCODING']));
}
?>
