<?php
/*
* Copyright (c) 2009-2010 ATMAIL. All rights reserved
* See http://atmail.com/license.php for license agreement
*/
/**
* Rather define these functions not as class functions because being general functions they don't 
* benefit from the class structure and just make the code look cumbersome
*
* Brad: would it not be better to still encapsulate them in a class designed to be used statically
*	   to avoid any function name collisions (ie crude namespacing)?
*/

//encode unhandled characters for JSON encoding
function json_encode_string( $encode , $options ) {
	$escape = '\\\0..\37';
	$needle = array();
	$replace = array();

	if ( $options & JSON_HEX_APOS ) {
		$needle[] = "'";
		$replace[] = '\u0027';
	} else {
		$escape .= "'";
	}

	if ( $options & JSON_HEX_QUOT ) {
		$needle[] = '"';
		$replace[] = '\u0022';
	} else {
		$escape .= '"';
	}

	if ( $options & JSON_HEX_AMP ) {
		$needle[] = '&';
		$replace[] = '\u0026';
	}

	if ( $options & JSON_HEX_TAG ) {
		$needle[] = '<';
		$needle[] = '>';
		$replace[] = '\u003C';
		$replace[] = '\u003E';
	}

	$encode = addcslashes( $encode , $escape );
	$encode = str_replace( $needle , $replace , $encode );

	return $encode;
}


// Image manipulation 
function thumbnailPngContentCachedFromAbsFilename($absFilename, $params = array('widthPx' => "40",'heightPx' => '50', 'cropPx' => '30','qualityPercentage' => '100') )
{
	
	// If GD is not installed, return
	if (!extension_loaded('gd') && !function_exists('gd_info'))
		return false;
	
	if( !$thumbnailContent = Atmail_Cache::fetch(Atmail_Cache::TYPE_PHOTOS, $absFilename . '-thumbnail') ) //TODO revise Atmail_Cache to allow caching of valid valie false = object value is false or processing failed so dont try to process again
	{
		
		//Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ": Cache file = " . $absFilename);
		require_once 'library/Atmail/ThumbNail.php'; // Load just for this function to reduce overhead
		$thumb = new Thumbnail($absFilename);
		if($thumb->init_success)
		{
			
			$thumb->resize($params['widthPx'], $params['heightPx']);
			if( $params['cropPx'] > 0 )
				$thumb->cropFromCenter($params['cropPx'], $params['cropPx']);
			$tmpFilename = users::getTmpFolder() . md5($absFilename);
			$thumb->save( $tmpFilename , $params['qualityPercentage']);
			$photoThumbnailContent = file_get_contents($tmpFilename);
			Atmail_Cache::store(Atmail_Cache::TYPE_PHOTOS, $absFilename . '-thumbnail',$photoThumbnailContent, 3600);
			unlink($tmpFilename);
			return $photoThumbnailContent;
			
		}
		else
			return false;
		
	}
	return $thumbnailContent;

}

function thumbnailGifPreviewCachedFromPDFABSFilename($absFilename)
{
    // If the PDF file is larger than our limit then do not process
    // as 'convert' may consume too much cpu
	$fileSize = filesize($absFilename);
	if ($fileSize > 20971520)
        return null;

	// If the path for 'convert' is empty or not executable then try some
	// standard locations
	$config = Zend_Registry::get('config')->global;
	if (empty($config['convertPath']) || !is_executable($config['convertPath']))
	{
		if (is_executable("/usr/bin/convert"))
			$config['convertPath'] = "/usr/bin/convert";
		elseif (is_executable("/usr/local/bin/convert"))
			$config['convertPath'] = "/usr/local/bin/convert";
		else
			return null;
	}
	
	if( !$thumbnailContent = Atmail_Cache::fetch(Atmail_Cache::TYPE_PHOTOS, $absFilename . '-thumbnail') ) //TODO revise Atmail_Cache to allow caching of valid valie false = object value is false or processing failed so dont try to process again
	{
		
		$tempABSFilename = tempnam(users::getTmpFolder(), 'pdfthumb');
		
		
		
		$tempABSThumbFilename = $tempABSFilename . '-thumb.gif';
		$tempThumbFilename = substr($tempABSThumbFilename, strlen(users::getTmpFolder()));

		$command = $config['convertPath'] . " -thumbnail x600 " . escapeshellarg($absFilename) . "[0] " . escapeshellarg($tempABSThumbFilename) . "";
		
		$output = array();
		exec($command, $output);
		if (strpos(implode("\n", $output), 'error') !== false)
			$thumbnailContent = null;
		else
			$thumbnailContent = file_get_contents($tempABSThumbFilename);
		//$file = array();
		//$file['filename'] = $tempThumbFilename;
		//$file['mimeType'] = "image/gif";
		//$file['content'] = file_get_contents($tempABSThumbFilename);
		unlink($tempABSFilename);
		unlink($tempABSThumbFilename);
		Atmail_Cache::store(Atmail_Cache::TYPE_PHOTOS, $absFilename . '-thumbnail',$thumbnailContent,3600);
	
	}
	return $thumbnailContent;
	
}

function dbAdapterFactory($dbConfig = null)
{
	
	//This is a Zend Framework and Atmail bootloader dependant dbAdapter factory method that has dependancies and care should be taken when using to make sure all dependancies are met.
	//N.B. This function also has paths to exit() the application - change if this functionality must be suppressed in future requirements change
	//returns Kill application with Fatal error or $dbAdapter;
	
	// Get database configuration
	if( $dbConfig === null )
	{

		try
		{

			$dbConfig = new Zend_Config_Ini( APP_ROOT . 'config/dbconfig.ini', 'production');
			Zend_Registry::set('dbConfig', $dbConfig);

		}
		catch(Exception $e)
		{

			if(isset($_SERVER['REQUEST_URI']) && strstr($_SERVER['REQUEST_URI'], "default/index/dberror") == null)
			{
				$error = $e->getMessage();
				include( APP_ROOT . 'application/modules/default/views/scripts/index/dberror.phtml');
				exit();
			}

		}

	}

	// Get database configuration
	$successfulDBConnection = false;
	foreach( array('database','database2','database3','database4','database5') as $dbProfile )
	{

		$profile = $dbConfig->$dbProfile;
		if( $dbConfig->$dbProfile === null || empty($profile->params->host) || empty($profile->params->username) || empty($profile->params->dbname) )
			continue; //skip uncomplete backup database entries.

		try
		{
			$dbAdapter = Zend_Db::Factory( $profile );
			$dbAdapter->query("SET NAMES 'utf8'");
			$successfulDBConnection = true;
			Zend_Registry::set('dbConfigProfile', $dbProfile);
			break; //break out of foreach loop once first working db config is found
		}
		catch(Exception $e)
		{

			$error = $e->getMessage();
			
			//ignore fatals if server missing only, other fatals must throw errors to show that config is wrong
			if( strpos($error,'SQLSTATE') !== null && strpos($error,'[HY000]') && (strpos($error,'[2013]') !== null || strpos($error,'[2003]') !== null) )
			{

				//ignore [2013] Lost connection to MySQL server at 'reading initial communication packet', system error: 113
				//ignore [2003] Can't connect to MySQL server on '192.168.1.255' (101)
				continue;


			}
			else if(isset($_SERVER['REQUEST_URI']) && strstr($_SERVER['REQUEST_URI'], "default/index/dberror") == null)
			{
				$error = $e->getMessage();
				include( APP_ROOT . 'application/modules/default/views/scripts/index/dberror.phtml');
				exit();
			}

		}
		
	}
	if( !$successfulDBConnection )
	{
		
		$error = "Unable to connect to database server.";
		include( APP_ROOT . 'application/modules/default/views/scripts/index/dberror.phtml');
		exit();
		
	}
	unset($profile);
	return $dbAdapter;
	
}

