<?php
/***************************************************************
*  Copyright notice
*
*  (c) 1999-2009 Kasper Skårhøj (kasperYYYY@typo3.com)
*  (c) 2006-2009 Sebastian Kurfürst (sebastian@garbage-group.de)
*  All rights reserved
*
*  This script is part of the TYPO3 project. The TYPO3 project is
*  free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  The GNU General Public License can be found at
*  http://www.gnu.org/copyleft/gpl.html.
*  A copy is found in the textfile GPL.txt and important notices to the license
*  from the author is found in LICENSE.txt distributed with these scripts.
*
*
*  This script is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
/**
 * Class for the record history display script (show_rechis.php)
 *
 * XHTML Compliant
 *
 * @author	Sebastian Kurfürst <sebastian@garbage-group.de>
 */


/**
 * Class for the record history display script (show_rechis.php)
 *
 * @author	Sebastian Kurfürst <sebastian@garbage-group.de>
 * @package TYPO3
 * @subpackage core
 */
class recordHistory {
		// External, static:
	var $maxSteps=20;		// Maximum number of sys_history steps to show.
	var $showDiff=1;		// display diff or not (0-no diff, 1-inline)
	var $showSubElements=1;		// on a pages table - show sub elements as well.
	var $showInsertDelete=1;		// show inserts and deletes as well

		// Internal, GPvars
	var $element;			// Element reference, syntax [tablename]:[uid]
	var $lastSyslogId;		// syslog ID which is not shown anymore
	var $returnUrl;

		// Internal
	var $changeLog;
	var $showMarked=FALSE;
	/**
	 * Constructor for the class
	 *
	 * @return	void
	 */
	function __construct()	{
			// GPvars:
		$this->element = t3lib_div::_GP('element');
		$this->returnUrl = t3lib_div::sanitizeLocalUrl(t3lib_div::_GP('returnUrl'));
		$this->lastSyslogId = t3lib_div::_GP('diff');
		$this->rollbackFields = t3lib_div::_GP('rollbackFields');
			// resolve sh_uid if set
		$this->resolveShUid();
	}

	/**
	 * Compatibility constructor.
	 *
	 * @deprecated since TYPO3 4.6 and will be removed in TYPO3 4.8. Use __construct() instead.
	 */
	public function recordHistory() {
		t3lib_div::logDeprecatedFunction();
			// Note: we cannot call $this->__construct() here because it would call the derived class constructor and cause recursion
			// This code uses official PHP behavior (http://www.php.net/manual/en/language.oop5.basic.php) when $this in the
			// statically called non-static method inherits $this from the caller's scope.
		recordHistory::__construct();
	}

	/**
	 * Main function for the listing of history.
	 * It detects incoming variables like element reference, history element uid etc. and renders the correct screen.
	 *
	 * @return	HTML		content for the module
	 */
	function main()	{
		$content = '';

			// single-click rollback
		if (t3lib_div::_GP('revert') && t3lib_div::_GP('sumUp'))	{
			$this->rollbackFields = t3lib_div::_GP('revert');
			$this->showInsertDelete = 0;
			$this->showSubElements = 0;

			$element = explode(':',$this->element);
			$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*','sys_history', 'tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($element[0], 'sys_history').' AND recuid='.intval($element[1]), '', 'uid DESC', '1');
			$record = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
			$this->lastSyslogId = $record['sys_log_uid'];

			$this->createChangeLog();
			$completeDiff = $this->createMultipleDiff();
			$this->performRollback($completeDiff);
			t3lib_utility_Http::redirect($this->returnUrl);
		}

			// save snapshot
		if (t3lib_div::_GP('highlight') && !t3lib_div::_GP('settings'))	{
			$this->toggleHighlight(t3lib_div::_GP('highlight'));
		}

