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