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