TYPO3 API  SVNRelease
GridData.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 2010-2011 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *  A copy is found in the textfile GPL.txt and important notices to the license
00017  *  from the author is found in LICENSE.txt distributed with these scripts.
00018  *
00019  *
00020  *  This script is distributed in the hope that it will be useful,
00021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  *  GNU General Public License for more details.
00024  *
00025  *  This copyright notice MUST APPEAR in all copies of the script!
00026  ***************************************************************/
00027 
00028 /**
00029  * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
00030  * @package Workspaces
00031  * @subpackage Service
00032  */
00033 class tx_Workspaces_Service_GridData {
00034     protected $currentWorkspace = NULL;
00035     protected $dataArray = array();
00036     protected $sort = '';
00037     protected $sortDir = '';
00038     protected $workspacesCache = NULL;
00039 
00040     /**
00041      * Generates grid list array from given versions.
00042      *
00043      * @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"
00044      * @param object $parameter
00045      * @return array
00046      * @throws InvalidArgumentException
00047      */
00048     public function generateGridListFromVersions($versions, $parameter, $currentWorkspace) {
00049 
00050             // Read the given parameters from grid. If the parameter is not set use default values.
00051         $filterTxt = isset($parameter->filterTxt) ? $parameter->filterTxt : '';
00052         $start = isset($parameter->start) ? intval($parameter->start) : 0;
00053         $limit = isset($parameter->limit) ? intval($parameter->limit) : 10;
00054         $this->sort = isset($parameter->sort) ? $parameter->sort : 't3ver_oid';
00055         $this->sortDir = isset($parameter->dir) ? $parameter->dir : 'ASC';
00056 
00057         if (is_int($currentWorkspace)) {
00058             $this->currentWorkspace = $currentWorkspace;
00059         } else {
00060             throw new InvalidArgumentException('No such workspace defined');
00061         }
00062 
00063         $data = array();
00064         $data['data'] = array();
00065 
00066         $this->generateDataArray($versions, $filterTxt);
00067 
00068         $data['total'] = count($this->dataArray);
00069         $data['data'] = $this->getDataArray($start, $limit);
00070 
00071         return $data;
00072     }
00073 
00074     /**
00075      * Generates grid list array from given versions.
00076      *
00077      * @param array $versions
00078      * @param string $filterTxt
00079      * @return void
00080      */
00081     protected function generateDataArray(array $versions, $filterTxt) {
00082         /** @var $stagesObj Tx_Workspaces_Service_Stages */
00083         $stagesObj = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
00084 
00085         /** @var $workspacesObj Tx_Workspaces_Service_Workspaces */
00086         $workspacesObj = t3lib_div::makeInstance('Tx_Workspaces_Service_Workspaces');
00087         $availableWorkspaces = $workspacesObj->getAvailableWorkspaces();
00088 
00089         $workspaceAccess = $GLOBALS['BE_USER']->checkWorkspace($GLOBALS['BE_USER']->workspace);
00090         $swapStage = ($workspaceAccess['publish_access'] & 1) ? Tx_Workspaces_Service_Stages::STAGE_PUBLISH_ID : 0;
00091         $swapAccess =  $GLOBALS['BE_USER']->workspacePublishAccess($GLOBALS['BE_USER']->workspace) &&
00092                        $GLOBALS['BE_USER']->workspaceSwapAccess();
00093 
00094         $this->initializeWorkspacesCachingFramework();
00095 
00096         // check for dataArray in cache
00097         if ($this->getDataArrayFromCache($versions, $filterTxt) == FALSE) {
00098             $stagesObj = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
00099 
00100             foreach ($versions as $table => $records) {
00101                 $versionArray = array('table' => $table);
00102 
00103                 foreach ($records as $record) {
00104 
00105                     $origRecord = t3lib_BEFunc::getRecord($table, $record['t3ver_oid']);
00106                     $versionRecord = t3lib_BEFunc::getRecord($table, $record['uid']);
00107 
00108                     if (isset($GLOBALS['TCA'][$table]['columns']['hidden'])) {
00109                         $recordState = $this->workspaceState($versionRecord['t3ver_state'], $origRecord['hidden'], $versionRecord['hidden']);
00110                     } else {
00111                         $recordState = $this->workspaceState($versionRecord['t3ver_state']);
00112                     }
00113                     $isDeletedPage = ($table == 'pages' && $recordState == 'deleted');
00114                     $viewUrl =  tx_Workspaces_Service_Workspaces::viewSingleRecord($table, $record['t3ver_oid'], $origRecord);
00115 
00116                     $pctChange = $this->calculateChangePercentage($table, $origRecord, $versionRecord);
00117                     $versionArray['uid'] = $record['uid'];
00118                     $versionArray['workspace'] = $versionRecord['t3ver_id'];
00119                     $versionArray['label_Workspace'] = htmlspecialchars($versionRecord[$GLOBALS['TCA'][$table]['ctrl']['label']]);
00120                     $versionArray['label_Live'] = htmlspecialchars($origRecord[$GLOBALS['TCA'][$table]['ctrl']['label']]);
00121                     $versionArray['label_Stage'] = htmlspecialchars($stagesObj->getStageTitle($versionRecord['t3ver_stage']));
00122                     $versionArray['change'] = $pctChange;
00123                     $versionArray['path_Live'] = htmlspecialchars(t3lib_BEfunc::getRecordPath($record['livepid'], '', 999));
00124                     $versionArray['path_Workspace'] = htmlspecialchars(t3lib_BEfunc::getRecordPath($record['wspid'], '', 999));
00125                     $versionArray['workspace_Title'] = htmlspecialchars(tx_Workspaces_Service_Workspaces::getWorkspaceTitle($versionRecord['t3ver_wsid']));
00126 
00127                     $versionArray['workspace_Tstamp'] = $versionRecord['tstamp'];
00128                     $versionArray['workspace_Formated_Tstamp'] = t3lib_BEfunc::datetime($versionRecord['tstamp']);
00129                     $versionArray['t3ver_oid'] = $record['t3ver_oid'];
00130                     $versionArray['livepid'] = $record['livepid'];
00131                     $versionArray['stage'] = $versionRecord['t3ver_stage'];
00132                     $versionArray['icon_Live'] = t3lib_iconWorks::mapRecordTypeToSpriteIconClass($table, $origRecord);
00133                     $versionArray['icon_Workspace'] = t3lib_iconWorks::mapRecordTypeToSpriteIconClass($table, $versionRecord);
00134 
00135                     $versionArray['allowedAction_nextStage'] = $stagesObj->isNextStageAllowedForUser($versionRecord['t3ver_stage']);
00136                     $versionArray['allowedAction_prevStage'] = $stagesObj->isPrevStageAllowedForUser($versionRecord['t3ver_stage']);
00137 
00138                     if ($swapAccess && $swapStage != 0 && $versionRecord['t3ver_stage'] == $swapStage) {
00139                         $versionArray['allowedAction_swap'] = $stagesObj->isNextStageAllowedForUser($swapStage);
00140                     } else if ($swapAccess && $swapStage == 0) {
00141                         $versionArray['allowedAction_swap'] = TRUE;
00142                     } else {
00143                         $versionArray['allowedAction_swap'] = FALSE;
00144                     }
00145                     $versionArray['allowedAction_delete'] = TRUE;
00146                         // preview and editing of a deleted page won't work ;)
00147                     $versionArray['allowedAction_view'] = !$isDeletedPage && $viewUrl;
00148                     $versionArray['allowedAction_edit'] = !$isDeletedPage;
00149                     $versionArray['allowedAction_editVersionedPage'] = !$isDeletedPage;
00150 
00151                     $versionArray['state_Workspace'] = $recordState;
00152 
00153                     if ($filterTxt == '' || $this->isFilterTextInVisibleColumns($filterTxt, $versionArray)) {
00154                         $this->dataArray[] = $versionArray;
00155                     }
00156                 }
00157             }
00158             $this->sortDataArray();
00159 
00160             $this->setDataArrayIntoCache($versions, $filterTxt);
00161         }
00162         $this->sortDataArray();
00163     }
00164 
00165     /**
00166      * Gets the data array by considering the page to be shown in the grid view.
00167      *
00168      * @param integer $start
00169      * @param integer $limit
00170      * @return array
00171      */
00172     protected function getDataArray($start, $limit) {
00173         $dataArrayPart = array();
00174         $end = $start + $limit < count($this->dataArray) ? $start + $limit : count($this->dataArray);
00175 
00176         for ($i = $start; $i < $end; $i++) {
00177             $dataArrayPart[] = $this->dataArray[$i];
00178         }
00179 
00180         return $dataArrayPart;
00181     }
00182 
00183 
00184     /**
00185      * Initialize the workspace cache
00186      *
00187      * @return void
00188      */
00189     protected function initializeWorkspacesCachingFramework() {
00190         if (TYPO3_UseCachingFramework === TRUE) {
00191             try {
00192                 $GLOBALS['typo3CacheFactory']->create(
00193                     'workspaces_cache',
00194                     't3lib_cache_frontend_StringFrontend',
00195                     $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['sys_workspace_cache']['backend'],
00196                     $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['sys_workspace_cache']['options']);
00197             } catch (t3lib_cache_exception_DuplicateIdentifier $e) {
00198                 // do nothing, a workspace cache already exists
00199             }
00200 
00201             $this->workspacesCache = $GLOBALS['typo3CacheManager']->getCache('workspaces_cache');
00202         }
00203     }
00204 
00205 
00206     /**
00207      * Put the generated dataArray into the workspace cache.
00208      *
00209      * @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"
00210      * @param string $filterTxt The given filter text from the grid.
00211      */
00212     protected function setDataArrayIntoCache (array $versions, $filterTxt) {
00213         if (TYPO3_UseCachingFramework === TRUE) {
00214             $hash = $this->calculateHash($versions, $filterTxt);
00215             $content = serialize($this->dataArray);
00216 
00217             $this->workspacesCache->set($hash, $content, array($this->currentWorkspace));
00218         }
00219     }
00220 
00221 
00222     /**
00223      * Checks if a cache entry is given for given versions and filter text and tries to load the data array from cache.
00224      *
00225      * @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"
00226      * @param string $filterTxt The given filter text from the grid.
00227      */
00228     protected function getDataArrayFromCache (array $versions, $filterTxt) {
00229         $cacheEntry = FALSE;
00230 
00231         if (TYPO3_UseCachingFramework === TRUE) {
00232             $hash = $this->calculateHash($versions, $filterTxt);
00233 
00234             $content = $this->workspacesCache->get($hash);
00235 
00236             if ($content != FALSE) {
00237                 $this->dataArray = unserialize($content);
00238                 $cacheEntry = TRUE;
00239             }
00240         }
00241 
00242         return $cacheEntry;
00243     }
00244 
00245     /**
00246      * 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.
00247      *
00248      * @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"
00249      * @param string $filterTxt The given filter text from the grid.
00250      */
00251     protected function calculateHash (array $versions, $filterTxt) {
00252         $hashArray = array(
00253             $GLOBALS['BE_USER']->workspace,
00254             $GLOBALS['BE_USER']->user['uid'],
00255             $versions,
00256             $filterTxt,
00257             $this->sort,
00258             $this->sortDir,
00259             $this->currentWorkspace);
00260         $hash = md5(serialize($hashArray));
00261 
00262         return $hash;
00263     }
00264 
00265 
00266     /**
00267      * Performs sorting on the data array accordant to the
00268      * selected column in the grid view to be used for sorting.
00269      *
00270      * @return void
00271      */
00272     protected function sortDataArray() {
00273         switch ($this->sort) {
00274             case 'uid';
00275             case 'change';
00276             case 'workspace_Tstamp';
00277             case 't3ver_oid';
00278             case 'liveid';
00279             case 'livepid':
00280                 usort($this->dataArray, array($this, 'intSort'));
00281                 break;
00282             case 'label_Workspace';
00283             case 'label_Live';
00284             case 'label_Stage';
00285             case 'workspace_Title';
00286             case 'path_Live':
00287                     // case 'path_Workspace': This is the first sorting attribute
00288                 usort($this->dataArray, array($this, 'stringSort'));
00289                 break;
00290         }
00291     }
00292 
00293     /**
00294      * Implements individual sorting for columns based on integer comparison.
00295      *
00296      * @param array $a
00297      * @param array $b
00298      * @return integer
00299      */
00300     protected function intSort(array $a, array $b) {
00301             // Als erstes nach dem Pfad sortieren
00302         $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
00303 
00304         if ($path_cmp < 0) {
00305             return $path_cmp;
00306         } elseif ($path_cmp == 0) {
00307             if ($a[$this->sort] == $b[$this->sort]) {
00308                 return 0;
00309             }
00310             if ($this->sortDir == 'ASC') {
00311                 return ($a[$this->sort] < $b[$this->sort]) ? -1 : 1;
00312             } elseif ($this->sortDir == 'DESC') {
00313                 return ($a[$this->sort] > $b[$this->sort]) ? -1 : 1;
00314             }
00315         } elseif ($path_cmp > 0) {
00316             return $path_cmp;
00317         }
00318         return 0; //ToDo: Throw Exception
00319     }
00320 
00321     /**
00322      * Implements individual sorting for columns based on string comparison.
00323      *
00324      * @param  $a
00325      * @param  $b
00326      * @return int
00327      */
00328     protected function stringSort($a, $b) {
00329         $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
00330 
00331         if ($path_cmp < 0) {
00332             return $path_cmp;
00333         } elseif ($path_cmp == 0) {
00334             if ($a[$this->sort] == $b[$this->sort]) {
00335                 return 0;
00336             }
00337             if ($this->sortDir == 'ASC') {
00338                 return (strcasecmp($a[$this->sort], $b[$this->sort]));
00339             } elseif ($this->sortDir == 'DESC') {
00340                 return (strcasecmp($a[$this->sort], $b[$this->sort]) * (-1));
00341             }
00342         } elseif ($path_cmp > 0) {
00343             return $path_cmp;
00344         }
00345         return 0; //ToDo: Throw Exception
00346     }
00347 
00348     /**
00349      * Determines whether the text used to filter the results is part of
00350      * a column that is visible in the grid view.
00351      *
00352      * @param string $filterText
00353      * @param array $versionArray
00354      * @return boolean
00355      */
00356     protected function isFilterTextInVisibleColumns($filterText, array $versionArray) {
00357         if (is_array($GLOBALS['BE_USER']->uc['moduleData']['Workspaces']['columns'])) {
00358             foreach ($GLOBALS['BE_USER']->uc['moduleData']['Workspaces']['columns'] as $column => $value) {
00359                 if (isset($value['hidden']) && isset($column) && isset($versionArray[$column])) {
00360                     if ($value['hidden'] == 0) {
00361                         switch ($column) {
00362                             case 'workspace_Tstamp':
00363                                 if (stripos($versionArray['workspace_Formated_Tstamp'], $filterText) !== FALSE) {
00364                                     return TRUE;
00365                                 }
00366                                 break;
00367                             case 'change':
00368                                 if (stripos(strval($versionArray[$column]), str_replace('%', '', $filterText)) !== FALSE) {
00369                                     return TRUE;
00370                                 }
00371                                 break;
00372                             default:
00373                                 if (stripos(strval($versionArray[$column]), $filterText) !== FALSE) {
00374                                     return TRUE;
00375                                 }
00376                         }
00377                     }
00378                 }
00379             }
00380         }
00381         return FALSE;
00382     }
00383 
00384     /**
00385      * Calculates the percentage of changes between two records.
00386      *
00387      * @param string $table
00388      * @param array $diffRecordOne
00389      * @param array $diffRecordTwo
00390      * @return integer
00391      */
00392     public function calculateChangePercentage($table, array $diffRecordOne, array $diffRecordTwo) {
00393         global $TCA;
00394 
00395             // Initialize:
00396         $changePercentage = 0;
00397         $changePercentageArray = array();
00398 
00399             // Check that records are arrays:
00400         if (is_array($diffRecordOne) && is_array($diffRecordTwo)) {
00401 
00402                 // Load full table description
00403             t3lib_div::loadTCA($table);
00404 
00405             $similarityPercentage = 0;
00406 
00407                 // Traversing the first record and process all fields which are editable:
00408             foreach ($diffRecordOne as $fieldName => $fieldValue) {
00409                 if ($TCA[$table]['columns'][$fieldName] && $TCA[$table]['columns'][$fieldName]['config']['type'] != 'passthrough' && !t3lib_div::inList('t3ver_label', $fieldName)) {
00410 
00411                     if (strcmp(trim($diffRecordOne[$fieldName]), trim($diffRecordTwo[$fieldName]))
00412                             && $TCA[$table]['columns'][$fieldName]['config']['type'] == 'group'
00413                             && $TCA[$table]['columns'][$fieldName]['config']['internal_type'] == 'file'
00414                     ) {
00415 
00416                             // Initialize:
00417                         $uploadFolder = $TCA[$table]['columns'][$fieldName]['config']['uploadfolder'];
00418                         $files1 = array_flip(t3lib_div::trimExplode(',', $diffRecordOne[$fieldName], 1));
00419                         $files2 = array_flip(t3lib_div::trimExplode(',', $diffRecordTwo[$fieldName], 1));
00420 
00421                             // Traverse filenames and read their md5 sum:
00422                         foreach ($files1 as $filename => $tmp) {
00423                             $files1[$filename] = @is_file(PATH_site . $uploadFolder . '/' . $filename) ? md5(t3lib_div::getUrl(PATH_site . $uploadFolder . '/' . $filename)) : $filename;
00424                         }
00425                         foreach ($files2 as $filename => $tmp) {
00426                             $files2[$filename] = @is_file(PATH_site . $uploadFolder . '/' . $filename) ? md5(t3lib_div::getUrl(PATH_site . $uploadFolder . '/' . $filename)) : $filename;
00427                         }
00428 
00429                             // Implode MD5 sums and set flag:
00430                         $diffRecordOne[$fieldName] = implode(' ', $files1);
00431                         $diffRecordTwo[$fieldName] = implode(' ', $files2);
00432                     }
00433 
00434                         // If there is a change of value:
00435                     if (strcmp(trim($diffRecordOne[$fieldName]), trim($diffRecordTwo[$fieldName]))) {
00436                             // Get the best visual presentation of the value to calculate differences:
00437                         $val1 = t3lib_BEfunc::getProcessedValue($table, $fieldName, $diffRecordOne[$fieldName], 0, 1);
00438                         $val2 = t3lib_BEfunc::getProcessedValue($table, $fieldName, $diffRecordTwo[$fieldName], 0, 1);
00439 
00440                         similar_text($val1, $val2, $similarityPercentage);
00441                         $changePercentageArray[] = $similarityPercentage > 0 ? abs($similarityPercentage - 100) : 0;
00442                     }
00443                 }
00444             }
00445 
00446                 // Calculate final change percentage:
00447             if (is_array($changePercentageArray)) {
00448                 $sumPctChange = 0;
00449                 foreach ($changePercentageArray as $singlePctChange) {
00450                     $sumPctChange += $singlePctChange;
00451                 }
00452                 count($changePercentageArray) > 0 ? $changePercentage = round($sumPctChange / count($changePercentageArray)) : $changePercentage = 0;
00453             }
00454 
00455         }
00456         return $changePercentage;
00457     }
00458 
00459     /**
00460      * Gets the state of a given state value.
00461      *
00462      * @param   integer stateId of offline record
00463      * @param   boolean hidden flag of online record
00464      * @param   boolean hidden flag of offline record
00465      * @return  string
00466      */
00467      protected function workspaceState($stateId, $hiddenOnline = FALSE, $hiddenOffline = FALSE) {
00468         switch ($stateId) {
00469             case -1:
00470                 $state = 'new';
00471                 break;
00472             case 1:
00473             case 2:
00474                 $state = 'deleted';
00475                 break;
00476             case 4:
00477                 $state = 'moved';
00478                 break;
00479             default:
00480                 $state = 'modified';
00481         }
00482 
00483         if ($hiddenOnline == 0 && $hiddenOffline == 1) {
00484             $state = 'hidden';
00485         } elseif ($hiddenOnline == 1 && $hiddenOffline == 0) {
00486             $state = 'unhidden';
00487         }
00488 
00489         return $state;
00490     }
00491 }
00492 
00493 
00494 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/GridData.php'])) {
00495     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/GridData.php']);
00496 }
00497 ?>