<?php
/**
 * e107 website system
 *
 * Copyright (C) 2008-2010 e107 Inc (e107.org)
 * Released under the terms and conditions of the
 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
 *
 * Admin Log Handler
 * 
 * USAGE: 
 * 
 * @example Log and Add to Message Handler: e107::getAdminLog()->addSuccess("Successfully executed")->save('PREF_01');
 * @example Log Only: e107::getAdminLog()->addSuccess("Successfully executed",false)->save('PREF_01');
 * @example Log Array Diff: e107::getAdminLog()->addArray($array1, $array2)->save('PREF_01');
 * @example Log Array Diff and Add to Message Handler: e107::getAdminLog()->addArray($array1, $array2, E_MESSAGE_ERROR )->save('PREF_01');
 *
*/

if (!defined('e107_INIT'))
{
	exit;
}

define('LOG_MESSAGE_NODISPLAY', 	'nodisplay');

/**
 *	Admin logging class.
 *
 *	@package	e107
 *	@subpackage	e107_handlers
 *	@version 	$Id$;
 *  @author 	e107steved
 */
class e_admin_log
{

	/**
	 * Contains default class options, plus any that are overidden by the constructor
	 *
	 * @var array
	 */
	protected	$_options = array('log_level'=>2, 'backtrace'=>false, );

	protected	$rldb = NULL; // Database used by logging routine
	
	
	
	protected 	$logFile = null;
	/**
	 * Log messages
	 * @var array
	 */
	protected $_messages;
	
	
	protected $_allMessages; // similar to $_messages except it is never flushed.


	protected $_current_plugin = null;

	private $_roll_log_active = false;

	private $_roll_log_days = 0;
	

	/**
	 * Constructor. Sets up constants and overwrites default options where set.
	 *
	 * @param array $options
	 * @return null
	 */
	public function __construct($options = array())
	{
		if(!empty($options))
		{
			foreach ($options as $key=>$val)
			{
				$this->_options[$key] = $val;
			}
		}

		if(!defined('E_LOG_INFORMATIVE'))
		{


			define('E_LOG_INFORMATIVE', 0); // Minimal Log Level, including really minor stuff
			define('E_LOG_NOTICE', 1); // More important than informative, but less important than notice
			define('E_LOG_WARNING', 2); // Not anything serious, but important information
			define('E_LOG_FATAL', 3); //  An event so bad your site ceased execution.
			define('E_LOG_PLUGIN', 4); // Plugin information

			// Logging actions
			define('LOG_TO_ADMIN', 1);
			define('LOG_TO_AUDIT', 2);
			define('LOG_TO_ROLLING', 4);

			// User audit logging (intentionally start at 10 - stick to 2 digits)
			// The last two digits must match that for the corresponding log message
			define('USER_AUDIT_ADMIN', 10); // User data changed by admin
			define('USER_AUDIT_SIGNUP', 11); // User signed up
			define('USER_AUDIT_EMAILACK', 12); // User responded to registration email
			define('USER_AUDIT_LOGIN', 13); // User logged in
			define('USER_AUDIT_LOGOUT', 14); // User logged out
			define('USER_AUDIT_NEW_DN', 15); // User changed display name
			define('USER_AUDIT_NEW_PW', 16); // User changed password
			define('USER_AUDIT_NEW_EML', 17); // User changed email
			define('USER_AUDIT_PW_RES', 18); // Password reset/resent activation email
			define('USER_AUDIT_NEW_SET', 19); // User changed other settings
			define('USER_AUDIT_ADD_ADMIN', 20); // User added by admin
			define('USER_AUDIT_MAIL_BOUNCE', 21); // User mail bounce
			define('USER_AUDIT_BANNED', 22); // User banned
			define('USER_AUDIT_BOUNCE_RESET', 23); // User bounce reset
			define('USER_AUDIT_TEMP_ACCOUNT', 24); // User temporary account

		}
		// Init E_MESSAGE_* constants if not already done
		// e107::getMessage(); - just include, message handler is creating session in construct
		// it breaks stuff (see class2 - language detection and comments)
		require_once(e_HANDLER.'message_handler.php');
		$this->_messages = array();
		$this->_allMessages = array();

		$pref = e107::getPref();
		$this->_roll_log_active = (bool) varset($pref['roll_log_active']);
		$this->_roll_log_days = (int) varset($pref['roll_log_days']);


	}