function getFavicon($basedir, $siteBaseUrl, $iconType = '')
{
	// processing fav icons
	
	// find images directory
	if(is_dir($basedir . '/images/'))
	{
		$dir = $basedir . '/images/';
	}
	else if(is_dir($basedir . '/webmail/images/'))
	{
		$dir = $basedir . '/webmail/images/';
	}
		

	$paths = array();
	$settings = Zend_Registry::get('UserSettings');
	$themePath = $settings['cssStyleTheme']; 
	$paths[] = '/themes/' . $themePath;
	$paths[] = '';

	$favicon = '';
	foreach($paths as $path)
	{
		if(is_file($dir . $path . '/favicon' . ($iconType != '' ? '-' . $iconType : '') . '.ico'))
		{
			$favicon = rtrim($siteBaseUrl, '/') . '/images' . rtrim($path, '/') . '/favicon' . ($iconType != '' ? '-' . $iconType : '') . '.ico' . '?' . Zend_registry::get('config')->global['version'];
			return $favicon;
		}
	}
	
	foreach($paths as $path)
	{
		if(is_file($dir . $path . '/favicon.ico'))
		{
			$favicon = rtrim($siteBaseUrl, '/') . '/images' . rtrim($path, '/') . '/favicon.ico';
			return $favicon . '?' . Zend_registry::get('config')->global['version'];
		}
	}
	
	return '';
}

//strcmp on Array[fieldNumber]
function strcmpAk($a, $b, $fieldNumber = 1)
{
    return strcasecmp($a[$fieldNumber],$b[$fieldNumber]);
}


function Unix_to_Outlook_Timestamp_GMT( $unixDate )
{

	return sprintf("%.6f", gmdate("U", $unixDate) / 60 / 1440 + 25569);

}

function MySQL_Locale_to_Unix_GMT( $dateStamp )
{
	
	//N.B. requires dbAdapter in registry
	$dbAdapter = Zend_Registry::get('dbAdapter');
	$q = 'SELECT UNIX_TIMESTAMP(' . $dbAdapter->quote($dateStamp) . ')';
	$r = $dbAdapter->fetchOne($q);
	
}



	
function Outlook_to_Unix_Timestamp_GMT($date)
{

	$seconds = time(); // Current time in seconds

	$datecalc = $date; // This has the date
	$seconds = '';
	if (preg_match('/\.(.*)/', $date, $match))
		$seconds = $match[1];

	$datecalc = preg_replace('/\.(.*)/', '', $datecalc);

	// Number of days
	$datecalc = $datecalc - "25569";

	// Convert to seconds
	$datecalc = round($datecalc * 60 * 60 * 24);

	// Int rounds it up - 86400 seconds in 1 day 0.[secsyougiveme]
	$newsecs = round("0.$seconds"  * 86400);

	$datecalc = $datecalc + $newsecs;

	return $datecalc;

}

function Outlook_to_Unix_Timestamp_GMTold1($outlookDate)
{

	$datecalc = $outlookDate; // This has the date
	$seconds = '';
	if (preg_match('/\.(.*)/', $outlookDate, $match))
		$seconds = $match[1];

	$outlookDate = preg_replace('/\.(.*)/', '', $outlookDate);

	// Number of days
	$outlookDate = $outlookDate - "25569";

	// Convert to seconds
	$outlookDate = round($outlookDate * 60 * 60 * 24);

	// Int rounds it up - 86400 seconds in 1 day 0.[secsyougiveme]
	$newsecs = round("0.$seconds"  * 86400);

	return $datecalc + $newsecs;

}

function ISO8601TZToUnixGMT( $timestamp, $tz = 'Zulu')
{
	
	if( $timestamp == '0' )
		return '0'; //technically illegal but neater to catch here than outside
	$date = new Zend_Date();
	$date->setTimezone( $tz );
	$date->setDate( $timestamp, 'YYYYMMddHHmmss' );
	$date->setTime( $timestamp, 'YYYYMMddHHmmss' );

	return $date->getTimestamp();
	
}

function UnixGMTToISO8601TZ( $timestamp, $tz = 'Zulu' )
{
	
	if( $timestamp == '0' )
		return '0'; //technically illegal but neater to catch here than outside
	$date = new Zend_Date($timestamp, Zend_Date::TIMESTAMP);
	if( $tz == 'Zulu' )
		$append = 'Z';
	else
		$append = '';
	$date->setTimezone($tz);
	return $date->toString('YYYYMMddHHmmss') . $append;
	
}

function Unix_to_MySQL_DATETIME_GMT( $unixTimestamp )
{

	return gmdate( 'Y-m-d H:i:s', $unixTimestamp );

}

function Unix_to_MySQL_DATETIME_Locale( $unixTimestamp )
{

	return date( 'Y-m-d H:i:s', $unixTimestamp );

}

function MySQL_to_Unix_Timestamp_Raw( $MySQLDatetime )
{

	list($date, $time) = explode(' ', $MySQLDatetime);
	list($year, $month, $day) = explode('-', $date);
	list($hour, $minute, $second) = explode(':', $time);
	return gmmktime($hour, $minute, $second, $month, $day, $year);

}

function object2array(&$data) 
{

	if( is_object($data) )
		$data = (array)$data;
	if( is_array($data) )
		foreach( $data as &$v )
			object2array($v); 

}

/**
* Return a subject with Re: if required
*
* @param string $string The subject to append
* @access public
* @return string
*/

/*
 * This method sucks in every possible way: see setResponsePrefix.
 */
function reReply($subject)
{
	
	if (!preg_match('/^\s*Re.*?:/i', $subject) && !preg_match('/^Ynt:/i', $subject)
	&& !preg_match('/^TR/i', $subject) && !preg_match('/^Oggetto:/i', $subject)
	&& !preg_match('/^VS:/i', $subject) && !preg_match('/^Awt:/i', $subject)
	&& !preg_match('/^Aw:/i', $subject) && !preg_match('/^TR:/i', $subject)
	&& !preg_match('/^R:/i', $subject) && !preg_match('/^RES:/i', $subject))
	$subject = "Re: " . $subject;
	
	return $subject;
}

/**
* Return a subject with Re: if required
*
* @param string $subject the subject to search and/or append
* @param string $prefix the prefix to search and append
* @access public
* @return string
*/

