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