	/**
	 * @deprecated
	 * BC Alias of add(); 
	 */
	public function log_event($event_title, $event_detail, $event_type = E_LOG_INFORMATIVE , $event_code = '')
	{
		trigger_error('<b>'.__METHOD__.' is deprecated.</b> Use add() instead.', E_USER_DEPRECATED);
		return $this->add($event_title, $event_detail, $event_type, $event_code);	
	}


	/**
	 * Save all logs in the queue to the database and render any unhidden messages with the message handler.
	 * @see alias flushMessages() method below.
	 * @param string $logTitle - title for log entry eg. 'PREF_01'
	 * @param int $logImportance [optional] default E_LOG_INFORMATIVE - passed directly to admin log
	 * @param string $logEventCode [optional] - passed directly to admin log
	 * @param string $mstack [optional] message stack passed to message handler
	 * @param int LOG_TO_ADMIN|LOG_TO_ROLLING|LOG_TO_AUDIT
	 * @return \e_admin_log
	 */
	public function save($logTitle, $logImportance = E_LOG_INFORMATIVE, $logEventCode = '', $mstack = false, $target = LOG_TO_ADMIN)
	{
		return $this->flushMessages($logTitle, $logImportance, $logEventCode, $mstack, $target);
	}


	/**
	 * Add and Save an event into the admin, rolling or user log.
	 * @param string $event_title
	 * @param string|array $event_detail
	 * @param integer $event_type [optional] Log level eg. E_LOG_INFORMATIVE, E_LOG_NOTICE, E_LOG_WARNING, E_LOG_FATAL
	 * @param string $event_code [optional] - eg. 'BOUNCE'
	 * @param integer $target [optional]  LOG_TO_ADMIN, LOG_TO_AUDIT, LOG_TO_ROLLING
	 * @param null $userData
	 * @return e_admin_log
	 *
	 * Alternative admin log entry point - compatible with legacy calls, and a bit simpler to use than the generic entry point.
	 * ($eventcode has been added - give it a reference to identify the source module, such as 'NEWS_12' or 'ECAL_03')
	 * We also log everything (unlike 0.7, where admin log and debug stuff were all mixed up together)
	 *
	 * For multi-lingual logging (where the event title is shown in the language of the current user), LAN defines may be used in the title
	 *
	 * For generic calls, leave $event_code as empty, and specify a constant string STRING_nn of less than 10 characters for the event title
	 * Typically the 'STRING' part of the name defines the area originating the log event, and the 'nn' is a numeric code
	 * This is stored as 'LAN_AL_STRING_NN', and must be defined in a language file which is loaded during log display.
	 */
	public function add($event_title, $event_detail, $event_type = E_LOG_INFORMATIVE , $event_code = '', $target = LOG_TO_ADMIN, $userData=null )
	{
		if (empty($event_code))
		{
			if (strlen($event_title) <= 12)
			{ // Assume the title is actually a reference to the event
				$event_code = $event_title;
				$event_title = 'LAN_AL_'.$event_title;
			}
			else
			{
				$event_code = 'ADMIN';
			}
		}

		if (!is_array($event_detail))
		{
			// auto-format long details -
			$event_detail = str_replace("\n", '[!br!]', $event_detail);
		}

		if ($this->_options['backtrace'] == true)
		{
			$event_detail .= "\n\n".debug_backtrace(false);
		}
		
		
		$this->addEvent($event_type, -1, $event_code, $event_title, $event_detail, FALSE, $target, $userData);

		return $this;
	}

	/**
	 * Enable/Disable the Rolling Log.
	 * @param $bool
	 */
	public function rollingLog($bool)
	{
		$this->_roll_log_active = (bool) $bool;
	}