function setResponsePrefix($subject,$prefix)
{
	if(!isset($prefix)){
		return reReply($subject);
	}

  if(preg_match("/^\s*(re|fwd*)\:/i",$subject)){
		return preg_replace('/^\s*(re|fwd*)\:/i',$prefix,$subject);
	} else {
		return "{$prefix} {$subject}";
	}
}

/**
* Encode a UTF-8 string to UTF7-IMAP
*
* @param string $str The string to encode
* @access public
* @return string
*/
function encodeUTF7( $string )
{
	
	//TODO: remove overide
	if ( true || (function_exists('mb_convert_encoding') && $this->_config['allowUTF7Folders']) )
	{
		return mb_convert_encoding($string, 'UTF7-IMAP', 'UTF-8');
	}
	else
	{
		return $string;
	}
}


/**
* Decode a UTF7-IMAP string to UTF-8
*
* @param string $string The string to decode
* @access public
* @return string
*/
function decodeUTF7($string)
{
	
	//TODO: remove overide
	if ( true || (function_exists('mb_convert_encoding') && $this->_config['allowUTF7Folders']) )
	{
		return mb_convert_encoding($string, 'UTF-8', 'UTF7-IMAP');
	}
	else
	{
		return $string;
	}
}

function findAvailableThemes()
{
    
	//find available themes
	$folderList = scandir( APP_ROOT . 'css/themes' );
	$availableThemes = array();
	foreach( $folderList as $item )
	{

		//ignore reserved folders and folder longer than 5 chars
		if( $item == '.' || $item == '..' || $item == '.svn' )
		{

			continue;

		}
		if( 
			is_dir(APP_ROOT . 'css/themes/' . $item) && 
			is_dir(APP_ROOT . 'images/themes/' . $item) && 
			file_exists( APP_ROOT . 'images/themes/' . $item . '.png') )
		{

			//return array here so that in the future can make more inteligent and less code required outside to handle themes and their screenshots etc.
			$availableThemes[$item] = array('name' => str_replace('-', ' ', $item), 'folder' => $item, 'screenshot' => 'images/themes/' . $item . '.png');

		}

	}
	return $availableThemes;
     
}

function convertPlainTextLinksToUrls($data)
{
	// disabled. Hoses some html formats
	return $data;
	
	//TODO: make a UT test to make sure this function continues to work as expected, using this list below of known legal and illegal links and compile expected lagal results.
	//first strip any crafted links.
	$data = preg_replace_callback('|__ATMAIL_ENCODED_EXISTING_URL_(.*)_ATMAIL_ENCODED_EXISTING_URL__|', 'remove', $data);
	
	//link and hide matches that have protocol
	$regexp = "[a-z]{3,10}://[a-z0-9\.\-]+[a-z]{2,6}([^\s<>,\)]*[^\s<,\.\)]+)*";
	$data = preg_replace_callback('|' . $regexp . '|si', 'linkURL', $data);
	
	//make basic links and hide strings that look like email addresses
	$regexp = "[_a-z0-9\-\.]+@([_a-z0-9\-]+\.)+[a-z]{2,6}";
	$data = preg_replace_callback('|' . $regexp . '|si','linkEmail', $data );
    
	//search for plain text domain name or ip addresses with possibble folders or url args
	$regexp = "([a-z0-9\-]+\.)+[a-z]{2,6}([^\s<>,]*[^\s<,\.]+)*";
	$data = preg_replace_callback('|' . $regexp . '|si','linkDomain', $data );
    
	//untransform encoded urls
	$regexp = '__ATMAIL_ENCODED_EXISTING_URL_([^_]*)_ATMAIL_ENCODED_EXISTING_URL__';
	return preg_replace_callback('|' . $regexp . '|U','decode',$data);
}

function encode($args) { return '__ATMAIL_ENCODED_EXISTING_URL_' . base64_encode($args[0]) . '_ATMAIL_ENCODED_EXISTING_URL__'; }
function decode($args) { return base64_decode($args[1]); }
function linkURL($args)	{ return encode( array('<a href="' . trim($args[0]) . '">' . trim($args[0]) . '</a>') ); }
function linkEmail($args)  { return encode( array('<a href="mailto:' . trim($args[0]) . '">' . trim($args[0]) . '</a>') ); }
function linkDomain($args) { return encode( array('<a href="http://' . trim($args[0]) . '">' . trim($args[0]) . '</a>') ); }
function remove($args)	 { return ''; }

function splitOnUnescapedUnquotedCommas( $string )
{
	if( empty($string) || !is_string($string) )
		return array();
	$tmpArray = explode( ',', $string );
	
	/*
	$testRecipientList = 
	'"perso\"n,,  , ,a\"l 0000" <0000@test.com>, ' . 
	'"personal 1111" <1111@test.com>, ' . 
	'"personal, home 2222" <2222@test.com>, ' . 
	'"personal\'s 3333" <3333@test.com>, ' . 
	'"personal\"tr 4444" <4444@test.com>, ' . 
	'"Burnburg, Bob\"y 5555" <5555@test.com>, ' . 
	'test 6666 <6666@test.com>, ' . 
	'7777@test.com, ' . 
	'"allan 8888" 8888@test.com, ' . 
	'=?UTF-8?B?55u46KuH44Ei44Gn44GN44KLIOOCveODi+ODvOOCt+ODp+ODg+ODl+OBrg==?= <9999@test.com>, ' . 
	'"\'allan 1010\'" 1010@test.com' . 
	'';
	
	$tmpArray = explode( ',', $testRecipientList);
	// */
	$tmpArrayC = count( $tmpArray );
	$processedArray = array();
	$b = 0;
	$insideOpeningDoubleQuote = false;
	for( $a=0 ; $a < $tmpArrayC ; $a++ )
	{
		
		$escapedQuotesCount = substr_count( $tmpArray[$a], '\"' );
		$quotesTotalCount = substr_count( $tmpArray[$a], '"' );
		 
		if( $insideOpeningDoubleQuote )
		{
			
			if( ($quotesTotalCount - $escapedQuotesCount)%2 == 0 )
			{
		
				$processedArray[$b] = $processedArray[$b] . ',' . $tmpArray[$a];
				
			}   
			else
			{
				
				$processedArray[$b] = $processedArray[$b] . ',' . $tmpArray[$a];
				$b++;
				$insideOpeningDoubleQuote = false;
				
			}
		
		}
		else
		{
			
			$processedArray[$b] = $tmpArray[$a];
			if( ($quotesTotalCount - $escapedQuotesCount)%2 == 0 )
			{
				
				$b++;
				
			}   
			else
			{
				
				$insideOpeningDoubleQuote = true;
				
			}
			
		}
		
	}
	
	/*
	Zend_Registry::get('log')->debug( "\n\n" . print_r($tmpArray, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$tmpArray \n\n");
	Zend_Registry::get('log')->debug( "\n" . print_r($processedArray, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$processedArray \n");
	Zend_Registry::get('log')->debug( "\n" . print_r($testRecipientList, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$testRecipientList \n");
	
	//die(__METHOD__ . ' #' . __LINE__);
	// */

	return $processedArray;
	
} 

