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