[FEATURE] Tooltip for next/prev stage icons with stage-title
[Packages/TYPO3.CMS.git] / typo3 / sysext / workspaces / Classes / Service / GridData.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2010-2011 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
30 * @package Workspaces
31 * @subpackage Service
32 */
33 class Tx_Workspaces_Service_GridData {
34 protected $currentWorkspace = NULL;
35 protected $dataArray = array();
36 protected $sort = '';
37 protected $sortDir = '';
38 protected $workspacesCache = NULL;
39
40 /**
41 * Generates grid list array from given versions.
42 *
43 * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid, t3ver_oid and t3ver_swapmode fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid"
44 * @param object $parameter
45 * @param integer $currentWorkspace
46 * @return array
47 * @throws InvalidArgumentException
48 */
49 public function generateGridListFromVersions($versions, $parameter, $currentWorkspace) {
50
51 // Read the given parameters from grid. If the parameter is not set use default values.
52 $filterTxt = isset($parameter->filterTxt) ? $parameter->filterTxt : '';
53 $start = isset($parameter->start) ? intval($parameter->start) : 0;
54 $limit = isset($parameter->limit) ? intval($parameter->limit) : 30;
55 $this->sort = isset($parameter->sort) ? $parameter->sort : 't3ver_oid';
56 $this->sortDir = isset($parameter->dir) ? $parameter->dir : 'ASC';
57
58 if (is_int($currentWorkspace)) {
59 $this->currentWorkspace = $currentWorkspace;
60 } else {
61 throw new InvalidArgumentException('No such workspace defined');
62 }
63
64 $data = array();
65 $data['data'] = array();
66
67 $this->generateDataArray($versions, $filterTxt);
68
69 $data['total'] = count($this->dataArray);
70 $data['data'] = $this->getDataArray($start, $limit);
71
72 return $data;
73 }
74
75 /**
76 * Generates grid list array from given versions.
77 *
78 * @param array $versions
79 * @param string $filterTxt
80 * @return void
81 */
82 protected function generateDataArray(array $versions, $filterTxt) {
83 /** @var $stagesObj Tx_Workspaces_Service_Stages */
84 $stagesObj = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
85
86 /** @var $workspacesObj Tx_Workspaces_Service_Workspaces */
87 $workspacesObj = t3lib_div::makeInstance('Tx_Workspaces_Service_Workspaces');
88 $availableWorkspaces = $workspacesObj->getAvailableWorkspaces();
89
90 $workspaceAccess = $GLOBALS['BE_USER']->checkWorkspace($GLOBALS['BE_USER']->workspace);
91 $swapStage = ($workspaceAccess['publish_access'] & 1) ? Tx_Workspaces_Service_Stages::STAGE_PUBLISH_ID : 0;
92 $swapAccess = $GLOBALS['BE_USER']->workspacePublishAccess($GLOBALS['BE_USER']->workspace) &&
93 $GLOBALS['BE_USER']->workspaceSwapAccess();
94
95 $this->initializeWorkspacesCachingFramework();
96
97 // check for dataArray in cache
98 if ($this->getDataArrayFromCache($versions, $filterTxt) == FALSE) {
99 $stagesObj = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
100
101 foreach ($versions as $table => $records) {
102 $versionArray = array('table' => $table);
103 $isRecordTypeAllowedToModify = $GLOBALS['BE_USER']->check('tables_modify', $table);
104
105 foreach ($records as $record) {
106
107 $origRecord = t3lib_BEFunc::getRecord($table, $record['t3ver_oid']);
108 $versionRecord = t3lib_BEFunc::getRecord($table, $record['uid']);
109
110 if (isset($GLOBALS['TCA'][$table]['columns']['hidden'])) {
111 $recordState = $this->workspaceState($versionRecord['t3ver_state'], $origRecord['hidden'], $versionRecord['hidden']);
112 } else {
113 $recordState = $this->workspaceState($versionRecord['t3ver_state']);
114 }
115 $isDeletedPage = ($table == 'pages' && $recordState == 'deleted');
116 $viewUrl = Tx_Workspaces_Service_Workspaces::viewSingleRecord($table, $record['t3ver_oid'], $origRecord);
117
118 $pctChange = $this->calculateChangePercentage($table, $origRecord, $versionRecord);
119 $versionArray['uid'] = $record['uid'];
120 $versionArray['workspace'] = $versionRecord['t3ver_id'];
121 $versionArray['label_Workspace'] = htmlspecialchars(t3lib_befunc::getRecordTitle($table, $versionRecord));
122 $versionArray['label_Live'] = htmlspecialchars(t3lib_befunc::getRecordTitle($table, $origRecord));
123 $versionArray['label_Stage'] = htmlspecialchars($stagesObj->getStageTitle($versionRecord['t3ver_stage']));
124 $tempStage = $stagesObj->getNextStage($versionRecord['t3ver_stage']);
125 $versionArray['label_nextStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
126 $tempStage = $stagesObj->getPrevStage($versionRecord['t3ver_stage']);
127 $versionArray['label_prevStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
128 $versionArray['change'] = $pctChange;
129 $versionArray['path_Live'] = htmlspecialchars(t3lib_BEfunc::getRecordPath($record['livepid'], '', 999));
130 $versionArray['path_Workspace'] = htmlspecialchars(t3lib_BEfunc::getRecordPath($record['wspid'], '', 999));
131 $versionArray['workspace_Title'] = htmlspecialchars(Tx_Workspaces_Service_Workspaces::getWorkspaceTitle($versionRecord['t3ver_wsid']));
132
133 $versionArray['workspace_Tstamp'] = $versionRecord['tstamp'];
134 $versionArray['workspace_Formated_Tstamp'] = t3lib_BEfunc::datetime($versionRecord['tstamp']);
135 $versionArray['t3ver_oid'] = $record['t3ver_oid'];
136 $versionArray['livepid'] = $record['livepid'];
137 $versionArray['stage'] = $versionRecord['t3ver_stage'];
138 $versionArray['icon_Live'] = t3lib_iconWorks::mapRecordTypeToSpriteIconClass($table, $origRecord);
139 $versionArray['icon_Workspace'] = t3lib_iconWorks::mapRecordTypeToSpriteIconClass($table, $versionRecord);
140
141 $versionArray['allowedAction_nextStage'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($versionRecord['t3ver_stage']);
142 $versionArray['allowedAction_prevStage'] = $isRecordTypeAllowedToModify && $stagesObj->isPrevStageAllowedForUser($versionRecord['t3ver_stage']);
143
144 if ($swapAccess && $swapStage != 0 && $versionRecord['t3ver_stage'] == $swapStage) {
145 $versionArray['allowedAction_swap'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($swapStage);
146 } elseif ($swapAccess && $swapStage == 0) {
147 $versionArray['allowedAction_swap'] = $isRecordTypeAllowedToModify;
148 } else {
149 $versionArray['allowedAction_swap'] = FALSE;
150 }
151 $versionArray['allowedAction_delete'] = $isRecordTypeAllowedToModify;
152 // preview and editing of a deleted page won't work ;)
153 $versionArray['allowedAction_view'] = !$isDeletedPage && $viewUrl;
154 $versionArray['allowedAction_edit'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
155 $versionArray['allowedAction_editVersionedPage'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
156
157 $versionArray['state_Workspace'] = $recordState;
158
159 if ($filterTxt == '' || $this->isFilterTextInVisibleColumns($filterTxt, $versionArray)) {
160 $this->dataArray[] = $versionArray;
161 }
162 }
163 }
164 $this->sortDataArray();
165
166 $this->setDataArrayIntoCache($versions, $filterTxt);
167 }
168 $this->sortDataArray();
169 }
170
171 /**
172 * Gets the data array by considering the page to be shown in the grid view.
173 *
174 * @param integer $start
175 * @param integer $limit
176 * @return array
177 */
178 protected function getDataArray($start, $limit) {
179 $dataArrayPart = array();
180 $end = $start + $limit < count($this->dataArray) ? $start + $limit : count($this->dataArray);
181
182 for ($i = $start; $i < $end; $i++) {
183 $dataArrayPart[] = $this->dataArray[$i];
184 }
185
186 return $dataArrayPart;
187 }
188
189
190 /**
191 * Initialize the workspace cache
192 *
193 * @return void
194 */
195 protected function initializeWorkspacesCachingFramework() {
196 $this->workspacesCache = $GLOBALS['typo3CacheManager']->getCache('workspaces_cache');
197 }
198
199
200 /**
201 * Put the generated dataArray into the workspace cache.
202 *
203 * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid, t3ver_oid and t3ver_swapmode fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid"
204 * @param string $filterTxt The given filter text from the grid.
205 */
206 protected function setDataArrayIntoCache (array $versions, $filterTxt) {
207 if (TYPO3_UseCachingFramework === TRUE) {
208 $hash = $this->calculateHash($versions, $filterTxt);
209 $this->workspacesCache->set($hash, $this->dataArray, array($this->currentWorkspace));
210 }
211 }
212
213
214 /**
215 * Checks if a cache entry is given for given versions and filter text and tries to load the data array from cache.
216 *
217 * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid, t3ver_oid and t3ver_swapmode fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid"
218 * @param string $filterTxt The given filter text from the grid.
219 * @return boolean TRUE if cache entry was successfully fetched from cache and content put to $this->dataArray
220 */
221 protected function getDataArrayFromCache (array $versions, $filterTxt) {
222 $cacheEntry = FALSE;
223
224 if (TYPO3_UseCachingFramework === TRUE) {
225 $hash = $this->calculateHash($versions, $filterTxt);
226
227 $content = $this->workspacesCache->get($hash);
228
229 if ($content !== FALSE) {
230 $this->dataArray = $content;
231 $cacheEntry = TRUE;
232 }
233 }
234
235 return $cacheEntry;
236 }
237
238 /**
239 * Calculate 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.
240 *
241 * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid, t3ver_oid and t3ver_swapmode fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid"
242 * @param string $filterTxt The given filter text from the grid.
243 * @return string
244 */
245 protected function calculateHash (array $versions, $filterTxt) {
246 $hashArray = array(
247 $GLOBALS['BE_USER']->workspace,
248 $GLOBALS['BE_USER']->user['uid'],
249 $versions,
250 $filterTxt,
251 $this->sort,
252 $this->sortDir,
253 $this->currentWorkspace);
254 $hash = md5(serialize($hashArray));
255
256 return $hash;
257 }
258
259
260 /**
261 * Performs sorting on the data array accordant to the
262 * selected column in the grid view to be used for sorting.
263 *
264 * @return void
265 */
266 protected function sortDataArray() {
267 if (is_array($this->dataArray)) {
268 switch ($this->sort) {
269 case 'uid';
270 case 'change';
271 case 'workspace_Tstamp';
272 case 't3ver_oid';
273 case 'liveid';
274 case 'livepid':
275 usort($this->dataArray, array($this, 'intSort'));
276 break;
277 case 'label_Workspace';
278 case 'label_Live';
279 case 'label_Stage';
280 case 'workspace_Title';
281 case 'path_Live':
282 // case 'path_Workspace': This is the first sorting attribute
283 usort($this->dataArray, array($this, 'stringSort'));
284 break;
285 }
286 } else {
287 t3lib_div::sysLog('Try to sort "' . $this->sort . '" in "Tx_Workspaces_Service_GridData::sortDataArray" but $this->dataArray is empty! This might be the Bug #26422 which could not reproduced yet.', 3);
288 }
289 }
290
291 /**
292 * Implements individual sorting for columns based on integer comparison.
293 *
294 * @param array $a
295 * @param array $b
296 * @return integer
297 */
298 protected function intSort(array $a, array $b) {
299 // Als erstes nach dem Pfad sortieren
300 $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
301
302 if ($path_cmp < 0) {
303 return $path_cmp;
304 } elseif ($path_cmp == 0) {
305 if ($a[$this->sort] == $b[$this->sort]) {
306 return 0;
307 }
308 if ($this->sortDir == 'ASC') {
309 return ($a[$this->sort] < $b[$this->sort]) ? -1 : 1;
310 } elseif ($this->sortDir == 'DESC') {
311 return ($a[$this->sort] > $b[$this->sort]) ? -1 : 1;
312 }
313 } elseif ($path_cmp > 0) {
314 return $path_cmp;
315 }
316 return 0; //ToDo: Throw Exception
317 }
318
319 /**
320 * Implements individual sorting for columns based on string comparison.
321 *
322 * @param $a
323 * @param $b
324 * @return int
325 */
326 protected function stringSort($a, $b) {
327 $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
328
329 if ($path_cmp < 0) {
330 return $path_cmp;
331 } elseif ($path_cmp == 0) {
332 if ($a[$this->sort] == $b[$this->sort]) {
333 return 0;
334 }
335 if ($this->sortDir == 'ASC') {
336 return (strcasecmp($a[$this->sort], $b[$this->sort]));
337 } elseif ($this->sortDir == 'DESC') {
338 return (strcasecmp($a[$this->sort], $b[$this->sort]) * (-1));
339 }
340 } elseif ($path_cmp > 0) {
341 return $path_cmp;
342 }
343 return 0; //ToDo: Throw Exception
344 }
345
346 /**
347 * Determines whether the text used to filter the results is part of
348 * a column that is visible in the grid view.
349 *
350 * @param string $filterText
351 * @param array $versionArray
352 * @return boolean
353 */
354 protected function isFilterTextInVisibleColumns($filterText, array $versionArray) {
355 if (is_array($GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'])) {
356 foreach ($GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'] as $column => $value) {
357 if (isset($value['hidden']) && isset($column) && isset($versionArray[$column])) {
358 if ($value['hidden'] == 0) {
359 switch ($column) {
360 case 'workspace_Tstamp':
361 if (stripos($versionArray['workspace_Formated_Tstamp'], $filterText) !== FALSE) {
362 return TRUE;
363 }
364 break;
365 case 'change':
366 if (stripos(strval($versionArray[$column]), str_replace('%', '', $filterText)) !== FALSE) {
367 return TRUE;
368 }
369 break;
370 default:
371 if (stripos(strval($versionArray[$column]), $filterText) !== FALSE) {
372 return TRUE;
373 }
374 }
375 }
376 }
377 }
378 }
379 return FALSE;
380 }
381
382 /**
383 * Calculates the percentage of changes between two records.
384 *
385 * @param string $table
386 * @param array $diffRecordOne
387 * @param array $diffRecordTwo
388 * @return integer
389 */
390 public function calculateChangePercentage($table, array $diffRecordOne, array $diffRecordTwo) {
391
392 // Initialize:
393 $changePercentage = 0;
394 $changePercentageArray = array();
395
396 // Check that records are arrays:
397 if (is_array($diffRecordOne) && is_array($diffRecordTwo)) {
398
399 // Load full table description
400 t3lib_div::loadTCA($table);
401
402 $similarityPercentage = 0;
403
404 // Traversing the first record and process all fields which are editable:
405 foreach ($diffRecordOne as $fieldName => $fieldValue) {
406 if ($GLOBALS['TCA'][$table]['columns'][$fieldName] && $GLOBALS['TCA'][$table]['columns'][$fieldName]['config']['type'] != 'passthrough' && !t3lib_div::inList('t3ver_label', $fieldName)) {
407
408 if (strcmp(trim($diffRecordOne[$fieldName]), trim($diffRecordTwo[$fieldName]))
409 && $GLOBALS['TCA'][$table]['columns'][$fieldName]['config']['type'] == 'group'
410 && $GLOBALS['TCA'][$table]['columns'][$fieldName]['config']['internal_type'] == 'file'
411 ) {
412
413 // Initialize:
414 $uploadFolder = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config']['uploadfolder'];
415 $files1 = array_flip(t3lib_div::trimExplode(',', $diffRecordOne[$fieldName], 1));
416 $files2 = array_flip(t3lib_div::trimExplode(',', $diffRecordTwo[$fieldName], 1));
417
418 // Traverse filenames and read their md5 sum:
419 foreach ($files1 as $filename => $tmp) {
420 $files1[$filename] = @is_file(PATH_site . $uploadFolder . '/' . $filename) ? md5(t3lib_div::getUrl(PATH_site . $uploadFolder . '/' . $filename)) : $filename;
421 }
422 foreach ($files2 as $filename => $tmp) {
423 $files2[$filename] = @is_file(PATH_site . $uploadFolder . '/' . $filename) ? md5(t3lib_div::getUrl(PATH_site . $uploadFolder . '/' . $filename)) : $filename;
424 }
425
426 // Implode MD5 sums and set flag:
427 $diffRecordOne[$fieldName] = implode(' ', $files1);
428 $diffRecordTwo[$fieldName] = implode(' ', $files2);
429 }
430
431 // If there is a change of value:
432 if (strcmp(trim($diffRecordOne[$fieldName]), trim($diffRecordTwo[$fieldName]))) {
433 // Get the best visual presentation of the value to calculate differences:
434 $val1 = t3lib_BEfunc::getProcessedValue($table, $fieldName, $diffRecordOne[$fieldName], 0, 1);
435 $val2 = t3lib_BEfunc::getProcessedValue($table, $fieldName, $diffRecordTwo[$fieldName], 0, 1);
436
437 similar_text($val1, $val2, $similarityPercentage);
438 $changePercentageArray[] = $similarityPercentage > 0 ? abs($similarityPercentage - 100) : 0;
439 }
440 }
441 }
442
443 // Calculate final change percentage:
444 if (is_array($changePercentageArray)) {
445 $sumPctChange = 0;
446 foreach ($changePercentageArray as $singlePctChange) {
447 $sumPctChange += $singlePctChange;
448 }
449 count($changePercentageArray) > 0 ? $changePercentage = round($sumPctChange / count($changePercentageArray)) : $changePercentage = 0;
450 }
451
452 }
453 return $changePercentage;
454 }
455
456 /**
457 * Gets the state of a given state value.
458 *
459 * @param integer stateId of offline record
460 * @param boolean hidden flag of online record
461 * @param boolean hidden flag of offline record
462 * @return string
463 */
464 protected function workspaceState($stateId, $hiddenOnline = FALSE, $hiddenOffline = FALSE) {
465 switch ($stateId) {
466 case -1:
467 $state = 'new';
468 break;
469 case 1:
470 case 2:
471 $state = 'deleted';
472 break;
473 case 4:
474 $state = 'moved';
475 break;
476 default:
477 $state = 'modified';
478 }
479
480 if ($hiddenOnline == 0 && $hiddenOffline == 1) {
481 $state = 'hidden';
482 } elseif ($hiddenOnline == 1 && $hiddenOffline == 0) {
483 $state = 'unhidden';
484 }
485
486 return $state;
487 }
488 }
489
490
491 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/GridData.php'])) {
492 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/GridData.php']);
493 }
494 ?>