function getProcessedRecipientObjects( $stringRaw, $charset = 'UTF-8' )
{
	$recipientArray = splitOnUnescapedUnquotedCommas( $stringRaw );

	require_once('Mail/RFC822.php');
	$rfc822 = new Mail_RFC822;
	
	$resultArray = array();
	foreach( $recipientArray as $k => $v )
	{
		$result = array(
			'recipientOriginal' => $v,  
			'recipientPrepared' => '', 
			'recipientPreparedUTF8' => '', 
			'address' => '',  
			'personal' => '',  
			'personalUTF8' => '',  
			'personalEncoded' => '', 
			'mailbox' => '', 
			'host' => '', 
			'comment' => array()
		);
		
		//various cleanup rules NB to clearly coment so that relevant rules understood, explained and justified
		$cleanupPatternHash = array(
			'^\s*;\s*' => '', //remove leading semi-colon
			// too late already split, and is ; a valid recipient delim?	  ';' => ',', //A6 splits recipients by , so convert ;
			'â€œ' => '"', // Remove "smart quotes" (aka dumb quotes)
			'â€�' => '"', // Remove "smart quotes" (aka dumb quotes)
			'â€˜' => '"', // Remove "smart quotes" (aka dumb quotes)
			'â€™' => '"', // Remove "smart quotes" (aka dumb quotes)
			"\x98" => '"', // Remove "smart quotes" (aka dumb quotes)
			"\x99" => '"', // Remove "smart quotes" (aka dumb quotes)
			"\x8c" => '"', // Remove "smart quotes" (aka dumb quotes)
			"\x9d" => '"', // Remove "smart quotes" (aka dumb quotes)
			chr(147) => '"', // Remove "smart quotes" (aka dumb quotes)
			chr(148) => '"', // Remove "smart quotes" (aka dumb quotes)
			chr(146) => '"', // Remove "smart quotes" (aka dumb quotes)
			'R20;' => '"', // Remove "smart quotes" (aka dumb quotes)
			'R21' => '"', // Remove "smart quotes" (aka dumb quotes)
			'R16;' => '"', // Remove "smart quotes" (aka dumb quotes)
			'R17' => '"', // Remove "smart quotes" (aka dumb quotes)
			"\\\([()])+" => '\\1', //Unescape escaped brackets, e.g. Company/Organization brackets 
			"\\\{2}+" => '\\', //Fix escaped backslashes 
		);
		foreach($cleanupPatternHash as $p => $r)
		{
			
			$fixBadChars = mb_ereg_replace( $p, $r, $v);
			//gracefully handle errors causing false result
			if( $fixBadChars !== false )
				$v = $fixBadChars;
			
		} 
		
		//reset personal for following addresses
		$personal = '';
		//if there is no @ symbol then local user
		if(mb_strpos($v, '@') === false )
		{
            // Allowing just username breaks stuff down the line
            // so lets skip (we have no use for it anyhow)
            // see Jira ticket #ATMAIL-2892 for why this is disabled
            continue;
            
			//address in in <>, else take string before first space
			if( mb_strpos($v, '<') !== false )
			{
				
				$splitPosStart = mb_strpos($v, '<');
				$splitPosEnd = mb_strpos($v, '>');
				$address = trim( mb_substr($v, $splitPosStart, $splitPosEnd-$splitPosStart), ' <>' );
				$personal = trim( mb_substr($v, 0, $splitPosStart), ' <>' );
				
			}
			else
			{
			
				$splitPos = mb_strpos($v, ' ');
				$address = trim( mb_substr($v, 0, $splitPos), ' <>' );
				$personal = trim( mb_substr($v, $splitPos), ' <>' );
				
			}
			
			$result['recipientPrepared'] = $v;
			$result['address'] = $address;
			$result['personal'] = $personal;
			$result['personalUTF8'] = $personal;
			$result['personalEncoded'] = $personal;
			$result['mailbox'] = $address; 
			$result['host'] = 'localhost'; 
			
			$resultArray[] = (object)$result;
			continue;
			
		}
		//if there are no quotations but there is a space in the address then we must have a name address senario
		if( !empty($v) && (mb_strrpos($v, ' ') !== false || mb_strrpos($v, '<') !== false) )
		{
			
			if( mb_strrpos($v, ' ') === false )
				$splitPos = mb_strrpos($v, '<'); //must have been the < that was found
			elseif( mb_strrpos($v, '<') === false )
				$splitPos = mb_strrpos($v, ' '); //must have been the space that was found
			else
				$splitPos = (mb_strrpos($v, '<')>mb_strrpos($v, ' ')?mb_strrpos($v, '<'):mb_strrpos($v, ' ')); //use the latter match
			
			//strip spaces and quotations from ends of $personal
			$personal = trim( mb_substr($v, 0, $splitPos), ' \'"' ); // trim spaces, single and double quotes from ends
			
			//strip double quotations from $personal until RFC confirmed if recipient mime headers should be scaped with \" or "" http://tools.ietf.org/html/rfc4180
			$personal = str_replace( '\"', '', $personal);
			
			//strip spaces and <> from ends of $address
			$address = trim( mb_substr($v, $splitPos), ' <>' ); //trim spaces, < and > from ends
			
			//decode personal if still encoded
			if( messageHandling::isMimeEncoded($personal) )
				$personal = messageHandling::mimeEncodedDecode( $personal );
			
			if (strlen($personal) > 0)
			{

				if( containsUTF8($personal) )
				{

					//strip quotations around $personal before encoding
					$personalEncoded = Atmail_MIMEWords::encode_mimeword($personal, "B", $charset);
					$result['recipientPrepared'] = '"' . $personalEncoded . '" <' . $address . '>';
					$result['recipientPreparedUTF8'] = '"' . $personal . '" <' . $address . '>';
					$result['address'] = $address;
					$result['personal'] = $personalEncoded;
					$result['personalUTF8'] = $personal;
					$result['personalEncoded'] = $personalEncoded;
					
				}
				else
				{
	
				 	$result['recipientPrepared'] = '"' . $personal . '" <' . $address . '>';
					$result['recipientPreparedUTF8'] = '"' . $personal . '" <' . $address . '>';
					$result['address'] = $address;
					$result['personal'] = $personal;
					$result['personalUTF8'] = $personal;
					$result['personalEncoded'] = $personal;
					
				}

			}
			else
			{
	
				$result['recipientPrepared'] = $address;
				$result['recipientPreparedUTF8'] = $address;
				$result['address'] = $address;
				$result['personal'] = '';
				$result['personalUTF8'] = '';
				$result['personalEncoded'] = '';
				
			}			
			
		}
		else
		{		
			//ok, no space or < so there is no personal part, if there is no @ then bad recipient (expecting just email address)
			if( mb_strpos($v, '@') !== false )
			{
				
				//aparently valid email address so add
				//strip spaces and <> from ends of $address
				$v = trim( $v, ' <>' ); //trim spaces, < and > from ends
				$result['recipientPrepared'] = $v;
				$result['recipientPreparedUTF8'] = $v;
				$result['address'] = $v;
				$result['personal'] = $personal;
				$result['personalUTF8'] = $personal;
				$result['personalEncoded'] = $personal;
				
			}
			
		}
		
		//final sanity check for each recipient
		$addressObjectsArray = $rfc822->parseAddressList($result['recipientPrepared'], null, false, true);
		if( gettype($addressObjectsArray) == "object" && get_class($addressObjectsArray) == 'PEAR_Error' )
		{
			
			//throw new Exception('Error validating recipient (' . $v . '):' . $addressObjectsArray->getMessage());
			
		}
		else
		{
			
			//Zend_Registry::get('log')->debug( "\n\n" . print_r($addressObjectsArray[0], true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$addressObjectsArray[0] \n\n");
			 
			//$result['personal'] = $addressObjectsArray[0]->personal;
			$result['mailbox'] = $addressObjectsArray[0]->mailbox;
			$result['host'] = $addressObjectsArray[0]->host;
			$result['comment'] = $addressObjectsArray[0]->comment;		
			
		}
		
		$resultArray[] = (object)$result;
				
	}

	return $resultArray;

}