	/**
	 * Alias for deprecated e_log_event
	 * @param int    $importance  - importance of event - 0..4 or so
	 * @param mixed  $source_call - either:	string identifying calling file/routine
	 *      or:		a number 0..9 identifying info to log from debug_backtrace()
	 *		or:		empty string, in which case first entry from debug_backtrace() logged
	 *		or:		an array, assumed to be from passing debug_backtrace() as a parameter, in which case relevant
	 *				 information is extracted and the argument list from the first entry logged
	 *		or:		-1, in which case no information logged
	 * @param string $eventcode - abbreviation listing event type
	 * @param string $event_title -  title of event - pass standard 'LAN_ERROR_nn' defines to allow language translation
	 * @param string $explain -  detail of event
	 * @param bool   $finished -  if TRUE, aborts execution
	 * @param int    $target_logs - table to save to : LOG_TO_ADMIN,  LOG_TO_AUDIT,  LOG_TO_ROLLING
	 * @param null   $userData - attribute user to log entry. array('user_id'=>2, 'user_name'=>'whatever');
	 *
	 * @return null
	 */
	public function addEvent($importance, $source_call, $eventcode = 'GEN', $event_title = 'Untitled', $explain = '', $finished = FALSE, $target_logs = LOG_TO_AUDIT, $userData=null )
	{
		$e107 = e107::getInstance();
		$tp = e107::getParser();

		list($time_usec, $time_sec) = explode(' ', microtime(FALSE)); // Log event time immediately to minimise uncertainty
		$time_usec = $time_usec * 1000000;

		if ($this->rldb === null) // Better use our own db - don't know what else is going on
		{
			$this->rldb = e107::getDb('adminlog');
		}

		if (is_bool($target_logs))
		{ // Handle the legacy stuff for now - some old code used a boolean to select admin or rolling logs
			$target_logs = $target_logs ? LOG_TO_ADMIN : LOG_TO_ROLLING;
		}

		//---------------------------------------
		// Calculations common to all logs
		//---------------------------------------

		$userid 		= deftrue('USER') ? USERID : 0;
		$userstring 	= deftrue('USER') ? USERNAME : 'LAN_ANONYMOUS';
		$userIP 		= e107::getIPHandler()->getIP(FALSE);

		if(!empty($userData['user_id']))
		{
			$userid = $userData['user_id'];
		}

		if(!empty($userData['user_name']))
		{
			$userstring  = $userData['user_name'];
		}

		if(!empty($userData['user_ip']))
		{
			$userIP  = $userData['user_ip'];
		}

		$importance 	= $tp->toDB($importance, true, false, 'no_html');
		$eventcode 		= $tp->toDB($eventcode, true, false, 'no_html');

		if (is_array($explain))
		{
			/*
			$line = '';
			$spacer = '';
			foreach ($explain as $k=>$v)
			{
				$line .= $spacer.$k.'=>'.$v;
				$spacer = '[!br!]';
			}
			$explain = $line;
			unset($line);
			*/
			$explain = str_replace("\n",'[!br!]',print_r($explain,true));

		}


		$explain = e107::getDb()->escape($tp->toDB($explain, true, false, 'no_html'));
		$event_title = $tp->toDB($event_title, true, false, 'no_html');

		//---------------------------------------
		// 			Admin Log
		//---------------------------------------
		if ($target_logs & LOG_TO_ADMIN) // Admin log - assume all fields valid
		{
		//	$qry = " null, ".intval($time_sec).','.intval($time_usec).", '{$importance}', '{$eventcode}', {$userid}, '{$userIP}', '{$event_title}', '{$explain}' ";

			$adminLogInsert = array(
				'dblog_id'			=> null,
				'dblog_type'		=> $importance,
				'dblog_eventcode'	=> $eventcode,
				'dblog_datestamp'	=> time(),
				'dblog_microtime'	=> (int) $time_usec,
				'dblog_user_id'		=> $userid,
				'dblog_ip'			=> $userIP,
				'dblog_title'		=> $event_title,
				'dblog_remarks'		=> $explain
			);

			$this->rldb->insert('admin_log', $adminLogInsert);
		}

		//---------------------------------------
		// 			Audit Log
		//---------------------------------------
		// Add in audit log here

		//---------------------------------------
		// 			Rolling Log
		//---------------------------------------
		if (($target_logs & LOG_TO_ROLLING) && $this->_roll_log_active)
		{ //	Rolling log

			// 	Process source_call info
			//---------------------------------------
			if (is_numeric($source_call) && ($source_call >= 0))
			{
				$back_count = 1;
				$i = 0;
				if (($source_call == ''))
				{
					$back_count = $source_call + 1;
					$source_call = debug_backtrace();
					$i = 1; // Don't want to print the entry parameters to this function - we know all that!
				}
			}

			if (is_array($source_call))
			{ // Print the debug_backtrace() array
				while ($i < $back_count)
				{
					$source_call[$i]['file'] = $e107->fix_windows_paths($source_call[$i]['file']); // Needed for Windoze hosts.
					$source_call[$i]['file'] = str_replace($e107->file_path, '', $source_call[$i]['file']); // We really just want a e107 root-relative path. Strip out the root bit
					$tmp = $source_call[$i]['file']. '|' .$source_call[$i]['class'].$source_call[$i]['type'].$source_call[$i]['function']. '@' .$source_call[$i]['line'];
					foreach ($source_call[$i]['args'] as $k=>$v)
					{ // Add in the arguments
						$explain .= '[!br!]' .$k. '=' .$v;
					}
					$i++;
					if ($i < $back_count)
					{
						$explain .= "[!br!]-------------------";
					}
					if (!isset($tmp1))
					{
						$tmp1 = $tmp;
					} // Pick off the immediate caller as the source
				}
				if (isset($tmp1))
				{
					$source_call = $tmp1;
				}
				else
				{
					$source_call = 'Root level';
				}
			}
			else
			{
				$source_call = $e107->fix_windows_paths($source_call); // Needed for Windoze hosts.
				$source_call = str_replace($e107->file_path, '', $source_call); // We really just want a e107 root-relative path. Strip out the root bit
				$source_call = $tp->toDB($source_call, true, false, 'no_html');
			}
			// else $source_call is a string

			// Save new rolling log record

			$insertArr = array(
					'dblog_id'          => 0,
					'dblog_datestamp'   => 0,
					'dblog_microtime'   => 0,
					'dblog_type'        => '',
					'dblog_eventcode'   => '',
					'dblog_user_id'     => '',
					'dblog_user_name'   => '',
					'dblog_ip'          => '',
					'dblog_caller'      => '',
					'dblog_title'       => '',
					'dblog_remarks'     => ''
			);




			$this->rldb->insert('dblog', '0, ' .intval($time_sec).', '.intval($time_usec).", '{$importance}', '{$eventcode}', {$userid}, '{$userstring}', '{$userIP}', '{$source_call}', '{$event_title}', '{$explain}' ");

			// Now delete any old stuff
			if(!empty($this->_roll_log_days))
			{
				$this->rldb->delete('dblog', "dblog_datestamp < '".intval(time() - ($this->_roll_log_days * 86400))."' ");
			}
		}

		if ($finished)
		{
			exit;
		} // Optional abort for all logs

		return null;

	}

