Fixed bug 10056: Misspelling english word "guesbooks"
[Packages/TYPO3.CMS.git] / typo3 / class.show_rechis.inc
1 <?php
2 /***************************************************************
3 *  Copyright notice
4 *
5 *  (c) 1999-2008 Kasper Skaarhoj (kasperYYYY@typo3.com)
6 *  (c) 2006-2008 Sebastian Kurfuerst (sebastian@garbage-group.de)
7 *  All rights reserved
8 *
9 *  This script is part of the TYPO3 project. The TYPO3 project is
10 *  free software; you can redistribute it and/or modify
11 *  it under the terms of the GNU General Public License as published by
12 *  the Free Software Foundation; either version 2 of the License, or
13 *  (at your option) any later version.
14 *
15 *  The GNU General Public License can be found at
16 *  http://www.gnu.org/copyleft/gpl.html.
17 *  A copy is found in the textfile GPL.txt and important notices to the license
18 *  from the author is found in LICENSE.txt distributed with these scripts.
19 *
20 *
21 *  This script is distributed in the hope that it will be useful,
22 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
23 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 *  GNU General Public License for more details.
25 *
26 *  This copyright notice MUST APPEAR in all copies of the script!
27 ***************************************************************/
28 /**
29  * Class for the record history display script (show_rechis.php)
30  *
31  * $Id$
32  * XHTML Compliant
33  *
34  * @author      Sebastian Kurfuerst <sebastian@garbage-group.de>
35  */
36
37
38 /**
39  * Class for the record history display script (show_rechis.php)
40  *
41  * @author      Sebastian Kurfuerst <sebastian@garbage-group.de>
42  * @package TYPO3
43  * @subpackage core
44  */
45 class recordHistory {
46                 // External, static:
47         var $maxSteps=20;               // Maximum number of sys_history steps to show.
48         var $showDiff=1;                // display diff or not (0-no diff, 1-inline)
49         var $showSubElements=1;         // on a pages table - show sub elements as well.
50         var $showInsertDelete=1;                // show inserts and deletes as well
51
52                 // Internal, GPvars
53         var $element;                   // Element reference, syntax [tablename]:[uid]
54         var $lastSyslogId;              // syslog ID which is not shown anymore
55         var $returnUrl;
56
57                 // Internal
58         var $changeLog;
59         var $showMarked=FALSE;
60         /**
61          * Constructor for the class
62          *
63          * @return      void
64          */
65         function recordHistory()        {
66                         // GPvars:
67                 $this->element = t3lib_div::_GP('element');
68                 $this->returnUrl = t3lib_div::_GP('returnUrl');
69                 $this->lastSyslogId = t3lib_div::_GP('diff');
70                 $this->rollbackFields = t3lib_div::_GP('rollbackFields');
71                         // resolve sh_uid if set
72                 $this->resolveShUid();
73         }
74
75         /**
76          * Main function for the listing of history.
77          * It detects incoming variables like element reference, history element uid etc. and renders the correct screen.
78          *
79          * @return      HTML            content for the module
80          */
81         function main() {
82                 $content = '';
83
84                         // single-click rollback
85                 if (t3lib_div::_GP('revert') && t3lib_div::_GP('sumUp'))        {
86                         $this->rollbackFields = t3lib_div::_GP('revert');
87                         $this->showInsertDelete = 0;
88                         $this->showSubElements = 0;
89
90                         $element = explode(':',$this->element);
91                         $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');
92                         $record = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
93                         $this->lastSyslogId = $record['sys_log_uid'];
94
95                         $this->createChangeLog();
96                         $completeDiff = $this->createMultipleDiff();
97                         $this->performRollback($completeDiff);
98                         Header ('Location: '.t3lib_div::locationHeaderUrl($this->returnUrl));
99                         exit;
100                 }
101
102                         // save snapshot
103                 if (t3lib_div::_GP('highlight') && !t3lib_div::_GP('settings')) {
104                         $this->toggleHighlight(t3lib_div::_GP('highlight'));
105                 }
106
107                 $content .= $this->displaySettings();
108                 if ($this->createChangeLog())   {
109                         if ($this->rollbackFields)      {
110                                 $completeDiff = $this->createMultipleDiff();
111                                 $content .= $this->performRollback($completeDiff);
112
113                         }
114                         if ($this->lastSyslogId)        {
115                                 $completeDiff = $this->createMultipleDiff();
116                                 $content .= $this->displayMultipleDiff($completeDiff);
117                         }
118                         if ($this->element)     {
119                                 $content .= $this->displayHistory();
120                         }
121                 }
122                 return $content;
123         }
124
125         /*******************************
126          *
127          * database actions
128          *
129          *******************************/
130
131         /**
132          * toggles highlight state of record
133          *
134          * @param       integer         uid of sys_history entry
135          * @return      [type]          ...
136          */
137         function toggleHighlight($uid)  {
138                 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('snapshot','sys_history','uid='.intval($uid));
139                 $tmp = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
140                 if ($tmp['snapshot'])   {
141                         $tmp = 0;
142                 } else {
143                         $tmp = 1;
144                 }
145                 $updateFields = array('snapshot' => $tmp);
146                 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('sys_history','uid='.intval($uid),$updateFields);
147         }
148
149         /**
150          * perform rollback
151          *
152          * @param       array           diff array to rollback
153          * @return      void
154          * @access private
155          */
156         function performRollback($diff) {
157                 if (!$this->rollbackFields)     {
158                         return 0;
159                 }
160
161                 $reloadPageFrame=0;
162                 $rollbackData = explode(':',$this->rollbackFields);
163
164                         // PROCESS INSERTS AND DELETES
165                         // rewrite inserts and deletes
166                 $cmdmapArray = array();
167                 if ($diff['insertsDeletes'])    {
168
169                         switch (count($rollbackData))   {
170                                 case 1: // all tables
171                                         $data = $diff['insertsDeletes'];
172                                         break;
173                                 case 2: // one record
174                                         if ($diff['insertsDeletes'][$this->rollbackFields]) {
175                                                 $data[$this->rollbackFields] = $diff['insertsDeletes'][$this->rollbackFields];
176                                         }
177                                         break;
178                                 case 3: // one field in one record -- ignore!
179                                         break;
180                         }
181                         if ($data)      {
182                                 foreach ($data as $key => $action)      {
183                                         $elParts = explode(':',$key);
184                                         if ($action == 1)       {       // inserted records should be deleted
185                                                 $cmdmapArray[$elParts[0]][$elParts[1]]['delete'] = 1;
186                                                         // when the record is deleted, the contents of the record do not need to be updated
187                                                 unset($diff['oldData'][$key]);
188                                                 unset($diff['newData'][$key]);
189                                         } elseif ($action == -1) {      // deleted records should be inserted again
190                                                 $cmdmapArray[$elParts[0]][$elParts[1]]['undelete'] = 1;
191                                         }
192                                 }
193                         }
194                 }
195                         // Writes the data:
196                 if ($cmdmapArray)       {
197                         $tce = t3lib_div::makeInstance('t3lib_TCEmain');
198                         $tce->stripslashes_values=0;
199                         $tce->debug=0;
200                         $tce->dontProcessTransformations=1;
201                         $tce->start(array(),$cmdmapArray);
202                         $tce->process_cmdmap();
203                         unset($tce);
204                         if (isset($cmdmapArray['pages']))       {
205                                 $reloadPageFrame=1;
206                         }
207                 }
208
209                         // PROCESS CHANGES
210                         // create an array for process_datamap
211                 $diff_modified = array();
212                 foreach ($diff['oldData'] as $key => $value)    {
213                         $splitKey = explode(':',$key);
214                         $diff_modified[$splitKey[0]][$splitKey[1]] = $value;
215                 }
216                 switch (count($rollbackData))   {
217                         case 1: // all tables
218                                 $data = $diff_modified;
219                                 break;
220                         case 2: // one record
221                                 $data[$rollbackData[0]][$rollbackData[1]] = $diff_modified[$rollbackData[0]][$rollbackData[1]];
222                                 break;
223                         case 3: // one field in one record
224                                 $data[$rollbackData[0]][$rollbackData[1]][$rollbackData[2]] = $diff_modified[$rollbackData[0]][$rollbackData[1]][$rollbackData[2]];
225                                 break;
226                 }
227                         // Removing fields:
228                 $data = $this->removeFilefields($rollbackData[0],$data);
229
230                         // Writes the data:
231                 $tce = t3lib_div::makeInstance('t3lib_TCEmain');
232                 $tce->stripslashes_values=0;
233                 $tce->debug=0;
234                 $tce->dontProcessTransformations=1;
235                 $tce->start($data,array());
236                 $tce->process_datamap();
237                 unset($tce);
238                 if (isset($data['pages']))      {
239                         $reloadPageFrame=1;
240                 }
241
242                         // return to normal operation
243                 $this->lastSyslogId = FALSE;
244                 $this->rollbackFields = FALSE;
245                 $this->createChangeLog();
246
247                         // reload page frame if necessary
248                 if ($reloadPageFrame)   {
249                         return '<script type="text/javascript">
250                         /*<![CDATA[*/
251                         if (top.content && top.content.nav_frame && top.content.nav_frame.refresh_nav)  {
252                                 top.content.nav_frame.refresh_nav();
253                         }
254                         /*]]>*/
255                         </script>';
256                 }
257         }
258
259         /*******************************
260          *
261          * Display functions
262          *
263          *******************************/
264
265         /**
266          * Displays settings
267          *
268          * @return      string          HTML code to modify settings
269          */
270         function displaySettings()      {
271                 global $BE_USER, $LANG, $SOBE;
272                         // get current selection from UC, merge data, write it back to UC
273                 $currentSelection = is_array($BE_USER->uc['moduleData']['history']) ? $BE_USER->uc['moduleData']['history'] : array('maxSteps' => '', 'showDiff' => 1, 'showSubElements' => 1, 'showInsertDelete' => 1);
274
275                 $currentSelectionOverride = t3lib_div::_GP('settings');
276                 if ($currentSelectionOverride)  {
277                         $currentSelection = array_merge($currentSelection,$currentSelectionOverride);
278                         $BE_USER->uc['moduleData']['history'] = $currentSelection;
279                         $BE_USER->writeUC($BE_USER->uc);
280                 }
281
282                         // display selector for number of history entries
283                 $selector['maxSteps'] = array(
284                         10 => 10,
285                         20 => 20,
286                         50 => 50,
287                         100 => 100,
288                         '' => 'maxSteps_all',
289                         'marked' => 'maxSteps_marked'
290                 );
291                 $selector['showDiff'] = array(
292                         0 => 'showDiff_no',
293                         1 => 'showDiff_inline'
294                 );
295                 $selector['showSubElements'] = array(
296                         0 => 'no',
297                         1 => 'yes',
298                 );
299                 $selector['showInsertDelete'] = array(
300                         0 => 'no',
301                         1 => 'yes',
302                 );
303                         // render selectors
304                 $displayCode = '';
305                 foreach ($selector as $key => $values)  {
306                         $displayCode .= '<tr><td>'.$LANG->getLL($key,1).'</td><td><select name="settings['.$key.']" onChange="document.settings.submit()" style="width:100px">';
307                         foreach ($values as $singleKey => $singleVal) {
308                                 $caption = $LANG->getLL($singleVal,1)?$LANG->getLL($singleVal,1):$singleVal;
309                                 $displayCode .= '<option value="'.$singleKey.'" '.(($singleKey ==  $currentSelection[$key])?'selected':'').'> '.$caption.'</option>';
310                         }
311                         $displayCode .= '</select></td></tr>';
312                 }
313                         // set values correctly
314                 if ($currentSelection['maxSteps'] != 'marked')  {
315                         $this->maxSteps = $currentSelection['maxSteps']?intval($currentSelection['maxSteps']):'';
316                 } else {
317                         $this->showMarked = TRUE;
318                         $this->maxSteps = FALSE;
319                 }
320                 $this->showDiff = intval($currentSelection['showDiff']);
321                 $this->showSubElements = intval($currentSelection['showSubElements']);
322                 $this->showInsertDelete = intval($currentSelection['showInsertDelete']);
323
324                 $content = '';
325                         // get link to page history if the element history is shown
326                 $elParts = explode(':',$this->element);
327                 if ($elParts[0] != 'pages')     {
328                         $content .= '<b>'.$LANG->getLL('elementHistory',1).'</b><br />';
329                         $pid = t3lib_BEfunc::getRecordRaw($elParts[0],'uid='.intval($elParts[1]));
330                         $content .= $this->linkPage($LANG->getLL('elementHistory_link',1),array('element' => 'pages:'.$pid['pid']));
331                 }
332                 $content .= '<form name="settings" action="'.t3lib_div::getIndpEnv('TYPO3_REQUEST_URL').'" method="post"><table>'.$displayCode.'</table></form>';
333                 return $SOBE->doc->section($LANG->getLL('settings',1),$content,0,1,0,0);
334
335         }
336
337         /**
338          * Shows the full change log
339          *
340          * @return      string          HTML for list, wrapped in a table.
341          */
342         function displayHistory()       {
343                 global $LANG;
344                 global $SOBE;
345                 global $TCA;
346
347                 $lines=array();
348
349                         // Initialize:
350                 $lines[] = '<tr class="bgColor5 c-head">
351                                 <td> </td>
352                                 <td>'.$LANG->getLL('time',1).'</td>
353                                 <td>'.$LANG->getLL('age',1).'</td>
354                                 <td>'.$LANG->getLL('user',1).'</td>
355                                 <td>'.$LANG->getLL('tableUid',1).'</td>
356                                 <td>'.$LANG->getLL('differences',1).'</td>
357                                 <td>&nbsp;</td>
358                         </tr>';
359
360                         // get default page TSconfig expiration time
361                 $elParts = explode(':',$this->element);
362                 if ($elParts[0] != 'pages')     {
363                         $tmp = t3lib_BEfunc::getRecordRaw($elParts[0],'uid='.intval($elParts[1]));
364                         $pid = $tmp['pid'];
365                 } else {
366                         $pid = $elParts[1];
367                 }
368                 $tmpTsConfig = $GLOBALS['BE_USER']->getTSConfig('TCEMAIN',t3lib_BEfunc::getPagesTSconfig($pid));
369                 $expirationTime = isset($tmpTsConfig['properties']['default.']['history.']['maxAgeDays']) ? $tmpTsConfig['properties']['default.']['history.']['maxAgeDays'] : 30;
370
371                 $expirationTimestamp = $expirationTime ? (time() - 60*60*24*$expirationTime) : 0;
372                 $expirationWarning = 0;
373
374                 $be_user_array = t3lib_BEfunc::getUserNames();
375
376                         // Traverse changelog array:
377                 if (!$this->changeLog)  {
378                         return 0;
379                 }
380                 $i = 0;
381                 foreach ($this->changeLog as $sysLogUid => $entry)      {
382                                 // stop after maxSteps
383                         if ($i > $this->maxSteps && $this->maxSteps)    {
384                                 break;
385                         }
386
387                                 // display inconsistency warning
388                         if ($entry['tstamp'] < $expirationTimestamp && !$expirationWarning)     {
389                                 $expirationWarning = 1;
390
391                                 $lines[] = '
392                                 <tr class="bgColor4-20">
393                                         <td colspan="7"><b>'.$LANG->getLL('consistenceWarning',1).'</b></td>
394                                 </tr>';
395                         }
396
397                                 // show only marked states
398                         if (!$entry['snapshot'] && $this->showMarked)   {
399                                 continue;
400                         }
401                         $i++;
402                                 // get user names
403                         $userName = ($entry['user']?$be_user_array[$entry['user']]['username']:$LANG->getLL('externalChange',1));
404
405                                 // build up single line
406                         $singleLine = array();
407
408                                 // diff link
409                         $image = '<img'.t3lib_iconWorks::skinImg('','gfx/button_top_right.gif').' align="top" alt="'.$LANG->getLL('sumUpChanges',1).'" title="'.$LANG->getLL('sumUpChanges',1).'" />';
410                         $singleLine[] = '<span>'.$this->linkPage($image,array('diff' => $sysLogUid)).'</span>'; // remove first link
411
412                         $singleLine[] = htmlspecialchars(t3lib_BEfunc::datetime($entry['tstamp']));     // add time
413                         $singleLine[] = htmlspecialchars(t3lib_BEfunc::calcAge(time()-$entry['tstamp'],$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.minutesHoursDaysYears')));  // add age
414                         $singleLine[] = htmlspecialchars($userName);    // add user name
415                         $singleLine[] = $this->linkPage($this->generateTitle($entry['tablename'],$entry['recuid']),array('element' => $entry['tablename'].':'.$entry['recuid']),'',$LANG->getLL('linkRecordHistory',1));        // add record UID
416
417                                 // show insert/delete/diff/changed field names
418                         if ($entry['action'])   {       // insert or delete of element
419                                 $singleLine[] = '<strong>'.htmlspecialchars($LANG->getLL($entry['action'],1)).'</strong>';
420                         } else {
421                                 if (!$this->showDiff)   {       // display field names instead of full diff
422                                         // re-write field names with labels
423                                 $tmpFieldList = explode(',',$entry['fieldlist']);
424                                 foreach ($tmpFieldList as $key => $value)       {
425                                         $tmp = str_replace(':','',$LANG->sl(t3lib_BEfunc::getItemLabel($entry['tablename'],$value),1));
426                                         if($tmp)        $tmpFieldList[$key] = $tmp;
427                                         else    unset($tmpFieldList[$key]);     // remove fields if no label available
428                                 }
429                                 $singleLine[] = htmlspecialchars(implode(',',$tmpFieldList));
430                                 } else {        // display diff
431                                         $diff = $this->renderDiff($entry,$entry['tablename']);
432                                         $singleLine[] = $diff;
433                                 }
434                         }
435                                 // show link to mark/unmark state
436                         if (!$entry['action'])  {
437                                 if ($entry['snapshot']) {
438                                         $image = '<img'.t3lib_iconWorks::skinImg('','gfx/unmarkstate.gif').' align="top" alt="'.$LANG->getLL('unmarkState',1).'" title="'.$LANG->getLL('unmarkState',1).'" />';
439                                 } else {
440                                         $image = '<img'.t3lib_iconWorks::skinImg('','gfx/markstate.gif').' align="top" alt="'.$LANG->getLL('markState',1).'" title="'.$LANG->getLL('markState',1).'" />';
441                                 }
442                                 $singleLine[] = $this->linkPage($image,array('highlight' => $entry['uid']));
443                         } else {
444                                 $singleLine[] = '';
445                         }
446
447                         $bgColorClass = $entry['snapshot'] ? 'bgColor2' : 'bgColor4-20';
448                                 // put line together
449                         $lines[] = '
450                                 <tr class="'.$bgColorClass.'">
451                                         <td>'.implode('</td><td>',$singleLine).'</td>
452                                 </tr>';
453                 }
454
455                         // Finally, put it all together:
456                 $theCode = '
457                         <!--
458                                 History (list):
459                         -->
460                         <table border="0" cellpadding="2" cellspacing="2" id="typo3-history">
461                                 '.implode('',$lines).'
462                         </table>';
463
464                 if ($this->lastSyslogId)        {
465                         $theCode .= '<br />' .  $this->linkPage('<img'.t3lib_iconWorks::skinImg('','gfx/group_tobottom.gif').' alt="'.$LANG->getLL('fullView',1).'" title="'.$LANG->getLL('fullView',1).'" />',array('diff' => ''));
466                 }
467                         // Add message about the difference view.
468                 $theCode .= '<br /><img'.t3lib_iconWorks::skinImg('','gfx/icon_note.gif','width="18" height="16"').' align="top" alt="" />'.$LANG->getLL('differenceMsg').'<br /><br />';
469
470                         // Add CSH:
471                 $theCode .= t3lib_BEfunc::cshItem('xMOD_csh_corebe', 'history_'.($this->sumUp ? 'sum' : 'log'), $GLOBALS['BACK_PATH'],'');
472
473                         // Add the whole content as a module section:
474                 return $SOBE->doc->section($LANG->getLL('changes'),$theCode,0,1);
475         }
476
477         /**
478          * Displays a diff over multiple fields including rollback links
479          *
480          * @param       array           difference array
481          * @return      string          HTML output
482          */
483         function displayMultipleDiff($diff)     {
484                 global $SOBE, $LANG;
485                 $content = '';
486
487                         // get all array keys needed
488                 $arrayKeys = array_merge(array_keys($diff['newData']),array_keys($diff['insertsDeletes']),array_keys($diff['oldData']));
489                 $arrayKeys = array_unique($arrayKeys);
490
491                 if ($arrayKeys) {
492                         foreach ($arrayKeys as $key)    {
493                                 $record = '';
494                                 $elParts = explode(':',$key);
495                                         // turn around diff because it should be a "rollback preview"
496                                 if ($diff['insertsDeletes'][$key] == 1) {       // insert
497                                         $record .= '<b>'.$LANG->getLL('delete',1).'</b>';
498                                         $record .= '<br />';
499                                 } elseif ($diff['insertsDeletes'][$key] == -1)  {
500                                         $record .= '<b>'.$LANG->getLL('insert',1).'</b>';
501                                         $record .= '<br />';
502                                 }
503                                         // build up temporary diff array
504                                         // turn around diff because it should be a "rollback preview"
505                                 if ($diff['newData'][$key])     {
506                                         $tmpArr['newRecord'] = $diff['oldData'][$key];
507                                         $tmpArr['oldRecord'] = $diff['newData'][$key];
508                                         $record .= $this->renderDiff($tmpArr, $elParts[0],$elParts[1]);
509                                 }
510
511                                 $elParts = explode(':',$key);
512                                 $titleLine = $this->createRollbackLink($key, $LANG->getLL('revertRecord',1),1) . $this->generateTitle($elParts[0],$elParts[1]);
513                                 $record = '<div style="margin-left:10px;padding-left:5px;border-left:1px solid black;border-bottom:1px dotted black;padding-bottom:2px;">'.$record.'</div>';
514
515                                 $content .= $SOBE->doc->section($titleLine,$record,0,0,0,1);
516                         }
517                         $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>';
518                 } else {
519                         $content = $LANG->getLL('noDifferences',1);
520                 }
521                 return $SOBE->doc->section($LANG->getLL('mergedDifferences',1),$content,0,1,0,1);
522         }
523
524         /**
525          * Renders HTML table-rows with the comparison information of an sys_history entry record
526          *
527          * @param       array           sys_history entry record.
528          * @param       string          The table name
529          * @param       integer         If set to UID of record, display rollback links
530          * @return      string          HTML table
531          * @access private
532          */
533         function renderDiff($entry,$table,$rollbackUid=0)       {
534                 global $SOBE, $LANG, $TCA;
535                 $lines=array();
536                 if (is_array($entry['newRecord']))      {
537
538                         $t3lib_diff_Obj = t3lib_div::makeInstance('t3lib_diff');
539
540                         $fieldsToDisplay = array_keys($entry['newRecord']);
541                         foreach($fieldsToDisplay as $fN)        {
542                                 t3lib_div::loadTCA($table);
543                                 if (is_array($TCA[$table]['columns'][$fN]) && $TCA[$table]['columns'][$fN]['config']['type']!='passthrough')    {
544
545                                                 // Create diff-result:
546                                         $diffres = $t3lib_diff_Obj->makeDiffDisplay(
547                                                 t3lib_BEfunc::getProcessedValue($table,$fN,$entry['oldRecord'][$fN],0,1),
548                                                 t3lib_BEfunc::getProcessedValue($table,$fN,$entry['newRecord'][$fN],0,1)
549                                         );
550                                         $lines[]='
551                                                 <tr class="bgColor4">
552                                                 '.($rollbackUid?'<td style="width:33px">'.$this->createRollbackLink($table.':'.$rollbackUid.':'.$fN, $LANG->getLL('revertField',1),2).'</td>':'').'
553                                                         <td style="width:90px"><em>'.$LANG->sl(t3lib_BEfunc::getItemLabel($table,$fN),1).'</em></td>
554                                                         <td style="width:300px">'.nl2br($diffres).'</td>
555                                                 </tr>';
556                                 }
557                         }
558                 }
559                 if ($lines)     {
560                         $content = '<table border="0" cellpadding="2" cellspacing="2" id="typo3-history-item">
561                                         '.implode('',$lines).'
562                                 </table>';
563                         return $content;
564                 }
565                 return NULL;    // error fallback
566         }
567
568         /*******************************
569          *
570          * build up history
571          *
572          *******************************/
573
574         /**
575          * Creates a diff between the current version of the records and the selected version
576          *
577          * @return      array           diff for many elements
578          */
579         function createMultipleDiff()   {
580                 $insertsDeletes = array();
581                 $newArr = array();
582                 $differences = array();
583                 if (!$this->changeLog)  {
584                         return 0;
585                 }
586
587                         // traverse changelog array
588                 foreach ($this->changeLog as $key => $value)    {
589                         $field = $value['tablename'].':'.$value['recuid'];
590                                 // inserts / deletes
591                         if ($value['action'])   {
592                                 if (!$insertsDeletes[$field])   {
593                                         $insertsDeletes[$field] = 0;
594                                 }
595                                 if ($value['action'] == 'insert')       {
596                                         $insertsDeletes[$field]++;
597                                 } else {
598                                         $insertsDeletes[$field]--;
599                                 }
600                                         // unset not needed fields
601                                 if ($insertsDeletes[$field] == 0)       {
602                                         unset($insertsDeletes[$field]);
603                                 }
604                         } else {
605                                         // update fields
606                                 if (!isset($newArr[$field]))    {       // first row of field
607                                         $newArr[$field] = $value['newRecord'];
608                                         $differences[$field] = $value['oldRecord'];
609                                 } else { // standard
610                                         $differences[$field] = array_merge($differences[$field],$value['oldRecord']);
611                                 }
612                         }
613                 }
614
615                         // remove entries where there were no changes effectively
616                 foreach ($newArr as $record => $value)  {
617                         foreach ($value as $key => $innerVal)   {
618                                 if ($newArr[$record][$key] == $differences[$record][$key])      {
619                                         unset($newArr[$record][$key]);
620                                         unset($differences[$record][$key]);
621                                 }
622                         }
623                         if (empty($newArr[$record]) && empty($differences[$record]))    {
624                                 unset($newArr[$record]);
625                                 unset($differences[$record]);
626                         }
627                 }
628                 return array(
629                         'newData' => $newArr,
630                         'oldData' => $differences,
631                         'insertsDeletes' => $insertsDeletes
632                 );
633         }
634
635         /**
636          * Creates change log including sub-elements, filling $this->changeLog
637          *
638          * @return      [type]          ...
639          */
640         function createChangeLog()      {
641
642                 global $TCA;
643                 $elParts = explode(':',$this->element);
644                 $changeLog = $this->getHistoryData($elParts[0],$elParts[1]);
645
646                         // get history of tables of this page and merge it into changelog
647                 if ($elParts[0] == 'pages' && $this->showSubElements)   {
648                         foreach ($TCA as $tablename => $value)  {
649                                 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid',$tablename,'pid='.intval($elParts[1]));     // check if there are records on the page
650                                 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))      {
651                                         if ($newChangeLog = $this->getHistoryData($tablename, $row['uid']))     {       // if there is history data available, merge it into changelog
652                                                 foreach ($newChangeLog as $key => $value)       {
653                                                         $changeLog[$key] = $value;
654                                                 }
655                                         }
656                                 }
657                         }
658                 }
659                 if(!$changeLog) {
660                         return 0;
661                 }
662
663                 krsort($changeLog);
664                 $this->changeLog = $changeLog;
665
666                 return 1;
667         }
668
669         /**
670          * Gets history and delete/insert data from sys_log and sys_history
671          *
672          * @param       string          DB table name
673          * @param       integer         UID of record
674          * @return      array           history data of the record
675          */
676         function getHistoryData($table,$uid)    {
677                 global $TCA;
678                 $uid = $this->resolveElement($table,$uid);
679                         // If table is found in $TCA:
680                 if ($TCA[$table])       {
681                                 // Selecting the $this->maxSteps most recent states:
682                         $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
683                                                 'sys_history.*,sys_log.userid',
684                                                 'sys_history,sys_log',
685                                                 'sys_history.sys_log_uid=sys_log.uid
686                                                         AND sys_history.tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($table, 'sys_history').'
687                                                         AND sys_history.recuid='.intval($uid),
688                                                 '',
689                                                 'sys_log.uid DESC',
690                                                 $this->maxSteps
691                                         );
692
693                                 // Traversing the result, building up changesArray / changeLog:
694                         #$changesArray=array(); // used temporarily to track intermedia changes
695                         $changeLog=array();
696                         while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))      {
697                                         // only history until a certain syslog ID needed
698                                 if ($row['sys_log_uid'] < $this->lastSyslogId && $this->lastSyslogId)   {
699                                         continue;
700                                 }
701                                 $hisDat = unserialize($row['history_data']);
702                                 if (is_array($hisDat['newRecord']) && is_array($hisDat['oldRecord']))   {
703
704                                                 // Add hisDat to the changeLog
705                                         $hisDat['uid']=$row['uid'];
706                                         $hisDat['tstamp']=$row['tstamp'];
707                                         $hisDat['user']=$row['userid'];
708                                         $hisDat['snapshot']=$row['snapshot'];
709                                         $hisDat['fieldlist']=$row['fieldlist'];
710                                         $hisDat['tablename']=$row['tablename'];
711                                         $hisDat['recuid']=$row['recuid'];
712
713                                         $changeLog[$row['sys_log_uid']]=$hisDat;
714
715                                                 // Update change array
716                                                 // This is used to detect if any intermedia changes have been made.
717                                         #$changesArray = array_merge($changesArray,$hisDat['oldRecord']);
718                                 } else {
719                                         debug('ERROR: [getHistoryData]');
720                                         return 0;       // error fallback
721                                 }
722                         }
723                                 // SELECT INSERTS/DELETES
724                         if ($this->showInsertDelete)    {
725                                         // Select most recent inserts and deletes // WITHOUT snapshots
726                                 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
727                                                 'uid,userid,action,tstamp',
728                                                 'sys_log',
729                                                 'type=1
730                                                         AND ( action=1 OR action=3 )
731                                                         AND tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($table, 'sys_log').'
732                                                         AND recuid='.intval($uid),
733                                                 '',
734                                                 'uid DESC',
735                                                 $this->maxSteps
736                                         );
737                                 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))      {
738
739                                         if ($row['uid'] < $this->lastSyslogId && $this->lastSyslogId)   {
740                                                 continue;
741                                         }
742                                         $hisDat = array();
743
744                                         switch ($row['action']) {
745                                                 case 1: // Insert
746                                                         $hisDat['action'] = 'insert';
747                                                         break;
748                                                 case 3: // Delete
749                                                         $hisDat['action'] = 'delete';
750                                                         break;
751                                         }
752                                         $hisDat['tstamp']=$row['tstamp'];
753                                         $hisDat['user']=$row['userid'];
754                                         $hisDat['tablename'] = $table;
755                                         $hisDat['recuid'] = $uid;
756                                         $changeLog[$row['uid']] = $hisDat;
757                                 }
758                         }
759                         return $changeLog;
760                 }
761                 return 0;       // error fallback
762         }
763
764
765         /*******************************
766          *
767          * Various helper functions
768          *
769          *******************************/
770
771         /**
772          * generates the title and puts the record title behind
773          *
774          * @param       [type]          $table: ...
775          * @param       [type]          $uid: ...
776          * @return      [type]          ...
777          */
778         function generateTitle($table, $uid)    {
779                 global $TCA;
780
781                 $out = $table.':'.$uid;
782                 if ($labelField = $TCA[$table]['ctrl']['label'])        {
783                         $record = t3lib_BEfunc::getRecordRaw($table, 'uid='.intval($uid));
784                         $out .= ' ('.t3lib_BEfunc::getRecordTitle($table, $record, TRUE).')';
785                 }
786                 return $out;
787         }
788         /**
789          * creates a link for the rollback
790          *
791          * @param       sting           parameter which is set to rollbackFields
792          * @param       string          optional, alternative label and title tag of image
793          * @param       integer         optional, type of rollback: 0 - ALL; 1 - element; 2 - field
794          * @return      string          HTML output
795          */
796         function createRollbackLink($key, $alt='', $type=0)     {
797                  global $LANG;
798
799                  return $this->linkPage('<img '.t3lib_iconWorks::skinImg('','gfx/revert_'.$type.'.gif','width="33" height="33"').' alt="'.$alt.'" title="'.$alt.'" align="middle" />',array('rollbackFields'=>$key));
800          }
801
802         /**
803          * Creates a link to the same page.
804          *
805          * @param       string          String to wrap in <a> tags (must be htmlspecialchars()'ed prior to calling function)
806          * @param       array           Array of key/value pairs to override the default values with.
807          * @param       string          Possible anchor value.
808          * @param       string          Possible title.
809          * @return      string          Link.
810          * @access private
811          */
812         function linkPage($str,$inparams=array(),$anchor='',$title='')  {
813
814                         // Setting default values based on GET parameters:
815                 $params['element']=$this->element;
816                 $params['returnUrl']=$this->returnUrl;
817                 $params['diff']=$this->lastSyslogId;
818                         // Mergin overriding values:
819                 $params = array_merge($params,$inparams);
820
821                         // Make the link:
822                 $Ahref = 'show_rechis.php?'.t3lib_div::implodeArrayForUrl('',$params).($anchor?'#'.$anchor:'');
823                 $link = '<a href="'.htmlspecialchars($Ahref).'"'.($title?' title="'.$title.'"':'').'>'.$str.'</a>';
824
825                         // Return link:
826                 return $link;
827         }
828
829         /**
830          * 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")
831          *
832          * @param       string          Table name
833          * @param       array           The data array
834          * @return      array           The modified data array
835          * @access private
836          */
837         function removeFilefields($table,$dataArray)    {
838                 global $TCA;
839
840                 if ($TCA[$table])       {
841                         t3lib_div::loadTCA($table);
842
843                         foreach($TCA[$table]['columns'] as $field => $config)   {
844                                 if ($config['config']['type']=='group' && $config['config']['internal_type']=='file')   {
845                                         unset($dataArray[$field]);
846                                 }
847                         }
848                 }
849                 return $dataArray;
850         }
851
852         /**
853          * Convert input element reference to workspace version if any.
854          *
855          * @param       string          table of input element
856          * @param       integer         UID of record
857          * @return      integer         converted UID of record
858          */
859         function resolveElement($table,$uid)    {
860                 if (isset($GLOBALS['TCA'][$table]))     {
861                         if ($workspaceVersion = t3lib_BEfunc::getWorkspaceVersionOfRecord($GLOBALS['BE_USER']->workspace, $table, $uid, 'uid')) {
862                                 $uid = $workspaceVersion['uid'];
863                         }
864                 }
865                 return $uid;
866         }
867
868         /**
869          * resolve sh_uid (used from log)
870          *
871          * @return      [type]          ...
872          */
873         function resolveShUid() {
874                 if (t3lib_div::_GP('sh_uid'))   {
875                         $sh_uid = t3lib_div::_GP('sh_uid');
876                         $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*','sys_history', 'uid='.intval($sh_uid));
877                         $record = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res);
878                         $this->element = $record['tablename'].':'.$record['recuid'];
879                         $this->lastSyslogId = $record['sys_log_uid']-1;
880                 }
881         }
882 }
883 ?>