Feature #15090 » bug_1650_undohistory_4.patch
TYPO3core_showrechis/t3lib/class.t3lib_tcemain.php 2006-01-12 23:02:51.000000000 +0100 | ||
---|---|---|
$modifyAccessList = $this->checkModifyAccessList($table);
|
||
if (!$modifyAccessList) {
|
||
$this->log($table,$id,2,0,1,"Attempt to modify table '%s' without permission",1,array($table));
|
||
}
|
||
} // FIXME: $id not set here (Comment added by Sebastian Kurfuerst)
|
||
// Check basic permissions and circumstances:
|
||
if (isset($TCA[$table]) && !$this->tableReadOnly($table) && is_array($this->cmdmap[$table]) && $modifyAccessList) {
|
||
... | ... | |
case 'delete':
|
||
$this->deleteAction($table, $id);
|
||
break;
|
||
case 'undelete':
|
||
$this->undeleteRecord($table, $id);
|
||
break;
|
||
}
|
||
foreach($hookObjectsArr as $hookObj) {
|
||
... | ... | |
}
|
||
/**
|
||
* Deleting a record
|
||
* Undelete a record
|
||
*
|
||
* @param string Table name
|
||
* @param integer Record UID
|
||
* @return void
|
||
*/
|
||
function undeleteRecord($table,$uid) {
|
||
$this->deleteRecord($table,$uid,TRUE,FALSE,TRUE);
|
||
}
|
||
/**
|
||
* Deleting/Undeleting a record
|
||
* This function may not be used to delete pages-records unless the underlying records are already deleted
|
||
* Deletes a record regardless of versioning state (live of offline, doesn't matter, the uid decides)
|
||
* If both $noRecordCheck and $forceHardDelete are set it could even delete a "deleted"-flagged record!
|
||
... | ... | |
* @param integer Record UID
|
||
* @param boolean Flag: If $noRecordCheck is set, then the function does not check permission to delete record
|
||
* @param boolean If TRUE, the "deleted" flag is ignored if applicable for record and the record is deleted COMPLETELY!
|
||
* @param boolean If TRUE, the "deleted" flag is set to 0 again and thus, the item is undeleted.
|
||
* @return void
|
||
*/
|
||
function deleteRecord($table,$uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE) {
|
||
function deleteRecord($table,$uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE,$undeleteRecord=FALSE) {
|
||
global $TCA;
|
||
$uid = intval($uid);
|
||
... | ... | |
$deleteRow = $TCA[$table]['ctrl']['delete'];
|
||
if ($deleteRow && !$forceHardDelete) {
|
||
$value = $undeleteRecord ? 0 : 1;
|
||
$updateFields = array(
|
||
$deleteRow => 1
|
||
$deleteRow => $value
|
||
);
|
||
// If the table is sorted, then the sorting number is set very high
|
||
if ($TCA[$table]['ctrl']['sortby']) {
|
||
if ($TCA[$table]['ctrl']['sortby'] && !$undeleteRecord) {
|
||
$updateFields[$TCA[$table]['ctrl']['sortby']] = 1000000000;
|
||
}
|
||
... | ... | |
$GLOBALS['TYPO3_DB']->exec_DELETEquery($table, 'uid='.intval($uid));
|
||
}
|
||
$state = $undeleteRecord ? 1 : 3;
|
||
if (!$GLOBALS['TYPO3_DB']->sql_error()) {
|
||
$this->log($table,$uid,3,0,0,'');
|
||
$this->log($table,$uid,$state,0,0,'');
|
||
} else {
|
||
$this->log($table,$uid,3,0,100,$GLOBALS['TYPO3_DB']->sql_error());
|
||
$this->log($table,$uid,$state,0,100,$GLOBALS['TYPO3_DB']->sql_error());
|
||
}
|
||
// Update reference index:
|
||
... | ... | |
$TSConfig = $this->getTCEMAIN_TSconfig($tscPID);
|
||
$tE = $this->getTableEntries($table,$TSConfig);
|
||
$keepEntries = strcmp($tE['history.']['keepEntries'],'') ? t3lib_div::intInRange($tE['history.']['keepEntries'],0,200) : 10;
|
||
$maxAgeSeconds = 60*60*24*(strcmp($tE['history.']['maxAgeDays'],'') ? t3lib_div::intInRange($tE['history.']['maxAgeDays'],0,200) : 7); // one week
|
||
$maxAgeSeconds = 60*60*24*(strcmp($tE['history.']['maxAgeDays'],'') ? t3lib_div::intInRange($tE['history.']['maxAgeDays'],0,365) : 30); // one month
|
||
// Garbage collect old entries:
|
||
$this->clearHistory($table,$id,t3lib_div::intInRange($keepEntries-1,0),$maxAgeSeconds);
|
||
$this->clearHistory($maxAgeSeconds, $table);
|
||
// Set history data:
|
||
if ($keepEntries) {
|
||
$fields_values = array();
|
||
$fields_values['history_data'] = serialize($this->historyRecords[$table.':'.$id]);
|
||
$fields_values['fieldlist'] = implode(',',array_keys($this->historyRecords[$table.':'.$id]['newRecord']));
|
||
$fields_values['tstamp'] = time();
|
||
$fields_values['tablename'] = $table;
|
||
$fields_values['recuid'] = $id;
|
||
$fields_values['sys_log_uid'] = $logId;
|
||
$fields_values = array();
|
||
$fields_values['history_data'] = serialize($this->historyRecords[$table.':'.$id]);
|
||
$fields_values['fieldlist'] = implode(',',array_keys($this->historyRecords[$table.':'.$id]['newRecord']));
|
||
$fields_values['tstamp'] = time();
|
||
$fields_values['tablename'] = $table;
|
||
$fields_values['recuid'] = $id;
|
||
$fields_values['sys_log_uid'] = $logId;
|
||
$GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_history', $fields_values);
|
||
$GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_history', $fields_values);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Clearing sys_history table from older entries that are expired.
|
||
* All snapshots are excluded of course.
|
||
*
|
||
* @param string Table name
|
||
* @param integer Record UID
|
||
* @param integer $keepEntries (int+) defines the number of current entries from sys_history table to keep in addition to the new one which is put in.
|
||
* @param integer $maxAgeSeconds (int+) however will set a max age in seconds so that any entry older than current time minus the age removed no matter what. If zero, this is not effective.
|
||
* @param string table where the history should be cleared
|
||
* @return void
|
||
*/
|
||
function clearHistory($table,$id,$keepEntries=10,$maxAgeSeconds=604800) {
|
||
function clearHistory($maxAgeSeconds=604800,$table) {
|
||
$tstampLimit = $maxAgeSeconds ? time()-$maxAgeSeconds : 0;
|
||
$where = '
|
||
tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($table, 'sys_history').'
|
||
AND recuid='.intval($id).'
|
||
AND snapshot=0';
|
||
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,tstamp', 'sys_history', $where, '', 'uid DESC', intval($keepEntries).',1');
|
||
$resRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
|
||
if ($tstampLimit && intval($resRow['tstamp'])<$tstampLimit) {
|
||
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid,tstamp', 'sys_history', $where.' AND tstamp<'.intval($tstampLimit), '', 'uid DESC', '1');
|
||
$resRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
|
||
$GLOBALS['TYPO3_DB']->exec_DELETEquery('sys_history', $where.' AND uid<='.intval($resRow['uid']));
|
||
} elseif (is_array($resRow)) {
|
||
$GLOBALS['TYPO3_DB']->exec_DELETEquery('sys_history', $where.' AND uid<='.intval($resRow['uid']));
|
||
}
|
||
$GLOBALS['TYPO3_DB']->exec_DELETEquery('sys_history', 'tstamp<'.intval($tstampLimit).' AND tablename="'.$table.'"');
|
||
}
|
||
/**
|
TYPO3core_showrechis/typo3/alt_clickmenu.php 2006-01-07 13:45:48.000000000 +0100 | ||
---|---|---|
$menuItems['spacer2']='spacer';
|
||
$menuItems['delete']=$this->DB_delete($table,$uid,$elInfo);
|
||
}
|
||
if(!in_array('history',$this->disabledItems)) {
|
||
$menuItems['history']=$this->DB_history($table,$uid,$elInfo);
|
||
}
|
||
}
|
||
// Adding external elements to the menuItems array
|
TYPO3core_showrechis/typo3/class.show_rechis.inc 2006-01-13 23:30:20.000000000 +0100 | ||
---|---|---|
* Class for the record history display script (show_rechis.php)
|
||
*
|
||
* $Id: class.show_rechis.inc,v 1.17 2005/11/02 22:51:52 typo3 Exp $
|
||
* Revised for TYPO3 3.6 November/2003 by Kasper Skaarhoj
|
||
* XHTML Compliant
|
||
*
|
||
* @author Kasper Skaarhoj <kasperYYYY@typo3.com>
|
||
* @author Sebastian Kurfuerst <sebastian@garbage-group.de>
|
||
*/
|
||
/**
|
||
* [CLASS/FUNCTION INDEX of SCRIPT]
|
||
*
|
||
*
|
||
*
|
||
* 83: class recordHistory
|
||
* 106: function recordHistory()
|
||
* 124: function main()
|
||
* 155: function displaySysHistoryEntry($sh_uid)
|
||
* 219: function revertToPreviousValues($element,$field)
|
||
* 285: function saveState($element,$sumUp)
|
||
* 339: function displayHistory($element)
|
||
*
|
||
* SECTION: Various helper functions
|
||
* 540: function nextHisUid($element,$hisUid)
|
||
* 586: function compareChangesWithCurrent($element,$changeRec)
|
||
* 631: function readFieldTypes($table,$id)
|
||
* 662: function cmp($changeStatus,$oldRecord)
|
||
* 685: function removeFilefields($table,$dataArray)
|
||
* 708: function renderEntry($entry,$table)
|
||
* 769: function listHeader()
|
||
* 813: function linkPage($str,$inparams=array(),$anchor='')
|
||
* 841: function getChangesSinceRecord($element,$hisUid=0,$hisUid_Stop=0)
|
||
*
|
||
* TOTAL FUNCTIONS: 15
|
||
* (This index is automatically created/updated by the extension "extdeveval")
|
||
*
|
||
*/
|
||
/**
|
||
* Class for the record history display script (show_rechis.php)
|
||
*
|
||
* @author Kasper Skaarhoj <kasperYYYY@typo3.com>
|
||
* @author Sebastian Kurfuerst <sebastian@garbage-group.de>
|
||
* @package TYPO3
|
||
* @subpackage core
|
||
*/
|
||
class recordHistory {
|
||
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, 2-popup)
|
||
// TODO: $displayDiff=2
|
||
var $showSubElements=1; // on a pages table - show sub elements as well.
|
||
var $showInsertDelete=1; // show inserts and deletes as well
|
||
// Internal, dynamic:
|
||
var $listType = 0; // This value determines the kind of list build. The variable is used as a parameter from some functions.
|
||
// Internal, static. GPvars:
|
||
var $sh_uid; // sh_uid is the id-number of the sys_history log item to SHOW
|
||
// Internal, GPvars
|
||
var $element; // Element reference, syntax [tablename]:[uid]
|
||
var $saveState; // Saving states: Points to a sys_history UID which should be saved.
|
||
var $returnUrl; // Return URL - kept in links, used to link back to calling module.
|
||
var $revert; // String identifying mode of reverting: Either all fields or only a single field to revert. See function revertToPreviousValues()
|
||
var $sumUp; // Generally used as a pointer to a sys_history uid as a state.
|
||
var $doReturn; // If set, function revertToPreviousValues() will perform a redirect to returnUrl
|
||
var $lastSyslogId; // syslog ID which is not shown anymore
|
||
var $returnUrl;
|
||
// Internal
|
||
var $changeLog;
|
||
var $showMarked=FALSE;
|
||
/**
|
||
* Constructor for the class
|
||
*
|
||
* @return void
|
||
*/
|
||
function recordHistory() {
|
||
// GPvars:
|
||
$this->sh_uid = t3lib_div::_GP('sh_uid');
|
||
$this->element = t3lib_div::_GP('element');
|
||
$this->saveState = t3lib_div::_GP('saveState');
|
||
$this->returnUrl = t3lib_div::_GP('returnUrl');
|
||
$this->revert = t3lib_div::_GP('revert');
|
||
$this->sumUp = t3lib_div::_GP('sumUp');
|
||
$this->doReturn = t3lib_div::_GP('doReturn');
|
||
$this->lastSyslogId = t3lib_div::_GP('diff');
|
||
$this->rollbackFields = t3lib_div::_GP('rollbackFields');
|
||
// resolve sh_uid if set
|
||
$this->resolveShUid();
|
||
}
|
||
/**
|
||
... | ... | |
* @return void
|
||
*/
|
||
function main() {
|
||
$content = '';
|
||
$content='';
|
||
// If link from sys log:
|
||
// sh_uid is the id-number of the sys_history log item
|
||
if ($this->sh_uid) {
|
||
$content.=$this->displaySysHistoryEntry($this->sh_uid);
|
||
// save snapshot
|
||
if (t3lib_div::_GP('highlight') && !t3lib_div::_GP('settings')) {
|
||
$this->toggleHighlight(t3lib_div::_GP('highlight'));
|
||
}
|
||
// If link to element:
|
||
if ($this->element) {
|
||
$this->resolveElement();
|
||
if ($this->revert && $this->sumUp) {
|
||
$content.=$this->revertToPreviousValues($this->element,$this->revert);
|
||
$content .= $this->displaySettings();
|
||
if ($this->createChangeLog()) {
|
||
if ($this->rollbackFields) {
|
||
$completeDiff = $this->createMultipleDiff();
|
||
$this->performRollback($completeDiff);
|
||
}
|
||
if ($this->lastSyslogId) {
|
||
$completeDiff = $this->createMultipleDiff();
|
||
$content .= $this->displayMultipleDiff($completeDiff);
|
||
}
|
||
if ($this->saveState) {
|
||
$content.=$this->saveState($this->element,$this->saveState);
|
||
if ($this->element) {
|
||
$content .= $this->displayHistory();
|
||
}
|
||
$content.=$this->displayHistory($this->element);
|
||
}
|
||
// Return content variable:
|
||
return $content;
|
||
}
|
||
/*******************************
|
||
*
|
||
* database actions
|
||
*
|
||
*******************************/
|
||
/**
|
||
* Displays a specific entry from the sys_history table
|
||
* toggles highlight state of record
|
||
*
|
||
* @param integer UID of sys_history table entry
|
||
* @return string HTML content
|
||
* @param integer uid of sys_history entry
|
||
*/
|
||
function displaySysHistoryEntry($sh_uid) {
|
||
global $SOBE, $LANG, $TCA;
|
||
// Select the entry from the table:
|
||
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_history', 'uid='.intval($sh_uid));
|
||
$newRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
|
||
// If an entry was found:
|
||
if (is_array($newRow)) {
|
||
// Init:
|
||
$this->listType=0;
|
||
$lines=array();
|
||
// Create header:
|
||
$recIdentString = $LANG->sL($TCA[$newRow['tablename']]['ctrl']['title']).'/'.$newRow['recuid'];
|
||
$recIdentString = $this->linkPage(htmlspecialchars($recIdentString),array('sh_uid'=>'','element'=>$newRow['tablename'].':'.$newRow['recuid']),'uid_'.$sh_uid);
|
||
$theTime = t3lib_BEfunc::datetime($newRow['tstamp']).', '.t3lib_BEfunc::calcAge(time()-$newRow['tstamp'],$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.minutesHoursDaysYears'));
|
||
$lines[]='
|
||
<tr class="bgColor5">
|
||
<td colspan="4">
|
||
<strong>'.$LANG->getLL('tableUid',1).':</strong> '.$recIdentString.'<br />
|
||
<strong>'.$LANG->getLL('time',1).':</strong> '.htmlspecialchars($theTime).'<br />
|
||
</td>
|
||
</tr>';
|
||
// Add header to accumulation:
|
||
$lines[]=$this->listHeader();
|
||
// Get the entry data and add it:
|
||
$historyData = unserialize($newRow['history_data']);
|
||
$lines = array_merge($lines,$this->renderEntry($historyData,$newRow['tablename']));
|
||
// Combine all content into a table for layout:
|
||
$theCode='
|
||
<!--
|
||
History for item:
|
||
-->
|
||
<table border="0" cellpadding="2" cellspacing="2" id="typo3-history-item">
|
||
'.implode('',$lines).'
|
||
</table>';
|
||
$theCode.='
|
||
<br /><img'.t3lib_iconWorks::skinImg('','gfx/icon_note.gif','width="18" height="16"').' align="top" alt="" />'.$LANG->getLL('differenceMsg').'<br /><br />';
|
||
// Add CSH:
|
||
$theCode.= t3lib_BEfunc::cshItem('xMOD_csh_corebe', 'history_entry', $GLOBALS['BACK_PATH'],'');
|
||
// Create the module section:
|
||
$content.=$SOBE->doc->section($LANG->getLL('changes'),$theCode,0,1);
|
||
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;
|
||
}
|
||
// Return content:
|
||
return $content;
|
||
$updateFields = array('snapshot' => $tmp);
|
||
$GLOBALS['TYPO3_DB']->exec_UPDATEquery('sys_history','uid='.intval($uid),$updateFields);
|
||
}
|
||
/**
|
||
* Return to previous values for element
|
||
* perform rollback
|
||
*
|
||
* @param string Element reference, syntax "[table]:[uid]"
|
||
* @param string Tells which field to restore. A single field (eg named "myField") is defined as "field:myField" while ALL fields is indicated by the string "ALL_FIELDS"
|
||
* @return void The function writes through tceMain and ends with a header-location, if instructed to.
|
||
*/
|
||
function revertToPreviousValues($element,$field) {
|
||
$sumUp = $this->sumUp; // sys_history uid from which to get previous values
|
||
$elParts = explode(':',$element);
|
||
$redirect = intval($this->doReturn);
|
||
if ($sumUp==-1) { // Undo/Redo
|
||
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
|
||
'uid',
|
||
'sys_history',
|
||
'sys_history.tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($elParts[0], 'sys_history').'
|
||
AND sys_history.recuid='.intval($elParts[1]),
|
||
'',
|
||
'uid DESC',
|
||
'1'
|
||
);
|
||
if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
|
||
$sumUp=$row['uid'];
|
||
}
|
||
$redirect = 1;
|
||
}
|
||
if ($sumUp!=-1) {
|
||
$changeRec=$this->compareChangesWithCurrent($element,$this->getChangesSinceRecord($element,$sumUp));
|
||
$data =array();
|
||
if (t3lib_BEfunc::getRecord($elParts[0],$elParts[1])) {
|
||
// Fields field(s) to restore:
|
||
if ($field=='ALL_FIELDS') {
|
||
$data=$changeRec['oldRecord'];
|
||
} elseif(substr($field,0,6)=='field:') {
|
||
$data[substr($field,6)]=$changeRec['oldRecord'][substr($field,6)];
|
||
}
|
||
// Removing fields:
|
||
$data = $this->removeFilefields($elParts[0],$data);
|
||
// If there are fields to write:
|
||
if (count($data)) {
|
||
// Setting data right:
|
||
$inData=array();
|
||
$inData[$elParts[0]][$elParts[1]]=$data;
|
||
// Writes the data:
|
||
$tce = t3lib_div::makeInstance('t3lib_TCEmain');
|
||
$tce->stripslashes_values=0;
|
||
$tce->debug=0;
|
||
$tce->dontProcessTransformations=1;
|
||
$tce->start($inData,array());
|
||
$tce->process_datamap();
|
||
* @param array diff array to rollback
|
||
* @return void
|
||
* @access private
|
||
*/
|
||
function performRollback($diff) {
|
||
if (!$this->rollbackFields) {
|
||
return 0;
|
||
}
|
||
// PROCESS INSERTS AND DELETES
|
||
// rewrite inserts and deletes
|
||
$cmdmapArray = array();
|
||
if ($diff['insertsDeletes']) {
|
||
foreach ($diff['insertsDeletes'] 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]);
|
||
} else { // deleted records should be inserted again
|
||
$cmdmapArray[$elParts[0]][$elParts[1]]['undelete'] = 1;
|
||
}
|
||
}
|
||
}
|
||
if ($redirect) {
|
||
Header ('Location: '.t3lib_div::locationHeaderUrl($this->returnUrl));
|
||
exit;
|
||
}
|
||
// 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);
|
||
}
|
||
// 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;
|
||
}
|
||
$rollbackData = explode(':',$this->rollbackFields);
|
||
switch (count($rollbackData)) {
|
||
case 1: // TODO: implement all tables / one table
|
||
$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);
|
||
// return to normal operation
|
||
$this->lastSyslogId = FALSE;
|
||
$this->rollbackFields = FALSE;
|
||
$this->createChangeLog();
|
||
}
|
||
/*******************************
|
||
*
|
||
* Display functions
|
||
*
|
||
*******************************/
|
||
/**
|
||
* Will save state uid $sumUp of element
|
||
* Displays settings
|
||
*
|
||
* @param string Element reference, syntax "[table]:[uid]"
|
||
* @param integer sys_history uid from which to get previous values
|
||
* @return void
|
||
* @return string HTML code to modify settings
|
||
*/
|
||
function saveState($element,$sumUp) {
|
||
$elParts = explode(':',$element);
|
||
// Find the changes since $sumUp sys_history uid
|
||
$changeRec = $this->getChangesSinceRecord($element,$sumUp);
|
||
// Select most recent sys_history record for the element:
|
||
$lastestData = array();
|
||
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
|
||
'history_data',
|
||
'sys_history',
|
||
'sys_history.tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($elParts[0], 'sys_history').'
|
||
AND sys_history.recuid='.intval($elParts[1]),
|
||
'',
|
||
'uid DESC',
|
||
'1'
|
||
);
|
||
if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
|
||
$lastestData = unserialize($row['history_data']);
|
||
}
|
||
// Create forged history data from the most recent state and the previous state to save:
|
||
$historyRecords=array();
|
||
$historyRecords['oldRecord'] = $changeRec['changes'];
|
||
$historyRecords['newRecord'] = array();
|
||
reset($historyRecords['oldRecord']);
|
||
while(list($kk)=each($historyRecords['oldRecord'])) {
|
||
$historyRecords['newRecord'][$kk]=$lastestData['newRecord'][$kk];
|
||
}
|
||
// Update log:
|
||
$updateID = $GLOBALS['BE_USER']->writelog(3,0,0,0,'Saved state in sys_history','');
|
||
// Create query for inserting into sys_history table:
|
||
$fields_values = array(
|
||
'history_data' => serialize($historyRecords),
|
||
'fieldlist' => implode(',',array_keys($historyRecords['oldRecord'])),
|
||
'tstamp' => time(),
|
||
'tablename' => $elParts[0],
|
||
'recuid' => $elParts[1],
|
||
'sys_log_uid' => $updateID,
|
||
'snapshot' => 1
|
||
function displaySettings() {
|
||
// TODO: remove "show sub elements" when just one record is selected and no page
|
||
global $BE_USER, $LANG, $SOBE;
|
||
// get current selection from UC, merge data, write it back to UC
|
||
$currentSelection = is_array($BE_USER->uc['moduleData']['history']) ? $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);
|
||
$BE_USER->uc['moduleData']['history'] = $currentSelection;
|
||
$BE_USER->writeUC($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',
|
||
);
|
||
// Save state by executing this query:
|
||
$GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_history', $fields_values);
|
||
// render selectors
|
||
$displayCode = '';
|
||
foreach ($selector as $key => $values) {
|
||
$displayCode .= '<tr><td>'.$LANG->getLL($key,1).'</td><td><select name="settings['.$key.']" onChange="document.settings.submit()" style="width:100px">';
|
||
foreach ($values as $singleKey => $singleVal) {
|
||
$caption = $LANG->getLL($singleVal,1)?$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 .= '<b>'.$LANG->getLL('elementHistory',1).'</b><br />';
|
||
$pid = t3lib_BEfunc::getRecordRaw($elParts[0],'uid='.intval($elParts[1]));
|
||
$content .= $this->linkPage($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 $SOBE->doc->section($LANG->getLL('settings',1),$content,0,1,0,0);
|
||
}
|
||
/**
|
||
* Displays the history states of an element
|
||
* Shows the full change log
|
||
*
|
||
* @param string Element reference, syntax "[table]:[uid]"
|
||
* @return string HTML for list, wrapped in a table.
|
||
*/
|
||
function displayHistory($element) {
|
||
global $SOBE, $LANG, $TCA;
|
||
// Initialize:
|
||
$elParts = explode(':',$element);
|
||
$table = $elParts[0];
|
||
// If table is found in $TCA:
|
||
if ($TCA[$table]) {
|
||
// Counting number of states:
|
||
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
|
||
'COUNT(*)',
|
||
'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($elParts[1])
|
||
);
|
||
list($Rcount) = $GLOBALS['TYPO3_DB']->sql_fetch_row($res);
|
||
// 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($elParts[1]),
|
||
'',
|
||
'sys_log.uid',
|
||
t3lib_div::intInRange($Rcount-$this->maxSteps,0).','.$this->maxSteps
|
||
);
|
||
function displayHistory() {
|
||
global $LANG;
|
||
global $SOBE;
|
||
global $TCA;
|
||
// Traversing the result, building up changesArray / changeLog:
|
||
$changesArray=array();
|
||
$changeLog=array();
|
||
while ($newRow = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
|
||
$hisDat = unserialize($newRow['history_data']);
|
||
if (is_array($hisDat['newRecord']) && is_array($hisDat['oldRecord'])) {
|
||
// If intermedia changes:
|
||
$intermediaChanges = $this->cmp($changesArray,$hisDat['oldRecord']);
|
||
if (count($intermediaChanges) && !$newRow['snapshot']) {
|
||
$changeLog[]=$intermediaChanges;
|
||
}
|
||
$lines=array();
|
||
// Add hisDat to the changeLog
|
||
$hisDat['uid']=$newRow['uid'];
|
||
$hisDat['tstamp']=$newRow['tstamp'];
|
||
$hisDat['user']=$newRow['userid'];
|
||
$hisDat['snapshot']=$newRow['snapshot'];
|
||
$changeLog[]=$hisDat;
|
||
// Initialize:
|
||
$lines[] = '<tr class="bgColor5 c-head">
|
||
<td> </td>
|
||
<td>'.$LANG->getLL('time',1).'</td>
|
||
<td>'.$LANG->getLL('age',1).'</td>
|
||
<td>'.$LANG->getLL('user',1).'</td>
|
||
<td>'.$LANG->getLL('tableUid',1).'</td>
|
||
<td>'.$LANG->getLL('differences',1).'</td>
|
||
<td> </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 ? (time() - 60*60*24*$expirationTime) : 0;
|
||
$expirationWarning = 0;
|
||
// TODO: Maybe user/groupname blinding necessary
|
||
#$be_group_Array=t3lib_BEfunc::getListGroupNames('title,uid');
|
||
#$groupArray=array_keys($be_group_Array);
|
||
$be_user_array = t3lib_BEfunc::getUserNames();
|
||
#if (!$GLOBALS['BE_USER']->isAdmin()) $be_user_Array = t3lib_BEfunc::blindUserNames($be_user_Array,$groupArray,1);
|
||
// 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="bgColor4-20">
|
||
<td colspan="7"><b>'.$LANG->getLL('consistenceWarning',1).'</b></td>
|
||
</tr>';
|
||
}
|
||
// Update change array
|
||
// This is used to detect if any intermedia changes has been made.
|
||
$changesArray = array_merge($changesArray,$hisDat['newRecord']);
|
||
// show only marked states
|
||
if (!$entry['snapshot'] && $this->showMarked) {
|
||
continue;
|
||
}
|
||
$i++;
|
||
// get user names
|
||
$userName = ($entry['user']?$be_user_array[$entry['user']]['username']:$LANG->getLL('externalChange',1));
|
||
// build up single line
|
||
$singleLine = array();
|
||
// diff link
|
||
$image = '<img'.t3lib_iconWorks::skinImg('','gfx/button_top_right.gif').' align="top" alt="'.$LANG->getLL('sumUpChanges',1).'" title="'.$LANG->getLL('sumUpChanges',1).'" />';
|
||
$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(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']),'',$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($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(':','',$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="'.$LANG->getLL('unmarkState',1).'" title="'.$LANG->getLL('unmarkState',1).'" />';
|
||
} else {
|
||
debug('ERROR: [displayHistory]');
|
||
$image = '<img'.t3lib_iconWorks::skinImg('','gfx/markstate.gif').' align="top" alt="'.$LANG->getLL('markState',1).'" title="'.$LANG->getLL('markState',1).'" />';
|
||
}
|
||
$singleLine[] = $this->linkPage($image,array('highlight' => $entry['uid']));
|
||
} else {
|
||
$singleLine[] = '';
|
||
}
|
||
$bgColorClass = $entry['snapshot'] ? 'bgColor2' : 'bgColor4-20';
|
||
// put line together
|
||
$lines[] = '
|
||
<tr class="'.$bgColorClass.'">
|
||
<td>'.implode('</td><td>',$singleLine).'</td>
|
||
</tr>';
|
||
}
|
||
$lines=array();
|
||
$darkerBgColor_interM = '#cccccc';
|
||
if ($this->sumUp) { // Show details for a single point in the list:
|
||
// Finally, put it all together:
|
||
$theCode = '
|
||
<!--
|
||
History (list):
|
||
-->
|
||
<table border="0" cellpadding="2" cellspacing="2" id="typo3-history">
|
||
'.implode('',$lines).'
|
||
</table>';
|
||
// Initialize:
|
||
$changeLog=array(); // array is reset here because we want to show only one item (and therefore we will build it all over again...)
|
||
$changeLog[]=$this->compareChangesWithCurrent($element,$this->getChangesSinceRecord($element,$this->sumUp));
|
||
$this->listType=2;
|
||
$lines[]=$this->listHeader();
|
||
$be_users = t3lib_BEfunc::getUserNames();
|
||
if ($this->lastSyslogId) {
|
||
$theCode .= '<br />' . $this->linkPage('<img'.t3lib_iconWorks::skinImg('','gfx/group_tobottom.gif').' alt="'.$LANG->getLL('fullView',1).'" title="'.$LANG->getLL('fullView',1).'" />',array('diff' => ''));
|
||
}
|
||
// Add message about the difference view.
|
||
$theCode .= '<br /><img'.t3lib_iconWorks::skinImg('','gfx/icon_note.gif','width="18" height="16"').' align="top" alt="" />'.$LANG->getLL('differenceMsg').'<br /><br />';
|
||
// Get the previous/next uids:
|
||
list($prevHisUid,$nextHisUid) = $this->nextHisUid($element,$this->sumUp);
|
||
// Add CSH:
|
||
// TODO: FIX CSH
|
||
$theCode .= t3lib_BEfunc::cshItem('xMOD_csh_corebe', 'history_'.($this->sumUp ? 'sum' : 'log'), $GLOBALS['BACK_PATH'],'');
|
||
// Create the set of navigation links:
|
||
$linkPack =
|
||
($prevHisUid ? $this->linkPage('<img'.t3lib_iconWorks::skinImg('','gfx/pilup.gif','width="14" height="14"').' title="'.$LANG->getLL('prev',1).'" alt="" />', array('sumUp'=>$prevHisUid)) : ''). // previous
|
||
($nextHisUid ? $this->linkPage('<img'.t3lib_iconWorks::skinImg('','gfx/pildown.gif','width="14" height="14"').' title="'.$LANG->getLL('next',1).'" alt="" />', array('sumUp'=>$nextHisUid)) : ''). // next
|
||
'<br />'.$this->linkPage('<img'.t3lib_iconWorks::skinImg('','gfx/history2.gif','width="13" height="12"').' title="'.$LANG->getLL('historyList',1).'" alt="" />', array('sumUp'=>''), 'uid_'.$this->sumUp). // back to list
|
||
$this->linkPage('<img'.t3lib_iconWorks::skinImg('','gfx/savesnapshot.gif','width="17" height="12"').' title="'.$LANG->getLL('saveState',1).'" alt="" />', array('saveState'=>$this->sumUp,'sumUp'=>''), 'latest'); // save state
|
||
// Add the whole content as a module section:
|
||
return $SOBE->doc->section($LANG->getLL('changes'),$theCode,0,1);
|
||
}
|
||
// Traverse changelog array:
|
||
foreach($changeLog as $entry) {
|
||
/**
|
||
* Displays a diff over multiple fields including rollback links
|
||
*
|
||
* @param array difference array
|
||
* @return string HTML output
|
||
*/
|
||
function displayMultipleDiff($diff) {
|
||
global $SOBE, $LANG;
|
||
$content = '';
|
||
// Set user-names:
|
||
if (!is_array($entry['userList'])) $entry['userList']=array();
|
||
foreach($entry['userList'] as $uLk => $uV) {
|
||
$entry['userList'][$uLk]=$be_users[$uV]['username'];
|
||
}
|
||
// get all array keys needed
|
||
$arrayKeys = array_merge(array_keys($diff['newData']),array_keys($diff['insertsDeletes']),array_keys($diff['oldData']));
|
||
$arrayKeys = array_unique($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 .= '<b>'.$LANG->getLL('delete',1).'</b>';
|
||
$record .= '<br />';
|
||
} elseif ($diff['insertsDeletes'][$key] == -1) {
|
||
$record .= '<b>'.$LANG->getLL('insert',1).'</b>';
|
||
$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, $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>';
|
||
// Add the header:
|
||
$theTime = t3lib_BEfunc::datetime($entry['tstamp']).', '.t3lib_BEfunc::calcAge(time()-$entry['tstamp'],$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.minutesHoursDaysYears'));
|
||
$lines[]='
|
||
<tr class="bgColor4-20">
|
||
<td valign="top">'.$linkPack.'</td>
|
||
<td colspan="4"><b>'.$LANG->getLL('time',1).':</b> '.htmlspecialchars($theTime).' - <b>'.$LANG->getLL('changeCount',1).':</b> '.$entry['counter'].'<br />
|
||
<b>'.$LANG->getLL('users',1).':</b> '.implode(', ',$entry['userList']).'
|
||
</td>
|
||
</tr>';
|
||
$content .= $SOBE->doc->section($titleLine,$record,0,0,0,1);
|
||
}
|
||
$content = $this->createRollbackLink('ALL', $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>';
|
||
// Add content:
|
||
if (isset($entry['oldRecord']) && isset($entry['newRecord'])) { // If there ARE differences to show, then add lines for each changed field:
|
||
$lines = array_merge($lines,$this->renderEntry($entry,$table));
|
||
} else { // Otherwise, if no changes - show a message about that!
|
||
$lines[]='
|
||
<tr class="bgColor4">
|
||
<td colspan="5" align="center"><br /><b>'.$LANG->getLL('similar',1).'</b><br /><br /></td>
|
||
</tr>';
|
||
}
|
||
}
|
||
} else { // Show the full change Log:
|
||
return $SOBE->doc->section($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) {
|
||
global $SOBE, $LANG, $TCA;
|
||
$lines=array();
|
||
if (is_array($entry['newRecord'])) {
|
||
// Initialize:
|
||
$this->listType=1;
|
||
$be_users = t3lib_BEfunc::getUserNames();
|
||
$lines[]=$this->listHeader();
|
||
$t3lib_diff_Obj = t3lib_div::makeInstance('t3lib_diff');
|
||
// Traverse changelog array:
|
||
foreach($changeLog as $c => $entry) {
|
||
$fieldsToDisplay = array_keys($entry['newRecord']);
|
||
foreach($fieldsToDisplay as $fN) {
|
||
t3lib_div::loadTCA($table);
|
||
if (is_array($TCA[$table]['columns'][$fN]) && $TCA[$table]['columns'][$fN]['config']['type']!='passthrough') {
|
||
// Add spacer line:
|
||
// 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>
|
||
<td colspan="3"> </td>
|
||
<tr class="bgColor4">
|
||
'.($rollbackUid?'<td style="width:33px">'.$this->createRollbackLink($table.':'.$rollbackUid.':'.$fN, $LANG->getLL('revertField',1),2).'</td>':'').'
|
||
<td style="width:90px"><em>'.$LANG->sl(t3lib_BEfunc::getItemLabel($table,$fN),1).'</em></td>
|
||
<td style="width:300px">'.nl2br($diffres).'</td>
|
||
</tr>';
|
||
// Anchor to latest entry:
|
||
$lastAnchor = ($c+1==count($changeLog)?'<a name="latest"></a>':'');
|
||
// Render state header:
|
||
if ($entry['uid']) { // This state was made by the backend:
|
||
$theTime = $this->linkPage(t3lib_BEfunc::datetime($entry['tstamp']),array('sh_uid'=>$entry['uid'],'element'=>''));
|
||
$theAge = ', '.t3lib_BEfunc::calcAge(time()-$entry['tstamp'],$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.minutesHoursDaysYears'));
|
||
$bgColorClass = $entry['snapshot'] ? 'bgColor2' : 'bgColor4-20';
|
||
$lines[]='
|
||
<tr class="'.$bgColorClass.'">
|
||
<td colspan="2">'.
|
||
$lastAnchor.
|
||
'<a name="uid_'.$entry['uid'].'"></a>'.
|
||
($entry['snapshot'] ? '<img'.t3lib_iconWorks::skinImg('','gfx/snapshot.gif','width="12" height="12"').' alt="" />':'').
|
||
'<b>'.$LANG->getLL('time',1).':</b> '.$theTime.htmlspecialchars($theAge).' - <b>'.$LANG->getLL('user',1).':</b> '.$be_users[$entry['user']]['username'].
|
||
'</td>
|
||
<td>'.
|
||
$this->linkPage('<img'.t3lib_iconWorks::skinImg('','gfx/history.gif','width="13" height="12"').' title="'.$LANG->getLL('revertAllFields',1).'" alt="" />', array('revert'=>'ALL_FIELDS','sumUp'=>$entry['uid'],'doReturn'=>1)).
|
||
$this->linkPage('<img'.t3lib_iconWorks::skinImg('','gfx/history_details.gif','width="12" height="12"').' title="'.$LANG->getLL('sumUpChanges',1).'" alt="" />', array('sumUp'=>$entry['uid'])).
|
||
'</td>
|
||
</tr>';
|
||
} else { // This state must have been some external change:
|
||
$lines[]='
|
||
<tr bgcolor="'.$darkerBgColor_interM.'">
|
||
<td colspan="3"><strong>'.$LANG->getLL('externalChange',1).'</strong></td>
|
||
</tr>';
|
||
}
|
||
// Merge state header with all entries in the state:
|
||
$lines = array_merge($lines,$this->renderEntry($entry,$table));
|
||
}
|
||
}
|
||
// Finally, put it all together:
|
||
$theCode='
|
||
<!--
|
||
Item history (either list or single):
|
||
-->
|
||
<table border="0" cellpadding="2" cellspacing="2" id="typo3-history">
|
||
}
|
||
if ($lines) {
|
||
$content = '<table border="0" cellpadding="2" cellspacing="2" id="typo3-history-item">
|
||
'.implode('',$lines).'
|
||
</table>';
|
||
// Add message about the difference view.
|
||
$theCode.= '<br /><img'.t3lib_iconWorks::skinImg('','gfx/icon_note.gif','width="18" height="16"').' align="top" alt="" />'.$LANG->getLL('differenceMsg').'<br /><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 $SOBE->doc->section($LANG->getLL('changes'),$theCode,0,1);
|
||
return $content;
|
||
}
|
||
return NULL; // error fallback
|
||
}
|
||
/*******************************
|
||
*
|
||
* Various helper functions
|
||
* build up history
|
||
*
|
||
*******************************/
|
||
/**
|
||
* Based on the uid of a sys_history record (a state) this method will find the uids of the previous and next state (if any)
|
||
* Creates a diff between the current version of the records and the selected version
|
||
*
|
||
* @param string Element reference, syntax "[table]:[uid]"
|
||
* @param integer Current state uid
|
||
* @return array Array with previous and next uid as key 0 / 1
|
||
* @access private
|
||
* @return array diff for many elements
|
||
*/
|
||
function nextHisUid($element,$hisUid) {
|
||
$elParts = explode(':',$element);
|
||
// Prev:
|
||
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
|
||
'uid',
|
||
'sys_history',
|
||
'tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($elParts[0], 'sys_history').'
|
||
AND recuid='.intval($elParts[1]).'
|
||
AND uid<'.intval($hisUid),
|
||
'',
|
||
'uid DESC',
|
||
'1'
|
||
);
|
||
if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
|
||
$prevUid = $row['uid'];
|
||
}
|
||
// Next:
|
||
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
|
||
'uid',
|
||
'sys_history',
|
||
'tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($elParts[0], 'sys_history').'
|
||
AND recuid='.intval($elParts[1]).'
|
||
AND uid>'.intval($hisUid),
|
||
'',
|
||
'uid',
|
||
'1'
|
||
);
|
||
if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
|
||
$nextUid = $row['uid'];
|
||
}
|
||
// Return next and previous ids:
|
||
return array($prevUid,$nextUid);
|
||
}
|
||
/**
|
||
* This compares a certain sys_history state (given by the $changeRec array) with the current values of the element refered to by $element.
|
||
*
|
||
* @param string Element reference, syntax "[table]:[uid]"
|
||
* @param array Array with the state information from a certain state. This kind of array is produced by getChangesSinceRecord()
|
||
* @return array Array with the changes registered in.
|
||
* @access private
|
||
* @see getChangesSinceRecord()
|
||
*/
|
||
function compareChangesWithCurrent($element,$changeRec) {
|
||
global $TCA;
|
||
// Initialize:
|
||
$sumChangesArray=array();
|
||
$elParts = explode(':',$element);
|
||
$newChangeRec=array();
|
||
// If tablename is found in $TCA:
|
||
if ($TCA[$elParts[0]]) {
|
||
// Select current record content of element:
|
||
$currentRecord = t3lib_BEfunc::getRecord($elParts[0],$elParts[1]);
|
||
// If that is found and the "changes" entry of the $changeRec is an array, then proceed:
|
||
if (is_array($currentRecord) && is_array($changeRec['changes'])) {
|
||
// For each entry in "changes" we compare the field content with the current and if there is a difference, it is tracked in the array $newChangeRec
|
||
foreach($changeRec['changes'] as $fN => $fV) {
|
||
if (strcmp($fV,$currentRecord[$fN])) {
|
||
$newChangeRec['oldRecord'][$fN]=$fV;
|
||
$newChangeRec['newRecord'][$fN]=$currentRecord[$fN];
|
||
function createMultipleDiff() {
|
||
$insertsDeletes = array();
|
||
$newArr = array();
|
||
$differences = array();
|
||
if (!$this->changeLog) {
|
||
return 0;
|
||
}
|
||
// traverse changelog array
|
||
$i = 0;
|
||
$total = count($this->changeLog);
|
||
foreach ($this->changeLog as $key => $value) {
|
||
$i++;
|
||
$field = $value['tablename'].':'.$value['recuid'];
|
||
// inserts / deletes
|
||
if ($value['action']) {
|
||
if($i < $total) { // don't revert the last log entry
|
||
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]);
|
||
}
|
||
}
|
||
// Finally, setting some general information fields:
|
||
$newChangeRec['tstamp']=min($changeRec['tstamp']);
|
||
$newChangeRec['counter']=$changeRec['counter'];
|
||
$newChangeRec['userList']=array_unique($changeRec['userList']);
|
||
} else {
|
||
return false; // No arrays, possibly no record
|
||
// update fields
|
||
if (!isset($newArr[$field]) && $i < $total) { // first row of field, not the last row
|
||
$newArr[$field] = $value['newRecord'];
|
||
$differences[$field] = array_merge($differences[$field],$value['oldRecord']);
|
||
} elseif (isset($newArr[$field]) && $i < $total) { // standard
|
||
$differences[$field] = array_merge($differences[$field],$value['oldRecord']);
|
||
} elseif (!isset($newArr[$field]) && $i == $total) { // last row
|
||
} elseif (isset($newArr[$field]) && $i == $total) { // last row
|
||
$differences[$field] = array_merge($differences[$field],$value['newRecord']);
|
||
}
|
||
}
|
||
}
|
||
// Returns the array of changes detected:
|
||
return $newChangeRec;
|
||
}
|
||
/**
|
||
* Returns the record of $table/$id along with the sql field types for each field
|
||
*
|
||
* @param string The table name
|
||
* @param integer The uid of the record
|
||
* @return array An array with two num keys; in 0 is the current record, in 1 is the field types for each field.
|
||
* @access private
|
||
*/
|
||
function readFieldTypes($table,$id) {
|
||
// Select record:
|
||
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $table, 'uid='.intval($id));
|
||
// Fetch the types of the fields.
|
||
if ($GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
|
||
$currentRecord = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
|
||
$c=0;
|
||
$cRecTypes=array();
|
||
foreach($currentRecord as $col => $val) {
|
||
//$cRecTypes[$col] = $GLOBALS['TYPO3_DB']->sql_field_type($table,$col);
|
||
// DBAL
|
||
$cRecTypes[$col] = $GLOBALS['TYPO3_DB']->sql_field_type($res,$c);
|
||
$c++;
|
||
// 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]);
|
||
}
|
||
}
|
||
}
|
||
$GLOBALS['TYPO3_DB']->sql_free_result($res);
|
||
return array($currentRecord,$cRecTypes);
|
||
}
|
||
return array(
|
||
'newData' => $newArr,
|
||
'oldData' => $differences,
|
||
'insertsDeletes' => $insertsDeletes
|
||
);
|
||
}
|
||
/**
|
||
* Compares the old record with the changed fields.
|
||
* Creates change log including sub-elements, filling $this->changeLog
|
||
*
|
||
* @param array Record with field/value pairs (what has changed)
|
||
* @param array Record with field/value pairs
|
||
* @return array Comparison result.
|
||
* @access private
|
||
*/
|
||
function cmp($changeStatus,$oldRecord) {
|
||
function createChangeLog() {
|
||
// Initialize:
|
||
$changes=array();
|
||
global $TCA;
|
||
$elParts = explode(':',$this->element);
|
||
$changeLog = $this->getHistoryData($elParts[0],$elParts[1]);
|
||
// Traverse $oldRecord
|
||
foreach($oldRecord as $fN => $fV) {
|
||
if (isset($changeStatus[$fN]) && strcmp($fV,$changeStatus[$fN])) {
|
||
$changes['oldRecord'][$fN]=$changeStatus[$fN];
|
||
$changes['newRecord'][$fN]=$fV;
|
||
// get history of tables of this page and merge it into changelog
|
||
if ($elParts[0] == 'pages' && $this->showSubElements) {
|
||
foreach ($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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return $changes;
|
||
if(!$changeLog) {
|
||
return 0;
|
||
}
|
||
krsort($changeLog);
|
||
$this->changeLog = $changeLog;
|
||
return 1;
|
||
}
|
||
/**
|
||
* Will traverse the field names in $dataArray and look in $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")
|
||
* Gets history and delete/insert data from sys_log and sys_history
|
||
*
|
||
* @param string Table name
|
||
* @param array The data array
|
||
* @return array The modified data array
|
||
* @access private
|
||
* @param string DB table name
|
||
* @param integer UID of record
|
||
* @return array history data of the record
|
||
*/
|
||
function removeFilefields($table,$dataArray) {
|
||
function getHistoryData($table,$uid) {
|
||
global $TCA;
|
||
$uid = $this->resolveElement($table,$uid);
|
||
// If table is found in $TCA:
|
||
if ($TCA[$table]) {
|
||
t3lib_div::loadTCA($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
|
||
);
|
||
foreach($TCA[$table]['columns'] as $field => $config) {
|
||
if ($config['config']['type']=='group' && $config['config']['internal_type']=='file') {
|
||
unset($dataArray[$field]);
|
||
// 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;
|
||
}
|
||
}
|
||
}
|
||
return $dataArray;
|
||
}
|
||
/**
|
||
* 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
|
||
* @return array HTML table rows in an array
|
||
* @access private
|
||
*/
|
||
function renderEntry($entry,$table) {
|
||
global $SOBE, $LANG, $TCA;
|
||
$hisDat = unserialize($row['history_data']);
|
||
if (is_array($hisDat['newRecord']) && is_array($hisDat['oldRecord'])) {
|
||
$lines=array();
|
||
if (is_array($entry['newRecord'])) {
|
||
// If intermedia changes:
|
||
#$intermediaChanges = $this->cmp($changesArray,$hisDat['newRecord']);
|
||
#if (count($intermediaChanges)) { // && !$row['snapshot']
|
||
//$changeLog[]=$intermediaChanges;
|
||
// FIXME: ARRAY INDEX MIGHT HAVE AN ERROR!!!!!
|
||
#}
|
||
$t3lib_diff_Obj = t3lib_div::makeInstance('t3lib_diff');
|
||
// Add hisDat to the changeLog
|
||
// TODO: Possible to overload it with $row?
|
||
$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'];
|
||
$fieldsToDisplay = array_keys($entry['newRecord']);
|
||
foreach($fieldsToDisplay as $fN) {
|
||
t3lib_div::loadTCA($table);
|
||
if (is_array($TCA[$table]['columns'][$fN]) && $TCA[$table]['columns'][$fN]['config']['type']!='passthrough') {
|
||
$changeLog[$row['sys_log_uid']]=$hisDat;
|
||
// 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)
|
||
// 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();
|
||
// Depending on list type, we make the row:
|
||
switch($this->listType) {
|
||
case 1:
|
||
$lines[]='
|
||
<tr class="bgColor4">
|
||
<td><em>'.$LANG->sl(t3lib_BEfunc::getItemLabel($table,$fN),1).'</em></td>
|
||
<td>'.nl2br($diffres).'</td>
|
||
<td> </td>
|
||
</tr>';
|
||
break;
|
||
case 2:
|
||
$lines[]='
|
||
<tr class="bgColor4">
|
||
<td><em>'.$LANG->sl(t3lib_BEfunc::getItemLabel($table,$fN)).'</em></td>
|
||
<td>'.htmlspecialchars(t3lib_BEfunc::getProcessedValue($table,$fN,$entry['oldRecord'][$fN])).'</td>
|
||
<td>'.$this->linkPage('<img'.t3lib_iconWorks::skinImg('','gfx/history.gif','width="13" height="12"').' title="'.$LANG->getLL('revertField',1).'" alt="" />', array('revert'=>'field:'.$fN)).'</td>
|
||
<td>'.htmlspecialchars(t3lib_BEfunc::getProcessedValue($table,$fN,$entry['newRecord'][$fN])).'</td>
|
||
<td>'.nl2br($diffres).'</td>
|
||
</tr>';
|
||
break;
|
||
default:
|
||
$lines[]='
|
||
<tr class="bgColor4">
|
||
<td><em>'.$LANG->sl(t3lib_BEfunc::getItemLabel($table,$fN)).'</em></td>
|
||
<td>'.htmlspecialchars(t3lib_BEfunc::getProcessedValue($table,$fN,$entry['oldRecord'][$fN])).'</td>
|
||
<td>'.htmlspecialchars(t3lib_BEfunc::getProcessedValue($table,$fN,$entry['newRecord'][$fN])).'</td>
|
||
<td>'.nl2br($diffres).'</td>
|
||
</tr>';
|
||
break;
|
||
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 $lines;
|
||
return 0; // error fallback
|
||
}
|
||
/**
|
||
* Creates a header row based on the value of $this->listType
|
||
/*******************************
|
||
*
|
||
* @return string HTML table header row
|
||
* @access private
|
||
* Various helper functions
|
||
*
|
||
*******************************/
|
||
/**
|
||
* generates the title and puts the record title behind
|
||
*/
|
||
function listHeader() {
|
||
global $SOBE, $LANG;
|
||
function generateTitle($table, $uid) {
|
||
global $TCA;
|
||
switch($this->listType) {
|
||
case 1:
|
||
$out='
|
||
<tr class="bgColor5 c-head">
|
||
<td>'.$LANG->getLL('fieldName',1).':</td>
|
||
<td>'.$LANG->getLL('difference',1).':</td>
|
||
<td> </td>
|
||
</tr>';
|
||
break;
|
||
case 2:
|
||
$out='
|
||
<tr class="bgColor5 c-head">
|
||
<td>'.$LANG->getLL('fieldName',1).':</td>
|
||
<td>'.$LANG->getLL('oldValue',1).':</td>
|
||
<td>'.$this->linkPage('<img'.t3lib_iconWorks::skinImg('','gfx/history.gif','width="13" height="12"').' title="'.$LANG->getLL('revertAllFields',1).'" alt="" />', array('revert'=>'ALL_FIELDS')).'</td>
|
||
<td>'.$LANG->getLL('currentValue',1).':</td>
|
||
<td>'.$LANG->getLL('difference',1).':</td>
|