2 namespace TYPO3\CMS\Backend\History;
111 $this->returnUrl = $this->
getArgument(
'returnUrl');
113 $this->rollbackFields = $this->
getArgument(
'rollbackFields');
129 $this->rollbackFields = $this->
getArgument(
'revert');
130 $this->showInsertDelete = 0;
131 $this->showSubElements = 0;
132 $element = explode(
':', $this->element);
140 $this->lastSyslogId = $record[
'sys_log_uid'];
154 if ($this->rollbackFields) {
158 if ($this->lastSyslogId) {
162 if ($this->element) {
163 $content .= $this->displayHistory();
183 $row = $this->
getDatabaseConnection()->exec_SELECTgetSingleRow(
'snapshot',
'sys_history',
'uid=' . $uid);
185 $this->
getDatabaseConnection()->exec_UPDATEquery(
'sys_history',
'uid=' . $uid, array(
'snapshot' => !$row[
'snapshot']));
198 if (!$this->rollbackFields) {
201 $reloadPageFrame = 0;
202 $rollbackData = explode(
':', $this->rollbackFields);
205 $cmdmapArray = array();
207 if ($diff[
'insertsDeletes']) {
208 switch (count($rollbackData)) {
211 $data = $diff[
'insertsDeletes'];
215 if ($diff[
'insertsDeletes'][$this->rollbackFields]) {
216 $data[$this->rollbackFields] = $diff[
'insertsDeletes'][$this->rollbackFields];
224 foreach ($data as $key => $action) {
225 $elParts = explode(
':', $key);
226 if ((
int)$action === 1) {
228 $cmdmapArray[$elParts[0]][$elParts[1]][
'delete'] = 1;
230 unset($diff[
'oldData'][$key]);
231 unset($diff[
'newData'][$key]);
232 }
elseif ((
int)$action === -1) {
234 $cmdmapArray[$elParts[0]][$elParts[1]][
'undelete'] = 1;
242 $tce->stripslashes_values = 0;
244 $tce->dontProcessTransformations = 1;
245 $tce->start(array(), $cmdmapArray);
246 $tce->process_cmdmap();
248 if (isset($cmdmapArray[
'pages'])) {
249 $reloadPageFrame = 1;
254 $diffModified = array();
255 foreach ($diff[
'oldData'] as $key => $value) {
256 $splitKey = explode(
':', $key);
257 $diffModified[$splitKey[0]][$splitKey[1]] = $value;
259 switch (count($rollbackData)) {
262 $data = $diffModified;
266 $data[$rollbackData[0]][$rollbackData[1]] = $diffModified[$rollbackData[0]][$rollbackData[1]];
270 $data[$rollbackData[0]][$rollbackData[1]][$rollbackData[2]] = $diffModified[$rollbackData[0]][$rollbackData[1]][$rollbackData[2]];
277 $tce->stripslashes_values = 0;
279 $tce->dontProcessTransformations = 1;
280 $tce->start($data, array());
281 $tce->process_datamap();
283 if (isset($data[
'pages'])) {
284 $reloadPageFrame = 1;
287 $this->lastSyslogId =
false;
288 $this->rollbackFields =
false;
291 if ($reloadPageFrame) {
292 return '<script type="text/javascript">
294 if (top.content && top.content.nav_frame && top.content.nav_frame.refresh_nav) {
295 top.content.nav_frame.refresh_nav();
316 $currentSelection = is_array($this->
getBackendUser()->uc[
'moduleData'][
'history'])
318 : array(
'maxSteps' =>
'',
'showDiff' => 1,
'showSubElements' => 1,
'showInsertDelete' => 1);
319 $currentSelectionOverride = $this->
getArgument(
'settings');
320 if ($currentSelectionOverride) {
321 $currentSelection = array_merge($currentSelection, $currentSelectionOverride);
322 $this->
getBackendUser()->uc[
'moduleData'][
'history'] = $currentSelection;
326 $selector[
'maxSteps'] = array(
331 '' =>
'maxSteps_all',
332 'marked' =>
'maxSteps_marked'
334 $selector[
'showDiff'] = array(
336 1 =>
'showDiff_inline'
338 $selector[
'showSubElements'] = array(
342 $selector[
'showInsertDelete'] = array(
350 foreach ($selector as $key => $values) {
351 $displayCode .=
'<tr><td>' . $languageService->getLL($key,
true) .
'</td>';
353 $label = ($currentSelection[$key] !==
''
354 ? ($languageService->getLL($selector[$key][$currentSelection[$key]],
true) ?: $selector[$key][$currentSelection[$key]])
355 : ($languageService->getLL($selector[$key][$currentSelection[0]],
true) ?: $selector[$key][$currentSelection[0]])
358 $displayCode .=
'<td>
359 <div class="btn-group">
360 <button class="btn btn-default dropdown-toggle" type="button" id="copymodeSelector" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
362 <span class="caret"></span>
364 <ul class="dropdown-menu" aria-labelledby="copymodeSelector">';
366 foreach ($values as $singleKey => $singleVal) {
367 $caption = $languageService->getLL($singleVal,
true) ?: htmlspecialchars($singleVal);
368 $displayCode .=
'<li><a href="#" onclick="document.settings.method=\'POST\'; document.settings.action=' . htmlspecialchars(
GeneralUtility::quoteJSvalue($scriptUrl .
'&settings[' . $key .
']=' . $singleKey)) .
'; document.settings.submit()">' . $caption .
'</a></li>';
378 if ($currentSelection[
'maxSteps'] !==
'marked') {
379 $this->maxSteps = $currentSelection[
'maxSteps'] ? (int)$currentSelection[
'maxSteps'] :
'';
381 $this->showMarked =
true;
382 $this->maxSteps =
false;
384 $this->showDiff = (int)$currentSelection[
'showDiff'];
385 $this->showSubElements = (int)$currentSelection[
'showSubElements'];
386 $this->showInsertDelete = (int)$currentSelection[
'showInsertDelete'];
389 $elParts = explode(
':', $this->element);
390 if (!empty($this->element) && $elParts[0] !==
'pages') {
391 $content .=
'<strong>' . $languageService->getLL(
'elementHistory',
true) .
'</strong><br />';
392 $pid = $this->
getRecord($elParts[0], $elParts[1]);
395 $content .= $this->
linkPage(
'<span class="btn btn-default" style="margin-bottom: 5px;">' . $languageService->getLL(
'elementHistory_link',
true) .
'</span>', array(
'element' =>
'pages:' . $pid[
'pid']));
399 $content .=
'<a name="settings_head"></a>
402 <div class="col-sm-12 col-md-6 col-lg-4">
403 <div class="panel panel-default">
404 <div class="panel-heading">' . $languageService->getLL(
'settings',
true) .
'</div>
405 <table class="table">
414 return '<div>' . $content .
'</div>';
422 public function displayHistory()
424 if (empty($this->changeLog)) {
430 $lines[] =
'<thead><tr>
431 <th>' . $languageService->getLL(
'rollback',
true) .
'</th>
432 <th>' . $languageService->getLL(
'time',
true) .
'</th>
433 <th>' . $languageService->getLL(
'age',
true) .
'</th>
434 <th>' . $languageService->getLL(
'user',
true) .
'</th>
435 <th>' . $languageService->getLL(
'tableUid',
true) .
'</th>
436 <th>' . $languageService->getLL(
'differences',
true) .
'</th>
446 foreach ($this->changeLog as $sysLogUid => $entry) {
448 if ($this->maxSteps && $i > $this->maxSteps) {
452 if (!$entry[
'snapshot'] && $this->showMarked) {
457 $userName = $entry[
'user'] ? $beUserArray[$entry[
'user']][
'username'] : $languageService->getLL(
'externalChange',
true);
459 $singleLine = array();
461 $image =
'<span title="' . $languageService->getLL(
'sumUpChanges',
true) .
'">' . $this->iconFactory->getIcon(
'actions-document-history-open',
Icon::SIZE_SMALL)->render() .
'</span>';
462 $singleLine[] =
'<span>' . $this->
linkPage($image, array(
'diff' => $sysLogUid)) .
'</span>';
466 $singleLine[] = htmlspecialchars(
BackendUtility::calcAge(
$GLOBALS[
'EXEC_TIME'] - $entry[
'tstamp'], $languageService->sL(
'LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears')));
468 $userEntry = is_array($beUserArray[$entry[
'user']]) ? $beUserArray[$entry[
'user']] : null;
469 $singleLine[] = $avatar->render($userEntry) .
' ' . htmlspecialchars($userName);
473 array(
'element' => $entry[
'tablename'] .
':' . $entry[
'recuid']),
475 $languageService->getLL(
'linkRecordHistory',
true)
479 if ($entry[
'action']) {
481 $singleLine[] =
'<strong>' . htmlspecialchars($languageService->getLL($entry[
'action'],
true)) .
'</strong>';
484 if (!$this->showDiff) {
486 $tmpFieldList = explode(
',', $entry[
'fieldlist']);
487 foreach ($tmpFieldList as $key => $value) {
490 $tmpFieldList[$key] = $tmp;
493 unset($tmpFieldList[$key]);
496 $singleLine[] = htmlspecialchars(implode(
',', $tmpFieldList));
499 $diff = $this->
renderDiff($entry, $entry[
'tablename']);
500 $singleLine[] = $diff;
504 if (!$entry[
'action']) {
505 if ($entry[
'snapshot']) {
506 $title = $languageService->getLL(
'unmarkState',
true);
507 $image = $this->iconFactory->getIcon(
'actions-unmarkstate',
Icon::SIZE_SMALL)->render();
509 $title = $languageService->getLL(
'markState',
true);
510 $image = $this->iconFactory->getIcon(
'actions-markstate',
Icon::SIZE_SMALL)->render();
512 $singleLine[] = $this->
linkPage($image, array(
'highlight' => $entry[
'uid']),
'', $title);
519 <td>' . implode(
'</td><td>', $singleLine) .
'</td>
524 $theCode =
'<div class="callout callout-info">'
525 .
'<div class="media"><div class="media-left"><span class="fa-stack fa-lg callout-icon"><i class="fa fa-circle fa-stack-2x"></i><i class="fa fa-info fa-stack-1x"></i></span></div>'
526 .
'<div class="media-body">'
527 .
'<p>' . $languageService->getLL(
'differenceMsg') .
'</p>'
528 .
' <div class="callout-body">'
529 .
' </div></div></div></div>';
537 <table class="table table-striped table-hover table-vertical-top" id="typo3-history">
538 ' . implode(
'', $lines) .
'
540 if ($this->lastSyslogId) {
541 $theCode .=
'<br />' . $this->
linkPage(
'<span class="btn btn-default">' . $languageService->getLL(
'fullView',
true) .
'</span>', array(
'diff' =>
''));
544 $theCode .=
'<br /><br />';
547 return '<h2>' . $languageService->getLL(
'changes',
true) .
'</h2><div>' . $theCode .
'</div>';
560 $arrayKeys = array_merge(array_keys($diff[
'newData']), array_keys($diff[
'insertsDeletes']), array_keys($diff[
'oldData']));
561 $arrayKeys = array_unique($arrayKeys);
564 foreach ($arrayKeys as $key) {
566 $elParts = explode(
':', $key);
568 if ((
int)$diff[
'insertsDeletes'][$key] === 1) {
570 $record .=
'<strong>' . $languageService->getLL(
'delete',
true) .
'</strong>';
572 }
elseif ((
int)$diff[
'insertsDeletes'][$key] === -1) {
573 $record .=
'<strong>' . $languageService->getLL(
'insert',
true) .
'</strong>';
578 if ($diff[
'newData'][$key]) {
579 $tmpArr[
'newRecord'] = $diff[
'oldData'][$key];
580 $tmpArr[
'oldRecord'] = $diff[
'newData'][$key];
581 $record .= $this->
renderDiff($tmpArr, $elParts[0], $elParts[1]);
583 $elParts = explode(
':', $key);
585 $record =
'<div style="padding-left:10px;border-left:5px solid darkgray;border-bottom:1px dotted darkgray;padding-bottom:2px;">' . $record .
'</div>';
587 $content .=
'<h3>' . $titleLine .
'</h3><div>' . $record .
'</div>';
591 $languageService->getLL(
'revertAll',
true),
593 ) .
'<div style="padding-left:10px;border-left:5px solid darkgray;border-bottom:1px dotted darkgray;padding-bottom:2px;">' . $content .
'</div>';
595 $content = $languageService->getLL(
'noDifferences',
true);
597 return '<h2>' . $languageService->getLL(
'mergedDifferences',
true) .
'</h2><div>' . $content .
'</div>';
612 if (is_array($entry[
'newRecord'])) {
614 $fieldsToDisplay = array_keys($entry[
'newRecord']);
616 foreach ($fieldsToDisplay as $fN) {
617 if (is_array(
$GLOBALS[
'TCA'][$table][
'columns'][$fN]) &&
$GLOBALS[
'TCA'][$table][
'columns'][$fN][
'config'][
'type'] !==
'passthrough') {
619 $diffres = $diffUtility->makeDiffDisplay(
620 BackendUtility::getProcessedValue($table, $fN, $entry[
'oldRecord'][$fN], 0,
true),
621 BackendUtility::getProcessedValue($table, $fN, $entry[
'newRecord'][$fN], 0,
true)
624 <div class="diff-item">
625 <div class="diff-item-title">
626 ' . ($rollbackUid ? $this->
createRollbackLink(($table .
':' . $rollbackUid .
':' . $fN), $languageService->getLL(
'revertField',
true), 2) :
'') .
'
629 <div class="diff-item-result">' . str_replace(
'\n', PHP_EOL, str_replace(
'\r\n',
'\n', $diffres)) .
'</div>
635 return '<div class="diff">' . implode(
'', $lines) .
'</div>';
653 $insertsDeletes = array();
655 $differences = array();
656 if (!$this->changeLog) {
660 foreach ($this->changeLog as $value) {
661 $field = $value[
'tablename'] .
':' . $value[
'recuid'];
663 if ($value[
'action']) {
664 if (!$insertsDeletes[$field]) {
665 $insertsDeletes[$field] = 0;
667 if ($value[
'action'] ===
'insert') {
668 $insertsDeletes[$field]++;
670 $insertsDeletes[$field]--;
673 if ($insertsDeletes[$field] === 0) {
674 unset($insertsDeletes[$field]);
679 if (!isset($newArr[$field])) {
680 $newArr[$field] = $value[
'newRecord'];
681 $differences[$field] = $value[
'oldRecord'];
684 $differences[$field] = array_merge($differences[$field], $value[
'oldRecord']);
689 foreach ($newArr as $record => $value) {
690 foreach ($value as $key => $innerVal) {
691 if ($newArr[$record][$key] == $differences[$record][$key]) {
692 unset($newArr[$record][$key]);
693 unset($differences[$record][$key]);
696 if (empty($newArr[$record]) && empty($differences[$record])) {
697 unset($newArr[$record]);
698 unset($differences[$record]);
702 'newData' => $newArr,
703 'oldData' => $differences,
704 'insertsDeletes' => $insertsDeletes
715 $elParts = explode(
':', $this->element);
717 if (empty($this->element)) {
723 if ($elParts[0] ==
'pages' && $this->showSubElements && $this->
hasPageAccess(
'pages', $elParts[1])) {
724 foreach (
$GLOBALS[
'TCA'] as $tablename => $value) {
726 $rows = $this->
getDatabaseConnection()->exec_SELECTgetRows(
'uid', $tablename,
'pid=' . (
int)$elParts[1]);
730 foreach ($rows as $row) {
733 if (is_array($newChangeLog) && !empty($newChangeLog)) {
734 foreach ($newChangeLog as $key => $newChangeLogEntry) {
766 $rows = $databaseConnection->exec_SELECTgetRows(
'sys_history.*, sys_log.userid',
'sys_history, sys_log',
'sys_history.sys_log_uid = sys_log.uid
767 AND sys_history.tablename = ' . $databaseConnection->fullQuoteStr($table,
'sys_history') .
'
768 AND sys_history.recuid = ' . (int)$uid,
'',
'sys_log.uid DESC', $this->maxSteps);
772 foreach ($rows as $row) {
774 if ($this->lastSyslogId && $row[
'sys_log_uid'] < $this->lastSyslogId) {
777 $hisDat = unserialize($row[
'history_data']);
778 if (is_array($hisDat[
'newRecord']) && is_array($hisDat[
'oldRecord'])) {
780 $hisDat[
'uid'] = $row[
'uid'];
781 $hisDat[
'tstamp'] = $row[
'tstamp'];
782 $hisDat[
'user'] = $row[
'userid'];
783 $hisDat[
'snapshot'] = $row[
'snapshot'];
784 $hisDat[
'fieldlist'] = $row[
'fieldlist'];
785 $hisDat[
'tablename'] = $row[
'tablename'];
786 $hisDat[
'recuid'] = $row[
'recuid'];
789 debug(
'ERROR: [getHistoryData]');
796 if ($this->showInsertDelete) {
798 $rows = $databaseConnection->exec_SELECTgetRows(
'uid, userid, action, tstamp',
'sys_log',
'type = 1
799 AND (action=1 OR action=3)
800 AND tablename = ' . $databaseConnection->fullQuoteStr($table,
'sys_log') .
'
801 AND recuid = ' . (int)$uid,
'',
'uid DESC', $this->maxSteps);
806 foreach ($rows as $row) {
807 if ($this->lastSyslogId && $row[
'uid'] < $this->lastSyslogId) {
811 switch ($row[
'action']) {
814 $hisDat[
'action'] =
'insert';
818 $hisDat[
'action'] =
'delete';
821 $hisDat[
'tstamp'] = $row[
'tstamp'];
822 $hisDat[
'user'] = $row[
'userid'];
823 $hisDat[
'tablename'] = $table;
824 $hisDat[
'recuid'] = $uid;
845 $out = $table .
':' . $uid;
846 if ($labelField =
$GLOBALS[
'TCA'][$table][
'ctrl'][
'label']) {
847 $record = $this->
getRecord($table, $uid);
863 return $this->
linkPage(
'<span class="btn btn-default" style="margin-right: 5px;">' . $alt .
'</span>', array(
'rollbackFields' => $key));
876 public function linkPage($str, $inparams = array(), $anchor =
'', $title =
'')
883 $params = array_merge($params, $inparams);
885 $link = BackendUtility::getModuleUrl(
'record_history', $params) . ($anchor ?
'#' . $anchor :
'');
886 return '<a href="' . htmlspecialchars($link) .
'"' . ($title ?
' title="' . $title .
'"' :
'') .
'>' . $str .
'</a>';
901 foreach (
$GLOBALS[
'TCA'][$table][
'columns'] as $field => $config) {
902 if ($config[
'config'][
'type'] ===
'group' && $config[
'config'][
'internal_type'] ===
'file') {
903 unset($dataArray[$field]);
919 if (isset(
$GLOBALS[
'TCA'][$table])) {
921 $uid = $workspaceVersion[
'uid'];
938 $record = $this->
getDatabaseConnection()->exec_SELECTgetSingleRow(
'*',
'sys_history',
'uid=' . (
int)$shUid);
939 if (empty($record)) {
942 $this->element = $record[
'tablename'] .
':' . $record[
'recuid'];
943 $this->lastSyslogId = $record[
'sys_log_uid'] - 1;
957 if ($table ===
'pages') {
960 $record = $this->
getRecord($table, $uid);
961 $pageId = $record[
'pid'];
964 if (!isset($this->pageAccessCache[$pageId])) {
970 return ($this->pageAccessCache[$pageId] !==
false);
993 if (!isset($this->recordCache[$table][$uid])) {
996 return $this->recordCache[$table][$uid];
1023 if ($value !==
'' && !preg_match(
'#^[a-z0-9_.]+:[0-9]+$#i', $value)) {
1027 case 'rollbackFields':
1029 if ($value !==
'' && !preg_match(
'#^[a-z0-9_.]+(:[0-9]+(:[a-z0-9_.]+)?)?$#i', $value)) {
1039 $value = (int)$value;
1042 if (!is_array($value)) {