TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
RecordHistory.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Backend\History;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
25 
30 {
36  public $maxSteps = 20;
37 
43  public $showDiff = 1;
44 
50  public $showSubElements = 1;
51 
57  public $showInsertDelete = 1;
58 
64  public $element;
65 
71  public $lastSyslogId;
72 
76  public $returnUrl;
77 
81  public $changeLog = [];
82 
86  public $showMarked = false;
87 
91  protected $recordCache = [];
92 
96  protected $pageAccessCache = [];
97 
101  protected $iconFactory;
102 
106  protected $view;
107 
111  public function __construct()
112  {
113  $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
114  // GPvars:
115  $this->element = $this->getArgument('element');
116  $this->returnUrl = $this->getArgument('returnUrl');
117  $this->lastSyslogId = $this->getArgument('diff');
118  $this->rollbackFields = $this->getArgument('rollbackFields');
119  // Resolve sh_uid if set
120  $this->resolveShUid();
121 
122  $this->view = $this->getFluidTemplateObject();
123  }
124 
131  public function main()
132  {
133  // Save snapshot
134  if ($this->getArgument('highlight') && !$this->getArgument('settings')) {
135  $this->toggleHighlight($this->getArgument('highlight'));
136  }
137 
138  $this->displaySettings();
139 
140  if ($this->createChangeLog()) {
141  if ($this->rollbackFields) {
142  $completeDiff = $this->createMultipleDiff();
143  $this->performRollback($completeDiff);
144  }
145  if ($this->lastSyslogId) {
146  $this->view->assign('lastSyslogId', $this->lastSyslogId);
147  $completeDiff = $this->createMultipleDiff();
148  $this->displayMultipleDiff($completeDiff);
149  }
150  if ($this->element) {
151  $this->displayHistory();
152  }
153  }
154 
155  return $this->view->render();
156  }
157 
158  /*******************************
159  *
160  * database actions
161  *
162  *******************************/
169  public function toggleHighlight($uid)
170  {
171  $uid = (int)$uid;
172  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_history');
173  $row = $queryBuilder
174  ->select('snapshot')
175  ->from('sys_history')
176  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)))
177  ->execute()
178  ->fetch();
179 
180  if (!empty($row)) {
181  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_history');
182  $queryBuilder
183  ->update('sys_history')
184  ->set('snapshot', (int)!$row['snapshot'])
185  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)))
186  ->execute();
187  }
188  }
189 
197  public function performRollback($diff)
198  {
199  if (!$this->rollbackFields) {
200  return '';
201  }
202  $reloadPageFrame = 0;
203  $rollbackData = explode(':', $this->rollbackFields);
204  // PROCESS INSERTS AND DELETES
205  // rewrite inserts and deletes
206  $cmdmapArray = [];
207  $data = [];
208  if ($diff['insertsDeletes']) {
209  switch (count($rollbackData)) {
210  case 1:
211  // all tables
212  $data = $diff['insertsDeletes'];
213  break;
214  case 2:
215  // one record
216  if ($diff['insertsDeletes'][$this->rollbackFields]) {
217  $data[$this->rollbackFields] = $diff['insertsDeletes'][$this->rollbackFields];
218  }
219  break;
220  case 3:
221  // one field in one record -- ignore!
222  break;
223  }
224  if (!empty($data)) {
225  foreach ($data as $key => $action) {
226  $elParts = explode(':', $key);
227  if ((int)$action === 1) {
228  // inserted records should be deleted
229  $cmdmapArray[$elParts[0]][$elParts[1]]['delete'] = 1;
230  // When the record is deleted, the contents of the record do not need to be updated
231  unset($diff['oldData'][$key]);
232  unset($diff['newData'][$key]);
233  } elseif ((int)$action === -1) {
234  // deleted records should be inserted again
235  $cmdmapArray[$elParts[0]][$elParts[1]]['undelete'] = 1;
236  }
237  }
238  }
239  }
240  // Writes the data:
241  if ($cmdmapArray) {
242  $tce = GeneralUtility::makeInstance(DataHandler::class);
243  $tce->debug = 0;
244  $tce->dontProcessTransformations = 1;
245  $tce->start([], $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  $diffModified = [];
255  foreach ($diff['oldData'] as $key => $value) {
256  $splitKey = explode(':', $key);
257  $diffModified[$splitKey[0]][$splitKey[1]] = $value;
258  }
259  switch (count($rollbackData)) {
260  case 1:
261  // all tables
262  $data = $diffModified;
263  break;
264  case 2:
265  // one record
266  $data[$rollbackData[0]][$rollbackData[1]] = $diffModified[$rollbackData[0]][$rollbackData[1]];
267  break;
268  case 3:
269  // one field in one record
270  $data[$rollbackData[0]][$rollbackData[1]][$rollbackData[2]] = $diffModified[$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(DataHandler::class);
277  $tce->debug = 0;
278  $tce->dontProcessTransformations = 1;
279  $tce->start($data, []);
280  $tce->process_datamap();
281  unset($tce);
282  if (isset($data['pages'])) {
283  $reloadPageFrame = 1;
284  }
285  // Return to normal operation
286  $this->lastSyslogId = false;
287  $this->rollbackFields = false;
288  $this->createChangeLog();
289  $this->view->assign('reloadPageFrame', $reloadPageFrame);
290  }
291 
292  /*******************************
293  *
294  * Display functions
295  *
296  *******************************/
300  public function displaySettings()
301  {
302  // Get current selection from UC, merge data, write it back to UC
303  $currentSelection = is_array($this->getBackendUser()->uc['moduleData']['history'])
304  ? $this->getBackendUser()->uc['moduleData']['history']
305  : ['maxSteps' => '', 'showDiff' => 1, 'showSubElements' => 1, 'showInsertDelete' => 1];
306  $currentSelectionOverride = $this->getArgument('settings');
307  if ($currentSelectionOverride) {
308  $currentSelection = array_merge($currentSelection, $currentSelectionOverride);
309  $this->getBackendUser()->uc['moduleData']['history'] = $currentSelection;
310  $this->getBackendUser()->writeUC($this->getBackendUser()->uc);
311  }
312  // Display selector for number of history entries
313  $selector['maxSteps'] = [
314  10 => [
315  'value' => 10
316  ],
317  20 => [
318  'value' => 20
319  ],
320  50 => [
321  'value' => 50
322  ],
323  100 => [
324  'value' => 100
325  ],
326  999 => [
327  'value' => 'maxSteps_all'
328  ],
329  'marked' => [
330  'value' => 'maxSteps_marked'
331  ]
332  ];
333  $selector['showDiff'] = [
334  0 => [
335  'value' => 'showDiff_no'
336  ],
337  1 => [
338  'value' => 'showDiff_inline'
339  ]
340  ];
341  $selector['showSubElements'] = [
342  0 => [
343  'value' => 'no'
344  ],
345  1 => [
346  'value' => 'yes'
347  ]
348  ];
349  $selector['showInsertDelete'] = [
350  0 => [
351  'value' => 'no'
352  ],
353  1 => [
354  'value' => 'yes'
355  ]
356  ];
357 
358  $scriptUrl = GeneralUtility::linkThisScript();
359  $languageService = $this->getLanguageService();
360 
361  foreach ($selector as $key => $values) {
362  foreach ($values as $singleKey => $singleVal) {
363  $selector[$key][$singleKey]['scriptUrl'] = htmlspecialchars(GeneralUtility::quoteJSvalue($scriptUrl . '&settings[' . $key . ']=' . $singleKey));
364  }
365  }
366  $this->view->assign('settings', $selector);
367  $this->view->assign('currentSelection', $currentSelection);
368  $this->view->assign('TYPO3_REQUEST_URI', htmlspecialchars(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')));
369 
370  // set values correctly
371  if ($currentSelection['maxSteps'] !== 'marked') {
372  $this->maxSteps = $currentSelection['maxSteps'] ? (int)$currentSelection['maxSteps'] : $this->maxSteps;
373  } else {
374  $this->showMarked = true;
375  $this->maxSteps = false;
376  }
377  $this->showDiff = (int)$currentSelection['showDiff'];
378  $this->showSubElements = (int)$currentSelection['showSubElements'];
379  $this->showInsertDelete = (int)$currentSelection['showInsertDelete'];
380 
381  // Get link to page history if the element history is shown
382  $elParts = explode(':', $this->element);
383  if (!empty($this->element) && $elParts[0] !== 'pages') {
384  $this->view->assign('singleElement', 'true');
385  $pid = $this->getRecord($elParts[0], $elParts[1]);
386 
387  if ($this->hasPageAccess('pages', $pid['pid'])) {
388  $this->view->assign('fullHistoryLink', $this->linkPage(htmlspecialchars($languageService->getLL('elementHistory_link')), ['element' => 'pages:' . $pid['pid']]));
389  }
390  }
391  }
392 
398  public function displayHistory()
399  {
400  if (empty($this->changeLog)) {
401  return '';
402  }
403  $languageService = $this->getLanguageService();
404  $lines = [];
405  $beUserArray = BackendUtility::getUserNames();
406 
407  $i = 0;
408 
409  // Traverse changeLog array:
410  foreach ($this->changeLog as $sysLogUid => $entry) {
411  // stop after maxSteps
412  if ($this->maxSteps && $i > $this->maxSteps) {
413  break;
414  }
415  // Show only marked states
416  if (!$entry['snapshot'] && $this->showMarked) {
417  continue;
418  }
419  $i++;
420  // Build up single line
421  $singleLine = [];
422 
423  // Get user names
424  $userName = $entry['user'] ? $beUserArray[$entry['user']]['username'] : $languageService->getLL('externalChange');
425  // Executed by switch-user
426  if (!empty($entry['originalUser'])) {
427  $userName .= ' (' . $languageService->getLL('viaUser') . ' ' . $beUserArray[$entry['originalUser']]['username'] . ')';
428  }
429  $singleLine['backendUserName'] = htmlspecialchars($userName);
430  $singleLine['backendUserUid'] = $entry['user'];
431  // add user name
432 
433  // Diff link
434  $image = $this->iconFactory->getIcon('actions-document-history-open', Icon::SIZE_SMALL)->render();
435  $singleLine['rollbackLink']= $this->linkPage($image, ['diff' => $sysLogUid]);
436  // remove first link
437  $singleLine['time'] = htmlspecialchars(BackendUtility::datetime($entry['tstamp']));
438  // add time
439  $singleLine['age'] = htmlspecialchars(BackendUtility::calcAge($GLOBALS['EXEC_TIME'] - $entry['tstamp'], $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')));
440  // add age
441 
442  $singleLine['tableUid'] = $this->linkPage(
443  $this->generateTitle($entry['tablename'], $entry['recuid']),
444  ['element' => $entry['tablename'] . ':' . $entry['recuid']],
445  '',
446  htmlspecialchars($languageService->getLL('linkRecordHistory'))
447  );
448  // add record UID
449  // Show insert/delete/diff/changed field names
450  if ($entry['action']) {
451  // insert or delete of element
452  $singleLine['action'] = htmlspecialchars($languageService->getLL($entry['action']));
453  } else {
454  // Display field names instead of full diff
455  if (!$this->showDiff) {
456  // Re-write field names with labels
457  $tmpFieldList = explode(',', $entry['fieldlist']);
458  foreach ($tmpFieldList as $key => $value) {
459  $tmp = str_replace(':', '', htmlspecialchars($languageService->sL(BackendUtility::getItemLabel($entry['tablename'], $value))));
460  if ($tmp) {
461  $tmpFieldList[$key] = $tmp;
462  } else {
463  // remove fields if no label available
464  unset($tmpFieldList[$key]);
465  }
466  }
467  $singleLine['fieldNames'] = htmlspecialchars(implode(',', $tmpFieldList));
468  } else {
469  // Display diff
470  $diff = $this->renderDiff($entry, $entry['tablename']);
471  $singleLine['differences'] = $diff;
472  }
473  }
474  // Show link to mark/unmark state
475  if (!$entry['action']) {
476  if ($entry['snapshot']) {
477  $title = htmlspecialchars($languageService->getLL('unmarkState'));
478  $image = $this->iconFactory->getIcon('actions-unmarkstate', Icon::SIZE_SMALL)->render();
479  } else {
480  $title = htmlspecialchars($languageService->getLL('markState'));
481  $image = $this->iconFactory->getIcon('actions-markstate', Icon::SIZE_SMALL)->render();
482  }
483  $singleLine['markState'] = $this->linkPage($image, ['highlight' => $entry['uid']], '', $title);
484  } else {
485  $singleLine['markState'] = '';
486  }
487  // put line together
488  $lines[] = $singleLine;
489  }
490  $this->view->assign('history', $lines);
491 
492  if ($this->lastSyslogId) {
493  $this->view->assign('fullViewLink', $this->linkPage(htmlspecialchars($languageService->getLL('fullView')), ['diff' => '']));
494  }
495  }
496 
502  public function displayMultipleDiff($diff)
503  {
504  // Get all array keys needed
505  $arrayKeys = array_merge(array_keys($diff['newData']), array_keys($diff['insertsDeletes']), array_keys($diff['oldData']));
506  $arrayKeys = array_unique($arrayKeys);
507  $languageService = $this->getLanguageService();
508  if ($arrayKeys) {
509  $lines = [];
510  foreach ($arrayKeys as $key) {
511  $singleLine = [];
512  $elParts = explode(':', $key);
513  // Turn around diff because it should be a "rollback preview"
514  if ((int)$diff['insertsDeletes'][$key] === 1) {
515  // insert
516  $singleLine['insertDelete'] = 'delete';
517  } elseif ((int)$diff['insertsDeletes'][$key] === -1) {
518  $singleLine['insertDelete'] = 'insert';
519  }
520  // Build up temporary diff array
521  // turn around diff because it should be a "rollback preview"
522  if ($diff['newData'][$key]) {
523  $tmpArr['newRecord'] = $diff['oldData'][$key];
524  $tmpArr['oldRecord'] = $diff['newData'][$key];
525  $singleLine['differences'] = $this->renderDiff($tmpArr, $elParts[0], $elParts[1]);
526  }
527  $elParts = explode(':', $key);
528  $singleLine['revertRecordLink'] = $this->createRollbackLink($key, htmlspecialchars($languageService->getLL('revertRecord')), 1);
529  $singleLine['title'] = $this->generateTitle($elParts[0], $elParts[1]);
530  $lines[] = $singleLine;
531  }
532  $this->view->assign('revertAllLink', $this->createRollbackLink('ALL', htmlspecialchars($languageService->getLL('revertAll')), 0));
533  $this->view->assign('multipleDiff', $lines);
534  }
535  }
536 
546  public function renderDiff($entry, $table, $rollbackUid = 0)
547  {
548  $lines = [];
549  if (is_array($entry['newRecord'])) {
550  /* @var DiffUtility $diffUtility */
551  $diffUtility = GeneralUtility::makeInstance(DiffUtility::class);
552  $fieldsToDisplay = array_keys($entry['newRecord']);
553  $languageService = $this->getLanguageService();
554  foreach ($fieldsToDisplay as $fN) {
555  if (is_array($GLOBALS['TCA'][$table]['columns'][$fN]) && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough') {
556  // Create diff-result:
557  $diffres = $diffUtility->makeDiffDisplay(
558  BackendUtility::getProcessedValue($table, $fN, $entry['oldRecord'][$fN], 0, true),
559  BackendUtility::getProcessedValue($table, $fN, $entry['newRecord'][$fN], 0, true)
560  );
561  $lines[] = [
562  'title' => ($rollbackUid ? $this->createRollbackLink(($table . ':' . $rollbackUid . ':' . $fN), htmlspecialchars($languageService->getLL('revertField')), 2) : '') . '
563  ' . htmlspecialchars($languageService->sL(BackendUtility::getItemLabel($table, $fN))),
564  'result' => str_replace('\n', PHP_EOL, str_replace('\r\n', '\n', $diffres))
565  ];
566  }
567  }
568  }
569  if ($lines) {
570  return $lines;
571  }
572  // error fallback
573  return null;
574  }
575 
576  /*******************************
577  *
578  * build up history
579  *
580  *******************************/
586  public function createMultipleDiff()
587  {
588  $insertsDeletes = [];
589  $newArr = [];
590  $differences = [];
591  if (!$this->changeLog) {
592  return 0;
593  }
594  // traverse changelog array
595  foreach ($this->changeLog as $value) {
596  $field = $value['tablename'] . ':' . $value['recuid'];
597  // inserts / deletes
598  if ($value['action']) {
599  if (!$insertsDeletes[$field]) {
600  $insertsDeletes[$field] = 0;
601  }
602  if ($value['action'] === 'insert') {
603  $insertsDeletes[$field]++;
604  } else {
605  $insertsDeletes[$field]--;
606  }
607  // unset not needed fields
608  if ($insertsDeletes[$field] === 0) {
609  unset($insertsDeletes[$field]);
610  }
611  } else {
612  // update fields
613  // first row of field
614  if (!isset($newArr[$field])) {
615  $newArr[$field] = $value['newRecord'];
616  $differences[$field] = $value['oldRecord'];
617  } else {
618  // standard
619  $differences[$field] = array_merge($differences[$field], $value['oldRecord']);
620  }
621  }
622  }
623  // remove entries where there were no changes effectively
624  foreach ($newArr as $record => $value) {
625  foreach ($value as $key => $innerVal) {
626  if ($newArr[$record][$key] == $differences[$record][$key]) {
627  unset($newArr[$record][$key]);
628  unset($differences[$record][$key]);
629  }
630  }
631  if (empty($newArr[$record]) && empty($differences[$record])) {
632  unset($newArr[$record]);
633  unset($differences[$record]);
634  }
635  }
636  return [
637  'newData' => $newArr,
638  'oldData' => $differences,
639  'insertsDeletes' => $insertsDeletes
640  ];
641  }
642 
648  public function createChangeLog()
649  {
650  $elParts = explode(':', $this->element);
651 
652  if (empty($this->element)) {
653  return 0;
654  }
655 
656  $changeLog = $this->getHistoryData($elParts[0], $elParts[1]);
657  // get history of tables of this page and merge it into changelog
658  if ($elParts[0] == 'pages' && $this->showSubElements && $this->hasPageAccess('pages', $elParts[1])) {
659  foreach ($GLOBALS['TCA'] as $tablename => $value) {
660  // check if there are records on the page
661  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tablename);
662  $queryBuilder->getRestrictions()->removeAll();
663 
664  $rows = $queryBuilder
665  ->select('uid')
666  ->from($tablename)
667  ->where(
668  $queryBuilder->expr()->eq(
669  'pid',
670  $queryBuilder->createNamedParameter($elParts[1], \PDO::PARAM_INT)
671  )
672  )
673  ->execute();
674  if ($rows->rowCount() === 0) {
675  continue;
676  }
677  foreach ($rows as $row) {
678  // if there is history data available, merge it into changelog
679  $newChangeLog = $this->getHistoryData($tablename, $row['uid']);
680  if (is_array($newChangeLog) && !empty($newChangeLog)) {
681  foreach ($newChangeLog as $key => $newChangeLogEntry) {
682  $changeLog[$key] = $newChangeLogEntry;
683  }
684  }
685  }
686  }
687  }
688  if (!$changeLog) {
689  return 0;
690  }
691  krsort($changeLog);
692  $this->changeLog = $changeLog;
693  return 1;
694  }
695 
703  public function getHistoryData($table, $uid)
704  {
705  if (empty($GLOBALS['TCA'][$table]) || !$this->hasTableAccess($table) || !$this->hasPageAccess($table, $uid)) {
706  // error fallback
707  return 0;
708  }
709  // If table is found in $GLOBALS['TCA']:
710  $uid = $this->resolveElement($table, $uid);
711  // Selecting the $this->maxSteps most recent states:
712  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_history');
713  $rows = $queryBuilder
714  ->select('sys_history.*', 'sys_log.userid', 'sys_log.log_data')
715  ->from('sys_history')
716  ->from('sys_log')
717  ->where(
718  $queryBuilder->expr()->eq(
719  'sys_history.sys_log_uid',
720  $queryBuilder->quoteIdentifier('sys_log.uid')
721  ),
722  $queryBuilder->expr()->eq(
723  'sys_history.tablename',
724  $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)
725  ),
726  $queryBuilder->expr()->eq(
727  'sys_history.recuid',
728  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
729  )
730  )
731  ->orderBy('sys_log.uid', 'DESC')
732  ->setMaxResults((int)$this->maxSteps)
733  ->execute()
734  ->fetchAll();
735 
736  $changeLog = [];
737  if (!empty($rows)) {
738  // Traversing the result, building up changesArray / changeLog:
739  foreach ($rows as $row) {
740  // Only history until a certain syslog ID needed
741  if ($this->lastSyslogId && $row['sys_log_uid'] < $this->lastSyslogId) {
742  continue;
743  }
744  $hisDat = unserialize($row['history_data']);
745  $logData = unserialize($row['log_data']);
746  if (is_array($hisDat['newRecord']) && is_array($hisDat['oldRecord'])) {
747  // Add information about the history to the changeLog
748  $hisDat['uid'] = $row['uid'];
749  $hisDat['tstamp'] = $row['tstamp'];
750  $hisDat['user'] = $row['userid'];
751  $hisDat['originalUser'] = (empty($logData['originalUser']) ? null : $logData['originalUser']);
752  $hisDat['snapshot'] = $row['snapshot'];
753  $hisDat['fieldlist'] = $row['fieldlist'];
754  $hisDat['tablename'] = $row['tablename'];
755  $hisDat['recuid'] = $row['recuid'];
756  $changeLog[$row['sys_log_uid']] = $hisDat;
757  } else {
758  debug('ERROR: [getHistoryData]');
759  // error fallback
760  return 0;
761  }
762  }
763  }
764  // SELECT INSERTS/DELETES
765  if ($this->showInsertDelete) {
766  // Select most recent inserts and deletes // WITHOUT snapshots
767  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
768  $result = $queryBuilder
769  ->select('uid', 'userid', 'action', 'tstamp', 'log_data')
770  ->from('sys_log')
771  ->where(
772  $queryBuilder->expr()->eq('type', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
773  $queryBuilder->expr()->orX(
774  $queryBuilder->expr()->eq('action', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
775  $queryBuilder->expr()->eq('action', $queryBuilder->createNamedParameter(3, \PDO::PARAM_INT))
776  ),
777  $queryBuilder->expr()->eq('tablename', $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)),
778  $queryBuilder->expr()->eq('recuid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT))
779  )
780  ->orderBy('uid', 'DESC')
781  ->setMaxResults((int)$this->maxSteps)
782  ->execute();
783 
784  // If none are found, nothing more to do
785  if ($result->rowCount() === 0) {
786  return $changeLog;
787  }
788  foreach ($result as $row) {
789  if ($this->lastSyslogId && $row['uid'] < $this->lastSyslogId) {
790  continue;
791  }
792  $hisDat = [];
793  $logData = unserialize($row['log_data']);
794  switch ($row['action']) {
795  case 1:
796  // Insert
797  $hisDat['action'] = 'insert';
798  break;
799  case 3:
800  // Delete
801  $hisDat['action'] = 'delete';
802  break;
803  }
804  $hisDat['tstamp'] = $row['tstamp'];
805  $hisDat['user'] = $row['userid'];
806  $hisDat['originalUser'] = (empty($logData['originalUser']) ? null : $logData['originalUser']);
807  $hisDat['tablename'] = $table;
808  $hisDat['recuid'] = $uid;
809  $changeLog[$row['uid']] = $hisDat;
810  }
811  }
812  return $changeLog;
813  }
814 
815  /*******************************
816  *
817  * Various helper functions
818  *
819  *******************************/
827  public function generateTitle($table, $uid)
828  {
829  $out = $table . ':' . $uid;
830  if ($labelField = $GLOBALS['TCA'][$table]['ctrl']['label']) {
831  $record = $this->getRecord($table, $uid);
832  $out .= ' (' . BackendUtility::getRecordTitle($table, $record, true) . ')';
833  }
834  return $out;
835  }
836 
845  public function createRollbackLink($key, $alt = '', $type = 0)
846  {
847  return $this->linkPage('<span class="btn btn-default" style="margin-right: 5px;">' . $alt . '</span>', ['rollbackFields' => $key]);
848  }
849 
860  public function linkPage($str, $inparams = [], $anchor = '', $title = '')
861  {
862  // Setting default values based on GET parameters:
863  $params['element'] = $this->element;
864  $params['returnUrl'] = $this->returnUrl;
865  $params['diff'] = $this->lastSyslogId;
866  // Merging overriding values:
867  $params = array_merge($params, $inparams);
868  // Make the link:
869  $link = BackendUtility::getModuleUrl('record_history', $params) . ($anchor ? '#' . $anchor : '');
870  return '<a href="' . htmlspecialchars($link) . '"' . ($title ? ' title="' . $title . '"' : '') . '>' . $str . '</a>';
871  }
872 
882  public function removeFilefields($table, $dataArray)
883  {
884  if ($GLOBALS['TCA'][$table]) {
885  foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $config) {
886  if ($config['config']['type'] === 'group' && $config['config']['internal_type'] === 'file') {
887  unset($dataArray[$field]);
888  }
889  }
890  }
891  return $dataArray;
892  }
893 
901  public function resolveElement($table, $uid)
902  {
903  if (isset($GLOBALS['TCA'][$table])) {
904  if ($workspaceVersion = BackendUtility::getWorkspaceVersionOfRecord($this->getBackendUser()->workspace, $table, $uid, 'uid')) {
905  $uid = $workspaceVersion['uid'];
906  }
907  }
908  return $uid;
909  }
910 
916  public function resolveShUid()
917  {
918  $shUid = $this->getArgument('sh_uid');
919  if (empty($shUid)) {
920  return;
921  }
922  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_history');
923  $record = $queryBuilder
924  ->select('*')
925  ->from('sys_history')
926  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($shUid, \PDO::PARAM_INT)))
927  ->execute()
928  ->fetch();
929 
930  if (empty($record)) {
931  return;
932  }
933  $this->element = $record['tablename'] . ':' . $record['recuid'];
934  $this->lastSyslogId = $record['sys_log_uid'] - 1;
935  }
936 
944  protected function hasPageAccess($table, $uid)
945  {
946  $uid = (int)$uid;
947 
948  if ($table === 'pages') {
949  $pageId = $uid;
950  } else {
951  $record = $this->getRecord($table, $uid);
952  $pageId = $record['pid'];
953  }
954 
955  if (!isset($this->pageAccessCache[$pageId])) {
956  $this->pageAccessCache[$pageId] = BackendUtility::readPageAccess(
957  $pageId, $this->getBackendUser()->getPagePermsClause(1)
958  );
959  }
960 
961  return $this->pageAccessCache[$pageId] !== false;
962  }
963 
970  protected function hasTableAccess($table)
971  {
972  return $this->getBackendUser()->check('tables_select', $table);
973  }
974 
982  protected function getRecord($table, $uid)
983  {
984  if (!isset($this->recordCache[$table][$uid])) {
985  $this->recordCache[$table][$uid] = BackendUtility::getRecord($table, $uid, '*', '', false);
986  }
987  return $this->recordCache[$table][$uid];
988  }
989 
995  protected function getBackendUser()
996  {
997  return $GLOBALS['BE_USER'];
998  }
999 
1008  protected function getArgument($name)
1009  {
1010  $value = GeneralUtility::_GP($name);
1011 
1012  switch ($name) {
1013  case 'element':
1014  if ($value !== '' && !preg_match('#^[a-z0-9_.]+:[0-9]+$#i', $value)) {
1015  $value = '';
1016  }
1017  break;
1018  case 'rollbackFields':
1019  case 'revert':
1020  if ($value !== '' && !preg_match('#^[a-z0-9_.]+(:[0-9]+(:[a-z0-9_.]+)?)?$#i', $value)) {
1021  $value = '';
1022  }
1023  break;
1024  case 'returnUrl':
1025  $value = GeneralUtility::sanitizeLocalUrl($value);
1026  break;
1027  case 'diff':
1028  case 'highlight':
1029  case 'sh_uid':
1030  $value = (int)$value;
1031  break;
1032  case 'settings':
1033  if (!is_array($value)) {
1034  $value = [];
1035  }
1036  break;
1037  default:
1038  $value = '';
1039  }
1040 
1041  return $value;
1042  }
1043 
1047  protected function getLanguageService()
1048  {
1049  return $GLOBALS['LANG'];
1050  }
1051 
1057  protected function getFluidTemplateObject()
1058  {
1060  $view = GeneralUtility::makeInstance(StandaloneView::class);
1061  $view->setLayoutRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Layouts')]);
1062  $view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
1063  $view->setTemplateRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates')]);
1064 
1065  $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/RecordHistory/Main.html'));
1066 
1067  $view->getRequest()->setControllerExtensionName('Backend');
1068  return $view;
1069  }
1070 }
linkPage($str, $inparams=[], $anchor= '', $title= '')
static calcAge($seconds, $labels= 'min|hrs|days|yrs|min|hour|day|year')
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
static getRecord($table, $uid, $fields= '*', $where= '', $useDeleteClause=true)
static linkThisScript(array $getParams=[])
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
static getUserNames($fields= 'username, usergroup, usergroup_cached_list, uid', $where= '')
debug($variable= '', $name= '*variable *', $line= '*line *', $file= '*file *', $recursiveDepth=3, $debugLevel=E_DEBUG)
static getFileAbsFileName($filename, $_=null, $_2=null)
static getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields= '*')
renderDiff($entry, $table, $rollbackUid=0)
createRollbackLink($key, $alt= '', $type=0)