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