function containsUTF8($string)
{

	return preg_match('/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff]/', rtrim($string) );
	
}

function makeFSSafe( $filename = null )
{
	//the minimum rules for FS: filenames is must be non null and must not contain slash and have max length
	if( $filename == null ) {
		$filename = 'unknown.txt';
	}
	//fix filename for FS filename limitations.
	if( strlen($filename) > 255 )
	 $filename = substr($filename, -254);

    // filter filename
    $filename = preg_replace("/^[\/.]+/", "", $filename);
    $filename = str_replace("../", "", $filename);

	return str_replace( array('/','\\'), '', $filename );
}

function explodePostToArray($array, $delimiter = '__')
{
	if(!is_array($array)) return false;
	$splitRE   = '/' . preg_quote($delimiter, '/') . '/';
	$returnArr = array();
	foreach ($array as $key => $v) {
		// Get parent parts and the current leaf
		$parts	= preg_split($splitRE, $key, -1, PREG_SPLIT_NO_EMPTY);
		$leafPart = array_pop($parts);
		$parentArr = &$returnArr;
		foreach ($parts as $part) {
			if (!isset($parentArr[$part]) || !is_array($parentArr[$part]))
				$parentArr[$part] = array(); 
			$parentArr = &$parentArr[$part];
		}
		if (empty($parentArr[$leafPart]))
			$parentArr[$leafPart] = $v;
	}
	return $returnArr;
}

/** 
 * Mainly used for summarizing search results into bar totals 
 */
function floorTimestamp($timestamp, $precision ) {
	if($precision == 'month')
		return strtotime(gmdate("Y-m", $timestamp)."-01 00:00:00 GMT");
	if($precision == 'day')
		return strtotime(gmdate("Y-m-d", $timestamp)." 00:00:00 GMT");
	if($precision == 'hour')
		return strtotime(gmdate("Y-m-d H:", $timestamp)."00:00 GMT");
}

/**
 * Tried and trusted handling of mime header date fields (sometimes are nonstandard)
 */   

function getnewdate($newdate, $TimeZone)
{
	global $atmail;

	if($this->Language == 'espanol')
	setlocale(LC_TIME, 'es_ES', 'en_US');
	else if($this->Language == 'italiano')
	setlocale(LC_TIME, 'it_IT', 'en_US');
	else if($this->Language == 'russian')
	setlocale(LC_TIME, 'ru_RU.utf8', 'en_US');

	else if($this->Language != 'japanese' && $this->Language != 'greek' && $this->Language != 'thai')
	setlocale(LC_TIME, strtolower($this->Language), 'en_US');

	$ctime = array();

	if (strlen( $newdate ) == 11)
		$newdate = '0'.$newdate;

	$newdate = $this->calc_timezone($newdate);

	// Change the date if we are using mySQL
	if (preg_match('/(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/', $newdate, $matches))
		$newdate = "$matches[2]/$matches[3]/$matches[1] $matches[4]:$matches[5]:$matches[6]";
	else
	{
		$newdate = preg_replace('/\+\d\d\d\d|-\d\d\d\d/', '', $newdate);
		$time = strtotime($newdate);
		$newdate = strftime("%D %X", $time );
	}

	$newdate = strtotime($newdate);
	$ctime = localtime(null, true);

	// Get the hour and minute of current time
	$hour   = $ctime['tm_hour'];
	$minute = $ctime['tm_min'];


	if ( $newdate > ( time() - ( ( $minute * 60 ) + ( $hour * 60 * 60 ) ) ) )
		$newdate = strftime( $atmail->parse("html/$atmail->Language/msg/today.html") . " $this->TimeFormat", $newdate );
	elseif ( $this->Language != "japanese" && $newdate > ( time() - ( 60 * 60 * 24 * 7 ) ) ) {
		$newdate = strftime( "%a $this->TimeFormat", $newdate);

		if($this->Language == 'polish')
		$newdate = iconv('iso-8859-2', "UTF-8", $newdate);
		else if($this->Language == 'russian')
		$newdate = $newdate;
		else
		$newdate = iconv('iso-8859-1', "UTF-8", $newdate);

	} elseif ( $this->Language == "japanese") {
		$newdate = strftime( "$this->DateFormat %a $this->TimeFormat", $newdate);

		if($this->Language == 'polish')
		$newdate = iconv('iso-8859-2', "UTF-8", $newdate);
		else if($this->Language == 'russian')
		$newdate = $newdate;
		else
		$newdate = iconv('iso-8859-1', "UTF-8", $newdate);

	} else {
		$newdate = strftime( "%a $this->DateFormat $this->TimeFormat", $newdate);

		if($this->Language == 'polish')
		$newdate = iconv('iso-8859-2', "UTF-8", $newdate);
		else if($this->Language == 'russian')
		$newdate = $newdate;
		else
		$newdate = iconv('iso-8859-1', "UTF-8", $newdate);

	}

	return $newdate;
}


/** 
 * Can be used to cull elements out (or only leave certain elements in) an array that you don't want 
 * i.e before rendering a form from array, or before processing a form result (to prevent protected field changes) 
*/
function &cullArray(&$array, $show, $omit = null)
{
	if( is_array($show) && count($show) > 0 )
		foreach( $array as $key => $value )
			if( !in_array($key,$show) )
				unset($array[$key]);
	if( is_array($omit) )
		foreach( $array as $key => $value )
			if( in_array($key,$omit) )
				unset($array[$key]);
	return $array;
}

function &intersectArray(&$targetArray, &$sourceArray)
{
	//return the intersection of the 2 arrays
	//mainly used for updating a only the source data that is found in target (i.e. prevent saving changes to protected fields)
	//i.e. will only accept certain fields back from forms for saving to db
	//N.B. intersected values are from $source
	foreach($targetArray as $key => $value)
		if( isset($sourceArray[$key]) ) //if source contains a matching field then update
			$targetArray[$key] = $sourceArray[$key];
		else
			unset($targetArray[$key]); //else drop the field (intersection)
	return $targetArray;
}

