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