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