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