function encode_language($data, $encoding = false)
{
	$config = Zend_Registry::get('config');
	$userConfig = Zend_Auth::getInstance()->getStorage()->read();
	if (!$encoding)
		$encoding = $userConfig['DefaultEncoding'];
	if (!$encoding || !$config['iconv'])
		return $data;
	// Check the encoding is valid, otherwise return without converting
	//Consider: logging invalid $encoding to facilitate diagnostics
	$encodedData = iconv('UTF-8', $encoding, $data);
	if ($encodedData === false)
		return $data;
	return $encodedData;
}
//TODO: global folder, file names, and references to these files and folders utf-7 encoding and decoding



/**
* encodes string as a legal file system name to utf-7 if enabled else return it unchanged
*/
function encode_fs_name($name)
{
	return $name;
}   

/**
* decodes legal file system name from utf-7 if enabled else return it unchanged 
*/
function decode_safe_name_for_fs($name)
{
	return $name;
}

/**
* encodes string as a legal file system name to utf-7 if enabled else return it unchanged
*/
function encode_safe_name_for_fs($name)
{
	return $name; //for now until properly implimented
}

/**
 * simplify user account names for use in tmp folder creation, caching etc.
 * ZF Caching functionality will only accept simple cache filename hash names (without @)
 * @return simplified string
*/
function simplifyString($string)
{

   	return preg_replace("/[^a-zA-Z0-9]/", "", $string);

}

function safeId($string)
{
	
	//return ereg_replace("[^A-Za-z0-9]", "", base64_encode($string) );
	return md5($string);

}   



function decode_language($data, $encoding = false)
{
	$config = Zend_Registry::get('config');
	$userConfig = Zend_Auth::getInstance()->getStorage()->read();
	if (!$encoding)
		$encoding = $userConfig['DefaultEncoding'];
	// Check the encoding is valid, otherwise return without converting
	if (!$encoding || !$config['iconv'] || !$data)
		return $data;
	// Map GB2321 > CP936 - iconv seems to need this to convert this encoding properly
	if (strtoupper($encoding) == 'GB2312')
		$encoding = 'CP936';
	$decodedData = iconv($encoding, 'UTF-8', $data);
	if (strlen($decodedData) > 0 )
		return $decodedData;
	return $data;
}

function UNUSEDExampleMb()
{
	mb_internal_encoding('UTF-8');
	$sampleString = 'Russian: Ð»ÑƒÑ‡ÑˆÐ°Ñ� Ð±ÐµÑ� Asian: æŽ¥ã�®å�‹äººãƒ»çŸ¥äºº';
	echo 'sampleString: ' . $sampleString . "<br />\n";
	$ASCIIencodedSampleString = mb_encode_mimeheader($sampleString);
	echo 'ASCIIencodedSampleString: ' . $ASCIIencodedSampleString . "<br />\n";
	$UFT8DecodedSampleString = mb_decode_mimeheader($ASCIIencodedSampleString);
	echo 'UFT8DecodedSampleString: ' . $UFT8DecodedSampleString . "<br />\n";
} 

function UNUSEDExampleIconv()
{
	echo print_r(iconv_get_encoding(),true) . "<br />\n";
	iconv_set_encoding("internal_encoding", "UTF-8");
	iconv_set_encoding("input_encoding", "UTF-8");
	iconv_set_encoding("output_encoding", "UTF-8");
	echo print_r(iconv_get_encoding(),true) . "<br />\n";
	
	$sampleString = 'Russian: Ð»ÑƒÑ‡ÑˆÐ°Ñ� Ð±ÐµÑ� Asian: æŽ¥ã�®å�‹äººãƒ»çŸ¥äºº';
	$sampleString = iconv_mime_encode(null, $sampleString);
	echo $sampleString;
	
}

function dump(&$var, $varName = 'dump', $return = false)
{
	 //returns html layed out vars for debugging purposes 
	if($return)
	 	return '<pre><h1>' . $varName . '</h1>' . htmlspecialchars(print_r($var, true), ENT_NOQUOTES) . '</pre>';
	else
		echo '<pre><h1>' . $varName . '</h1>' . htmlspecialchars(print_r($var, true), ENT_NOQUOTES) . '</pre>';
}

function ddie(&$var, $varName = 'dump')
{   
	die( dump($var, $varName, true) );
}
   
/**
 * Encodes HTML safely for UTF-8. Use instead of htmlentities.
 *
 * @param string $var
 * @return string
 */
function html_encode($var)
{

	return htmlentities($var, ENT_QUOTES, 'UTF-8') ;

}

function calc_timezone($newdate)
{

	global $pref;
	$pref['datetime'] = 'currently unused';
	$TimeZone = Zend_Registry::get('UserSettings');
	try { 

			$TimeZone = Zend_Registry::get('UserSettings');
			date_default_timezone_set($TimeZone);

	} catch ( Exception $e ) {
		  //dont bother trying to set user timezone if UserSettings not jet available
		$TimeZone = 'Australia/Brisbane';  
	}
	
	

	$months = array('Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05',
					'Jun' => '06', 'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10',
					'Nov' => '11', 'Dec' => '12');

	if (!$pref['datetime'] || !$TimeZone) {
		$newdate = date("D, j M Y G:i O", strtotime($newdate));
		return $newdate;
	}

	require_once('Date.php');

	// convert to ISO 8601 format
	if(preg_match('/(\d+) ([a-z]+) (\d\d\d\d) (\d\d:\d\d:\d\d) ((\+|-)\d\d\d\d|[a-z]+)/i', $newdate, $m))	{

	if (strlen($m[1]) == 1)
		$m[1] = "0$m[1]";

	// Convert Timezone ID to GMT offset
	if (!is_numeric($m[5])) {
		$dt = new Date_TimeZone($m[5]);
		$m[5] = $dt->getRawOffset();
		if ($m[5] == 0) {
			$m[5] = 'Z';
		} else {
			$m[5] = $m[5] / 36000;
			settype($m[5], 'string');
			$m[5] = preg_replace('/(-|\+)/', '${1}0', $m[5]);
		}
	}

	$newdate = "$m[3]{$months[$m[2]]}$m[1]T$m[4]$m[5]";

	// Do timezone conversion
	$date = new Date($newdate);

	$date->convertTZbyID($TimeZone);

	$newdate = $date->getDate();

	$newdate = date("D, j M Y G:i O", strtotime($newdate));
	} elseif(preg_match('/(\d+) ([a-z]+) (\d\d\d\d) (\d\d:\d\d:\d\d) (\w+)/i', $newdate, $m))	{

	if (strlen($m[1]) == 1)
		$m[1] = "0$m[1]";

	$newdate = "$m[3]{$months[$m[2]]}$m[1]";

	// Convert date from timezone ID, rather then +0900 or -1100 format, format in GMT, MST, etc
	$date = new Date($newdate);
	$date->setTZByID($m[5]);

	$newdate = $date->getDate();
	$newdate = date("D, j M Y G:i O", strtotime($newdate));

	} else	{

	$newdate = date("D, j M Y G:i O", strtotime($newdate));

	}


	return $newdate;
}

