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