	/**
	 * @deprecated - use add() method instead or addEvent() as a direct replacement.
	 */
	public function e_log_event($importance, $source_call, $eventcode = 'GEN', $event_title = 'Untitled', $explain = '', $finished = FALSE, $target_logs = LOG_TO_AUDIT, $userData=null )
	{
		trigger_error('<b>'.__METHOD__.' is deprecated.</b> Use add()->save() or addEvent() instead.', E_USER_DEPRECATED);

		return $this->addEvent($importance, $source_call, $eventcode, $event_title, $explain, $finished, $target_logs, $userData);
	}


	/**
	 * @param $plugdir
	 * @return $this
	 */
	public function setCurrentPlugin($plugdir)
	{
		$this->_current_plugin = $plugdir;

		return $this;
	}

	/**--------------------------------------
	 *        USER AUDIT ENTRY
	 *--------------------------------------
	 *    Log user-related events
	 * @param string $event_type
	 * @param array|string $event_data is an array of data fields whose keys and values are logged (usually user data, but doesn't have to be - can add messages here)
	 * @param int $id user-id
	 * @param string $u_name user-name
	 *    both $id and $u_name are left blank except for admin edits and user login, where they specify the id and login name of the 'target' user
	 *
	 * @return bool
	 */
	function user_audit($event_type, $event_data, $id = null, $u_name = '')
	{
		list($time_usec, $time_sec) = explode(' ', microtime()); // Log event time immediately to minimise uncertainty

		$time_usec = $time_usec * 1000000;

		if(!is_numeric($event_type))
		{
			$title = 'User Audit Event-Type Failure: ';
			$title .= (string) $event_type;
			$debug = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,4);
			$debug[0] = e_REQUEST_URI;

			$this->addEvent(4, $debug[1]['file']. '|' .$debug[1]['function']. '@' .$debug[1]['line'], 'USERAUDIT', $title, $debug, FALSE);
			return false;
		}