function headerDateToLocaleDate($headerDate)
{
	//Ben opted to reuse existing code
	return calc_timezone($headerDate);

	//abstracted here just in case PEAR ever gets removed etc.
	//$date = new Zend_Date($headerDate); //Ben opted to reuse existing  
	//$date = date($date->get());
	//return strftime( "%c", $date);
}

function is_attachment($part)
{
	//What about marking all parts as attachments that are not text/plain or text/html ??
	if( isset($part->contentType) ) {
		if (strpos(strtolower($part->contentType), 'name=') !== false)
			return true;

		if (strpos(strtolower($part->contentType), 'filename=') !== false)
			return true;
	
		//if (!empty($this->contentDisposition) && strtolower($this->content_disposition) != 'inline')
		//	return true;

		//if (isset($this->headers['content-disposition']) && strpos(strtolower($this->headers['content-disposition']), 'filename='))
		//	return true;

		//if ($this->disposition)
		//	return true;
		
		if (preg_match('/image\//i', $part->contentType))
				return true;
	}
	return false;
}

function bytesToHumanReadable($bytes,$precision = 0)
{
	$sizes = array('YB', 'ZB', 'EB', 'PB', 'TB', 'GB', 'MB', 'KB');
	$sizesc = count($sizes);
	$bytes /= 1024;
	while($sizesc-- && $bytes > 1024) $bytes /= 1024;
	$rounded = round($bytes, $precision);
	if( $rounded < 1 )
	{
		
		$rounded = 1;
		
	}   
	return $rounded . ' ' . $sizes[$sizesc];   
}

function bytesToHumanReadableold1($bytes, $precision = -2)
{
	//Will message sizes ever be bigger than 1GB? famous last words
	//return round($bytes / 1024,4);
		 if( $bytes / 1048576 > 5 ) 
		return round($bytes/1048576, $precision) . 'MB'; 
	else if( $bytes / 1048576 > 0.5 ) 
		return round($bytes/1048576, 0) . 'MB'; 
	else if( $bytes / 1024 > 5 ) 
		return round($bytes/1024,$precision) . 'KB'; 
	else if( $bytes / 1024 > 0.5 ) 
		return round($bytes/1024,0) . 'KB'; 
	else 
		return round($bytes, 0) . 'B'; 

}

function getOperatingSystemInfo()
{
	
	//Supported OS rules are maintained here
	$os = '';
	$brand = '';
	$version = '';
	$supported = false;
	
	if (PHP_OS == "Linux") 
	{
	
		$linuxDistros = array(
			'Ubuntu' => '/etc/lsb-release',
			'Debian' => '/etc/debian_version',
			'SuSE' =>   '/etc/SuSE-release', 
			'Redhat' => '/etc/redhat-release',
			'Slackware' => '/etc/slackware-release',
			'Mandrake' => '/etc/mandrake-release',
			'Yellow Dog' => '/etc/yellowdog-release',
			'Sun' => '/etc/sun-release',
			'Unknown' => '/etc/release',
			'Gentoo' => '/etc/gentoo-release',
			'United' => '/etc/UnitedLinux-release',
		);
		
		foreach($linuxDistros as $k => $file) 
		{
		
			if(!@file_exists($file)) 
			{
			
				continue;
			
			}
			
			if($k != 'Ubuntu')
			{
				$os = @file_get_contents($file);
				$os = trim(str_replace(array('"', "'", '<', '>'), '', $os));
				$brand = $k;
			}
			
			if($k == 'Ubuntu')
			{
				$os = @file_get_contents($file);
				
				if(strpos ( $os , "DISTRIB_RELEASE" ))
				{
				
					$os = trim(str_replace(array('"', "'", '<', '>'), '', $os));
					$brand = $k;
				}
				
				else
				{ 
					continue;
				}
			}
			
			
			if( $k == 'SuSE' )
			{
                $result = preg_match("/VERSION = (.*)/", $os, $m);
				$version = $m[1];
				//$os = $brand . ' ' . $version;
				if( version_compare($version, '11', '>=') )
				{

					$supported = true;

				}

			} 
			elseif( $k == 'Debian' )
			{

				$version = $os;
				$os = 'Debian ' . $version;
				if( version_compare($version, '5.0', '>=') )
				{

					$supported = true;

				}

			} 
			elseif( $k == 'Ubuntu' )
			{
				
				$releaseArray = @parse_ini_file($file);
				$version = $releaseArray['DISTRIB_RELEASE'];
				$os = $releaseArray['DISTRIB_DESCRIPTION'];
				if( version_compare($version, '9', '>=') )
				{
					
					$supported = true;
					
				}
				
			}
			elseif( $k == 'Redhat' )
			{
				//some brands have sub distros
				
				if( stripos($os,'CentOS') !== false )
				{
					
					$brand = 'CentOS';
					$posVersionStart = strpos($os,'release')!==false?strpos($os,'release')+7:null;
					$posVersionEnd = strpos($os,'(')!==false?strpos($os,'('):strlen($os);
					$version = 0;                                                                   
					
					if( $posVersionEnd !== null)                                                       
					{
						
						$version = trim( substr($os, $posVersionStart, $posVersionEnd-$posVersionStart) );
						
					} 
					
					if( version_compare($version, '5', '>=') )
					{

						$supported = true;

					}
					
				}
				elseif( stripos($os,'Fedora') !== false )
				{
					
					$brand = 'Fedora';
					$posVersionStart = strpos($os,'release')!==false?strpos($os,'release')+7:null;
					$posVersionEnd = strpos($os,'(')!==false?strpos($os,'('):strlen($os);
					$version = 0;                                                                   
					
					if( $posVersionEnd !== null)                                                       
					{
						
						$version = trim( substr($os, $posVersionStart, $posVersionEnd-$posVersionStart) );
						
					} 
					
					if( version_compare($version, '12', '>=') )
					{

						$supported = true;

					}
					
				}
				else
				{
					
					$posVersionStart = strpos($os,'release')!==false?strpos($os,'release')+7:null;
					$posVersionEnd = strpos($os,'(')!==false?strpos($os,'('):strlen($os);
					$version = 0;                                                                   
					
					if( $posVersionEnd !== null)                                                       
					{
						
						$version = trim( substr($os, $posVersionStart, $posVersionEnd-$posVersionStart) );
						
					} 
					
					if( version_compare($version, '5', '>=') )
					{

						$supported = true;

					}
					
				}
				
				
			}
			break;
		
		}
	} 
	elseif (PHP_OS == "FreeBSD") 
	{
	
		$release = `uname -r`;
		$os = "FreeBSD $release";
		$brand = PHP_OS;
		$supported = true;
	
	}
	elseif (PHP_OS == "Darwin") 
	{

		$release = `uname -r`;
		$os = "Mac OS X (Darwin $release)";
        $brand = PHP_OS;
		$supported = true;
		
	}
	
	$operatingSystemInfo = array( 'operatingSystem' => $os, 'brand' => $brand, 'version' => $version, 'supported' => $supported );
	return $operatingSystemInfo;
	
}

