[BUGFIX] Fix several typos in php comments
[Packages/TYPO3.CMS.git] / typo3 / sysext / workspaces / Classes / Controller / Remote / RemoteServer.php
1 <?php
2 namespace TYPO3\CMS\Workspaces\Controller\Remote;
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
17 use TYPO3\CMS\Backend\Backend\Avatar\Avatar;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Imaging\Icon;
22 use TYPO3\CMS\Core\Imaging\IconFactory;
23 use TYPO3\CMS\Core\Localization\LanguageService;
24 use TYPO3\CMS\Core\Resource\FileReference;
25 use TYPO3\CMS\Core\Resource\ProcessedFile;
26 use TYPO3\CMS\Core\Utility\DiffUtility;
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
28 use TYPO3\CMS\Core\Utility\MathUtility;
29 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
30 use TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord;
31 use TYPO3\CMS\Workspaces\Service\GridDataService;
32 use TYPO3\CMS\Workspaces\Service\HistoryService;
33 use TYPO3\CMS\Workspaces\Service\IntegrityService;
34 use TYPO3\CMS\Workspaces\Service\StagesService;
35 use TYPO3\CMS\Workspaces\Service\WorkspaceService;
36
37 /**
38 * Class RemoteServer
39 * @internal This is a specific Backend Controller implementation and is not considered part of the Public TYPO3 API.
40 */
41 class RemoteServer
42 {
43 /**
44 * @var GridDataService
45 */
46 protected $gridDataService;
47
48 /**
49 * @var StagesService
50 */
51 protected $stagesService;
52
53 /**
54 * @var WorkspaceService
55 */
56 protected $workspaceService;
57
58 /**
59 * @var DiffUtility
60 */
61 protected $differenceHandler;
62
63 public function __construct()
64 {
65 $this->workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
66 $this->gridDataService = GeneralUtility::makeInstance(GridDataService::class);
67 $this->stagesService = GeneralUtility::makeInstance(StagesService::class);
68 }
69
70 /**
71 * Checks integrity of elements before performing actions on them.
72 *
73 * @param \stdClass $parameters
74 * @return array
75 */
76 public function checkIntegrity(\stdClass $parameters)
77 {
78 $integrity = $this->createIntegrityService($this->getAffectedElements($parameters));
79 $integrity->check();
80 $response = [
81 'result' => $integrity->getStatusRepresentation()
82 ];
83 return $response;
84 }
85
86 /**
87 * Get List of workspace changes
88 *
89 * @param \stdClass $parameter
90 * @return array $data
91 */
92 public function getWorkspaceInfos($parameter)
93 {
94 // To avoid too much work we use -1 to indicate that every page is relevant
95 $pageId = $parameter->id > 0 ? $parameter->id : -1;
96 if (!isset($parameter->language) || !MathUtility::canBeInterpretedAsInteger($parameter->language)) {
97 $parameter->language = null;
98 }
99 $versions = $this->workspaceService->selectVersionsInWorkspace($this->getCurrentWorkspace(), 0, -99, $pageId, $parameter->depth, 'tables_select', $parameter->language);
100 $data = $this->gridDataService->generateGridListFromVersions($versions, $parameter, $this->getCurrentWorkspace());
101 return $data;
102 }
103
104 /**
105 * Get List of available workspace actions
106 *
107 * @return array $data
108 */
109 public function getStageActions()
110 {
111 $currentWorkspace = $this->getCurrentWorkspace();
112 $stages = [];
113 if ($currentWorkspace != WorkspaceService::SELECT_ALL_WORKSPACES) {
114 $stages = $this->stagesService->getStagesForWSUser();
115 }
116 $data = [
117 'total' => count($stages),
118 'data' => $stages
119 ];
120 return $data;
121 }
122
123 /**
124 * Fetch further information to current selected workspace record.
125 *
126 * @param \stdClass $parameter
127 * @return array $data
128 */
129 public function getRowDetails($parameter)
130 {
131 $diffReturnArray = [];
132 $liveReturnArray = [];
133 $diffUtility = $this->getDifferenceHandler();
134 $liveRecord = BackendUtility::getRecord($parameter->table, $parameter->t3ver_oid);
135 $versionRecord = BackendUtility::getRecord($parameter->table, $parameter->uid);
136 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
137 $icon_Live = $iconFactory->getIconForRecord($parameter->table, $liveRecord, Icon::SIZE_SMALL)->render();
138 $icon_Workspace = $iconFactory->getIconForRecord($parameter->table, $versionRecord, Icon::SIZE_SMALL)->render();
139 $stagePosition = $this->stagesService->getPositionOfCurrentStage($parameter->stage);
140 $fieldsOfRecords = array_keys($liveRecord);
141 if ($GLOBALS['TCA'][$parameter->table]) {
142 if ($GLOBALS['TCA'][$parameter->table]['interface']['showRecordFieldList']) {
143 $fieldsOfRecords = $GLOBALS['TCA'][$parameter->table]['interface']['showRecordFieldList'];
144 $fieldsOfRecords = GeneralUtility::trimExplode(',', $fieldsOfRecords, true);
145 }
146 }
147 foreach ($fieldsOfRecords as $fieldName) {
148 if (empty($GLOBALS['TCA'][$parameter->table]['columns'][$fieldName]['config'])) {
149 continue;
150 }
151 // Get the field's label. If not available, use the field name
152 $fieldTitle = $this->getLanguageService()->sL(BackendUtility::getItemLabel($parameter->table, $fieldName));
153 if (empty($fieldTitle)) {
154 $fieldTitle = $fieldName;
155 }
156 // Gets the TCA configuration for the current field
157 $configuration = $GLOBALS['TCA'][$parameter->table]['columns'][$fieldName]['config'];
158 // check for exclude fields
159 if ($this->getBackendUser()->isAdmin() || $GLOBALS['TCA'][$parameter->table]['columns'][$fieldName]['exclude'] == 0 || GeneralUtility::inList($this->getBackendUser()->groupData['non_exclude_fields'], $parameter->table . ':' . $fieldName)) {
160 // call diff class only if there is a difference
161 if ($configuration['type'] === 'inline' && $configuration['foreign_table'] === 'sys_file_reference') {
162 $useThumbnails = false;
163 if (!empty($configuration['overrideChildTca']['columns']['uid_local']['config']['appearance']['elementBrowserAllowed']) && !empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'])) {
164 $fileExtensions = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], true);
165 $allowedExtensions = GeneralUtility::trimExplode(',', $configuration['overrideChildTca']['columns']['uid_local']['config']['appearance']['elementBrowserAllowed'], true);
166 $differentExtensions = array_diff($allowedExtensions, $fileExtensions);
167 $useThumbnails = empty($differentExtensions);
168 }
169
170 $liveFileReferences = BackendUtility::resolveFileReferences(
171 $parameter->table,
172 $fieldName,
173 $liveRecord,
174 0
175 );
176 $versionFileReferences = BackendUtility::resolveFileReferences(
177 $parameter->table,
178 $fieldName,
179 $versionRecord,
180 $this->getCurrentWorkspace()
181 );
182 $fileReferenceDifferences = $this->prepareFileReferenceDifferences(
183 $liveFileReferences,
184 $versionFileReferences,
185 $useThumbnails
186 );
187
188 if ($fileReferenceDifferences === null) {
189 continue;
190 }
191
192 $diffReturnArray[] = [
193 'field' => $fieldName,
194 'label' => $fieldTitle,
195 'content' => $fileReferenceDifferences['differences']
196 ];
197 $liveReturnArray[] = [
198 'field' => $fieldName,
199 'label' => $fieldTitle,
200 'content' => $fileReferenceDifferences['live']
201 ];
202 } elseif ((string)$liveRecord[$fieldName] !== (string)$versionRecord[$fieldName]) {
203 // Select the human readable values before diff
204 $liveRecord[$fieldName] = BackendUtility::getProcessedValue(
205 $parameter->table,
206 $fieldName,
207 $liveRecord[$fieldName],
208 0,
209 1,
210 false,
211 $liveRecord['uid']
212 );
213 $versionRecord[$fieldName] = BackendUtility::getProcessedValue(
214 $parameter->table,
215 $fieldName,
216 $versionRecord[$fieldName],
217 0,
218 1,
219 false,
220 $versionRecord['uid']
221 );
222
223 $diffReturnArray[] = [
224 'field' => $fieldName,
225 'label' => $fieldTitle,
226 'content' => $diffUtility->makeDiffDisplay($liveRecord[$fieldName], $versionRecord[$fieldName])
227 ];
228 $liveReturnArray[] = [
229 'field' => $fieldName,
230 'label' => $fieldTitle,
231 'content' => $liveRecord[$fieldName]
232 ];
233 }
234 }
235 }
236 // Hook for modifying the difference and live arrays
237 // (this may be used by custom or dynamically-defined fields)
238 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['modifyDifferenceArray'] ?? [] as $className) {
239 $hookObject = GeneralUtility::makeInstance($className);
240 if (method_exists($hookObject, 'modifyDifferenceArray')) {
241 $hookObject->modifyDifferenceArray($parameter, $diffReturnArray, $liveReturnArray, $diffUtility);
242 }
243 }
244 $commentsForRecord = $this->getCommentsForRecord($parameter->uid, $parameter->table);
245
246 $historyService = GeneralUtility::makeInstance(HistoryService::class);
247 $history = $historyService->getHistory($parameter->table, $parameter->t3ver_oid);
248
249 $prevStage = $this->stagesService->getPrevStage($parameter->stage);
250 $nextStage = $this->stagesService->getNextStage($parameter->stage);
251
252 if (isset($prevStage[0])) {
253 $prevStage = current($prevStage);
254 }
255
256 if (isset($nextStage[0])) {
257 $nextStage = current($nextStage);
258 }
259
260 return [
261 'total' => 1,
262 'data' => [
263 [
264 // these parts contain HTML (don't escape)
265 'diff' => $diffReturnArray,
266 'live_record' => $liveReturnArray,
267 'icon_Live' => $icon_Live,
268 'icon_Workspace' => $icon_Workspace,
269 // this part is already escaped in getCommentsForRecord()
270 'comments' => $commentsForRecord,
271 // escape/sanitize the others
272 'path_Live' => htmlspecialchars(BackendUtility::getRecordPath($liveRecord['pid'], '', 999)),
273 'label_Stage' => htmlspecialchars($this->stagesService->getStageTitle($parameter->stage)),
274 'label_PrevStage' => $prevStage,
275 'label_NextStage' => $nextStage,
276 'stage_position' => (int)$stagePosition['position'],
277 'stage_count' => (int)$stagePosition['count'],
278 'parent' => [
279 'table' => htmlspecialchars($parameter->table),
280 'uid' => (int)$parameter->uid
281 ],
282 'history' => [
283 'data' => $history,
284 'total' => count($history)
285 ]
286 ]
287 ]
288 ];
289 }
290
291 /**
292 * Prepares difference view for file references.
293 *
294 * @param FileReference[] $liveFileReferences
295 * @param FileReference[] $versionFileReferences
296 * @param bool|false $useThumbnails
297 * @return array|null
298 */
299 protected function prepareFileReferenceDifferences(array $liveFileReferences, array $versionFileReferences, $useThumbnails = false)
300 {
301 $randomValue = uniqid('file');
302
303 $liveValues = [];
304 $versionValues = [];
305 $candidates = [];
306 $substitutes = [];
307
308 // Process live references
309 foreach ($liveFileReferences as $identifier => $liveFileReference) {
310 $identifierWithRandomValue = $randomValue . '__' . $liveFileReference->getUid() . '__' . $randomValue;
311 $candidates[$identifierWithRandomValue] = $liveFileReference;
312 $liveValues[] = $identifierWithRandomValue;
313 }
314
315 // Process version references
316 foreach ($versionFileReferences as $identifier => $versionFileReference) {
317 $identifierWithRandomValue = $randomValue . '__' . $versionFileReference->getUid() . '__' . $randomValue;
318 $candidates[$identifierWithRandomValue] = $versionFileReference;
319 $versionValues[] = $identifierWithRandomValue;
320 }
321
322 // Combine values and surround by spaces
323 // (to reduce the chunks Diff will find)
324 $liveInformation = ' ' . implode(' ', $liveValues) . ' ';
325 $versionInformation = ' ' . implode(' ', $versionValues) . ' ';
326
327 // Return if information has not changed
328 if ($liveInformation === $versionInformation) {
329 return null;
330 }
331
332 /**
333 * @var string $identifierWithRandomValue
334 * @var FileReference $fileReference
335 */
336 foreach ($candidates as $identifierWithRandomValue => $fileReference) {
337 if ($useThumbnails) {
338 $thumbnailFile = $fileReference->getOriginalFile()->process(
339 ProcessedFile::CONTEXT_IMAGEPREVIEW,
340 ['width' => 40, 'height' => 40]
341 );
342 $thumbnailMarkup = '<img src="' . $thumbnailFile->getPublicUrl(true) . '" />';
343 $substitutes[$identifierWithRandomValue] = $thumbnailMarkup;
344 } else {
345 $substitutes[$identifierWithRandomValue] = $fileReference->getPublicUrl();
346 }
347 }
348
349 $differences = $this->getDifferenceHandler()->makeDiffDisplay($liveInformation, $versionInformation);
350 $liveInformation = str_replace(array_keys($substitutes), array_values($substitutes), trim($liveInformation));
351 $differences = str_replace(array_keys($substitutes), array_values($substitutes), trim($differences));
352
353 return [
354 'live' => $liveInformation,
355 'differences' => $differences
356 ];
357 }
358
359 /**
360 * Gets an array with all sys_log entries and their comments for the given record uid and table
361 *
362 * @param int $uid uid of changed element to search for in log
363 * @param string $table Name of the record's table
364 * @return array
365 */
366 public function getCommentsForRecord($uid, $table)
367 {
368 $sysLogReturnArray = [];
369 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
370
371 $result = $queryBuilder
372 ->select('log_data', 'tstamp', 'userid')
373 ->from('sys_log')
374 ->where(
375 $queryBuilder->expr()->eq(
376 'action',
377 $queryBuilder->createNamedParameter(6, \PDO::PARAM_INT)
378 ),
379 $queryBuilder->expr()->eq(
380 'details_nr',
381 $queryBuilder->createNamedParameter(30, \PDO::PARAM_INT)
382 ),
383 $queryBuilder->expr()->eq(
384 'tablename',
385 $queryBuilder->createNamedParameter($table, \PDO::PARAM_STR)
386 ),
387 $queryBuilder->expr()->eq(
388 'recuid',
389 $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
390 )
391 )
392 ->orderBy('tstamp', 'DESC')
393 ->execute();
394
395 /** @var Avatar $avatar */
396 $avatar = GeneralUtility::makeInstance(Avatar::class);
397
398 while ($sysLogRow = $result->fetch()) {
399 $sysLogEntry = [];
400 $data = unserialize($sysLogRow['log_data']);
401 $beUserRecord = BackendUtility::getRecord('be_users', $sysLogRow['userid']);
402 $sysLogEntry['stage_title'] = htmlspecialchars($this->stagesService->getStageTitle($data['stage']));
403 $sysLogEntry['user_uid'] = (int)$sysLogRow['userid'];
404 $sysLogEntry['user_username'] = is_array($beUserRecord) ? htmlspecialchars($beUserRecord['username']) : '';
405 $sysLogEntry['tstamp'] = htmlspecialchars(BackendUtility::datetime($sysLogRow['tstamp']));
406 $sysLogEntry['user_comment'] = nl2br(htmlspecialchars($data['comment']));
407 $sysLogEntry['user_avatar'] = $avatar->render($beUserRecord);
408 $sysLogReturnArray[] = $sysLogEntry;
409 }
410 return $sysLogReturnArray;
411 }
412
413 /**
414 * Gets all available system languages.
415 *
416 * @param \stdClass $parameters
417 * @return array
418 */
419 public function getSystemLanguages(\stdClass $parameters)
420 {
421 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
422 $systemLanguages = [
423 [
424 'uid' => 'all',
425 'title' => LocalizationUtility::translate('language.allLanguages', 'workspaces'),
426 'icon' => $iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render()
427 ]
428 ];
429 foreach ($this->gridDataService->getSystemLanguages($parameters->pageUid ?? 0) as $id => $systemLanguage) {
430 if ($id < 0) {
431 continue;
432 }
433 $systemLanguages[] = [
434 'uid' => $id,
435 'title' => htmlspecialchars($systemLanguage['title']),
436 'icon' => $iconFactory->getIcon($systemLanguage['flagIcon'], Icon::SIZE_SMALL)->render()
437 ];
438 }
439 $result = [
440 'total' => count($systemLanguages),
441 'data' => $systemLanguages
442 ];
443 return $result;
444 }
445
446 /**
447 * @return BackendUserAuthentication
448 */
449 protected function getBackendUser()
450 {
451 return $GLOBALS['BE_USER'];
452 }
453
454 /**
455 * @return LanguageService
456 */
457 protected function getLanguageService()
458 {
459 return $GLOBALS['LANG'];
460 }
461
462 /**
463 * Gets the difference handler, parsing differences based on sentences.
464 *
465 * @return DiffUtility
466 */
467 protected function getDifferenceHandler()
468 {
469 if (!isset($this->differenceHandler)) {
470 $this->differenceHandler = GeneralUtility::makeInstance(DiffUtility::class);
471 $this->differenceHandler->stripTags = false;
472 }
473 return $this->differenceHandler;
474 }
475
476 /**
477 * Creates a new instance of the integrity service for the
478 * given set of affected elements.
479 *
480 * @param CombinedRecord[] $affectedElements
481 * @return IntegrityService
482 * @see getAffectedElements
483 */
484 protected function createIntegrityService(array $affectedElements)
485 {
486 $integrityService = GeneralUtility::makeInstance(IntegrityService::class);
487 $integrityService->setAffectedElements($affectedElements);
488 return $integrityService;
489 }
490
491 /**
492 * Gets affected elements on publishing/swapping actions.
493 * Affected elements have a dependency, e.g. translation overlay
494 * and the default origin record - thus, the default record would be
495 * affected if the translation overlay shall be published.
496 *
497 * @param \stdClass $parameters
498 * @return array
499 */
500 protected function getAffectedElements(\stdClass $parameters)
501 {
502 $affectedElements = [];
503 if ($parameters->type === 'selection') {
504 foreach ((array)$parameters->selection as $element) {
505 $affectedElements[] = CombinedRecord::create($element->table, $element->liveId, $element->versionId);
506 }
507 } elseif ($parameters->type === 'all') {
508 $versions = $this->workspaceService->selectVersionsInWorkspace($this->getCurrentWorkspace(), 0, -99, -1, 0, 'tables_select', $this->validateLanguageParameter($parameters));
509 foreach ($versions as $table => $tableElements) {
510 foreach ($tableElements as $element) {
511 $affectedElement = CombinedRecord::create($table, $element['t3ver_oid'], $element['uid']);
512 $affectedElement->getVersionRecord()->setRow($element);
513 $affectedElements[] = $affectedElement;
514 }
515 }
516 }
517 return $affectedElements;
518 }
519
520 /**
521 * Validates whether the submitted language parameter can be
522 * interpreted as integer value.
523 *
524 * @param \stdClass $parameters
525 * @return int|null
526 */
527 protected function validateLanguageParameter(\stdClass $parameters)
528 {
529 $language = null;
530 if (isset($parameters->language) && MathUtility::canBeInterpretedAsInteger($parameters->language)) {
531 $language = $parameters->language;
532 }
533 return $language;
534 }
535
536 /**
537 * Gets the current workspace ID.
538 *
539 * @return int The current workspace ID
540 */
541 protected function getCurrentWorkspace()
542 {
543 return $this->workspaceService->getCurrentWorkspace();
544 }
545 }