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