TYPO3 API  SVNRelease
Workspaces.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_Workspaces {
00034     const TABLE_WORKSPACE = 'sys_workspace';
00035     const SELECT_ALL_WORKSPACES = -98;
00036     const LIVE_WORKSPACE_ID = 0;
00037     const DRAFT_WORKSPACE_ID = -1;
00038 
00039     /**
00040      * retrieves the available workspaces from the database and checks whether
00041      * they're available to the current BE user
00042      *
00043      * @return  array   array of worspaces available to the current user
00044      */
00045     public function getAvailableWorkspaces() {
00046         $availableWorkspaces = array();
00047 
00048             // add default workspaces
00049         if ($GLOBALS['BE_USER']->checkWorkspace(array('uid' => (string) self::LIVE_WORKSPACE_ID))) {
00050             $availableWorkspaces[self::LIVE_WORKSPACE_ID] = self::getWorkspaceTitle(self::LIVE_WORKSPACE_ID);
00051         }
00052         if ($GLOBALS['BE_USER']->checkWorkspace(array('uid' => (string) self::DRAFT_WORKSPACE_ID))) {
00053             $availableWorkspaces[self::DRAFT_WORKSPACE_ID] = self::getWorkspaceTitle(self::DRAFT_WORKSPACE_ID);
00054         }
00055 
00056             // add custom workspaces (selecting all, filtering by BE_USER check):
00057         $customWorkspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, title, adminusers, members', 'sys_workspace', 'pid = 0' . t3lib_BEfunc::deleteClause('sys_workspace'), '', 'title');
00058         if (count($customWorkspaces)) {
00059             foreach ($customWorkspaces as $workspace) {
00060                 if ($GLOBALS['BE_USER']->checkWorkspace($workspace)) {
00061                     $availableWorkspaces[$workspace['uid']] = htmlspecialchars($workspace['title']);
00062                 }
00063             }
00064         }
00065 
00066         return $availableWorkspaces;
00067     }
00068 
00069 
00070     /**
00071      * Find the title for the requested workspace.
00072      *
00073      * @param integer $wsId
00074      * @return string
00075      */
00076     public static function getWorkspaceTitle($wsId) {
00077         $title = FALSE;
00078         switch ($wsId) {
00079             case self::LIVE_WORKSPACE_ID:
00080                 $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:shortcut_onlineWS');
00081                 break;
00082             case self::DRAFT_WORKSPACE_ID:
00083                 $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:shortcut_offlineWS');
00084                 break;
00085             default:
00086                 $labelField = $GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
00087                 $wsRecord = t3lib_beFunc::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
00088                 if (is_array($wsRecord)) {
00089                     $title = $wsRecord[$labelField];
00090                 }
00091         }
00092 
00093         if ($title === FALSE) {
00094             throw new InvalidArgumentException('No such workspace defined');
00095         }
00096 
00097         return $title;
00098     }
00099 
00100 
00101     /**
00102      * Building tcemain CMD-array for swapping all versions in a workspace.
00103      *
00104      * @param   integer     Real workspace ID, cannot be ONLINE (zero).
00105      * @param   boolean     If set, then the currently online versions are swapped into the workspace in exchange for the offline versions. Otherwise the workspace is emptied.
00106      * @param   integer     $pageId: ...
00107      * @return  array       Command array for tcemain
00108      */
00109     public function getCmdArrayForPublishWS($wsid, $doSwap, $pageId = 0) {
00110 
00111         $wsid = intval($wsid);
00112         $cmd = array();
00113 
00114         if ($wsid >= -1 && $wsid!==0) {
00115 
00116                 // Define stage to select:
00117             $stage = -99;
00118             if ($wsid > 0) {
00119                 $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $wsid);
00120                 if ($workspaceRec['publish_access'] & 1) {
00121                     $stage = Tx_Workspaces_Service_Stages::STAGE_PUBLISH_ID;
00122                 }
00123             }
00124 
00125                 // Select all versions to swap:
00126             $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, ($pageId ? $pageId : -1));
00127 
00128                 // Traverse the selection to build CMD array:
00129             foreach ($versions as $table => $records) {
00130                 foreach ($records as $rec) {
00131                         // Build the cmd Array:
00132                     $cmd[$table][$rec['t3ver_oid']]['version'] = array('action' => 'swap', 'swapWith' => $rec['uid'], 'swapIntoWS' => $doSwap ? 1 : 0);
00133                 }
00134             }
00135         }
00136         return $cmd;
00137     }
00138 
00139 
00140     /**
00141      * Building tcemain CMD-array for releasing all versions in a workspace.
00142      *
00143      * @param   integer     Real workspace ID, cannot be ONLINE (zero).
00144      * @param   boolean     Run Flush (true) or ClearWSID (false) command
00145      * @param   integer     $pageId: ...
00146      * @return  array       Command array for tcemain
00147      */
00148     public function getCmdArrayForFlushWS($wsid, $flush = TRUE, $pageId = 0) {
00149 
00150         $wsid = intval($wsid);
00151         $cmd = array();
00152 
00153         if ($wsid >= -1 && $wsid!==0) {
00154                 // Define stage to select:
00155             $stage = -99;
00156 
00157                 // Select all versions to swap:
00158             $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, ($pageId ? $pageId : -1));
00159 
00160                 // Traverse the selection to build CMD array:
00161             foreach ($versions as $table => $records) {
00162                 foreach ($records as $rec) {
00163                     // Build the cmd Array:
00164                     $cmd[$table][$rec['uid']]['version'] = array('action' => ($flush ? 'flush' : 'clearWSID'));
00165                 }
00166             }
00167         }
00168         return $cmd;
00169     }
00170 
00171 
00172     /**
00173      * Select all records from workspace pending for publishing
00174      * Used from backend to display workspace overview
00175      * User for auto-publishing for selecting versions for publication
00176      *
00177      * @param   integer     Workspace ID. If -99, will select ALL versions from ANY workspace. If -98 will select all but ONLINE. >=-1 will select from the actual workspace
00178      * @param   integer     Lifecycle filter: 1 = select all drafts (never-published), 2 = select all published one or more times (archive/multiple), anything else selects all.
00179      * @param   integer     Stage filter: -99 means no filtering, otherwise it will be used to select only elements with that stage. For publishing, that would be "10"
00180      * @param   integer     Page id: Live page for which to find versions in workspace!
00181      * @param   integer     Recursion Level - select versions recursive - parameter is only relevant if $pageId != -1
00182      * @return  array       Array of 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"
00183      */
00184     public function selectVersionsInWorkspace($wsid, $filter = 0, $stage = -99, $pageId = -1, $recursionLevel = 0) {
00185 
00186         $wsid = intval($wsid);
00187         $filter = intval($filter);
00188         $output = array();
00189 
00190             // Contains either nothing or a list with live-uids
00191         if ($pageId != -1 && $recursionLevel > 0) {
00192             $pageList = $this->getTreeUids($pageId, $wsid, $recursionLevel);
00193         } else if ($pageId != -1) {
00194             $pageList = $pageId;
00195         } else {
00196             $pageList = '';
00197         }
00198 
00199             // Traversing all tables supporting versioning:
00200         foreach ($GLOBALS['TCA'] as $table => $cfg) {
00201             if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
00202 
00203                 $recs = $this->selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage);
00204                 if (intval($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) === 2) {
00205                     $moveRecs = $this->getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage);
00206                     $recs = array_merge($recs, $moveRecs);
00207                 }
00208                 $recs = $this->filterPermittedElements($recs, $table);
00209                 if (count($recs)) {
00210                     $output[$table] = $recs;
00211                 }
00212             }
00213         }
00214         return $output;
00215     }
00216 
00217     /**
00218      * Find all versionized elements except moved records.
00219      *
00220      * @param string $table
00221      * @param string $pageList
00222      * @param integer $filter
00223      * @param integer $stage
00224      * @return array
00225      */
00226     protected function selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage) {
00227 
00228         $fields = 'A.uid, A.t3ver_oid,' . ($table==='pages' ? ' A.t3ver_swapmode,' : '') . 'B.pid AS wspid, B.pid AS livepid';
00229         $from = $table . ' A,' . $table . ' B';
00230 
00231             // Table A is the offline version and pid=-1 defines offline
00232         $where = 'A.pid=-1 AND A.t3ver_state!=4';
00233         if ($pageList) {
00234             $pidField = ($table==='pages' ? 'uid' : 'pid');
00235             $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
00236             $where .= ' AND B.' . $pidField . $pidConstraint;
00237         }
00238 
00239         /**
00240          * For "real" workspace numbers, select by that.
00241          * If = -98, select all that are NOT online (zero).
00242          * Anything else below -1 will not select on the wsid and therefore select all!
00243          */
00244         if ($wsid > self::SELECT_ALL_WORKSPACES) {
00245             $where .= ' AND A.t3ver_wsid=' . $wsid;
00246         } else if ($wsid === self::SELECT_ALL_WORKSPACES) {
00247             $where .= ' AND A.t3ver_wsid!=0';
00248         }
00249 
00250         /**
00251          * lifecycle filter:
00252          * 1 = select all drafts (never-published),
00253          * 2 = select all published one or more times (archive/multiple)
00254          */
00255         if ($filter===1 || $filter===2) {
00256             $where .= ' AND A.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
00257         }
00258 
00259         if ($stage != -99) {
00260             $where .= ' AND A.t3ver_stage=' . intval($stage);
00261         }
00262 
00263             // Table B (online) must have PID >= 0 to signify being online.
00264         $where .= ' AND B.pid>=0';
00265             // ... and finally the join between the two tables.
00266         $where .= ' AND A.t3ver_oid=B.uid';
00267         $where .= t3lib_BEfunc::deleteClause($table, 'A');
00268         $where .= t3lib_BEfunc::deleteClause($table, 'B');
00269 
00270         /**
00271          * Select all records from this table in the database from the workspace
00272          * This joins the online version with the offline version as tables A and B
00273          * Order by UID, mostly to have a sorting in the backend overview module which doesn't "jump around" when swapping.
00274          */
00275         $res = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'B.uid');
00276         return is_array($res) ? $res : array();
00277     }
00278 
00279     /**
00280      *  Find all moved records at their new position.
00281      *
00282      * @param string $table
00283      * @param string $pageList
00284      * @param integer $wsid
00285      * @return array
00286      */
00287     protected function getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage) {
00288 
00289         /**
00290          * Aliases:
00291          * A - moveTo placeholder
00292          * B - online record
00293          * C - moveFrom placeholder
00294          */
00295         $fields = 'A.pid AS wspid, B.uid AS t3ver_oid, C.uid AS uid, B.pid AS livepid';
00296         $from = $table . ' A, ' . $table . ' B,' . $table . ' C';
00297         $where = 'A.t3ver_state=3 AND B.pid>0 AND B.t3ver_state=0 AND B.t3ver_wsid=0 AND C.pid=-1 AND C.t3ver_state=4';
00298 
00299         if ($wsid > self::SELECT_ALL_WORKSPACES) {
00300             $where .= ' AND A.t3ver_wsid=' . $wsid . ' AND C.t3ver_wsid=' . $wsid;
00301         } else if ($wsid === self::SELECT_ALL_WORKSPACES) {
00302             $where .= ' AND A.t3ver_wsid!=0 AND C.t3ver_wsid!=0 ';
00303         }
00304 
00305         /**
00306          * lifecycle filter:
00307          * 1 = select all drafts (never-published),
00308          * 2 = select all published one or more times (archive/multiple)
00309          */
00310         if ($filter===1 || $filter===2) {
00311             $where .= ' AND C.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
00312         }
00313 
00314         if ($stage != -99) {
00315             $where .= ' AND C.t3ver_stage=' . intval($stage);
00316         }
00317 
00318         if ($pageList) {
00319             $pidField = ($table==='pages' ? 'B.uid' : 'A.pid');
00320             $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
00321             $where .= ' AND ' . $pidField . $pidConstraint;
00322         }
00323 
00324         $where .= ' AND A.t3ver_move_id = B.uid AND B.uid = C.t3ver_oid';
00325         $where .= t3lib_BEfunc::deleteClause($table, 'A');
00326         $where .= t3lib_BEfunc::deleteClause($table, 'B');
00327         $where .= t3lib_BEfunc::deleteClause($table, 'C');
00328         $res = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'A.uid');
00329 
00330         return is_array($res) ? $res : array();
00331     }
00332 
00333 
00334     /**
00335      * Find all page uids recursive starting from a specific page
00336      *
00337      * @param    integer    $pageId
00338      * @param    integer    $wsid
00339      * @param    integer    $recursionLevel
00340      * @return  string  Comma sep. uid list
00341      */
00342     protected function getTreeUids($pageId, $wsid, $recursionLevel) {
00343         /**
00344          * Reusing existing functionality with the drawback that
00345          * mount points are not covered yet
00346          **/
00347         $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
00348         $searchObj = t3lib_div::makeInstance('t3lib_fullsearch');
00349         $pageList = FALSE;
00350         if ($pageId > 0) {
00351             $pageList = $searchObj->getTreeList($pageId, $recursionLevel, 0, $perms_clause);
00352         } else {
00353             $mountPoints = $GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint'];
00354             if (!is_array($mountPoints) || empty($mountPoints)) {
00355                 $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
00356                 $mountPoints = array_unique($mountPoints);
00357             }
00358             $newList = array();
00359             foreach($mountPoints as $mountPoint) {
00360                 $newList[] = $searchObj->getTreeList($mountPoint, $recursionLevel, 0, $perms_clause);
00361             }
00362             $pageList = implode(',', $newList);
00363         }
00364         unset($searchObj);
00365         if (intval($GLOBALS['TCA']['pages']['ctrl']['versioningWS']) === 2 && $pageList) {
00366             if ($pageList) {
00367                     // Remove the "subbranch" if a page was moved away
00368                 $movedAwayPages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, pid, t3ver_move_id', 'pages', 't3ver_move_id IN (' . $pageList . ') AND t3ver_wsid=' . $wsid . t3lib_BEfunc::deleteClause($table), '', 'uid', '', 't3ver_move_id');
00369                 $pageIds = t3lib_div::intExplode(',', $pageList, TRUE);
00370 
00371                     // move all pages away
00372                 $newList = array_diff($pageIds, array_keys($movedAwayPages));
00373 
00374                     // move back in if still connected to the "remaining" pages
00375                 do {
00376                     $changed = FALSE;
00377                     foreach ($movedAwayPages as $uid => $rec) {
00378                         if (in_array($rec['pid'], $newList) && !in_array($uid, $newList)) {
00379                             $newList[] = $uid;
00380                             $changed = TRUE;
00381                         }
00382                     }
00383                 } while ($changed);
00384 
00385                 $pageList = implode(',', $newList);
00386             }
00387                 // In case moving pages is enabled we need to replace all move-to pointer with their origin
00388             $pages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, t3ver_move_id', 'pages', 'uid IN (' . $pageList . ')' . t3lib_BEfunc::deleteClause($table), '', 'uid', '', 'uid');
00389 
00390             $newList = array();
00391             $pageIds = t3lib_div::intExplode(',', $pageList, TRUE);
00392             if (!in_array($pageId, $pageIds)) {
00393                 $pageIds[] = $pageId;
00394             }
00395             foreach ($pageIds as $pageId) {
00396                 if (intval($pages[$pageId]['t3ver_move_id']) > 0) {
00397                     $newList[] = intval($pages[$pageId]['t3ver_move_id']);
00398                 } else {
00399                     $newList[] = $pageId;
00400                 }
00401             }
00402             $pageList = implode(',', $newList);
00403         }
00404         return $pageList;
00405     }
00406 
00407     /**
00408      * Remove all records which are not permitted for the user
00409      *
00410      * @param array $recs
00411      * @param string $table
00412      * @return array
00413      */
00414     protected function filterPermittedElements($recs, $table) {
00415         $checkField = ($table == 'pages') ? 'uid' : 'wspid';
00416         $permittedElements = array();
00417         if (is_array($recs)) {
00418             foreach ($recs as $rec) {
00419                 $page = t3lib_beFunc::getRecord('pages', $rec[$checkField], 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
00420                 if ($GLOBALS['BE_USER']->doesUserHaveAccess($page, 1)) {
00421                     $permittedElements[] = $rec;
00422                 }
00423             }
00424         }
00425         return $permittedElements;
00426     }
00427 
00428 
00429     /**
00430      * Trivial check to see if the user already migrated his workspaces
00431      * to the new style (either manually or with the migrator scripts)
00432      *
00433      * @return bool
00434      */
00435     public static function isOldStyleWorkspaceUsed() {
00436         $oldStyleWorkspaceIsUsed = FALSE;
00437         $cacheKey = 'workspace-oldstyleworkspace-notused';
00438         $cacheResult = $GLOBALS['BE_USER']->getSessionData($cacheKey);
00439         if (!$cacheResult) {
00440             $where = 'adminusers != "" AND adminusers NOT LIKE "%be_users%" AND adminusers NOT LIKE "%be_groups%" AND deleted=0';
00441             $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'sys_workspace', $where);
00442             $oldStyleWorkspaceIsUsed = $count > 0;
00443             $GLOBALS['BE_USER']->setAndSaveSessionData($cacheKey, !$oldStyleWorkspaceIsUsed);
00444         } else {
00445             $oldStyleWorkspaceIsUsed = !$cacheResult;
00446         }
00447         return $oldStyleWorkspaceIsUsed;
00448     }
00449 
00450     /**
00451      * Determine whether a specific page is new and not yet available in the LIVE workspace
00452      *
00453      * @static
00454      * @param $id Primary key of the page to check
00455      * @param $language Language for which to check the page
00456      * @return bool
00457      */
00458     public static function isNewPage($id, $language = 0) {
00459         $isNewPage = FALSE;
00460             // If the language is not default, check state of overlay
00461         if ($language > 0) {
00462             $whereClause = 'pid = ' . $id;
00463             $whereClause .= ' AND ' .$GLOBALS['TCA']['pages_language_overlay']['ctrl']['languageField'] . ' = ' . $language;
00464             $whereClause .= ' AND t3ver_wsid = ' . $GLOBALS['BE_USER']->workspace;
00465             $whereClause .= t3lib_BEfunc::deleteClause('pages_language_overlay');
00466             $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('t3ver_state', 'pages_language_overlay', $whereClause);
00467             if (($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))) {
00468                 $isNewPage = (int) $row['t3ver_state'] === 1;
00469             }
00470 
00471             // Otherwise check state of page itself
00472         } else {
00473             $rec = t3lib_BEfunc::getRecord('pages', $id, 't3ver_state');
00474             if (is_array($rec)) {
00475                 $isNewPage = (int) $rec['t3ver_state'] === 1;
00476             }
00477         }
00478         return $isNewPage;
00479     }
00480 
00481     /**
00482      * Generates a view link for a page.
00483      *
00484      * @static
00485      * @param  $table
00486      * @param  $uid
00487      * @param  $record
00488      * @return string
00489      */
00490     public static function viewSingleRecord($table, $uid, $record=null) {
00491         $viewUrl = '';
00492         if ($table == 'pages') {
00493             $viewUrl = t3lib_BEfunc::viewOnClick(t3lib_BEfunc::getLiveVersionIdOfRecord('pages', $uid));
00494         } elseif ($table == 'pages_language_oderlay' || $table == 'tt_content') {
00495             $elementRecord = is_array($record) ? $record : t3lib_BEfunc::getLiveVersionOfRecord($table, $uid);
00496             $viewUrl = t3lib_BEfunc::viewOnClick($elementRecord['pid']);
00497         } else {
00498             if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'])) {
00499                 $_params = array('table' => $table, 'uid' => $uid, 'record' => $record);
00500                 $_funcRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'];
00501                 $viewUrl = t3lib_div::callUserFunction($_funcRef, $_params, null);
00502             }
00503         }
00504         return $viewUrl;
00505     }
00506 }
00507 
00508 
00509 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Workspaces.php'])) {
00510     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Workspaces.php']);
00511 }
00512 ?>