[TASK][workspaces] Remove @package and @subpackage annotations
[Packages/TYPO3.CMS.git] / typo3 / sysext / workspaces / Classes / Service / GridDataService.php
1 <?php
2 namespace TYPO3\CMS\Workspaces\Service;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2010-2011 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /**
30 * Grid data service
31 *
32 * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
33 */
34 class GridDataService {
35
36 const SIGNAL_GenerateDataArray_BeforeCaching = 'generateDataArray.beforeCaching';
37 const SIGNAL_GenerateDataArray_PostProcesss = 'generateDataArray.postProcess';
38 const SIGNAL_GetDataArray_PostProcesss = 'getDataArray.postProcess';
39 const SIGNAL_SortDataArray_PostProcesss = 'sortDataArray.postProcess';
40 /**
41 * Id of the current active workspace.
42 *
43 * @var integer
44 */
45 protected $currentWorkspace = NULL;
46
47 /**
48 * Version record information (filtered, sorted and limited)
49 *
50 * @var array
51 */
52 protected $dataArray = array();
53
54 /**
55 * Name of the field used for sorting.
56 *
57 * @var string
58 */
59 protected $sort = '';
60
61 /**
62 * Direction used for sorting (ASC, DESC).
63 *
64 * @var string
65 */
66 protected $sortDir = '';
67
68 /**
69 * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
70 */
71 protected $workspacesCache = NULL;
72
73 /**
74 * @var array
75 */
76 protected $systemLanguages;
77
78 /**
79 * @var \TYPO3\CMS\Workspaces\Service\IntegrityService
80 */
81 protected $integrityService;
82
83 /**
84 * Generates grid list array from given versions.
85 *
86 * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
87 * @param object $parameter Parameters as submitted by JavaScript component
88 * @param integer $currentWorkspace The current workspace
89 * @return array Version record information (filtered, sorted and limited)
90 * @throws \InvalidArgumentException
91 */
92 public function generateGridListFromVersions($versions, $parameter, $currentWorkspace) {
93 // Read the given parameters from grid. If the parameter is not set use default values.
94 $filterTxt = isset($parameter->filterTxt) ? $parameter->filterTxt : '';
95 $start = isset($parameter->start) ? intval($parameter->start) : 0;
96 $limit = isset($parameter->limit) ? intval($parameter->limit) : 30;
97 $this->sort = isset($parameter->sort) ? $parameter->sort : 't3ver_oid';
98 $this->sortDir = isset($parameter->dir) ? $parameter->dir : 'ASC';
99 if (is_int($currentWorkspace)) {
100 $this->currentWorkspace = $currentWorkspace;
101 } else {
102 throw new \InvalidArgumentException('No such workspace defined');
103 }
104 $data = array();
105 $data['data'] = array();
106 $this->generateDataArray($versions, $filterTxt);
107 $data['total'] = count($this->dataArray);
108 $data['data'] = $this->getDataArray($start, $limit);
109 return $data;
110 }
111
112 /**
113 * Generates grid list array from given versions.
114 *
115 * @param array $versions All available version records
116 * @param string $filterTxt Text to be used to filter record result
117 * @return void
118 */
119 protected function generateDataArray(array $versions, $filterTxt) {
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();
123 $this->initializeWorkspacesCachingFramework();
124 // check for dataArray in cache
125 if ($this->getDataArrayFromCache($versions, $filterTxt) === FALSE) {
126 /** @var $stagesObj \TYPO3\CMS\Workspaces\Service\StagesService */
127 $stagesObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\StagesService');
128 foreach ($versions as $table => $records) {
129 $versionArray = array('table' => $table);
130 $isRecordTypeAllowedToModify = $GLOBALS['BE_USER']->check('tables_modify', $table);
131 foreach ($records as $record) {
132 $origRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $record['t3ver_oid']);
133 $versionRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $record['uid']);
134 $combinedRecord = \TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord::createFromArrays($table, $origRecord, $versionRecord);
135 $this->getIntegrityService()->checkElement($combinedRecord);
136 if (isset($GLOBALS['TCA'][$table]['columns']['hidden'])) {
137 $recordState = $this->workspaceState($versionRecord['t3ver_state'], $origRecord['hidden'], $versionRecord['hidden']);
138 } else {
139 $recordState = $this->workspaceState($versionRecord['t3ver_state']);
140 }
141 $isDeletedPage = $table == 'pages' && $recordState == 'deleted';
142 $viewUrl = \TYPO3\CMS\Workspaces\Service\WorkspaceService::viewSingleRecord($table, $record['t3ver_oid'], $origRecord);
143 $versionArray['id'] = $table . ':' . $record['uid'];
144 $versionArray['uid'] = $record['uid'];
145 $versionArray['workspace'] = $versionRecord['t3ver_id'];
146 $versionArray['label_Workspace'] = htmlspecialchars(\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle($table, $versionRecord));
147 $versionArray['label_Live'] = htmlspecialchars(\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle($table, $origRecord));
148 $versionArray['label_Stage'] = htmlspecialchars($stagesObj->getStageTitle($versionRecord['t3ver_stage']));
149 $tempStage = $stagesObj->getNextStage($versionRecord['t3ver_stage']);
150 $versionArray['label_nextStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
151 $tempStage = $stagesObj->getPrevStage($versionRecord['t3ver_stage']);
152 $versionArray['label_prevStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
153 $versionArray['path_Live'] = htmlspecialchars(\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordPath($record['livepid'], '', 999));
154 $versionArray['path_Workspace'] = htmlspecialchars(\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordPath($record['wspid'], '', 999));
155 $versionArray['workspace_Title'] = htmlspecialchars(\TYPO3\CMS\Workspaces\Service\WorkspaceService::getWorkspaceTitle($versionRecord['t3ver_wsid']));
156 $versionArray['workspace_Tstamp'] = $versionRecord['tstamp'];
157 $versionArray['workspace_Formated_Tstamp'] = \TYPO3\CMS\Backend\Utility\BackendUtility::datetime($versionRecord['tstamp']);
158 $versionArray['t3ver_oid'] = $record['t3ver_oid'];
159 $versionArray['livepid'] = $record['livepid'];
160 $versionArray['stage'] = $versionRecord['t3ver_stage'];
161 $versionArray['icon_Live'] = \TYPO3\CMS\Backend\Utility\IconUtility::mapRecordTypeToSpriteIconClass($table, $origRecord);
162 $versionArray['icon_Workspace'] = \TYPO3\CMS\Backend\Utility\IconUtility::mapRecordTypeToSpriteIconClass($table, $versionRecord);
163 $languageValue = $this->getLanguageValue($table, $versionRecord);
164 $versionArray['languageValue'] = $languageValue;
165 $versionArray['language'] = array(
166 'cls' => \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconClasses($this->getSystemLanguageValue($languageValue, 'flagIcon')),
167 'title' => htmlspecialchars($this->getSystemLanguageValue($languageValue, 'title'))
168 );
169 $versionArray['allowedAction_nextStage'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($versionRecord['t3ver_stage']);
170 $versionArray['allowedAction_prevStage'] = $isRecordTypeAllowedToModify && $stagesObj->isPrevStageAllowedForUser($versionRecord['t3ver_stage']);
171 if ($swapAccess && $swapStage != 0 && $versionRecord['t3ver_stage'] == $swapStage) {
172 $versionArray['allowedAction_swap'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($swapStage);
173 } elseif ($swapAccess && $swapStage == 0) {
174 $versionArray['allowedAction_swap'] = $isRecordTypeAllowedToModify;
175 } else {
176 $versionArray['allowedAction_swap'] = FALSE;
177 }
178 $versionArray['allowedAction_delete'] = $isRecordTypeAllowedToModify;
179 // preview and editing of a deleted page won't work ;)
180 $versionArray['allowedAction_view'] = !$isDeletedPage && $viewUrl;
181 $versionArray['allowedAction_edit'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
182 $versionArray['allowedAction_editVersionedPage'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
183 $versionArray['state_Workspace'] = $recordState;
184 if ($filterTxt == '' || $this->isFilterTextInVisibleColumns($filterTxt, $versionArray)) {
185 $this->dataArray[] = $versionArray;
186 }
187 }
188 }
189 // Suggested slot method:
190 // methodName(Tx_Workspaces_Service_GridData $gridData, array &$dataArray, array $versions)
191 $this->emitSignal(self::SIGNAL_GenerateDataArray_BeforeCaching, $this->dataArray, $versions);
192 // Enrich elements after everything has been processed:
193 foreach ($this->dataArray as &$element) {
194 $identifier = $element['table'] . ':' . $element['t3ver_oid'];
195 $element['integrity'] = array(
196 'status' => $this->getIntegrityService()->getStatusRepresentation($identifier),
197 'messages' => htmlspecialchars($this->getIntegrityService()->getIssueMessages($identifier, TRUE))
198 );
199 }
200 $this->setDataArrayIntoCache($versions, $filterTxt);
201 }
202 // Suggested slot method:
203 // methodName(Tx_Workspaces_Service_GridData $gridData, array &$dataArray, array $versions)
204 $this->emitSignal(self::SIGNAL_GenerateDataArray_PostProcesss, $this->dataArray, $versions);
205 $this->sortDataArray();
206 }
207
208 /**
209 * Gets the data array by considering the page to be shown in the grid view.
210 *
211 * @param integer $start
212 * @param integer $limit
213 * @return array
214 */
215 protected function getDataArray($start, $limit) {
216 $dataArrayPart = array();
217 $end = $start + $limit < count($this->dataArray) ? $start + $limit : count($this->dataArray);
218 for ($i = $start; $i < $end; $i++) {
219 $dataArrayPart[] = $this->dataArray[$i];
220 }
221 // Suggested slot method:
222 // methodName(Tx_Workspaces_Service_GridData $gridData, array &$dataArray, $start, $limit)
223 $this->emitSignal(self::SIGNAL_GetDataArray_PostProcesss, $this->dataArray, $start, $limit);
224 return $dataArrayPart;
225 }
226
227 /**
228 * Initializes the workspace cache
229 *
230 * @return void
231 */
232 protected function initializeWorkspacesCachingFramework() {
233 $this->workspacesCache = $GLOBALS['typo3CacheManager']->getCache('workspaces_cache');
234 }
235
236 /**
237 * Puts the generated dataArray into the workspace cache.
238 *
239 * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
240 * @param string $filterTxt The given filter text from the grid.
241 */
242 protected function setDataArrayIntoCache(array $versions, $filterTxt) {
243 $hash = $this->calculateHash($versions, $filterTxt);
244 $this->workspacesCache->set($hash, $this->dataArray, array($this->currentWorkspace, 'user_' . $GLOBALS['BE_USER']->user['uid']));
245 }
246
247 /**
248 * Checks if a cache entry is given for given versions and filter text and tries to load the data array from cache.
249 *
250 * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
251 * @param string $filterTxt The given filter text from the grid.
252 * @return boolean TRUE if cache entry was successfully fetched from cache and content put to $this->dataArray
253 */
254 protected function getDataArrayFromCache(array $versions, $filterTxt) {
255 $cacheEntry = FALSE;
256 $hash = $this->calculateHash($versions, $filterTxt);
257 $content = $this->workspacesCache->get($hash);
258 if ($content !== FALSE) {
259 $this->dataArray = $content;
260 $cacheEntry = TRUE;
261 }
262 return $cacheEntry;
263 }
264
265 /**
266 * Calculates the hash value of the used workspace, the user id, the versions array, the filter text, the sorting attribute, the workspace selected in grid and the sorting direction.
267 *
268 * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
269 * @param string $filterTxt The given filter text from the grid.
270 * @return string
271 */
272 protected function calculateHash(array $versions, $filterTxt) {
273 $hashArray = array(
274 $GLOBALS['BE_USER']->workspace,
275 $GLOBALS['BE_USER']->user['uid'],
276 $versions,
277 $filterTxt,
278 $this->sort,
279 $this->sortDir,
280 $this->currentWorkspace
281 );
282 $hash = md5(serialize($hashArray));
283 return $hash;
284 }
285
286 /**
287 * Performs sorting on the data array accordant to the
288 * selected column in the grid view to be used for sorting.
289 *
290 * @return void
291 */
292 protected function sortDataArray() {
293 if (is_array($this->dataArray)) {
294 switch ($this->sort) {
295 case 'uid':
296
297 case 'change':
298
299 case 'workspace_Tstamp':
300
301 case 't3ver_oid':
302
303 case 'liveid':
304
305 case 'livepid':
306
307 case 'languageValue':
308 usort($this->dataArray, array($this, 'intSort'));
309 break;
310 case 'label_Workspace':
311
312 case 'label_Live':
313
314 case 'label_Stage':
315
316 case 'workspace_Title':
317
318 case 'path_Live':
319 // case 'path_Workspace': This is the first sorting attribute
320 usort($this->dataArray, array($this, 'stringSort'));
321 break;
322 }
323 } else {
324 \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog('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.', 3);
325 }
326 // Suggested slot method:
327 // methodName(Tx_Workspaces_Service_GridData $gridData, array &$dataArray, $sortColumn, $sortDirection)
328 $this->emitSignal(self::SIGNAL_SortDataArray_PostProcesss, $this->dataArray, $this->sort, $this->sortDir);
329 }
330
331 /**
332 * Implements individual sorting for columns based on integer comparison.
333 *
334 * @param array $a First value
335 * @param array $b Second value
336 * @return integer
337 */
338 protected function intSort(array $a, array $b) {
339 // First sort by using the page-path in current workspace
340 $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
341 if ($path_cmp < 0) {
342 return $path_cmp;
343 } elseif ($path_cmp == 0) {
344 if ($a[$this->sort] == $b[$this->sort]) {
345 return 0;
346 }
347 if ($this->sortDir == 'ASC') {
348 return $a[$this->sort] < $b[$this->sort] ? -1 : 1;
349 } elseif ($this->sortDir == 'DESC') {
350 return $a[$this->sort] > $b[$this->sort] ? -1 : 1;
351 }
352 } elseif ($path_cmp > 0) {
353 return $path_cmp;
354 }
355 return 0;
356 }
357
358 /**
359 * Implements individual sorting for columns based on string comparison.
360 *
361 * @param string $a First value
362 * @param string $b Second value
363 * @return integer
364 */
365 protected function stringSort($a, $b) {
366 $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
367 if ($path_cmp < 0) {
368 return $path_cmp;
369 } elseif ($path_cmp == 0) {
370 if ($a[$this->sort] == $b[$this->sort]) {
371 return 0;
372 }
373 if ($this->sortDir == 'ASC') {
374 return strcasecmp($a[$this->sort], $b[$this->sort]);
375 } elseif ($this->sortDir == 'DESC') {
376 return strcasecmp($a[$this->sort], $b[$this->sort]) * -1;
377 }
378 } elseif ($path_cmp > 0) {
379 return $path_cmp;
380 }
381 return 0;
382 }
383
384 /**
385 * Determines whether the text used to filter the results is part of
386 * a column that is visible in the grid view.
387 *
388 * @param string $filterText
389 * @param array $versionArray
390 * @return boolean
391 */
392 protected function isFilterTextInVisibleColumns($filterText, array $versionArray) {
393 if (is_array($GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'])) {
394 foreach ($GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'] as $column => $value) {
395 if (isset($value['hidden']) && isset($column) && isset($versionArray[$column])) {
396 if ($value['hidden'] == 0) {
397 switch ($column) {
398 case 'workspace_Tstamp':
399 if (stripos($versionArray['workspace_Formated_Tstamp'], $filterText) !== FALSE) {
400 return TRUE;
401 }
402 break;
403 case 'change':
404 if (stripos(strval($versionArray[$column]), str_replace('%', '', $filterText)) !== FALSE) {
405 return TRUE;
406 }
407 break;
408 default:
409 if (stripos(strval($versionArray[$column]), $filterText) !== FALSE) {
410 return TRUE;
411 }
412 }
413 }
414 }
415 }
416 }
417 return FALSE;
418 }
419
420 /**
421 * Gets the state of a given state value.
422 *
423 * @param integer $stateId stateId of offline record
424 * @param boolean $hiddenOnline hidden status of online record
425 * @param boolean $hiddenOffline hidden status of offline record
426 * @return string
427 */
428 protected function workspaceState($stateId, $hiddenOnline = FALSE, $hiddenOffline = FALSE) {
429 switch ($stateId) {
430 case -1:
431 $state = 'new';
432 break;
433 case 1:
434
435 case 2:
436 $state = 'deleted';
437 break;
438 case 4:
439 $state = 'moved';
440 break;
441 default:
442 $state = 'modified';
443 }
444 if ($hiddenOnline == 0 && $hiddenOffline == 1) {
445 $state = 'hidden';
446 } elseif ($hiddenOnline == 1 && $hiddenOffline == 0) {
447 $state = 'unhidden';
448 }
449 return $state;
450 }
451
452 /**
453 * Gets the used language value (sys_language.uid) of
454 * a given database record.
455 *
456 * @param string $table Name of the table
457 * @param array $record Database record
458 * @return integer
459 */
460 protected function getLanguageValue($table, array $record) {
461 $languageValue = 0;
462 if (\TYPO3\CMS\Backend\Utility\BackendUtility::isTableLocalizable($table)) {
463 $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
464 if (!empty($record[$languageField])) {
465 $languageValue = $record[$languageField];
466 }
467 }
468 return $languageValue;
469 }
470
471 /**
472 * Gets a named value of the available sys_language elements.
473 *
474 * @param integer $id sys_language uid
475 * @param string $key Name of the value to be fetched (e.g. title)
476 * @return string|NULL
477 * @see getSystemLanguages
478 */
479 protected function getSystemLanguageValue($id, $key) {
480 $value = NULL;
481 $systemLanguages = $this->getSystemLanguages();
482 if (!empty($systemLanguages[$id][$key])) {
483 $value = $systemLanguages[$id][$key];
484 }
485 return $value;
486 }
487
488 /**
489 * Gets all available system languages.
490 *
491 * @return array
492 * @see t3lib_transl8tools::getSystemLanguages
493 */
494 public function getSystemLanguages() {
495 if (!isset($this->systemLanguages)) {
496 /** @var $translateTools \TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider */
497 $translateTools = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Configuration\\TranslationConfigurationProvider');
498 $this->systemLanguages = $translateTools->getSystemLanguages();
499 }
500 return $this->systemLanguages;
501 }
502
503 /**
504 * Gets an instance of the integrity service.
505 *
506 * @return \TYPO3\CMS\Workspaces\Service\IntegrityService
507 */
508 protected function getIntegrityService() {
509 if (!isset($this->integrityService)) {
510 $this->integrityService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Workspaces\\Service\\IntegrityService');
511 }
512 return $this->integrityService;
513 }
514
515 /**
516 * Emits a signal to be handled by any registered slots.
517 *
518 * @param string $signalName Name of the signal
519 * @return void
520 */
521 protected function emitSignal($signalName) {
522 // Arguments are always ($this, [method argument], [method argument], ...)
523 $signalArguments = array_merge(array($this), array_slice(func_get_args(), 1));
524 $this->getSignalSlotDispatcher()->dispatch('TYPO3\\CMS\\Workspaces\\Service\\GridDataService', $signalName, $signalArguments);
525 }
526
527 /**
528 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
529 */
530 protected function getSignalSlotDispatcher() {
531 return $this->getObjectManager()->get('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
532 }
533
534 /**
535 * @return \TYPO3\CMS\Extbase\Object\ObjectManagerException
536 */
537 protected function getObjectManager() {
538 return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManagerException');
539 }
540
541 }
542
543
544 ?>