		// See whether we should log this
		$user_logging_opts = e107::getConfig()->get('user_audit_opts');
		
		if (!isset($user_logging_opts[$event_type]))  // Finished if not set to log this event type
		{
			return false;
		}

		if(empty($event_data))
		{
			$backt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,4);
			$event_data = $backt;
		}


		if($this->rldb == null)
		{
			$this->rldb = e107::getDb('rldb'); // Better use our own db - don't know what else is going on
		}

		if(!empty($id))
		{
			 $userid = $id;
		}
		else
		{
			 $userid = (USER === true) ? USERID : 0;
		}

		if(!empty($u_name))
		{
			 $userstring = $u_name;
		}
		else
		{
			$userstring = (USER === true ? USERNAME : 'LAN_ANONYMOUS');
		}

		$userIP = e107::getIPHandler()->getIP(false);

		$eventcode = 'USER_'.$event_type;

		$title = 'LAN_AUDIT_LOG_0'.$event_type; // This creates a string which will be displayed as a constant

		$insertQry = array(
			'dblog_id'          => 0,
			'dblog_datestamp'   => intval($time_sec),
			'dblog_microtime'   => intval($time_usec),
			'dblog_eventcode'   => $eventcode,
			'dblog_user_id'     => $userid,
			'dblog_user_name'   => $userstring,
			'dblog_ip'          => $userIP,
			'dblog_title'       => $title,
			'dblog_remarks'     => print_r($event_data,true),
		);

		if($this->rldb->insert('audit_log', $insertQry))
		{
			return true;
		}

		return false;
	}


	/* Legacy function probably not needed
	function get_log_events($count = 15, $offset)
	{
		global $sql;
		$count = intval($count);
		return "Not implemented yet";
	}
	*/



	/**
	 * Removes all events older than $days, or truncates the table if $days == false
	 *
	 * @param integer|false $days
	 * @return void
	 */
	public function purge_log_events($days)
	{
		global $sql;
		if ($days == false)
		{ // $days is false, so truncate the log table
			$sql->gen('TRUNCATE TABLE #dblog ');
		}
		else
		{ // $days is set, so remove all entries older than that.
			$days = intval($days);
			$mintime = $days * 24 * 60 * 60;
			$time = time() - $mintime;
			$sql->delete('dblog', "WHERE `dblog_datestamp` < {$time}", true);
		}
	}

	//--------------------------------------
	//		HELPER ROUTINES
	//--------------------------------------
	/**
	 *	Generic routine to log changes to an array. Only elements in $new are checked
	 *
	 *	@param array $new - most recent data being saved
	 *	@param array $old existing data - array is updated with changes, but not saved anywhere
	 *	@param string $event - LAN define or string used as title in log
	 *
	 *	@return bool true if changes found and logged, false otherwise.
	 */
	function logArrayDiffs($new, $old, $event, $logNow = true)
	{
		// $changes = array();
				
		$changes = array_diff_recursive($new,$old);
		
		if (count($changes))
		{
			if($logNow) 
			{
				$this->add($event, print_r($changes,true), E_LOG_INFORMATIVE, '');
			}
			else
			{
				$this->logMessage($changes, LOG_MESSAGE_NODISPLAY, E_MESSAGE_INFO);
			}

			return TRUE;
		}
		
		return FALSE;
	}


	/**
	 *	Logs an entry with all the data from an array, one field per line.
	 *  @deprecated Use e107::getLog()->addArray($arrayData)->save($event);
	 *	@param string $event - LAN define or string used as title in log
	 *	@param array $target - data to be logged
	 *	@param string $extra - if non-empty, it goes on the first line.
	 *	@param array $niceNames - Normally data is logged in the format keyname=>value, one per line.
	 *		If the $niceName array exists and has a definition, the 'nice Name' is displayed instead of the key name
	 *
	 *	@return null
	 */
	public function logArrayAll($event, $target, $extra = '', $niceNames = NULL)
	{
		trigger_error('<b>'.__METHOD__.' is deprecated.</b> Use e107::getLog()->addArray($arrayData)->save($event) instead.', E_USER_DEPRECATED); // no LAN
		if($extra == '' && $niceNames == null)
		{
			return $this->add($event, $target, E_LOG_INFORMATIVE, '');	// supports arrays
			
		}
		
		
		$logString = '';
		if ($extra)
		{
			$logString = $extra.'[!br!]';
		}
		$spacer = '';
		$checkNice = ($niceNames != NULL) && is_array($niceNames);
		foreach ($target as $k=>$v)
		{
			if ($checkNice && isset($niceNames[$k]['niceName']))
			{
				$logString .= $spacer.$niceNames[$k]['niceName'].'=>'.$v;
			}
			else
			{
				$logString .= $spacer.$k.'=>'.$v;
			}
			$spacer = '[!br!]';
		}
		$this->add($event, $logString, E_LOG_INFORMATIVE, '');

		return null;
	}

	/**
	 *	The next two routines accept and buffers messages which are destined for both admin log and message handler
	 */

	/**
	 *	Add a message to the queue
	 *
	 *	@param string|array $text - the message text for logging/display
	 *	@param int $type - the 'importance' of the message. E_MESSAGE_SUCCESS|E_MESSAGE_ERROR|E_MESSAGE_INFO|E_MESSAGE_DEBUG|E_MESSAGE_NODISPLAY
	 *				(Values as used in message handler, apart from the last, which causes the message to not be passed to the message handler
	 *	@param boolean|int $logLevel - TRUE to give same importance as for message display. FALSE to not log.
	 *										one of the values specified for $mesLevel to determine the prefix attached to the log message
	 *  @param boolean $session add session message
	 *
	 *	@return e_admin_log
	 */
	public function logMessage($text, $type = '', $logLevel = TRUE, $session = FALSE)
	{
		
		if(is_array($text))
		{
			$text = print_r($text,true);	
		}
		elseif(empty($text))
		{
			$bt = debug_backtrace(true);
			e107::getMessage()->addDebug('Log Message was empty: ' .print_a($bt[1],true));
			return $this;	// changing to text will break chained methods. 
		} 
		
		if(!$type)
		{
			$type = E_MESSAGE_INFO;
		}
		if($logLevel === TRUE)
		{
			$logLevel = $type;
		}
		
		$logArray = array('message' => $text, 'dislevel' => $type, 'loglevel' => $logLevel, 'session' => $session, 'time'=>time());
		
		$this->_messages[] = $logArray;
		$this->_allMessages[] = $logArray;
		
		return $this;
	}



	/**
	 * @deprecated
	 * BC Alias for addSuccess(); 
	 */
	public function logSuccess($text, $message = true, $session = false)
	{
		trigger_error('<b>'.__METHOD__.' is deprecated.</b> Use e107::getLog()->addSuccess($arrayData)->save($event) instead.', E_USER_DEPRECATED); // no LAN
		return $this->addSuccess($text,$message,$session);	
	}



	/**
	 * @deprecated
	 * BC Alias for addError(); 
	 */
	public function logError($text, $message = true, $session = false)
	{
		trigger_error('<b>'.__METHOD__.' is deprecated.</b> Use e107::getLog()->addError($arrayData)->save($event) instead.', E_USER_DEPRECATED); // no LAN
		return $this->addError($text,$message,$session);
	}


	/**
	 * Add a success message to the log queue
	 *
	 * @param string|array $text
	 * @param boolean $message if true - register with eMessage handler
	 * @param boolean $session add session message
	 * @return e_admin_log
	 */
	public function addSuccess($text, $message = true, $session = false)
	{
		return $this->logMessage($text, ($message ? E_MESSAGE_SUCCESS : LOG_MESSAGE_NODISPLAY), E_MESSAGE_SUCCESS, $session);
	}


	/**
	 * Add an error message to the log queue
	 *
	 * @param string $text
	 * @param boolean $message if true (default) - register with eMessage handler, set to false to hide. 
	 * @param boolean $session add session message
	 * @return e_admin_log
	 */
	public function addError($text, $message = true, $session = false)
	{
		return $this->logMessage($text, ($message ? E_MESSAGE_ERROR : LOG_MESSAGE_NODISPLAY), E_MESSAGE_ERROR, $session);
	}


	/**
	 * Add an Debug message to the log queue
	 *
	 * @param string $text
	 * @param boolean $message if true (default) - register with eMessage handler, set to false to hide . 
	 * @param boolean $session add session message
	 * @return e_admin_log
	 */
	public function addDebug($text, $message = true, $session = false)
	{
		return $this->logMessage($text, ($message ? E_MESSAGE_DEBUG : LOG_MESSAGE_NODISPLAY), E_MESSAGE_DEBUG, $session);
	}


	/**
	 * Add an Warning message to the log queue
	 *
	 * @param string $text
	 * @param boolean $message if true (default) - register with eMessage handler, set to false to hide. 
	 * @param boolean $session add session message
	 * @return e_admin_log
	 */
	public function addWarning($text, $message = true, $session = false)
	{
		return $this->logMessage($text, ($message ? E_MESSAGE_WARNING : LOG_MESSAGE_NODISPLAY), E_MESSAGE_WARNING, $session);
	}


	/**
	 * Add an array to the log queue
	 * @param $array
	 * @param null $oldArray (optional) - when included, only the changes between the arrays is saved.
	 * @param string $type (optional) default: LOG_MESSAGE_NODISPLAY. or E_MESSAGE_WARNING, E_MESSAGE_DEBUG, E_MESSAGE_SUCCESS
	 * @param bool $session
	 * @return e_admin_log
	 */
	public function addArray($array, $oldArray= null, $type = LOG_MESSAGE_NODISPLAY , $session = false)
	{
		if(is_array($oldArray)) 
		{
			$text = array_diff_recursive($array,$oldArray); // Located in core_functions.php 
			if(count($text) < 1)
			{
				$text = 'No differences found';
			}
			
		}
		else
		{
			$text = $array;	
		}
			
		return $this->logMessage($text, $type, $type, $session);	
	}

	/**
	 * Return the last row added to the log table.
	 * @param int $type
	 * @return array|string
	 */
	public function getLastLog($type=LOG_TO_ADMIN)
	{

		switch ($type)
		{
			case LOG_TO_AUDIT:
				$table = 'audit_log';
				break;

			case LOG_TO_ROLLING:
				$table = 'dblog';
				break;

			case LOG_TO_ADMIN:
			default:
				$table = 'admin_log';
		}

		$query = 'SELECT * FROM #'.$table.' ORDER BY dblog_id DESC LIMIT 1';
		return e107::getDb()->retrieve($query);

	}


	/**
	 *	Empty the messages - pass to both admin log and message handler
	 *
	 *	@param string $logTitle - title for log entry
	 *	@param int $logImportance - passed directly to admin log
	 *	@param string $logEventCode - passed directly to admin log
	 *	@param string $mstack [optional] message stack passed to message handler
	 *	@return e_admin_log
	 */
	public function flushMessages($logTitle, $logImportance = E_LOG_INFORMATIVE, $logEventCode = '', $mstack = false, $target =LOG_TO_ADMIN)
	{
		$mes = e107::getMessage();
				
		$resultTypes = array(E_MESSAGE_SUCCESS => 'Success', E_MESSAGE_ERROR => 'Fail');	// Add LANS here. Could add other codes
		$separator = '';
		$logString = '';
		foreach ($this->_messages as $m)
		{
			if ($m['loglevel'] !== FALSE)
			{
				$logString .= $separator;
				if ($m['loglevel'] == LOG_MESSAGE_NODISPLAY) { $logString .= '  '; }		// Indent supplementary messages
			// Not sure about next line - might want to log the <br /> as text, rather than it forcing a newline
				$logString .= strip_tags(str_replace(array('<br>', '<br/>', '<br />'), '[!br!]', $m['message']));
				if (isset($resultTypes[$m['loglevel']]))
				{
					$logString .= ' - '.$resultTypes[$m['loglevel']];
				}
				$separator = '[!br!]';
			}
			if ($m['dislevel'] != LOG_MESSAGE_NODISPLAY)
			{
				if($mstack) 
				{
					$mes->addStack($m['message'], $mstack, $m['dislevel'], $m['session']);
					// move to main stack OUTSIDE if needed 
				}
				else
				{
					$mes->add($m['message'], $m['dislevel'], $m['session']);
				}
			}
		}
		$this->add($logTitle, $logString, $logImportance, $logEventCode, $target);
		$this->_messages = array();		// Clear the memory for reuse

		return $this;
	}





	/**
	 * Clear all messages in 'memory'. 
	 */
	public function clear()
	{
		$this->_messages = array();	
		
		return $this;		
	}

	
	/**
	 * Save Message stack to File. 
	 */
	private function saveToFile($logTitle='', $append=false, $opts = array())
	{
		if($this->logFile == null)
		{
			 return null;
		}
				
		if(count($this->_allMessages))
		{
			$head = '  e107 CMS Log file : ' .$logTitle. '   ' .date('Y-m-d_H-i-s')."\n";
			$head .= "-------------------------------------------------------------------------------------------\n\n";		
		}
		else 
		{
			return null;
		}		

		$text = '';

		foreach($this->_allMessages as $m)
		{
			$text .= date('Y-m-d H:i:s', $m['time'])."  \t".str_pad($m['loglevel'],10, ' ',STR_PAD_RIGHT)."\t".strip_tags($m['message'])."\n";
		}
		
		$date = ($append == true) ? date('Y-m-d') : date('Y-m-d_H-i-s').'_'.crc32($text);


		
		$dir = e_LOG;

		if(empty($this->_current_plugin))
		{
			$this->_current_plugin = deftrue('e_CURRENT_PLUGIN');
		}

		if(!empty($this->_current_plugin)) // If it's a plugin, create a subfolder.
		{
			$dir = e_LOG.$this->_current_plugin. '/';
			
			if(!is_dir($dir))
			{
				if (!mkdir($dir, 0755) && !is_dir($dir))
				{
					$this->add('Directory creation Failed', sprintf('Directory "%s" was not created', $dir));
				}
			}	
		}
		
		$fileName = $dir.$date. '_' .$this->logFile. '.log';

		if(!empty($opts['filename']))
		{
			$fileName = $dir.basename($opts['filename']);
		}
		
		if($append == true)
		{
			$app = FILE_APPEND;
			if(!file_exists($fileName))
			{
				$text = $head . $text;	
			}
		}
		else 
		{
			$app = 0;
			$text = $head . $text;	
		}

		if (file_put_contents($fileName, $text, $app))
		{
			$this->_allMessages = array();
			$this->_current_plugin = null;
			return $this->logFile;
		}

		if(E107_DEBUG_LEVEL > 0 && getperms('0'))
		{
			e107::getMessage()->addDebug("Couldn't Save to Log File: ".$fileName);
		}

		$this->_current_plugin = null;

		return false;
	}	
	



	/**
	 * Set and save accumulated log to a file. 
	 * Use addDebug(), addError() or addSuccess() prior to executing.  
	 * @param string name without the extension. (ie. date prefix and .log suffix will be added automatically)
	 * @param string Title for use inside the Log file
	 * @param boolean true = append to file, false = new file each save. 
	 */
	public function toFile($name, $logTitle='',$append=false, $opts=array())
	{

		$this->logFile	= $name;
		$file = $this->saveToFile($logTitle,$append,$opts);

		$this->logFile = null;
		return $file;
	}


}
