‪TYPO3CMS  ‪main
ElementHistoryController.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ResponseInterface;
21 use Psr\Http\Message\ServerRequestInterface;
29 use TYPO3\CMS\Backend\Utility\BackendUtility;
34 use TYPO3\CMS\Core\Imaging\IconSize;
40 
46 #[AsController]
48 {
52  protected ‪$historyObject;
53 
59  protected ‪$showDiff = true;
60 
64  protected ‪$recordCache = [];
65 
66  protected ‪ModuleTemplate ‪$view;
67 
68  protected string ‪$returnUrl = '';
69 
70  public function ‪__construct(
71  protected readonly ‪IconFactory $iconFactory,
72  protected readonly ‪UriBuilder $uriBuilder,
73  protected readonly ‪ModuleTemplateFactory $moduleTemplateFactory,
74  ) {}
75 
83  public function ‪mainAction(ServerRequestInterface $request): ResponseInterface
84  {
85  $this->view = $this->moduleTemplateFactory->create($request);
86  $backendUser = $this->‪getBackendUser();
87  $this->view->getDocHeaderComponent()->setMetaInformation([]);
88  $buttonBar = $this->view->getDocHeaderComponent()->getButtonBar();
89 
90  $parsedBody = $request->getParsedBody();
91  $queryParams = $request->getQueryParams();
92 
93  $this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
94 
95  $lastHistoryEntry = (int)($parsedBody['historyEntry'] ?? $queryParams['historyEntry'] ?? 0);
96  $rollbackFields = $parsedBody['rollbackFields'] ?? $queryParams['rollbackFields'] ?? null;
97  $element = $parsedBody['element'] ?? $queryParams['element'] ?? null;
98  $moduleSettings = $this->‪processSettings($request);
99  $this->view->assign('isUserInWorkspace', $backendUser->workspace > 0);
100 
101  $this->showDiff = (bool)$moduleSettings['showDiff'];
102 
103  // Start history object
104  $this->historyObject = GeneralUtility::makeInstance(RecordHistory::class, $element, $rollbackFields);
105  $this->historyObject->setShowSubElements((bool)$moduleSettings['showSubElements']);
106  $this->historyObject->setLastHistoryEntryNumber($lastHistoryEntry);
107  if ($moduleSettings['maxSteps']) {
108  $this->historyObject->setMaxSteps((int)$moduleSettings['maxSteps']);
109  }
110 
111  // Do the actual logic now (rollback, show a diff for certain changes,
112  // or show the full history of a page or a specific record)
113  $changeLog = $this->historyObject->getChangeLog();
114  if (!empty($changeLog)) {
115  if ($rollbackFields !== null) {
116  $diff = $this->historyObject->getDiff($changeLog);
117  GeneralUtility::makeInstance(RecordHistoryRollback::class)->performRollback($rollbackFields, $diff);
118  } elseif ($lastHistoryEntry) {
119  $completeDiff = $this->historyObject->getDiff($changeLog);
120  $this->‪displayMultipleDiff($completeDiff);
121  $button = $buttonBar->makeLinkButton()
122  ->setHref($this->‪buildUrl(['historyEntry' => '']))
123  ->setIcon($this->iconFactory->getIcon('actions-view-go-back', IconSize::SMALL))
124  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_show_rechis.xlf:fullView'))
125  ->setShowLabelText(true);
126  $buttonBar->addButton($button);
127  }
128  if ($this->historyObject->getElementString() !== '') {
129  $this->‪displayHistory($changeLog);
130  }
131  }
132 
133  $elementData = $this->historyObject->getElementInformation();
134  $editLock = false;
135  if (!empty($elementData)) {
136  [$elementTable, $elementUid] = $elementData;
137  $this->‪setPagePath($elementTable, $elementUid);
138  $editLock = $this->‪getEditLockFromElement($elementTable, $elementUid);
139  // Get link to page history if the element history is shown
140  if ($elementTable !== 'pages') {
141  $parentPage = BackendUtility::getRecord($elementTable, $elementUid, '*', '', false);
142  if ($parentPage['pid'] > 0 && BackendUtility::readPageAccess($parentPage['pid'], $backendUser->getPagePermsClause(‪Permission::PAGE_SHOW))) {
143  $button = $buttonBar->makeLinkButton()
144  ->setHref($this->‪buildUrl([
145  'element' => 'pages:' . $parentPage['pid'],
146  'historyEntry' => '',
147  ]))
148  ->setIcon($this->iconFactory->getIcon('apps-pagetree-page-default', IconSize::SMALL))
149  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_show_rechis.xlf:elementHistory_link'))
150  ->setShowLabelText(true);
151  $buttonBar->addButton($button, ‪ButtonBar::BUTTON_POSITION_LEFT, 2);
152  }
153  }
154  }
155 
156  $this->view->assign('editLock', $editLock);
157  $this->view->assign('moduleSettings', $moduleSettings);
158  $this->view->assign('settingsFormUrl', $this->‪buildUrl());
159 
160  // Setting up the buttons and markers for docheader
161  $this->‪getButtons();
162 
163  return $this->view->renderResponse('RecordHistory/Main');
164  }
165 
172  protected function ‪setPagePath($table, ‪$uid)
173  {
174  ‪$uid = (int)‪$uid;
175 
176  ‪$record = BackendUtility::getRecord($table, ‪$uid, '*', '', false);
177  if ($table === 'pages') {
178  $pageId = ‪$uid;
179  } else {
180  $pageId = ‪$record['pid'];
181  }
182 
183  $pageAccess = BackendUtility::readPageAccess($pageId, $this->‪getBackendUser()->getPagePermsClause(‪Permission::PAGE_SHOW));
184  if (is_array($pageAccess)) {
185  $this->view->getDocHeaderComponent()->setMetaInformation($pageAccess);
186  }
187 
188  $this->view->assignMultiple([
189  'recordTable' => $table,
190  'recordTableReadable' => $this->‪getLanguageService()->sL(‪$GLOBALS['TCA'][$table]['ctrl']['title']),
191  'recordUid' => ‪$uid,
192  'recordTitle' => $this->‪generateTitle($table, (string)‪$uid),
193  ]);
194  }
195 
196  protected function ‪getButtons(): void
197  {
198  $buttonBar = $this->view->getDocHeaderComponent()->getButtonBar();
199 
200  if ($this->returnUrl) {
201  $backButton = $buttonBar->makeLinkButton()
202  ->setHref($this->returnUrl)
203  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
204  ->setShowLabelText(true)
205  ->setIcon($this->iconFactory->getIcon('actions-close', IconSize::SMALL));
206  $buttonBar->addButton($backButton);
207  }
208  }
209 
210  protected function ‪processSettings(ServerRequestInterface $request): array
211  {
212  // Get current selection from UC, merge data, write it back to UC
213  $currentSelection = $this->‪getBackendUser()->getModuleData('history');
214  if (!is_array($currentSelection)) {
215  $currentSelection = ['maxSteps' => '', 'showDiff' => 1, 'showSubElements' => 1];
216  }
217  $currentSelectionOverride = $request->getParsedBody()['settings'] ?? null;
218  if (is_array($currentSelectionOverride) && !empty($currentSelectionOverride)) {
219  $currentSelection = array_merge($currentSelection, $currentSelectionOverride);
220  $this->‪getBackendUser()->pushModuleData('history', $currentSelection);
221  }
222  return $currentSelection;
223  }
224 
230  protected function ‪displayMultipleDiff(array $diff)
231  {
232  $languageService = $this->‪getLanguageService();
233 
234  // Get all array keys needed
236  $arrayKeys = array_merge(array_keys($diff['newData']), array_keys($diff['insertsDeletes']), array_keys($diff['oldData']));
237  $arrayKeys = array_unique($arrayKeys);
238  if (!empty($arrayKeys)) {
239  $lines = [];
240  foreach ($arrayKeys as $key) {
241  $singleLine = [];
242  $elParts = explode(':', $key);
243  // Turn around diff because it should be a "rollback preview"
244  if ((int)($diff['insertsDeletes'][$key] ?? 0) === 1) {
245  // insert
246  $singleLine['insertDelete'] = 'delete';
247  } elseif ((int)($diff['insertsDeletes'][$key] ?? 0) === -1) {
248  $singleLine['insertDelete'] = 'insert';
249  }
250  // Build up temporary diff array
251  // turn around diff because it should be a "rollback preview"
252  if ($diff['newData'][$key] ?? false) {
253  $tmpArr = [
254  'newRecord' => $diff['oldData'][$key],
255  'oldRecord' => $diff['newData'][$key],
256  ];
257 
258  // show changes
259  if (!$this->showDiff) {
260  // Display field names instead of full diff
261  // Re-write field names with labels
263  $tmpFieldList = array_keys($tmpArr['newRecord']);
264  foreach ($tmpFieldList as $fieldKey => $value) {
265  $tmp = str_replace(':', '', $languageService->sL(BackendUtility::getItemLabel($elParts[0], $value)));
266  if ($tmp) {
267  $tmpFieldList[$fieldKey] = $tmp;
268  } else {
269  // remove fields if no label available
270  unset($tmpFieldList[$fieldKey]);
271  }
272  }
273  $singleLine['fieldNames'] = implode(',', $tmpFieldList);
274  } else {
275  // Display diff
276  $singleLine['differences'] = $this->‪renderDiff($tmpArr, $elParts[0], (int)$elParts[1], true);
277  }
278  }
279  $elParts = explode(':', $key);
280  $singleLine['revertRecordUrl'] = $this->‪buildUrl(['rollbackFields' => $key]);
281  $singleLine['title'] = $this->‪generateTitle($elParts[0], $elParts[1]);
282  $singleLine['recordTable'] = $elParts[0];
283  $singleLine['recordUid'] = $elParts[1];
284  $lines[] = $singleLine;
285  }
286  $this->view->assign('revertAllUrl', $this->‪buildUrl(['rollbackFields' => 'ALL']));
287  $this->view->assign('multipleDiff', $lines);
288  }
289  $this->view->assign('showDifferences', true);
290  }
291 
295  protected function ‪displayHistory(array $historyEntries)
296  {
297  if (empty($historyEntries)) {
298  return;
299  }
300  $languageService = $this->‪getLanguageService();
301  $lines = [];
302  $beUserArray = BackendUtility::getUserNames('username,realName,usergroup,uid');
303 
304  // Traverse changeLog array:
305  foreach ($historyEntries as $entry) {
306  // Build up single line
307  $singleLine = [];
308 
309  // Get user names
310  $singleLine['backendUserUid'] = $entry['userid'];
311  $singleLine['backendUserName'] = $beUserArray[$entry['userid']]['username'] ?? '';
312  $singleLine['backendUserRealName'] = $beUserArray[$entry['userid']]['realName'] ?? '';
313  // Executed by switch user
314  if (!empty($entry['originaluserid'])) {
315  $singleLine['originalBackendUserUid'] = $entry['originaluserid'];
316  $singleLine['originalBackendUserName'] = $beUserArray[$entry['originaluserid']]['username'] ?? '';
317  $singleLine['originalBackendRealName'] = $beUserArray[$entry['originaluserid']]['realName'] ?? '';
318  }
319 
320  // Is a change in a workspace?
321  $singleLine['isChangedInWorkspace'] = (int)$entry['workspace'] > 0;
322 
323  // Diff link
324  $singleLine['diffUrl'] = $this->‪buildUrl(['historyEntry' => $entry['uid']]);
325  // Add time
326  $singleLine['day'] = BackendUtility::date($entry['tstamp']);
327  $singleLine['time'] = BackendUtility::time($entry['tstamp']);
328 
329  $singleLine['title'] = $this->‪generateTitle($entry['tablename'], $entry['recuid']);
330  $singleLine['recordTable'] = $entry['tablename'];
331  $singleLine['recordUid'] = $entry['recuid'];
332 
333  $singleLine['elementUrl'] = $this->‪buildUrl(['element' => $entry['tablename'] . ':' . $entry['recuid']]);
334  $singleLine['actiontype'] = $entry['actiontype'];
335  if ((int)$entry['actiontype'] === ‪RecordHistoryStore::ACTION_MODIFY) {
336  // show changes
337  if (!$this->showDiff) {
338  // Display field names instead of full diff
339  // Re-write field names with labels
341  $tmpFieldList = array_keys($entry['newRecord']);
342  foreach ($tmpFieldList as $key => $value) {
343  $tmp = str_replace(':', '', $languageService->sL(BackendUtility::getItemLabel($entry['tablename'], $value)));
344  if ($tmp) {
345  $tmpFieldList[$key] = $tmp;
346  } else {
347  // remove fields if no label available
348  unset($tmpFieldList[$key]);
349  }
350  }
351  $singleLine['fieldNames'] = implode(',', $tmpFieldList);
352  } else {
353  // Display diff
354  $singleLine['differences'] = $this->‪renderDiff($entry, $entry['tablename'], $entry['recuid']);
355  }
356  }
357  // put line together
358  $lines[] = $singleLine;
359  }
360  $this->view->assign('history', $lines);
361  }
362 
372  protected function ‪renderDiff($entry, $table, $rollbackUid = 0, bool $showRollbackLink = false): array
373  {
374  $lines = [];
375  if (is_array($entry['newRecord'] ?? null)) {
376  $diffUtility = GeneralUtility::makeInstance(DiffUtility::class);
377  $fieldsToDisplay = array_keys($entry['newRecord']);
378  $languageService = $this->‪getLanguageService();
379  foreach ($fieldsToDisplay as $fN) {
380  $tcaType = ‪$GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] ?? '';
381  if (is_array(‪$GLOBALS['TCA'][$table]['columns'][$fN] ?? null) && $tcaType !== 'passthrough') {
382  $granularity = DiffGranularity::WORD;
383  if ($tcaType === 'flex') {
384  $granularity = ‪DiffGranularity::CHARACTER;
385  $flexFormValueFormatter = GeneralUtility::makeInstance(FlexFormValueFormatter::class);
386  $colConfig = ‪$GLOBALS['TCA'][$table]['columns'][$fN]['config'] ?? [];
387  $old = $flexFormValueFormatter->format($table, $fN, ($entry['oldRecord'][$fN] ?? ''), $rollbackUid, $colConfig);
388  $new = $flexFormValueFormatter->format($table, $fN, ($entry['newRecord'][$fN] ?? ''), $rollbackUid, $colConfig);
389  } else {
390  $old = (string)BackendUtility::getProcessedValue($table, $fN, ($entry['oldRecord'][$fN] ?? ''), 0, true, uid: $rollbackUid);
391  $new = (string)BackendUtility::getProcessedValue($table, $fN, ($entry['newRecord'][$fN] ?? ''), 0, true, uid: $rollbackUid);
392  }
393  $diffResult = $diffUtility->makeDiffDisplay($old, $new, $granularity);
394  $rollbackUrl = '';
395  if ($rollbackUid && $showRollbackLink) {
396  $rollbackUrl = $this->‪buildUrl(['rollbackFields' => $table . ':' . $rollbackUid . ':' . $fN]);
397  }
398  $lines[] = [
399  'title' => $languageService->sL(BackendUtility::getItemLabel($table, $fN)),
400  'rollbackUrl' => $rollbackUrl,
401  'result' => str_replace('\n', PHP_EOL, str_replace('\r\n', '\n', $diffResult)),
402  ];
403  }
404  }
405  }
406  return $lines;
407  }
408 
414  protected function ‪buildUrl($overrideParameters = []): string
415  {
416  $params = [];
417 
418  // Setting default values based on GET parameters:
419  $elementString = $this->historyObject->getElementString();
420  if ($elementString !== '') {
421  $params['element'] = $elementString;
422  }
423  $params['historyEntry'] = $this->historyObject->getLastHistoryEntryNumber();
424 
425  if (!empty($this->returnUrl)) {
426  $params['returnUrl'] = ‪$this->returnUrl;
427  }
428 
429  // Merging overriding values:
430  $params = array_merge($params, $overrideParameters);
431 
432  // Make the link:
433  return (string)$this->uriBuilder->buildUriFromRoute('record_history', $params);
434  }
435 
442  protected function ‪generateTitle($table, ‪$uid): string
443  {
444  $title = '';
445  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['label'])) {
446  ‪$record = $this->‪getRecord($table, (int)‪$uid) ?? [];
447  $title .= BackendUtility::getRecordTitle($table, ‪$record);
448  }
449  return $title;
450  }
451 
459  protected function ‪getRecord($table, ‪$uid)
460  {
461  if (!isset($this->recordCache[$table][‪$uid])) {
462  $this->recordCache[$table][‪$uid] = BackendUtility::getRecord($table, ‪$uid, '*', '', false);
463  }
464  return $this->recordCache[$table][‪$uid];
465  }
466 
467  protected function ‪getLanguageService(): ‪LanguageService
468  {
469  return ‪$GLOBALS['LANG'];
470  }
471 
472  protected function ‪getBackendUser(): ‪BackendUserAuthentication
473  {
474  return ‪$GLOBALS['BE_USER'];
475  }
476 
483  protected function ‪getEditLockFromElement($tableName, $elementUid): bool
484  {
485  // If the user is admin, then he may always edit the page.
486  if ($this->‪getBackendUser()->isAdmin()) {
487  return false;
488  }
489 
490  // Early return if $elementUid is zero
491  if ((int)$elementUid === 0) {
492  return !(‪$GLOBALS['TCA'][$tableName]['ctrl']['security']['ignoreRootLevelRestriction'] ?? false);
493  }
494 
495  ‪$record = BackendUtility::getRecord($tableName, $elementUid, '*', '', false);
496  // we need the parent page record for the editlock info if element isn't a page
497  if ($tableName !== 'pages') {
498  $pageId = ‪$record['pid'];
499  ‪$record = BackendUtility::getRecord('pages', $pageId, '*', '', false);
500  }
501 
502  return (bool)‪$record['editlock'];
503  }
504 }
‪TYPO3\CMS\Core\Utility\DiffUtility
Definition: DiffUtility.php:28
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\getLanguageService
‪getLanguageService()
Definition: ElementHistoryController.php:464
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\getBackendUser
‪getBackendUser()
Definition: ElementHistoryController.php:469
‪TYPO3\CMS\Backend\Template\Components\ButtonBar\BUTTON_POSITION_LEFT
‪const BUTTON_POSITION_LEFT
Definition: ButtonBar.php:37
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\renderDiff
‪array renderDiff($entry, $table, $rollbackUid=0, bool $showRollbackLink=false)
Definition: ElementHistoryController.php:369
‪TYPO3\CMS\Backend\Template\Components\ButtonBar
Definition: ButtonBar.php:33
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\__construct
‪__construct(protected readonly IconFactory $iconFactory, protected readonly UriBuilder $uriBuilder, protected readonly ModuleTemplateFactory $moduleTemplateFactory,)
Definition: ElementHistoryController.php:67
‪TYPO3\CMS\Backend\Template\ModuleTemplateFactory
Definition: ModuleTemplateFactory.php:33
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\getRecord
‪array null getRecord($table, $uid)
Definition: ElementHistoryController.php:456
‪TYPO3\CMS\Backend\Controller\ContentElement
Definition: ElementHistoryController.php:18
‪TYPO3\CMS\Backend\History\RecordHistoryRollback
Definition: RecordHistoryRollback.php:29
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore\ACTION_MODIFY
‪const ACTION_MODIFY
Definition: RecordHistoryStore.php:33
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\$returnUrl
‪string $returnUrl
Definition: ElementHistoryController.php:65
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Backend\Template\ModuleTemplate
Definition: ModuleTemplate.php:46
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore
Definition: RecordHistoryStore.php:31
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\buildUrl
‪buildUrl($overrideParameters=[])
Definition: ElementHistoryController.php:411
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\setPagePath
‪setPagePath($table, $uid)
Definition: ElementHistoryController.php:169
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\mainAction
‪ResponseInterface mainAction(ServerRequestInterface $request)
Definition: ElementHistoryController.php:80
‪TYPO3\CMS\Backend\History\RecordHistory
Definition: RecordHistory.php:32
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:44
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController
Definition: ElementHistoryController.php:48
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\$showDiff
‪bool $showDiff
Definition: ElementHistoryController.php:57
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Backend\View\ValueFormatter\FlexFormValueFormatter
Definition: FlexFormValueFormatter.php:34
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:35
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\processSettings
‪processSettings(ServerRequestInterface $request)
Definition: ElementHistoryController.php:207
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\$view
‪ModuleTemplate $view
Definition: ElementHistoryController.php:63
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\$recordCache
‪array $recordCache
Definition: ElementHistoryController.php:61
‪TYPO3\CMS\Webhooks\Message\$uid
‪identifier readonly int $uid
Definition: PageModificationMessage.php:35
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\getButtons
‪getButtons()
Definition: ElementHistoryController.php:193
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\displayHistory
‪displayHistory(array $historyEntries)
Definition: ElementHistoryController.php:292
‪TYPO3\CMS\Core\Utility\DiffGranularity
‪DiffGranularity
Definition: DiffGranularity.php:21
‪TYPO3\CMS\Backend\Attribute\AsController
Definition: AsController.php:25
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\getEditLockFromElement
‪getEditLockFromElement($tableName, $elementUid)
Definition: ElementHistoryController.php:480
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Utility\CHARACTER
‪@ CHARACTER
Definition: DiffGranularity.php:25
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\$historyObject
‪RecordHistory $historyObject
Definition: ElementHistoryController.php:51
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\displayMultipleDiff
‪displayMultipleDiff(array $diff)
Definition: ElementHistoryController.php:227
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\generateTitle
‪generateTitle($table, $uid)
Definition: ElementHistoryController.php:439