‪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 #[Controller]
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  $lines[] = $singleLine;
283  }
284  $this->view->assign('revertAllUrl', $this->‪buildUrl(['rollbackFields' => 'ALL']));
285  $this->view->assign('multipleDiff', $lines);
286  }
287  $this->view->assign('showDifferences', true);
288  }
289 
293  protected function ‪displayHistory(array $historyEntries)
294  {
295  if (empty($historyEntries)) {
296  return;
297  }
298  $languageService = $this->‪getLanguageService();
299  $lines = [];
300  $beUserArray = BackendUtility::getUserNames('username,realName,usergroup,uid');
301 
302  // Traverse changeLog array:
303  foreach ($historyEntries as $entry) {
304  // Build up single line
305  $singleLine = [];
306 
307  // Get user names
308  $singleLine['backendUserUid'] = $entry['userid'];
309  $singleLine['backendUserName'] = $beUserArray[$entry['userid']]['username'] ?? '';
310  $singleLine['backendUserRealName'] = $beUserArray[$entry['userid']]['realName'] ?? '';
311  // Executed by switch user
312  if (!empty($entry['originaluserid'])) {
313  $singleLine['originalBackendUserUid'] = $entry['originaluserid'];
314  $singleLine['originalBackendUserName'] = $beUserArray[$entry['originaluserid']]['username'] ?? '';
315  $singleLine['originalBackendRealName'] = $beUserArray[$entry['originaluserid']]['realName'] ?? '';
316  }
317 
318  // Is a change in a workspace?
319  $singleLine['isChangedInWorkspace'] = (int)$entry['workspace'] > 0;
320 
321  // Diff link
322  $singleLine['diffUrl'] = $this->‪buildUrl(['historyEntry' => $entry['uid']]);
323  // Add time
324  $singleLine['day'] = BackendUtility::date($entry['tstamp']);
325  $singleLine['time'] = BackendUtility::time($entry['tstamp']);
326 
327  $singleLine['title'] = $this->‪generateTitle($entry['tablename'], $entry['recuid']);
328  $singleLine['recordTable'] = $entry['tablename'];
329  $singleLine['recordUid'] = $entry['recuid'];
330 
331  $singleLine['elementUrl'] = $this->‪buildUrl(['element' => $entry['tablename'] . ':' . $entry['recuid']]);
332  $singleLine['actiontype'] = $entry['actiontype'];
333  if ((int)$entry['actiontype'] === ‪RecordHistoryStore::ACTION_MODIFY) {
334  // show changes
335  if (!$this->showDiff) {
336  // Display field names instead of full diff
337  // Re-write field names with labels
339  $tmpFieldList = array_keys($entry['newRecord']);
340  foreach ($tmpFieldList as $key => $value) {
341  $tmp = str_replace(':', '', $languageService->sL(BackendUtility::getItemLabel($entry['tablename'], $value)));
342  if ($tmp) {
343  $tmpFieldList[$key] = $tmp;
344  } else {
345  // remove fields if no label available
346  unset($tmpFieldList[$key]);
347  }
348  }
349  $singleLine['fieldNames'] = implode(',', $tmpFieldList);
350  } else {
351  // Display diff
352  $singleLine['differences'] = $this->‪renderDiff($entry, $entry['tablename'], $entry['recuid']);
353  }
354  }
355  // put line together
356  $lines[] = $singleLine;
357  }
358  $this->view->assign('history', $lines);
359  }
360 
370  protected function ‪renderDiff($entry, $table, $rollbackUid = 0, bool $showRollbackLink = false): array
371  {
372  $lines = [];
373  if (is_array($entry['newRecord'] ?? null)) {
374  $diffUtility = GeneralUtility::makeInstance(DiffUtility::class);
375  $fieldsToDisplay = array_keys($entry['newRecord']);
376  $languageService = $this->‪getLanguageService();
377  foreach ($fieldsToDisplay as $fN) {
378  $tcaType = ‪$GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] ?? '';
379  if (is_array(‪$GLOBALS['TCA'][$table]['columns'][$fN] ?? null) && $tcaType !== 'passthrough') {
380  $granularity = DiffGranularity::WORD;
381  if ($tcaType === 'flex') {
382  $granularity = ‪DiffGranularity::CHARACTER;
383  $flexFormValueFormatter = GeneralUtility::makeInstance(FlexFormValueFormatter::class);
384  $colConfig = ‪$GLOBALS['TCA'][$table]['columns'][$fN]['config'] ?? [];
385  $old = $flexFormValueFormatter->format($table, $fN, ($entry['oldRecord'][$fN] ?? ''), $rollbackUid, $colConfig);
386  $new = $flexFormValueFormatter->format($table, $fN, ($entry['newRecord'][$fN] ?? ''), $rollbackUid, $colConfig);
387  } else {
388  $old = (string)BackendUtility::getProcessedValue($table, $fN, ($entry['oldRecord'][$fN] ?? ''), 0, true, uid: $rollbackUid);
389  $new = (string)BackendUtility::getProcessedValue($table, $fN, ($entry['newRecord'][$fN] ?? ''), 0, true, uid: $rollbackUid);
390  }
391  $diffResult = $diffUtility->makeDiffDisplay($old, $new, $granularity);
392  $rollbackUrl = '';
393  if ($rollbackUid && $showRollbackLink) {
394  $rollbackUrl = $this->‪buildUrl(['rollbackFields' => $table . ':' . $rollbackUid . ':' . $fN]);
395  }
396  $lines[] = [
397  'title' => $languageService->sL(BackendUtility::getItemLabel($table, $fN)),
398  'rollbackUrl' => $rollbackUrl,
399  'result' => str_replace('\n', PHP_EOL, str_replace('\r\n', '\n', $diffResult)),
400  ];
401  }
402  }
403  }
404  return $lines;
405  }
406 
412  protected function ‪buildUrl($overrideParameters = []): string
413  {
414  $params = [];
415 
416  // Setting default values based on GET parameters:
417  $elementString = $this->historyObject->getElementString();
418  if ($elementString !== '') {
419  $params['element'] = $elementString;
420  }
421  $params['historyEntry'] = $this->historyObject->getLastHistoryEntryNumber();
422 
423  if (!empty($this->returnUrl)) {
424  $params['returnUrl'] = ‪$this->returnUrl;
425  }
426 
427  // Merging overriding values:
428  $params = array_merge($params, $overrideParameters);
429 
430  // Make the link:
431  return (string)$this->uriBuilder->buildUriFromRoute('record_history', $params);
432  }
433 
440  protected function ‪generateTitle($table, ‪$uid): string
441  {
442  $title = '';
443  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['label'])) {
444  ‪$record = $this->‪getRecord($table, (int)‪$uid) ?? [];
445  $title .= '"' . BackendUtility::getRecordTitle($table, ‪$record) . '"';
446  $title .= ' (' . $table . ':' . ‪$uid . ')';
447  }
448  return $title;
449  }
450 
458  protected function ‪getRecord($table, ‪$uid)
459  {
460  if (!isset($this->recordCache[$table][‪$uid])) {
461  $this->recordCache[$table][‪$uid] = BackendUtility::getRecord($table, ‪$uid, '*', '', false);
462  }
463  return $this->recordCache[$table][‪$uid];
464  }
465 
466  protected function ‪getLanguageService(): ‪LanguageService
467  {
468  return ‪$GLOBALS['LANG'];
469  }
470 
471  protected function ‪getBackendUser(): ‪BackendUserAuthentication
472  {
473  return ‪$GLOBALS['BE_USER'];
474  }
475 
482  protected function ‪getEditLockFromElement($tableName, $elementUid): bool
483  {
484  // If the user is admin, then he may always edit the page.
485  if ($this->‪getBackendUser()->isAdmin()) {
486  return false;
487  }
488 
489  // Early return if $elementUid is zero
490  if ((int)$elementUid === 0) {
491  return !(‪$GLOBALS['TCA'][$tableName]['ctrl']['security']['ignoreRootLevelRestriction'] ?? false);
492  }
493 
494  ‪$record = BackendUtility::getRecord($tableName, $elementUid, '*', '', false);
495  // we need the parent page record for the editlock info if element isn't a page
496  if ($tableName !== 'pages') {
497  $pageId = ‪$record['pid'];
498  ‪$record = BackendUtility::getRecord('pages', $pageId, '*', '', false);
499  }
500 
501  return (bool)‪$record['editlock'];
502  }
503 }
‪TYPO3\CMS\Core\Utility\DiffUtility
Definition: DiffUtility.php:28
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\getLanguageService
‪getLanguageService()
Definition: ElementHistoryController.php:463
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\getBackendUser
‪getBackendUser()
Definition: ElementHistoryController.php:468
‪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:367
‪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:455
‪TYPO3\CMS\Backend\Controller\ContentElement
Definition: ElementHistoryController.php:18
‪TYPO3\CMS\Backend\Attribute\Controller
Definition: Controller.php:25
‪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:409
‪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:60
‪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:290
‪TYPO3\CMS\Core\Utility\DiffGranularity
‪DiffGranularity
Definition: DiffGranularity.php:21
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Backend\Controller\ContentElement\ElementHistoryController\getEditLockFromElement
‪getEditLockFromElement($tableName, $elementUid)
Definition: ElementHistoryController.php:479
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪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:437