		$content .= $this->displaySettings();
		if ($this->createChangeLog())	{
			if ($this->rollbackFields)	{
				$completeDiff = $this->createMultipleDiff();
				$content .= $this->performRollback($completeDiff);

			}
			if ($this->lastSyslogId)	{
				$completeDiff = $this->createMultipleDiff();
				$content .= $this->displayMultipleDiff($completeDiff);
			}
			if ($this->element)	{
				$content .= $this->displayHistory();
			}
		}
		return $content;
	}

	/*******************************
	 *
	 * database actions
	 *
	 *******************************/

	/**
	 * toggles highlight state of record
	 *
	 * @param	integer		uid of sys_history entry
	 * @return	[type]		...
	 */
	function toggleHighlight($uid)	{
		$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('snapshot','sys_history','uid='.intval($uid));
		$tmp = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
		if ($tmp['snapshot'])	{
			$tmp = 0;
		} else {
			$tmp = 1;
		}
		$updateFields = array('snapshot' => $tmp);
		$GLOBALS['TYPO3_DB']->exec_UPDATEquery('sys_history','uid='.intval($uid),$updateFields);
	}

	/**
	 * perform rollback
	 *
	 * @param	array		diff array to rollback
	 * @return	void
	 * @access private
	 */
	function performRollback($diff)	{
		if (!$this->rollbackFields)	{
			return 0;
		}

		$reloadPageFrame=0;
		$rollbackData = explode(':',$this->rollbackFields);

			// PROCESS INSERTS AND DELETES
			// rewrite inserts and deletes
		$cmdmapArray = array();
		if ($diff['insertsDeletes'])	{

			switch (count($rollbackData))	{
				case 1:	// all tables
					$data = $diff['insertsDeletes'];
					break;
				case 2:	// one record
					if ($diff['insertsDeletes'][$this->rollbackFields]) {
						$data[$this->rollbackFields] = $diff['insertsDeletes'][$this->rollbackFields];
					}
					break;
				case 3:	// one field in one record -- ignore!
					break;
			}
			if ($data)	{
				foreach ($data as $key => $action)	{
					$elParts = explode(':',$key);
					if ($action == 1)	{	// inserted records should be deleted
						$cmdmapArray[$elParts[0]][$elParts[1]]['delete'] = 1;
							// when the record is deleted, the contents of the record do not need to be updated
						unset($diff['oldData'][$key]);
						unset($diff['newData'][$key]);
					} elseif ($action == -1) {	// deleted records should be inserted again
						$cmdmapArray[$elParts[0]][$elParts[1]]['undelete'] = 1;
					}
				}
			}
		}
			// Writes the data:
		if ($cmdmapArray)	{
			$tce = t3lib_div::makeInstance('t3lib_TCEmain');
			$tce->stripslashes_values=0;
			$tce->debug=0;
			$tce->dontProcessTransformations=1;
			$tce->start(array(),$cmdmapArray);
			$tce->process_cmdmap();
			unset($tce);
			if (isset($cmdmapArray['pages']))	{
				$reloadPageFrame=1;
			}
		}

			// PROCESS CHANGES
			// create an array for process_datamap
		$diff_modified = array();
		foreach ($diff['oldData'] as $key => $value)	{
			$splitKey = explode(':',$key);
			$diff_modified[$splitKey[0]][$splitKey[1]] = $value;
		}
		switch (count($rollbackData))	{
			case 1:	// all tables
				$data = $diff_modified;
				break;
			case 2:	// one record
				$data[$rollbackData[0]][$rollbackData[1]] = $diff_modified[$rollbackData[0]][$rollbackData[1]];
				break;
			case 3:	// one field in one record
				$data[$rollbackData[0]][$rollbackData[1]][$rollbackData[2]] = $diff_modified[$rollbackData[0]][$rollbackData[1]][$rollbackData[2]];
				break;
		}
			// Removing fields:
		$data = $this->removeFilefields($rollbackData[0],$data);

			// Writes the data:
		$tce = t3lib_div::makeInstance('t3lib_TCEmain');
		$tce->stripslashes_values=0;
		$tce->debug=0;
		$tce->dontProcessTransformations=1;
		$tce->start($data,array());
		$tce->process_datamap();
		unset($tce);
		if (isset($data['pages']))	{
			$reloadPageFrame=1;
		}

			// return to normal operation
		$this->lastSyslogId = FALSE;
		$this->rollbackFields = FALSE;
		$this->createChangeLog();

			// reload page frame if necessary
		if ($reloadPageFrame)	{
			return '<script type="text/javascript">
			/*<![CDATA[*/
			if (top.content && top.content.nav_frame && top.content.nav_frame.refresh_nav)	{
				top.content.nav_frame.refresh_nav();
			}
			/*]]>*/
			</script>';
		}
	}

	/*******************************
	 *
	 * Display functions
	 *
	 *******************************/

	/**
	 * Displays settings
	 *
	 * @return	string		HTML code to modify settings
	 */
	function displaySettings()	{
			// get current selection from UC, merge data, write it back to UC
		$currentSelection = is_array($GLOBALS['BE_USER']->uc['moduleData']['history']) ? $GLOBALS['BE_USER']->uc['moduleData']['history'] : array('maxSteps' => '', 'showDiff' => 1, 'showSubElements' => 1, 'showInsertDelete' => 1);

		$currentSelectionOverride = t3lib_div::_GP('settings');
		if ($currentSelectionOverride)	{
			$currentSelection = array_merge($currentSelection,$currentSelectionOverride);
			$GLOBALS['BE_USER']->uc['moduleData']['history'] = $currentSelection;
			$GLOBALS['BE_USER']->writeUC($GLOBALS['BE_USER']->uc);
		}

			// display selector for number of history entries
		$selector['maxSteps'] = array(
			10 => 10,
			20 => 20,
			50 => 50,
			100 => 100,
			'' => 'maxSteps_all',
			'marked' => 'maxSteps_marked'
		);
		$selector['showDiff'] = array(
			0 => 'showDiff_no',
			1 => 'showDiff_inline'
		);
		$selector['showSubElements'] = array(
			0 => 'no',
			1 => 'yes',
		);
		$selector['showInsertDelete'] = array(
			0 => 'no',
			1 => 'yes',
		);
			// render selectors
		$displayCode = '';
		foreach ($selector as $key => $values)	{
			$displayCode .= '<tr><td>'.$GLOBALS['LANG']->getLL($key,1).'</td><td><select name="settings['.$key.']" onChange="document.settings.submit()" style="width:100px">';
			foreach ($values as $singleKey => $singleVal) {
				$caption = $GLOBALS['LANG']->getLL($singleVal,1)?$GLOBALS['LANG']->getLL($singleVal,1):$singleVal;
				$displayCode .= '<option value="'.$singleKey.'" '.(($singleKey ==  $currentSelection[$key])?'selected':'').'> '.$caption.'</option>';
			}
			$displayCode .= '</select></td></tr>';
		}
			// set values correctly
		if ($currentSelection['maxSteps'] != 'marked')	{
			$this->maxSteps = $currentSelection['maxSteps']?intval($currentSelection['maxSteps']):'';
		} else {
			$this->showMarked = TRUE;
			$this->maxSteps = FALSE;
		}
		$this->showDiff = intval($currentSelection['showDiff']);
		$this->showSubElements = intval($currentSelection['showSubElements']);
		$this->showInsertDelete = intval($currentSelection['showInsertDelete']);

		$content = '';
			// get link to page history if the element history is shown
		$elParts = explode(':',$this->element);
		if ($elParts[0] != 'pages')	{
			$content .= '<strong>'.$GLOBALS['LANG']->getLL('elementHistory',1).'</strong><br />';
			$pid = t3lib_BEfunc::getRecordRaw($elParts[0],'uid='.intval($elParts[1]));
			$content .= $this->linkPage($GLOBALS['LANG']->getLL('elementHistory_link',1),array('element' => 'pages:'.$pid['pid']));
		}
		$content .= '<form name="settings" action="'.t3lib_div::getIndpEnv('TYPO3_REQUEST_URL').'" method="post"><table>'.$displayCode.'</table></form>';
		return $GLOBALS['SOBE']->doc->section($GLOBALS['LANG']->getLL('settings',1),$content,0,1,0,0);

	}

	/**
	 * Shows the full change log
	 *
	 * @return	string		HTML for list, wrapped in a table.
	 */
	function displayHistory()	{
		$lines=array();

			// Initialize:
		$lines[] = '<tr class="t3-row-header">
				<td> </td>
				<td>'.$GLOBALS['LANG']->getLL('time',1).'</td>
				<td>'.$GLOBALS['LANG']->getLL('age',1).'</td>
				<td>'.$GLOBALS['LANG']->getLL('user',1).'</td>
				<td>'.$GLOBALS['LANG']->getLL('tableUid',1).'</td>
				<td>'.$GLOBALS['LANG']->getLL('differences',1).'</td>
				<td>&nbsp;</td>
			</tr>';

			// get default page TSconfig expiration time
		$elParts = explode(':',$this->element);
		if ($elParts[0] != 'pages')	{
			$tmp = t3lib_BEfunc::getRecordRaw($elParts[0],'uid='.intval($elParts[1]));
			$pid = $tmp['pid'];
		} else {
			$pid = $elParts[1];
		}
		$tmpTsConfig = $GLOBALS['BE_USER']->getTSConfig('TCEMAIN',t3lib_BEfunc::getPagesTSconfig($pid));
		$expirationTime = isset($tmpTsConfig['properties']['default.']['history.']['maxAgeDays']) ? $tmpTsConfig['properties']['default.']['history.']['maxAgeDays'] : 30;

		$expirationTimestamp = $expirationTime ? ($GLOBALS['EXEC_TIME'] - 60 * 60 * 24 * $expirationTime) : 0;
		$expirationWarning = 0;

		$be_user_array = t3lib_BEfunc::getUserNames();

			// Traverse changelog array:
		if (!$this->changeLog)	{
			return 0;
		}
		$i = 0;
		foreach ($this->changeLog as $sysLogUid => $entry)	{
				// stop after maxSteps
			if ($i > $this->maxSteps && $this->maxSteps)	{
				break;
			}

				// display inconsistency warning
			if ($entry['tstamp'] < $expirationTimestamp && !$expirationWarning)	{
				$expirationWarning = 1;

				$lines[] = '
				<tr class="c-headLine">
					<td colspan="7"><strong>'.$GLOBALS['LANG']->getLL('consistenceWarning',1).'</strong></td>
				</tr>';
			}

				// show only marked states
			if (!$entry['snapshot'] && $this->showMarked)	{
				continue;
			}
			$i++;
				// get user names
			$userName = ($entry['user']?$be_user_array[$entry['user']]['username']:$GLOBALS['LANG']->getLL('externalChange',1));

				// build up single line
			$singleLine = array();

				// diff link
			$image = t3lib_iconWorks::getSpriteIcon('actions-view-go-forward', array('title' => $GLOBALS['LANG']->getLL('sumUpChanges', TRUE)));
			$singleLine[] = '<span>'.$this->linkPage($image,array('diff' => $sysLogUid)).'</span>';	// remove first link

			$singleLine[] = htmlspecialchars(t3lib_BEfunc::datetime($entry['tstamp']));	// add time
			$singleLine[] = htmlspecialchars(t3lib_BEfunc::calcAge($GLOBALS['EXEC_TIME'] - $entry['tstamp'], $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.minutesHoursDaysYears')));	// add age
			$singleLine[] = htmlspecialchars($userName);	// add user name
			$singleLine[] = $this->linkPage($this->generateTitle($entry['tablename'],$entry['recuid']),array('element' => $entry['tablename'].':'.$entry['recuid']),'',$GLOBALS['LANG']->getLL('linkRecordHistory',1));	// add record UID

				// show insert/delete/diff/changed field names
			if ($entry['action'])	{	// insert or delete of element
				$singleLine[] = '<strong>'.htmlspecialchars($GLOBALS['LANG']->getLL($entry['action'],1)).'</strong>';
			} else {
				if (!$this->showDiff)	{	// display field names instead of full diff
					// re-write field names with labels
				$tmpFieldList = explode(',',$entry['fieldlist']);
				foreach ($tmpFieldList as $key => $value)	{
					$tmp = str_replace(':','',$GLOBALS['LANG']->sl(t3lib_BEfunc::getItemLabel($entry['tablename'],$value),1));
					if($tmp)	$tmpFieldList[$key] = $tmp;
					else	unset($tmpFieldList[$key]);	// remove fields if no label available
				}
				$singleLine[] = htmlspecialchars(implode(',',$tmpFieldList));
				} else {	// display diff
					$diff = $this->renderDiff($entry,$entry['tablename']);
					$singleLine[] = $diff;
				}
			}
				// show link to mark/unmark state
			if (!$entry['action'])	{
				if ($entry['snapshot'])	{
					$image = '<img'.t3lib_iconWorks::skinImg('','gfx/unmarkstate.gif').' align="top" alt="'.$GLOBALS['LANG']->getLL('unmarkState',1).'" title="'.$GLOBALS['LANG']->getLL('unmarkState',1).'" />';
				} else {
					$image = '<img'.t3lib_iconWorks::skinImg('','gfx/markstate.gif').' align="top" alt="'.$GLOBALS['LANG']->getLL('markState',1).'" title="'.$GLOBALS['LANG']->getLL('markState',1).'" />';
				}
				$singleLine[] = $this->linkPage($image,array('highlight' => $entry['uid']));
			} else {
				$singleLine[] = '';
			}

				// put line together
			$lines[] = '
				<tr class="db_list_normal">
					<td>' . implode('</td><td>', $singleLine) . '</td>
				</tr>';
		}

			// Finally, put it all together:
		$theCode = '
			<!--
				History (list):
			-->
			<table class="typo3-dblist" border="0" cellpadding="0" cellspacing="0" id="typo3-history">
				' . implode('', $lines) . '
			</table>';

		if ($this->lastSyslogId)	{
			$theCode .= '<br />' .  $this->linkPage(t3lib_iconWorks::getSpriteIcon('actions-move-to-bottom', array('title' => $GLOBALS['LANG']->getLL('fullView', TRUE))), array('diff' => ''));
		}

			// Add message about the difference view.
		$flashMessage = t3lib_div::makeInstance(
			't3lib_FlashMessage',
			$GLOBALS['LANG']->getLL('differenceMsg'),
			'',
			t3lib_FlashMessage::INFO
		);

		$theCode .= '<br /><br />' . $flashMessage->render() . '<br />';

			// Add CSH:
		$theCode .= t3lib_BEfunc::cshItem('xMOD_csh_corebe', 'history_'.($this->sumUp ? 'sum' : 'log'), $GLOBALS['BACK_PATH'],'');

			// Add the whole content as a module section:
		return $GLOBALS['SOBE']->doc->section($GLOBALS['LANG']->getLL('changes'),$theCode,0,1);
	}

	/**
	 * Displays a diff over multiple fields including rollback links
	 *
	 * @param	array		difference array
	 * @return	string		HTML output
	 */
	function displayMultipleDiff($diff)	{
		$content = '';

			// get all array keys needed
		$arrayKeys = array_merge(array_keys($diff['newData']),array_keys($diff['insertsDeletes']),array_keys($diff['oldData']));
		$arrayKeys = array_unique($arrayKeys);

		if ($arrayKeys)	{
			foreach ($arrayKeys as $key)	{
				$record = '';
				$elParts = explode(':',$key);
					// turn around diff because it should be a "rollback preview"
				if ($diff['insertsDeletes'][$key] == 1)	{	// insert
					$record .= '<strong>'.$GLOBALS['LANG']->getLL('delete',1).'</strong>';
					$record .= '<br />';
				} elseif ($diff['insertsDeletes'][$key] == -1)	{
					$record .= '<strong>'.$GLOBALS['LANG']->getLL('insert',1).'</strong>';
					$record .= '<br />';
				}
					// build up temporary diff array
					// turn around diff because it should be a "rollback preview"
				if ($diff['newData'][$key])	{
					$tmpArr['newRecord'] = $diff['oldData'][$key];
					$tmpArr['oldRecord'] = $diff['newData'][$key];
					$record .= $this->renderDiff($tmpArr, $elParts[0],$elParts[1]);
				}

				$elParts = explode(':',$key);
				$titleLine = $this->createRollbackLink($key, $GLOBALS['LANG']->getLL('revertRecord',1),1) . $this->generateTitle($elParts[0],$elParts[1]);
				$record = '<div style="margin-left:10px;padding-left:5px;border-left:1px solid black;border-bottom:1px dotted black;padding-bottom:2px;">'.$record.'</div>';

				$content .= $GLOBALS['SOBE']->doc->section($titleLine,$record,0,0,0,1);
			}
			$content = $this->createRollbackLink('ALL', $GLOBALS['LANG']->getLL('revertAll',1),0)  . '<div style="margin-left:10px;padding-left:5px;border-left:1px solid black;border-bottom:1px dotted black;padding-bottom:2px;">'.$content.'</div>';
		} else {
			$content = $GLOBALS['LANG']->getLL('noDifferences',1);
		}
		return $GLOBALS['SOBE']->doc->section($GLOBALS['LANG']->getLL('mergedDifferences',1),$content,0,1,0,1);
	}

	/**
	 * Renders HTML table-rows with the comparison information of an sys_history entry record
	 *
	 * @param	array		sys_history entry record.
	 * @param	string		The table name
	 * @param	integer		If set to UID of record, display rollback links
	 * @return	string		HTML table
	 * @access private
	 */
	function renderDiff($entry,$table,$rollbackUid=0)	{
		$lines=array();
 		if (is_array($entry['newRecord']))	{

			$t3lib_diff_Obj = t3lib_div::makeInstance('t3lib_diff');

			$fieldsToDisplay = array_keys($entry['newRecord']);
			foreach($fieldsToDisplay as $fN)	{
				t3lib_div::loadTCA($table);
				if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type']!='passthrough')	{

						// Create diff-result:
					$diffres = $t3lib_diff_Obj->makeDiffDisplay(
						t3lib_BEfunc::getProcessedValue($table,$fN,$entry['oldRecord'][$fN],0,1),
						t3lib_BEfunc::getProcessedValue($table,$fN,$entry['newRecord'][$fN],0,1)
					);
					$lines[]='
						<tr class="bgColor4">
						'.($rollbackUid?'<td style="width:33px">'.$this->createRollbackLink($table.':'.$rollbackUid.':'.$fN, $GLOBALS['LANG']->getLL('revertField',1),2).'</td>':'').'
							<td style="width:90px"><em>'.$GLOBALS['LANG']->sl(t3lib_BEfunc::getItemLabel($table,$fN),1).'</em></td>
							<td style="width:300px">'.nl2br($diffres).'</td>
						</tr>';
				}
			}
		}
		if ($lines)	{
			$content = '<table border="0" cellpadding="2" cellspacing="2" id="typo3-history-item">
					'.implode('',$lines).'
				</table>';
			return $content;
		}
		return NULL;	// error fallback
	}

	/*******************************
	 *
	 * build up history
	 *
	 *******************************/

	/**
	 * Creates a diff between the current version of the records and the selected version
	 *
	 * @return	array		diff for many elements
	 */
	function createMultipleDiff()	{
		$insertsDeletes = array();
		$newArr = array();
		$differences = array();
		if (!$this->changeLog)	{
			return 0;
		}

			// traverse changelog array
		foreach ($this->changeLog as $key => $value)	{
			$field = $value['tablename'].':'.$value['recuid'];
				// inserts / deletes
			if ($value['action'])	{
				if (!$insertsDeletes[$field])	{
					$insertsDeletes[$field] = 0;
				}
				if ($value['action'] == 'insert')	{
					$insertsDeletes[$field]++;
				} else {
					$insertsDeletes[$field]--;
				}
					// unset not needed fields
				if ($insertsDeletes[$field] == 0)	{
					unset($insertsDeletes[$field]);
				}
			} else {
					// update fields
				if (!isset($newArr[$field]))	{	// first row of field
					$newArr[$field] = $value['newRecord'];
					$differences[$field] = $value['oldRecord'];
				} else { // standard
					$differences[$field] = array_merge($differences[$field],$value['oldRecord']);
				}
			}
		}

			// remove entries where there were no changes effectively
		foreach ($newArr as $record => $value)	{
			foreach ($value as $key => $innerVal)	{
				if ($newArr[$record][$key] == $differences[$record][$key])	{
					unset($newArr[$record][$key]);
					unset($differences[$record][$key]);
				}
			}
			if (empty($newArr[$record]) && empty($differences[$record]))	{
				unset($newArr[$record]);
				unset($differences[$record]);
			}
		}
		return array(
			'newData' => $newArr,
			'oldData' => $differences,
			'insertsDeletes' => $insertsDeletes
		);
	}

	/**
	 * Creates change log including sub-elements, filling $this->changeLog
	 *
	 * @return	[type]		...
	 */
	function createChangeLog()	{
		$elParts = explode(':',$this->element);
		$changeLog = $this->getHistoryData($elParts[0],$elParts[1]);

			// get history of tables of this page and merge it into changelog
		if ($elParts[0] == 'pages' && $this->showSubElements)	{
			foreach ($GLOBALS['TCA'] as $tablename => $value)	{
				$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid',$tablename,'pid='.intval($elParts[1]));	// check if there are records on the page
				while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))	{
					if ($newChangeLog = $this->getHistoryData($tablename, $row['uid']))	{	// if there is history data available, merge it into changelog
						foreach ($newChangeLog as $key => $value)	{
							$changeLog[$key] = $value;
						}
					}
				}
			}
		}
		if(!$changeLog)	{
			return 0;
		}

		krsort($changeLog);
		$this->changeLog = $changeLog;

		return 1;
	}

	/**
	 * Gets history and delete/insert data from sys_log and sys_history
	 *
	 * @param	string		DB table name
	 * @param	integer		UID of record
	 * @return	array		history data of the record
	 */
	function getHistoryData($table,$uid)	{
		$uid = $this->resolveElement($table,$uid);
			// If table is found in $GLOBALS['TCA']:
		if ($GLOBALS['TCA'][$table])	{
				// Selecting the $this->maxSteps most recent states:
			$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
						'sys_history.*,sys_log.userid',
						'sys_history,sys_log',
						'sys_history.sys_log_uid=sys_log.uid
							AND sys_history.tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($table, 'sys_history').'
							AND sys_history.recuid='.intval($uid),
						'',
						'sys_log.uid DESC',
						$this->maxSteps
					);

				// Traversing the result, building up changesArray / changeLog:
			#$changesArray=array();	// used temporarily to track intermedia changes
			$changeLog=array();
			while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))	{
					// only history until a certain syslog ID needed
				if ($row['sys_log_uid'] < $this->lastSyslogId && $this->lastSyslogId)	{
					continue;
				}
				$hisDat = unserialize($row['history_data']);
				if (is_array($hisDat['newRecord']) && is_array($hisDat['oldRecord']))	{

						// Add hisDat to the changeLog
					$hisDat['uid']=$row['uid'];
					$hisDat['tstamp']=$row['tstamp'];
					$hisDat['user']=$row['userid'];
					$hisDat['snapshot']=$row['snapshot'];
					$hisDat['fieldlist']=$row['fieldlist'];
					$hisDat['tablename']=$row['tablename'];
					$hisDat['recuid']=$row['recuid'];

					$changeLog[$row['sys_log_uid']]=$hisDat;

						// Update change array
						// This is used to detect if any intermedia changes have been made.
					#$changesArray = array_merge($changesArray,$hisDat['oldRecord']);
				} else {
					debug('ERROR: [getHistoryData]');
					return 0;	// error fallback
				}
			}
				// SELECT INSERTS/DELETES
			if ($this->showInsertDelete)	{
					// Select most recent inserts and deletes // WITHOUT snapshots
				$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
						'uid,userid,action,tstamp',
						'sys_log',
						'type=1
							AND ( action=1 OR action=3 )
							AND tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($table, 'sys_log').'
							AND recuid='.intval($uid),
						'',
						'uid DESC',
						$this->maxSteps
					);
				while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))	{

					if ($row['uid'] < $this->lastSyslogId && $this->lastSyslogId)	{
						continue;
					}
					$hisDat = array();

					switch ($row['action'])	{
						case 1:	// Insert
							$hisDat['action'] = 'insert';
							break;
						case 3:	// Delete
							$hisDat['action'] = 'delete';
							break;
					}
					$hisDat['tstamp']=$row['tstamp'];
					$hisDat['user']=$row['userid'];
					$hisDat['tablename'] = $table;
					$hisDat['recuid'] = $uid;
					$changeLog[$row['uid']] = $hisDat;
				}
			}
			return $changeLog;
		}
		return 0;	// error fallback
	}


	/*******************************
	 *
	 * Various helper functions
	 *
	 *******************************/

	/**
	 * generates the title and puts the record title behind
	 *
	 * @param	[type]		$table: ...
	 * @param	[type]		$uid: ...
	 * @return	[type]		...
	 */
	function generateTitle($table, $uid)	{
		$out = $table.':'.$uid;
		if ($labelField = $GLOBALS['TCA'][$table]['ctrl']['label'])	{
			$record = t3lib_BEfunc::getRecordRaw($table, 'uid='.intval($uid));
			$out .= ' ('.t3lib_BEfunc::getRecordTitle($table, $record, TRUE).')';
		}
		return $out;
	}
	/**
	 * creates a link for the rollback
	 *
	 * @param	sting		parameter which is set to rollbackFields
	 * @param	string		optional, alternative label and title tag of image
	 * @param	integer		optional, type of rollback: 0 - ALL; 1 - element; 2 - field
	 * @return	string		HTML output
	 */
	function createRollbackLink($key, $alt='', $type=0)	{
		 return $this->linkPage('<img '.t3lib_iconWorks::skinImg('','gfx/revert_'.$type.'.gif','width="33" height="33"').' alt="'.$alt.'" title="'.$alt.'" align="middle" />',array('rollbackFields'=>$key));
	 }

	/**
	 * Creates a link to the same page.
	 *
	 * @param	string		String to wrap in <a> tags (must be htmlspecialchars()'ed prior to calling function)
	 * @param	array		Array of key/value pairs to override the default values with.
	 * @param	string		Possible anchor value.
	 * @param	string		Possible title.
	 * @return	string		Link.
	 * @access private
	 */
	function linkPage($str,$inparams=array(),$anchor='',$title='')	{

			// Setting default values based on GET parameters:
		$params['element']=$this->element;
		$params['returnUrl']=$this->returnUrl;
		$params['diff']=$this->lastSyslogId;
			// Mergin overriding values:
		$params = array_merge($params,$inparams);

			// Make the link:
		$Ahref = 'show_rechis.php?'.t3lib_div::implodeArrayForUrl('',$params).($anchor?'#'.$anchor:'');
		$link = '<a href="'.htmlspecialchars($Ahref).'"'.($title?' title="'.$title.'"':'').'>'.$str.'</a>';

			// Return link:
		return $link;
	}

	/**
	 * Will traverse the field names in $dataArray and look in $GLOBALS['TCA'] if the fields are of types which cannot be handled by the sys_history (that is currently group types with internal_type set to "file")
	 *
	 * @param	string		Table name
	 * @param	array		The data array
	 * @return	array		The modified data array
	 * @access private
	 */
	function removeFilefields($table,$dataArray) {
		if ($GLOBALS['TCA'][$table])	{
			t3lib_div::loadTCA($table);

			foreach($GLOBALS['TCA'][$table]['columns'] as $field => $config)	{
				if ($config['config']['type']=='group' && $config['config']['internal_type']=='file')	{
					unset($dataArray[$field]);
				}
			}
		}
		return $dataArray;
	}

	/**
	 * Convert input element reference to workspace version if any.
	 *
	 * @param	string		table of input element
	 * @param	integer		UID of record
	 * @return	integer		converted UID of record
	 */
	function resolveElement($table,$uid)	{
		if (isset($GLOBALS['TCA'][$table]))	{
			if ($workspaceVersion = t3lib_BEfunc::getWorkspaceVersionOfRecord($GLOBALS['BE_USER']->workspace, $table, $uid, 'uid'))	{
				$uid = $workspaceVersion['uid'];
			}
		}
		return $uid;
	}

	/**
	 * resolve sh_uid (used from log)
	 *
	 * @return	[type]		...
	 */
	function resolveShUid()	{
		if (t3lib_div::_GP('sh_uid'))	{
			$sh_uid = t3lib_div::_GP('sh_uid');
			$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*','sys_history', 'uid='.intval($sh_uid));
			$record = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
			$this->element = $record['tablename'].':'.$record['recuid'];
			$this->lastSyslogId = $record['sys_log_uid']-1;
		}
	}
}
?>