TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
GridDataService.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Workspaces\Service;
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 
21 
26 {
27  const SIGNAL_GenerateDataArray_BeforeCaching = 'generateDataArray.beforeCaching';
28  const SIGNAL_GenerateDataArray_PostProcesss = 'generateDataArray.postProcess';
29  const SIGNAL_GetDataArray_PostProcesss = 'getDataArray.postProcess';
30  const SIGNAL_SortDataArray_PostProcesss = 'sortDataArray.postProcess';
31 
32  const GridColumn_Collection = 'Workspaces_Collection';
33  const GridColumn_CollectionLevel = 'Workspaces_CollectionLevel';
34  const GridColumn_CollectionParent = 'Workspaces_CollectionParent';
35  const GridColumn_CollectionCurrent = 'Workspaces_CollectionCurrent';
36  const GridColumn_CollectionChildren = 'Workspaces_CollectionChildren';
37 
43  protected $currentWorkspace = null;
44 
50  protected $dataArray = [];
51 
57  protected $sort = '';
58 
64  protected $sortDir = '';
65 
69  protected $workspacesCache = null;
70 
74  protected $systemLanguages;
75 
79  protected $integrityService;
80 
90  public function generateGridListFromVersions($versions, $parameter, $currentWorkspace)
91  {
92  // Read the given parameters from grid. If the parameter is not set use default values.
93  $filterTxt = isset($parameter->filterTxt) ? $parameter->filterTxt : '';
94  $start = isset($parameter->start) ? (int)$parameter->start : 0;
95  $limit = isset($parameter->limit) ? (int)$parameter->limit : 30;
96  $this->sort = isset($parameter->sort) ? $parameter->sort : 't3ver_oid';
97  $this->sortDir = isset($parameter->dir) ? $parameter->dir : 'ASC';
98  if (is_int($currentWorkspace)) {
99  $this->currentWorkspace = $currentWorkspace;
100  } else {
101  throw new \InvalidArgumentException('No such workspace defined', 1476048304);
102  }
103  $data = [];
104  $data['data'] = [];
105  $this->generateDataArray($versions, $filterTxt);
106  $data['total'] = count($this->dataArray);
107  $data['data'] = $this->getDataArray($start, $limit);
108  return $data;
109  }
110 
118  protected function generateDataArray(array $versions, $filterTxt)
119  {
120  $workspaceAccess = $GLOBALS['BE_USER']->checkWorkspace($GLOBALS['BE_USER']->workspace);
121  $swapStage = $workspaceAccess['publish_access'] & 1 ? \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_ID : 0;
122  $swapAccess = $GLOBALS['BE_USER']->workspacePublishAccess($GLOBALS['BE_USER']->workspace) && $GLOBALS['BE_USER']->workspaceSwapAccess();
125  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
126  // check for dataArray in cache
127  if ($this->getDataArrayFromCache($versions, $filterTxt) === false) {
129  $stagesObj = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\StagesService::class);
130  $defaultGridColumns = [
131  self::GridColumn_Collection => 0,
132  self::GridColumn_CollectionLevel => 0,
133  self::GridColumn_CollectionParent => '',
134  self::GridColumn_CollectionCurrent => '',
135  self::GridColumn_CollectionChildren => 0,
136  ];
137  foreach ($versions as $table => $records) {
138  $hiddenField = $this->getTcaEnableColumnsFieldName($table, 'disabled');
139  $isRecordTypeAllowedToModify = $GLOBALS['BE_USER']->check('tables_modify', $table);
140 
141  foreach ($records as $record) {
142  $origRecord = BackendUtility::getRecord($table, $record['t3ver_oid']);
143  $versionRecord = BackendUtility::getRecord($table, $record['uid']);
144  $combinedRecord = \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord::createFromArrays($table, $origRecord, $versionRecord);
145  $this->getIntegrityService()->checkElement($combinedRecord);
146 
147  if ($hiddenField !== null) {
148  $recordState = $this->workspaceState($versionRecord['t3ver_state'], $origRecord[$hiddenField], $versionRecord[$hiddenField]);
149  } else {
150  $recordState = $this->workspaceState($versionRecord['t3ver_state']);
151  }
152 
153  $isDeletedPage = $table == 'pages' && $recordState == 'deleted';
154  $viewUrl = \TYPO3\CMS\Workspaces\Service\WorkspaceService::viewSingleRecord($table, $record['uid'], $origRecord, $versionRecord);
155  $versionArray = [];
156  $versionArray['table'] = $table;
157  $versionArray['id'] = $table . ':' . $record['uid'];
158  $versionArray['uid'] = $record['uid'];
159  $versionArray['workspace'] = $versionRecord['t3ver_id'];
160  $versionArray = array_merge($versionArray, $defaultGridColumns);
161  $versionArray['label_Workspace'] = htmlspecialchars(BackendUtility::getRecordTitle($table, $versionRecord));
162  $versionArray['label_Live'] = htmlspecialchars(BackendUtility::getRecordTitle($table, $origRecord));
163  $versionArray['label_Stage'] = htmlspecialchars($stagesObj->getStageTitle($versionRecord['t3ver_stage']));
164  $tempStage = $stagesObj->getNextStage($versionRecord['t3ver_stage']);
165  $versionArray['label_nextStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
166  $versionArray['value_nextStage'] = (int)$tempStage['uid'];
167  $tempStage = $stagesObj->getPrevStage($versionRecord['t3ver_stage']);
168  $versionArray['label_prevStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
169  $versionArray['value_prevStage'] = (int)$tempStage['uid'];
170  $versionArray['path_Live'] = htmlspecialchars(BackendUtility::getRecordPath($record['livepid'], '', 999));
171  $versionArray['path_Workspace'] = htmlspecialchars(BackendUtility::getRecordPath($record['wspid'], '', 999));
172  $versionArray['workspace_Title'] = htmlspecialchars(\TYPO3\CMS\Workspaces\Service\WorkspaceService::getWorkspaceTitle($versionRecord['t3ver_wsid']));
173  $versionArray['workspace_Tstamp'] = $versionRecord['tstamp'];
174  $versionArray['workspace_Formated_Tstamp'] = BackendUtility::datetime($versionRecord['tstamp']);
175  $versionArray['t3ver_wsid'] = $versionRecord['t3ver_wsid'];
176  $versionArray['t3ver_oid'] = $record['t3ver_oid'];
177  $versionArray['livepid'] = $record['livepid'];
178  $versionArray['stage'] = $versionRecord['t3ver_stage'];
179  $versionArray['icon_Live'] = $iconFactory->getIconForRecord($table, $origRecord, Icon::SIZE_SMALL)->render();
180  $versionArray['icon_Workspace'] = $iconFactory->getIconForRecord($table, $versionRecord, Icon::SIZE_SMALL)->render();
181  $languageValue = $this->getLanguageValue($table, $versionRecord);
182  $versionArray['languageValue'] = $languageValue;
183  $versionArray['language'] = [
184  'icon' => $iconFactory->getIcon($this->getSystemLanguageValue($languageValue, 'flagIcon'), Icon::SIZE_SMALL)->render()
185  ];
186  $versionArray['allowedAction_nextStage'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($versionRecord['t3ver_stage']);
187  $versionArray['allowedAction_prevStage'] = $isRecordTypeAllowedToModify && $stagesObj->isPrevStageAllowedForUser($versionRecord['t3ver_stage']);
188  if ($swapAccess && $swapStage != 0 && $versionRecord['t3ver_stage'] == $swapStage) {
189  $versionArray['allowedAction_swap'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($swapStage);
190  } elseif ($swapAccess && $swapStage == 0) {
191  $versionArray['allowedAction_swap'] = $isRecordTypeAllowedToModify;
192  } else {
193  $versionArray['allowedAction_swap'] = false;
194  }
195  $versionArray['allowedAction_delete'] = $isRecordTypeAllowedToModify;
196  // preview and editing of a deleted page won't work ;)
197  $versionArray['allowedAction_view'] = !$isDeletedPage && $viewUrl;
198  $versionArray['allowedAction_edit'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
199  $versionArray['allowedAction_editVersionedPage'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
200  $versionArray['state_Workspace'] = $recordState;
201 
202  $versionArray = array_merge(
203  $versionArray,
204  $this->getAdditionalColumnService()->getData($combinedRecord)
205  );
206 
207  if ($filterTxt == '' || $this->isFilterTextInVisibleColumns($filterTxt, $versionArray)) {
208  $versionIdentifier = $versionArray['id'];
209  $this->dataArray[$versionIdentifier] = $versionArray;
210  }
211  }
212  }
213  // Suggested slot method:
214  // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array $dataArray, array $versions)
215  list($this->dataArray, $versions) = $this->emitSignal(self::SIGNAL_GenerateDataArray_BeforeCaching, $this->dataArray, $versions);
216  // Enrich elements after everything has been processed:
217  foreach ($this->dataArray as &$element) {
218  $identifier = $element['table'] . ':' . $element['t3ver_oid'];
219  $element['integrity'] = [
220  'status' => $this->getIntegrityService()->getStatusRepresentation($identifier),
221  'messages' => htmlspecialchars($this->getIntegrityService()->getIssueMessages($identifier, true))
222  ];
223  }
224  $this->setDataArrayIntoCache($versions, $filterTxt);
225  }
226  // Suggested slot method:
227  // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array $dataArray, array $versions)
228  list($this->dataArray, $versions) = $this->emitSignal(self::SIGNAL_GenerateDataArray_PostProcesss, $this->dataArray, $versions);
229  $this->sortDataArray();
231  }
232 
239  protected function resolveDataArrayDependencies()
240  {
241  $collectionService = $this->getDependencyCollectionService();
242  $dependencyResolver = $collectionService->getDependencyResolver();
243 
244  foreach ($this->dataArray as $dataElement) {
245  $dependencyResolver->addElement($dataElement['table'], $dataElement['uid']);
246  }
247 
248  $this->dataArray = $collectionService->process($this->dataArray);
249  }
250 
258  protected function getDataArray($start, $limit)
259  {
260  $dataArrayPart = [];
261  $dataArrayCount = count($this->dataArray);
262  $end = ($start + $limit < $dataArrayCount ? $start + $limit : $dataArrayCount);
263 
264  // Ensure that there are numerical indexes
265  $this->dataArray = array_values(($this->dataArray));
266  for ($i = $start; $i < $end; $i++) {
267  $dataArrayPart[] = $this->dataArray[$i];
268  }
269 
270  // Ensure that collections are not cut for the pagination
271  if (!empty($this->dataArray[$i][self::GridColumn_Collection])) {
272  $collectionIdentifier = $this->dataArray[$i][self::GridColumn_Collection];
273  for ($i = $i + 1; $i < $dataArrayCount && $collectionIdentifier === $this->dataArray[$i][self::GridColumn_Collection]; $i++) {
274  $dataArrayPart[] = $this->dataArray[$i];
275  }
276  }
277 
278  // Suggested slot method:
279  // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array $dataArray, $start, $limit, array $dataArrayPart)
280  list($this->dataArray, $start, $limit, $dataArrayPart) = $this->emitSignal(self::SIGNAL_GetDataArray_PostProcesss, $this->dataArray, $start, $limit, $dataArrayPart);
281  return $dataArrayPart;
282  }
283 
290  {
291  $this->workspacesCache = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('workspaces_cache');
292  }
293 
300  protected function setDataArrayIntoCache(array $versions, $filterTxt)
301  {
302  $hash = $this->calculateHash($versions, $filterTxt);
303  $this->workspacesCache->set($hash, $this->dataArray, [$this->currentWorkspace, 'user_' . $GLOBALS['BE_USER']->user['uid']]);
304  }
305 
313  protected function getDataArrayFromCache(array $versions, $filterTxt)
314  {
315  $cacheEntry = false;
316  $hash = $this->calculateHash($versions, $filterTxt);
317  $content = $this->workspacesCache->get($hash);
318  if ($content !== false) {
319  $this->dataArray = $content;
320  $cacheEntry = true;
321  }
322  return $cacheEntry;
323  }
324 
332  protected function calculateHash(array $versions, $filterTxt)
333  {
334  $hashArray = [
335  $GLOBALS['BE_USER']->workspace,
336  $GLOBALS['BE_USER']->user['uid'],
337  $versions,
338  $filterTxt,
339  $this->sort,
342  ];
343  $hash = md5(serialize($hashArray));
344  return $hash;
345  }
346 
353  protected function sortDataArray()
354  {
355  if (is_array($this->dataArray)) {
356  switch ($this->sort) {
357  case 'uid':
358  case 'change':
359  case 'workspace_Tstamp':
360  case 't3ver_oid':
361  case 'liveid':
362  case 'livepid':
363  case 'languageValue':
364  uasort($this->dataArray, [$this, 'intSort']);
365  break;
366  case 'label_Workspace':
367  case 'label_Live':
368  case 'label_Stage':
369  case 'workspace_Title':
370  case 'path_Live':
371  // case 'path_Workspace': This is the first sorting attribute
372  uasort($this->dataArray, [$this, 'stringSort']);
373  break;
374  default:
375  // Do nothing
376  }
377  } else {
378  GeneralUtility::sysLog(
379  'Try to sort "' . $this->sort . '" in "TYPO3\\CMS\\Workspaces\\Service\\GridDataService::sortDataArray" but $this->dataArray is empty! This might be the Bug #26422 which could not reproduced yet.',
380  'workspaces',
382  );
383  }
384  // Suggested slot method:
385  // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array $dataArray, $sortColumn, $sortDirection)
386  list($this->dataArray, $this->sort, $this->sortDir) = $this->emitSignal(self::SIGNAL_SortDataArray_PostProcesss, $this->dataArray, $this->sort, $this->sortDir);
387  }
388 
396  protected function intSort(array $a, array $b)
397  {
398  if (!$this->isSortable($a, $b)) {
399  return 0;
400  }
401  // First sort by using the page-path in current workspace
402  $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
403  if ($path_cmp < 0) {
404  return $path_cmp;
405  } elseif ($path_cmp == 0) {
406  if ($a[$this->sort] == $b[$this->sort]) {
407  return 0;
408  }
409  if ($this->sortDir == 'ASC') {
410  return $a[$this->sort] < $b[$this->sort] ? -1 : 1;
411  } elseif ($this->sortDir == 'DESC') {
412  return $a[$this->sort] > $b[$this->sort] ? -1 : 1;
413  }
414  } elseif ($path_cmp > 0) {
415  return $path_cmp;
416  }
417  return 0;
418  }
419 
427  protected function stringSort($a, $b)
428  {
429  if (!$this->isSortable($a, $b)) {
430  return 0;
431  }
432  $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
433  if ($path_cmp < 0) {
434  return $path_cmp;
435  } elseif ($path_cmp == 0) {
436  if ($a[$this->sort] == $b[$this->sort]) {
437  return 0;
438  }
439  if ($this->sortDir == 'ASC') {
440  return strcasecmp($a[$this->sort], $b[$this->sort]);
441  } elseif ($this->sortDir == 'DESC') {
442  return strcasecmp($a[$this->sort], $b[$this->sort]) * -1;
443  }
444  } elseif ($path_cmp > 0) {
445  return $path_cmp;
446  }
447  return 0;
448  }
449 
459  protected function isSortable(array $a, array $b)
460  {
461  return
462  $a[self::GridColumn_CollectionLevel] === 0 && $b[self::GridColumn_CollectionLevel] === 0
463  || $a[self::GridColumn_CollectionParent] === $b[self::GridColumn_CollectionParent]
464  ;
465  }
466 
475  protected function isFilterTextInVisibleColumns($filterText, array $versionArray)
476  {
477  if (is_array($GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'])) {
478  foreach ($GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'] as $column => $value) {
479  if (isset($value['hidden']) && isset($column) && isset($versionArray[$column])) {
480  if ($value['hidden'] == 0) {
481  switch ($column) {
482  case 'workspace_Tstamp':
483  if (stripos($versionArray['workspace_Formated_Tstamp'], $filterText) !== false) {
484  return true;
485  }
486  break;
487  case 'change':
488  if (stripos(strval($versionArray[$column]), str_replace('%', '', $filterText)) !== false) {
489  return true;
490  }
491  break;
492  default:
493  if (stripos(strval($versionArray[$column]), $filterText) !== false) {
494  return true;
495  }
496  }
497  }
498  }
499  }
500  }
501  return false;
502  }
503 
512  protected function workspaceState($stateId, $hiddenOnline = false, $hiddenOffline = false)
513  {
514  $hiddenState = null;
515  if ($hiddenOnline == 0 && $hiddenOffline == 1) {
516  $hiddenState = 'hidden';
517  } elseif ($hiddenOnline == 1 && $hiddenOffline == 0) {
518  $hiddenState = 'unhidden';
519  }
520  switch ($stateId) {
521  case -1:
522  $state = 'new';
523  break;
524  case 2:
525  $state = 'deleted';
526  break;
527  case 4:
528  $state = 'moved';
529  break;
530  default:
531  $state = ($hiddenState ?: 'modified');
532  }
533  return $state;
534  }
535 
543  protected function getTcaEnableColumnsFieldName($table, $type)
544  {
545  $fieldName = null;
546 
547  if (!(empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type]))) {
548  $fieldName = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type];
549  }
550 
551  return $fieldName;
552  }
553 
562  protected function getLanguageValue($table, array $record)
563  {
564  $languageValue = 0;
566  $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
567  if (!empty($record[$languageField])) {
568  $languageValue = $record[$languageField];
569  }
570  }
571  return $languageValue;
572  }
573 
582  protected function getSystemLanguageValue($id, $key)
583  {
584  $value = null;
585  $systemLanguages = $this->getSystemLanguages();
586  if (!empty($systemLanguages[$id][$key])) {
587  $value = $systemLanguages[$id][$key];
588  }
589  return $value;
590  }
591 
597  public function getSystemLanguages()
598  {
599  if (!isset($this->systemLanguages)) {
601  $translateTools = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider::class);
602  $this->systemLanguages = $translateTools->getSystemLanguages();
603  }
604  return $this->systemLanguages;
605  }
606 
612  protected function getIntegrityService()
613  {
614  if (!isset($this->integrityService)) {
615  $this->integrityService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\IntegrityService::class);
616  }
618  }
619 
627  protected function emitSignal($signalName, ...$arguments)
628  {
629  // Arguments are always ($this, [method argument], [method argument], ...)
630  $signalArguments = $arguments;
631  array_unshift($signalArguments, $this);
632  $slotReturn = $this->getSignalSlotDispatcher()->dispatch(\TYPO3\CMS\Workspaces\Service\GridDataService::class, $signalName, $signalArguments);
633  return array_slice($slotReturn, 1);
634  }
635 
639  protected function getDependencyCollectionService()
640  {
641  return GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\Dependency\CollectionService::class);
642  }
643 
647  protected function getAdditionalColumnService()
648  {
649  return $this->getObjectManager()->get(\TYPO3\CMS\Workspaces\Service\AdditionalColumnService::class);
650  }
651 
655  protected function getSignalSlotDispatcher()
656  {
657  return $this->getObjectManager()->get(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
658  }
659 
663  protected function getObjectManager()
664  {
665  return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
666  }
667 }
static getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit=0)
getDataArrayFromCache(array $versions, $filterTxt)
isFilterTextInVisibleColumns($filterText, array $versionArray)
static viewSingleRecord($table, $uid, array $liveRecord=null, array $versionRecord=null)
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
static createFromArrays($table, array $liveRow, array $versionRow)
setDataArrayIntoCache(array $versions, $filterTxt)
static getRecord($table, $uid, $fields= '*', $where= '', $useDeleteClause=true)
calculateHash(array $versions, $filterTxt)
workspaceState($stateId, $hiddenOnline=false, $hiddenOffline=false)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
generateGridListFromVersions($versions, $parameter, $currentWorkspace)