function widePrint_r( $data, $keyWidthArray = null )
{
	
	if( !is_array($data) || count($data) == 0 )
		return;
	$defaultOutputWidth = 160;
	if( $keyWidthArray == null )
	{
		
		$keyWidthArray = array();
		$dataKeys = array_keys($data);
		$keys = array_keys($data[$dataKeys[0]]);
		$colWidth = floor($defaultOutputWidth/count($keys));
		foreach( $keys as $k )
			$keyWidthArray[$k] = '-' . $colWidth;
		
	}
	$keys = array_keys($keyWidthArray);
	$lineFormat = '';
	foreach( $keyWidthArray as $k => &$v )
		$lineFormat .= " %" . $v . "." . (strpos($v,'-')!==false?substr($v,1):$v) . "s |";
	$result = call_user_func_array('sprintf', array_merge((array)$lineFormat, $keys)) . "\n";
	foreach( $data as &$row )
	{

	    $rowSpecificFields = array();
		foreach( $keys as &$k)
		{
			if( is_string($row[$k]) || is_numeric($row[$k]) )
			{
				//nothing to do
			}	
			elseif( is_array($row[$k]) && isset($row[$k][0]) && is_string($row[$k][0]) )
				$row[$k] = implode(', ', (Array)$row[$k]);
			elseif( is_object($row[$k]) && is_array((Array)$row[$k]) && is_string($row[$k][0]) )
				$row[$k] = implode(', ', (Array)$row[$k]);
			else
			{
				Zend_Registry::get('log')->debug( "\n" . print_r($row[$k], true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$row[$k] \n");
				
				$row[$k] = 'complex array';
			}
			$rowSpecificFields[] = str_replace(array("\n","\r"),'',$row[$k]);
		}
		$result .= call_user_func_array('sprintf', array_merge((array)$lineFormat, $rowSpecificFields)) . "\n";
	
	}
	return $result;

}


function _checkEximSupport($check) {

        // If DKIM supported
        $eximSupport = `/usr/local/atmail/mailserver/bin/exim -bV | grep "Support for"`;

		// Take out the \n
        $eximSupport = trim($eximSupport);

        $modules = explode(" ", $eximSupport);

        foreach($modules as $module)    {

                if($check == $module)
                        return 1;

        }

        return 0;

}

function fetchLatestVersion()
{
	
	$dlid = Zend_Registry::get('config')->reg['downloadId'];
	$expiry = Zend_Registry::get('config')->reg['expiry'];
	$p = base64_encode(serialize(getOperatingSystemInfo()));
	$v = Zend_Registry::get('config')->global['version'];
	//calculate seats taken grand total
	$u = 0;
	foreach( Zend_Registry::get('w4m7bYt0hYUduM2r2lrrIuY0zgKG4A3vvftl4Nb0CWfW') as $ui )
		$u += ($ui>0?$ui:0);
	$t = Zend_Registry::get('config')->global['install_type'];
	$tmp = dirname(dirname(dirname(__FILE__))) . "/tmp";
	
	$url = "http://atmail.com/latestversion.php?dlid=$dlid&u=$u&p=$p&v=$v&t=$t";
	// get latest version, check cache first to improve performance ( every 60mins refresh )
   
	if(!$res = Atmail_Cache::fetch(Atmail_Cache::TYPE_VERSION, $url))
	{
		try 
		{

			$res = @file_get_contents($url);
			if( $res !== false )
				Atmail_Cache::store(Atmail_Cache::TYPE_VERSION, $url, $res);
	    
		} 
		catch( Exception $e ) 
		{
		
			if( $e->getMessage() == 'cache_dir is not writable' )
				Zend_Registry::get('log')->debug($e->getMessage() . ' : Cache disabled.');
			throw $e;

	    }

	}
	return $res;
	
}

function getVersionCurrentLocalCodebase()
{
	// see if we can retrieve from the current working directory
	// else down one dir (client
	// else use approot
	if (is_file('./.VERSION') )	$cwd = './';
	else if (is_file('../.VERSION') ) $cwd = '../';
	else if (is_file(APP_ROOT . '.VERSION') ) $cwd = APP_ROOT;
	else if (is_file('webmail/.VERSION') ) $cwd = 'webmail/';
	else return false;

	// attempt to fetch code base revision
	if(is_file($cwd . '.VERSION.revision'))
	{
		$revision = file_get_contents( $cwd . '.VERSION.revision');
	}
	else
	{
		try
		{

			$cache_file = $cwd . '.VERSION.revision.cache';
			$cache_life = '86400'; // caching time, in seconds (1 day)

			$filemtime = @filemtime($cache_file);  // returns FALSE if file does not exist
			if (!$filemtime or (time() - $filemtime >= $cache_life))
			{

				// update the revision
				try {
					list($revision, $junk) = explode("\n", @`svnversion | grep -Eo "[0-9]+"`);
				}
				catch(Exception $e)
				{
					$revision = '';
				}
				file_put_contents($cache_file, $revision);
			}
			else
			{
				$revision = file_get_contents( $cache_file );
			}
		}
		catch(Exception $e)
		{
			$revision = '';
		}
	}
	$revision = trim($revision);
	
	if($revision == '')
	{
		$revision = '0';
	}
	$version = trim(file_get_contents( $cwd . '.VERSION')) . '.' . $revision;

	if ( false !== $version )
	{
		return trim($version);
	}
	else
	{
		return false;
	}
}

function generateVersionString($version, $fullString = false)
{
	if($version == '') 
	{
		return '';
	}
	
	$versionExplode = explode('.', $version, 4);
	$major = 1;
	$minor = 0;
	$build = 0;
	$revision = 0;
	$description = '';

	if(isset($versionExplode[0]))
	{
		$major = $versionExplode[0];
	}
	if(isset($versionExplode[1]))
	{
		$minor = $versionExplode[1];
	}
	if(isset($versionExplode[2]))
	{
		$build = $versionExplode[2];
	}
	if(isset($versionExplode[3]))
	{
		$revision = $versionExplode[3];
	}
	if(strpos($revision, '-') !== false)
	{
		list($revision, $description) = explode('-', $revision, 2);
	}
	$minorLength = strlen($minor) - 1;

	for($a=$minorLength; $a>0; $a-- )
	{
		if($minor[$a] != '0')
		{
			break;
		}
	}
	$minor = substr($minor, 0, $a + 1);
	$build = intval($build);
	$revision = intval($revision);

	if($build > 1000) {
		$revision = $build;
		$build = 0;
	}
	
	return $major . ( $minor ? '.' . $minor : '.0') . ( $build ? '.' . $build : '.0') . ($fullString ? ( $revision ? '.' . $revision : '') : '');
}

// only use this function from outside of the controllers/views
function pluginCall($event, &$args=array())
{
	try {
		foreach (Zend_Controller_Front::getInstance()->getPlugins() as $plugin)  {
			if ($plugin instanceof Atmail_Controller_Plugin && method_exists($plugin, $event)) {
				$plugin->$event($args);
			}
		}
	} catch (Exception $e) {}
}
