Best of today...
authorKasper Skårhøj <kasper@typo3.org>
Fri, 28 Oct 2005 19:18:02 +0000 (19:18 +0000)
committerKasper Skårhøj <kasper@typo3.org>
Fri, 28 Oct 2005 19:18:02 +0000 (19:18 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@831 709f56b5-9817-0410-a4d7-c38de5d9e867

TODO.txt
t3lib/class.t3lib_tcemain.php
t3lib/class.t3lib_userauthgroup.php
t3lib/stddb/tables.sql
t3lib/stddb/tbl_be.php
typo3/mod/user/ws/class.wslib.php
typo3/mod/user/ws/index.php
typo3/stylesheet.css
typo3/sysext/lang/locallang_csh_sysws.xml
typo3/sysext/version/cm1/index.php

index 53a9ff9..910dac0 100755 (executable)
--- a/TODO.txt
+++ b/TODO.txt
@@ -302,16 +302,7 @@ Versioning/Workspaces:
 *******************************
 - delete:
        - something twice? / delete a version, not just unlink it?
-       - unlink a version which has deleted flag must remove that version completely!
-
-
-- Implement flag in Workspace:
-       - Publish only content in "Publish" stage
-       - Only workspace owner can publish
-       - Stage change notification ->review (reviewers) ->publish (owner) ->rejection (reviewers and members) + message (goes into log as well)
-
-- Set branch-versioning to 100 by default in versioning API.
-- Way to change versioning type from element to page to branch for new records?
+       - "Delete versions" link in versioning module.
 
 ---------------------------------------
 
@@ -325,8 +316,8 @@ Workspace Manager TODO:
 - Details view for versions (when clicked)
        - display change log for specific element?
        - Also available as a Web >* module / click menu item (another way to browse stuff in need of preview, raise level, log checking etc.)
-- Show log of who raised content levels.
 
+Set up TemplaVoila package for testing.
 
 ----------------------------------------------
 - copy/move:
@@ -392,8 +383,9 @@ Frontend Preview:
 - Documentation. As frontend preview implementation is dealt with, describe any persisting incompatibilities in "TYPO3 Core API"
 
 
-Swapping versions:
+Versioning API:
        - (Support for) swapping using temporary file (instead of using negative ID)
+       - Way to change versioning type from element to page to branch for new records?
 
 Preview modes with no BE login; For single pages, for whole workspace?
        - Can bypass hidden pages, fe_groups, set workspace
index 7d8549b..8379a3a 100755 (executable)
  *
  *
  *
- *  218: class t3lib_TCEmain
- *  335:     function start($data,$cmd,$altUserObject='')
- *  370:     function setMirror($mirror)
- *  395:     function setDefaultsFromUserTS($userTS)
- *  418:     function process_uploads($postFiles)
- *  456:     function process_uploads_traverseArray(&$outputArr,$inputArr,$keyToSet)
+ *  223: class t3lib_TCEmain
+ *  340:     function start($data,$cmd,$altUserObject='')
+ *  375:     function setMirror($mirror)
+ *  400:     function setDefaultsFromUserTS($userTS)
+ *  423:     function process_uploads($postFiles)
+ *  461:     function process_uploads_traverseArray(&$outputArr,$inputArr,$keyToSet)
  *
  *              SECTION: PROCESSING DATA
- *  492:     function process_datamap()
- *  789:     function fillInFieldArray($table,$id,$fieldArray,$incomingFieldArray,$realPid,$status,$tscPID)
+ *  497:     function process_datamap()
+ *  795:     function fillInFieldArray($table,$id,$fieldArray,$incomingFieldArray,$realPid,$status,$tscPID)
  *
  *              SECTION: Evaluation of input values
- * 1009:     function checkValue($table,$field,$value,$id,$status,$realPid,$tscPID)
- * 1069:     function checkValue_SW($res,$value,$tcaFieldConf,$table,$id,$curValue,$status,$realPid,$recFID,$field,$uploadedFiles,$tscPID)
- * 1115:     function checkValue_input($res,$value,$tcaFieldConf,$PP,$field='')
- * 1153:     function checkValue_check($res,$value,$tcaFieldConf,$PP)
- * 1176:     function checkValue_radio($res,$value,$tcaFieldConf,$PP)
- * 1202:     function checkValue_group_select($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field)
- * 1302:     function checkValue_group_select_file($valueArray,$tcaFieldConf,$curValue,$uploadedFileArray,$status,$table,$id,$recFID)
- * 1455:     function checkValue_flex($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field)
- * 1528:     function checkValue_flexArray2Xml($array)
- * 1545:     function _DELETE_FLEX_FORMdata(&$valueArrayToRemoveFrom,$deleteCMDS)
- * 1567:     function _MOVE_FLEX_FORMdata(&$valueArrayToMoveIn, $moveCMDS, $direction)
+ * 1015:     function checkValue($table,$field,$value,$id,$status,$realPid,$tscPID)
+ * 1075:     function checkValue_SW($res,$value,$tcaFieldConf,$table,$id,$curValue,$status,$realPid,$recFID,$field,$uploadedFiles,$tscPID)
+ * 1121:     function checkValue_input($res,$value,$tcaFieldConf,$PP,$field='')
+ * 1159:     function checkValue_check($res,$value,$tcaFieldConf,$PP)
+ * 1182:     function checkValue_radio($res,$value,$tcaFieldConf,$PP)
+ * 1208:     function checkValue_group_select($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field)
+ * 1308:     function checkValue_group_select_file($valueArray,$tcaFieldConf,$curValue,$uploadedFileArray,$status,$table,$id,$recFID)
+ * 1461:     function checkValue_flex($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field)
+ * 1534:     function checkValue_flexArray2Xml($array)
+ * 1551:     function _DELETE_FLEX_FORMdata(&$valueArrayToRemoveFrom,$deleteCMDS)
+ * 1573:     function _MOVE_FLEX_FORMdata(&$valueArrayToMoveIn, $moveCMDS, $direction)
  *
  *              SECTION: Helper functions for evaluation functions.
- * 1629:     function getUnique($table,$field,$value,$id,$newPid=0)
- * 1667:     function checkValue_input_Eval($value,$evalArray,$is_in)
- * 1755:     function checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,$type)
- * 1788:     function checkValue_group_select_explodeSelectGroupValue($value)
- * 1811:     function checkValue_flex_procInData($dataPart,$dataPart_current,$uploadedFiles,$dataStructArray,$pParams,$callBackFunc='')
- * 1850:     function checkValue_flex_procInData_travDS(&$dataValues,$dataValues_current,$uploadedFiles,$DSelements,$pParams,$callBackFunc,$structurePath)
+ * 1635:     function getUnique($table,$field,$value,$id,$newPid=0)
+ * 1673:     function checkValue_input_Eval($value,$evalArray,$is_in)
+ * 1761:     function checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,$type)
+ * 1794:     function checkValue_group_select_explodeSelectGroupValue($value)
+ * 1817:     function checkValue_flex_procInData($dataPart,$dataPart_current,$uploadedFiles,$dataStructArray,$pParams,$callBackFunc='')
+ * 1856:     function checkValue_flex_procInData_travDS(&$dataValues,$dataValues_current,$uploadedFiles,$DSelements,$pParams,$callBackFunc,$structurePath)
  *
  *              SECTION: PROCESSING COMMANDS
- * 1996:     function process_cmdmap()
+ * 2002:     function process_cmdmap()
  *
  *              SECTION: Cmd: Copying
- * 2140:     function copyRecord($table,$uid,$destPid,$first=0,$overrideValues=array(),$excludeFields='')
- * 2248:     function copyPages($uid,$destPid)
- * 2303:     function copySpecificPage($uid,$destPid,$copyTablesArray,$first=0)
- * 2337:     function copyRecord_raw($table,$uid,$pid,$overrideArray=array())
- * 2399:     function rawCopyPageContent($old_pid,$new_pid,$copyTablesArray)
- * 2423:     function insertNewCopyVersion($table,$fieldArray,$realPid)
- * 2474:     function copyRecord_procBasedOnFieldType($table,$uid,$field,$value,$row,$conf)
- * 2530:     function copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
- * 2558:     function copyRecord_procFilesRefs($conf, $uid, $value)
+ * 2153:     function copyRecord($table,$uid,$destPid,$first=0,$overrideValues=array(),$excludeFields='')
+ * 2274:     function copyPages($uid,$destPid)
+ * 2328:     function copySpecificPage($uid,$destPid,$copyTablesArray,$first=0)
+ * 2362:     function copyRecord_raw($table,$uid,$pid,$overrideArray=array())
+ * 2424:     function rawCopyPageContent($old_pid,$new_pid,$copyTablesArray)
+ * 2448:     function insertNewCopyVersion($table,$fieldArray,$realPid)
+ * 2499:     function copyRecord_procBasedOnFieldType($table,$uid,$field,$value,$row,$conf)
+ * 2555:     function copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
+ * 2583:     function copyRecord_procFilesRefs($conf, $uid, $value)
  *
  *              SECTION: Cmd: Moving, Localizing
- * 2627:     function moveRecord($table,$uid,$destPid)
- * 2777:     function localize($table,$uid,$language)
+ * 2652:     function moveRecord($table,$uid,$destPid)
+ * 2816:     function localize($table,$uid,$language)
  *
  *              SECTION: Cmd: Versioning
- * 2852:     function versionizeRecord($table,$id,$label,$delete=FALSE,$versionizeTree=-1)
- * 2929:     function versionizePages($uid,$label,$versionizeTree)
- * 2986:     function version_swap($table,$id,$swapWith,$swapIntoWS=0)
- * 3134:     function version_clearWSID($table,$id)
- * 3154:     function version_setStage($table,$id,$stageId)
+ * 2891:     function versionizeRecord($table,$id,$label,$delete=FALSE,$versionizeTree=-1)
+ * 2967:     function versionizePages($uid,$label,$versionizeTree)
+ * 3030:     function version_swap($table,$id,$swapWith,$swapIntoWS=0)
+ * 3195:     function version_clearWSID($table,$id)
+ * 3222:     function version_setStage($table,$id,$stageId,$comment='')
  *
  *              SECTION: Cmd: Deleting
- * 3195:     function deleteRecord($table,$uid, $noRecordCheck)
- * 3249:     function deletePages($uid)
- * 3283:     function deleteSpecificPage($uid)
+ * 3273:     function deleteRecord($table,$uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE)
+ * 3329:     function deletePages($uid,$force=FALSE,$forceHardDelete=FALSE)
+ * 3357:     function deleteSpecificPage($uid,$forceHardDelete=FALSE)
+ * 3380:     function canDeletePage($uid)
+ * 3407:     function cannotDeleteRecord($table,$id)
  *
  *              SECTION: Cmd: Helper functions
- * 3324:     function remapListedDBRecords()
- * 3401:     function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
- * 3427:     function remapListedDBRecords_procDBRefs($conf, $value, $MM_localUid)
+ * 3438:     function remapListedDBRecords()
+ * 3515:     function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
+ * 3541:     function remapListedDBRecords_procDBRefs($conf, $value, $MM_localUid)
  *
  *              SECTION: Access control / Checking functions
- * 3491:     function checkModifyAccessList($table)
- * 3503:     function isRecordInWebMount($table,$id)
- * 3517:     function isInWebMount($pid)
- * 3531:     function checkRecordUpdateAccess($table,$id)
- * 3555:     function checkRecordInsertAccess($insertTable,$pid,$action=1)
- * 3589:     function isTableAllowedForThisPage($page_uid, $checkTable)
- * 3622:     function doesRecordExist($table,$id,$perms)
- * 3684:     function doesRecordExist_pageLookUp($id, $perms)
- * 3710:     function doesBranchExist($inList,$pid,$perms,$recurse)
- * 3744:     function tableReadOnly($table)
- * 3756:     function tableAdminOnly($table)
- * 3768:     function isReferenceField($conf)
- * 3780:     function destNotInsideSelf($dest,$id)
- * 3811:     function getExcludeListArray()
- * 3834:     function doesPageHaveUnallowedTables($page_uid,$doktype)
+ * 3605:     function checkModifyAccessList($table)
+ * 3617:     function isRecordInWebMount($table,$id)
+ * 3631:     function isInWebMount($pid)
+ * 3645:     function checkRecordUpdateAccess($table,$id)
+ * 3669:     function checkRecordInsertAccess($insertTable,$pid,$action=1)
+ * 3703:     function isTableAllowedForThisPage($page_uid, $checkTable)
+ * 3736:     function doesRecordExist($table,$id,$perms)
+ * 3798:     function doesRecordExist_pageLookUp($id, $perms)
+ * 3824:     function doesBranchExist($inList,$pid,$perms,$recurse)
+ * 3858:     function tableReadOnly($table)
+ * 3870:     function tableAdminOnly($table)
+ * 3884:     function destNotInsideSelf($dest,$id)
+ * 3916:     function getExcludeListArray()
+ * 3939:     function doesPageHaveUnallowedTables($page_uid,$doktype)
  *
  *              SECTION: Information lookup
- * 3883:     function pageInfo($id,$field)
- * 3903:     function recordInfo($table,$id,$fieldList)
- * 3922:     function getRecordProperties($table,$id)
- * 3935:     function getRecordPropertiesFromRow($table,$row)
+ * 3988:     function pageInfo($id,$field)
+ * 4008:     function recordInfo($table,$id,$fieldList)
+ * 4027:     function getRecordProperties($table,$id)
+ * 4040:     function getRecordPropertiesFromRow($table,$row)
  *
  *              SECTION: Storing data to Database Layer
- * 3978:     function updateDB($table,$id,$fieldArray)
- * 4026:     function insertDB($table,$id,$fieldArray,$newVersion=FALSE,$suggestedUid=0,$dontSetNewIdIndex=FALSE)
- * 4094:     function checkStoredRecord($table,$id,$fieldArray,$action)
- * 4131:     function setHistory($table,$id,$logId)
- * 4170:     function clearHistory($table,$id,$keepEntries=10,$maxAgeSeconds=604800)
+ * 4083:     function updateDB($table,$id,$fieldArray)
+ * 4131:     function insertDB($table,$id,$fieldArray,$newVersion=FALSE,$suggestedUid=0,$dontSetNewIdIndex=FALSE)
+ * 4199:     function checkStoredRecord($table,$id,$fieldArray,$action)
+ * 4236:     function setHistory($table,$id,$logId)
+ * 4275:     function clearHistory($table,$id,$keepEntries=10,$maxAgeSeconds=604800)
  *
  *              SECTION: Misc functions
- * 4219:     function getSortNumber($table,$uid,$pid)
- * 4285:     function resorting($table,$pid,$sortRow, $return_SortNumber_After_This_Uid)
- * 4316:     function setTSconfigPermissions($fieldArray,$TSConfig_p)
- * 4333:     function newFieldArray($table)
- * 4365:     function overrideFieldArray($table,$data)
- * 4381:     function compareFieldArrayWithCurrentAndUnset($table,$id,$fieldArray)
- * 4427:     function assemblePermissions($string)
- * 4444:     function rmComma($input)
- * 4454:     function convNumEntityToByteValue($input)
- * 4476:     function destPathFromUploadFolder($folder)
- * 4486:     function deleteClause($table)
- * 4502:     function getTCEMAIN_TSconfig($tscPID)
- * 4517:     function getTableEntries($table,$TSconfig)
- * 4530:     function getPID($table,$uid)
- * 4543:     function dbAnalysisStoreExec()
- * 4559:     function removeRegisteredFiles()
- * 4571:     function removeCacheFiles()
- * 4596:     function int_pageTreeInfo($CPtable,$pid,$counter, $rootID)
- * 4617:     function compileAdminTables()
- * 4634:     function fixUniqueInPid($table,$uid)
- * 4670:     function fixCopyAfterDuplFields($table,$uid,$prevUid,$update, $newData=array())
- * 4695:     function extFileFields($table)
- * 4721:     function getCopyHeader($table,$pid,$field,$value,$count,$prevTitle='')
- * 4750:     function prependLabel($table)
- * 4767:     function resolvePid($table,$pid)
- * 4785:     function clearPrefixFromValue($table,$value)
- * 4800:     function extFileFunctions($table,$field,$filelist,$func)
- * 4830:     function noRecordsFromUnallowedTables($inList)
+ * 4324:     function getSortNumber($table,$uid,$pid)
+ * 4397:     function resorting($table,$pid,$sortRow, $return_SortNumber_After_This_Uid)
+ * 4428:     function setTSconfigPermissions($fieldArray,$TSConfig_p)
+ * 4445:     function newFieldArray($table)
+ * 4477:     function overrideFieldArray($table,$data)
+ * 4493:     function compareFieldArrayWithCurrentAndUnset($table,$id,$fieldArray)
+ * 4539:     function assemblePermissions($string)
+ * 4556:     function rmComma($input)
+ * 4566:     function convNumEntityToByteValue($input)
+ * 4588:     function destPathFromUploadFolder($folder)
+ * 4598:     function deleteClause($table)
+ * 4614:     function getTCEMAIN_TSconfig($tscPID)
+ * 4629:     function getTableEntries($table,$TSconfig)
+ * 4642:     function getPID($table,$uid)
+ * 4655:     function dbAnalysisStoreExec()
+ * 4671:     function removeRegisteredFiles()
+ * 4683:     function removeCacheFiles()
+ * 4708:     function int_pageTreeInfo($CPtable,$pid,$counter, $rootID)
+ * 4729:     function compileAdminTables()
+ * 4746:     function fixUniqueInPid($table,$uid)
+ * 4782:     function fixCopyAfterDuplFields($table,$uid,$prevUid,$update, $newData=array())
+ * 4807:     function extFileFields($table)
+ * 4828:     function getUniqueFields($table)
+ * 4853:     function isReferenceField($conf)
+ * 4868:     function getCopyHeader($table,$pid,$field,$value,$count,$prevTitle='')
+ * 4897:     function prependLabel($table)
+ * 4914:     function resolvePid($table,$pid)
+ * 4944:     function clearPrefixFromValue($table,$value)
+ * 4959:     function extFileFunctions($table,$field,$filelist,$func)
+ * 4989:     function noRecordsFromUnallowedTables($inList)
+ * 5015:     function notifyStageChange($stat,$stageId,$table,$id,$comment)
+ * 5109:     function notifyStageChange_getEmails($listOfUsers,$noTablePrefix=FALSE)
  *
  *              SECTION: Clearing cache
- * 4872:     function clear_cache($table,$uid)
- * 4981:     function clear_cacheCmd($cacheCmd)
+ * 5155:     function clear_cache($table,$uid)
+ * 5264:     function clear_cacheCmd($cacheCmd)
  *
  *              SECTION: Logging
- * 5087:     function log($table,$recuid,$action,$recpid,$error,$details,$details_nr=-1,$data=array(),$event_pid=-1,$NEWid='')
- * 5104:     function newlog($message, $error=0)
- * 5114:     function printLogErrorMessages($redirect)
+ * 5370:     function log($table,$recuid,$action,$recpid,$error,$details,$details_nr=-1,$data=array(),$event_pid=-1,$NEWid='')
+ * 5387:     function newlog($message, $error=0)
+ * 5397:     function printLogErrorMessages($redirect)
  *
- * TOTAL FUNCTIONS: 104
+ * TOTAL FUNCTIONS: 109
  * (This index is automatically created/updated by the extension "extdeveval")
  *
  */
@@ -2074,7 +2079,7 @@ class t3lib_TCEmain       {
                                                                                $this->version_clearWSID($table,$id);
                                                                        break;
                                                                        case 'setStage':
-                                                                               $this->version_setStage($table,$id,$value['stageId']);
+                                                                               $this->version_setStage($table,$id,$value['stageId'],$value['comment']);
                                                                        break;
                                                                }
                                                        break;
@@ -2085,11 +2090,7 @@ class t3lib_TCEmain      {
                                                                                // Look, if record is "online" or in a versionized branch, then delete directly.
                                                                        if ($res = $this->BE_USER->workspaceAllowLiveRecordsInPID($delRec['pid'], $table))      {       // Directly delete:
                                                                                if ($res>0)     {
-                                                                                       if ($table === 'pages') {
-                                                                                               $this->deletePages($id);
-                                                                                       } else {
-                                                                                               $this->deleteRecord($table,$id);
-                                                                                       }
+                                                                                       $this->deleteEl($table, $id);
                                                                                } else {
                                                                                        $this->newlog('Stage of root point did not allow for deletion',1);
                                                                                }
@@ -2902,13 +2903,7 @@ class t3lib_TCEmain      {
                                                                $workspaceCheck = TRUE;
                                                                if ($this->BE_USER->workspace!==0)      {
                                                                                // Look for version already in workspace:
-                                                                       $workspaceCheck = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
-                                                                               'uid',
-                                                                               $table,
-                                                                               '(t3ver_oid='.$id.' || uid='.$id.')
-                                                                                       AND t3ver_wsid='.intval($this->BE_USER->workspace).
-                                                                                       $this->deleteClause($table)
-                                                                       ) ? FALSE : TRUE;
+                                                                       $workspaceCheck = t3lib_BEfunc::getWorkspaceVersionOfRecord($this->BE_USER->workspace,$table,$id,'uid') ? FALSE : TRUE;
                                                                }
 
                                                                if ($workspaceCheck)    {
@@ -3059,7 +3054,7 @@ class t3lib_TCEmain       {
 
                                // Find fields to select:
                        $keepFields = $this->getUniqueFields($table);
-                       $selectFields = array_unique(array_merge(array('uid','pid','t3ver_oid','t3ver_wsid','t3ver_state','t3ver_count'),$keepFields));
+                       $selectFields = array_unique(array_merge(array('uid','pid','t3ver_oid','t3ver_wsid','t3ver_state','t3ver_stage','t3ver_count'),$keepFields));
                        if ($TCA[$table]['ctrl']['sortby'])     {
                                $selectFields[] = $keepFields[] = $TCA[$table]['ctrl']['sortby'];
                        }
@@ -3073,111 +3068,106 @@ class t3lib_TCEmain   {
                        $swapVersion = t3lib_BEfunc::getRecord($table,$swapWith,implode(',',$selectFields));
 
                        if (is_array($curVersion) && is_array($swapVersion))    {
-
                                if ($this->BE_USER->workspacePublishAccess($swapVersion['t3ver_wsid'])) {
-                                       if ($this->doesRecordExist($table,$swapWith,'show') && $this->checkRecordUpdateAccess($table,$swapWith)) {
-                                               if (!$swapIntoWS || $this->BE_USER->workspaceSwapAccess())      {
-                                                       if (!is_array(t3lib_BEfunc::getRecord($table,-$id,'uid')))      {
-
-                                                                       // Add "keepfields"
-                                                               $swapVerBaseArray = array();
-                                                               $curVerBaseArray = array();
-                                                               foreach($keepFields as $fN)     {
-                                                                       $swapVerBaseArray[$fN] = $curVersion[$fN];
-                                                                       $curVerBaseArray[$fN] = $swapVersion[$fN];
-                                                               }
-
-                                                                       // Check if the swapWith record really IS a version of the original!
-                                                               if ((int)$swapVersion['pid']==-1 && (int)$curVersion['pid']>=0 && !strcmp($swapVersion['t3ver_oid'],$id))       {
-
-                                                                       $sqlErrors=array();
-
-                                                                               // Step 1: Negate ID of online version:
-                                                                       $sArray = $curVerBaseArray;
-                                                                       $sArray['uid'] = -intval($id);
-                                                                       $sArray['t3ver_oid'] = intval($swapWith);
-                                                                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table,'uid='.intval($id),$sArray);
-                                                                       if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
-
-                                                                               // Step 2: Set online ID for offline version:
-                                                                       $sArray = $swapVerBaseArray;
-                                                                       $sArray['uid'] = intval($id);
-                                                                       $sArray['pid'] = intval($curVersion['pid']);    // Set pid for ONLINE
-                                                                       $sArray['t3ver_oid'] = intval($id);
-                                                                       $sArray['t3ver_wsid'] = $swapIntoWS ? intval($curVersion['t3ver_wsid']) : 0;
-                                                                       $sArray['t3ver_tstamp'] = time();
-                                                                       $sArray['t3ver_stage'] = 0;
-                                                                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table,'uid='.intval($swapWith),$sArray);
-                                                                       if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
-
-                                                                               // Step 3: Set offline id for the previously online version:
-                                                                       $sArray = array();
-                                                                       $sArray['uid'] = intval($swapWith);
-                                                                       $sArray['pid'] = -1;    // Set pid for OFFLINE
-                                                                       $sArray['t3ver_oid'] = intval($id);
-                                                                       $sArray['t3ver_wsid'] = $swapIntoWS ? intval($swapVersion['t3ver_wsid']) : 0;
-                                                                       $sArray['t3ver_tstamp'] = time();
-                                                                       $sArray['t3ver_count'] = $curVersion['t3ver_count']+1;  // Increment lifecycle counter
-                                                                       $sArray['t3ver_stage'] = 0;
-
-                                                                       if ($table==='pages') {         // Keeping the swapmode state
-                                                                               $sArray['t3ver_swapmode'] = $swapVersion['t3ver_swapmode'];
+                                       $wsAccess = $this->BE_USER->checkWorkspace($swapVersion['t3ver_wsid']);
+                                       if ($swapVersion['t3ver_wsid']<=0 || !($wsAccess['publish_access']&1) || (int)$swapVersion['t3ver_stage']===10) {
+
+                                               if ($this->doesRecordExist($table,$swapWith,'show') && $this->checkRecordUpdateAccess($table,$swapWith)) {
+                                                       if (!$swapIntoWS || $this->BE_USER->workspaceSwapAccess())      {
+                                                               if (!is_array(t3lib_BEfunc::getRecord($table,-$id,'uid')))      {
+
+                                                                               // Add "keepfields"
+                                                                       $swapVerBaseArray = array();
+                                                                       $curVerBaseArray = array();
+                                                                       foreach($keepFields as $fN)     {
+                                                                               $swapVerBaseArray[$fN] = $curVersion[$fN];
+                                                                               $curVerBaseArray[$fN] = $swapVersion[$fN];
                                                                        }
-                                                                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table,'uid=-'.intval($id),$sArray);
-                                                                       if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
 
-                                                                               // Checking for delete:
-                                                                       if ($swapVersion['t3ver_state']==2)     {
-                                                                               if ($table == 'pages')  {
-                                                                                       $this->deletePages($id, TRUE);  // Force delete
-                                                                               } else {
-                                                                                       $this->deleteRecord($table,$id,TRUE);   // Force delete
+                                                                               // Check if the swapWith record really IS a version of the original!
+                                                                       if ((int)$swapVersion['pid']==-1 && (int)$curVersion['pid']>=0 && !strcmp($swapVersion['t3ver_oid'],$id))       {
+
+                                                                               $sqlErrors=array();
+
+                                                                                       // Step 1: Negate ID of online version:
+                                                                               $sArray = $curVerBaseArray;
+                                                                               $sArray['uid'] = -intval($id);
+                                                                               $sArray['t3ver_oid'] = intval($swapWith);
+                                                                               $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table,'uid='.intval($id),$sArray);
+                                                                               if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
+
+                                                                                       // Step 2: Set online ID for offline version:
+                                                                               $sArray = $swapVerBaseArray;
+                                                                               $sArray['uid'] = intval($id);
+                                                                               $sArray['pid'] = intval($curVersion['pid']);    // Set pid for ONLINE
+                                                                               $sArray['t3ver_oid'] = intval($id);
+                                                                               $sArray['t3ver_wsid'] = $swapIntoWS ? intval($curVersion['t3ver_wsid']) : 0;
+                                                                               $sArray['t3ver_tstamp'] = time();
+                                                                               $sArray['t3ver_stage'] = 0;
+                                                                               $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table,'uid='.intval($swapWith),$sArray);
+                                                                               if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
+
+                                                                                       // Step 3: Set offline id for the previously online version:
+                                                                               $sArray = array();
+                                                                               $sArray['uid'] = intval($swapWith);
+                                                                               $sArray['pid'] = -1;    // Set pid for OFFLINE
+                                                                               $sArray['t3ver_oid'] = intval($id);
+                                                                               $sArray['t3ver_wsid'] = $swapIntoWS ? intval($swapVersion['t3ver_wsid']) : 0;
+                                                                               $sArray['t3ver_tstamp'] = time();
+                                                                               $sArray['t3ver_count'] = $curVersion['t3ver_count']+1;  // Increment lifecycle counter
+                                                                               $sArray['t3ver_stage'] = 0;
+
+                                                                               if ($table==='pages') {         // Keeping the swapmode state
+                                                                                       $sArray['t3ver_swapmode'] = $swapVersion['t3ver_swapmode'];
                                                                                }
-                                                                       }
-                                                                               // Checking for "new-placeholder" and if found, delete it:
-                                                                       if ($curVersion['t3ver_state']==1)      {
-                                                                               if ($table == 'pages')  {
-                                                                                       $this->deletePages($swapWith, TRUE, TRUE);      // Force HARD delete!
-                                                                               } else {
-                                                                                       $this->deleteRecord($table, $swapWith, TRUE, TRUE);     // Force HARD delete!
+                                                                               $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table,'uid=-'.intval($id),$sArray);
+                                                                               if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
+
+                                                                                       // Checking for delete:
+                                                                               if ($swapVersion['t3ver_state']==2)     {
+                                                                                       $this->deleteEl($table,$id,TRUE);       // Force delete
+                                                                               }
+                                                                                       // Checking for "new-placeholder" and if found, delete it:
+                                                                               if ($curVersion['t3ver_state']==1)      {
+                                                                                       $this->deleteEl($table, $swapWith, TRUE, TRUE);         // For delete + completely delete!
                                                                                }
-                                                                       }
 
-                                                                       if (!count($sqlErrors)) {
-                                                                               $this->newlog('Swapping successful for table "'.$table.'" uid '.$id.'=>'.$swapWith);
+                                                                               if (!count($sqlErrors)) {
+                                                                                       $this->newlog('Swapping successful for table "'.$table.'" uid '.$id.'=>'.$swapWith);
 
-                                                                                       // SWAPPING pids for subrecords:
-                                                                               if ($table=='pages' && $swapVersion['t3ver_swapmode']>=0)       {
+                                                                                               // SWAPPING pids for subrecords:
+                                                                                       if ($table=='pages' && $swapVersion['t3ver_swapmode']>=0)       {
 
-                                                                                               // Collect table names that should be copied along with the tables:
-                                                                                       foreach($TCA as $tN => $tCfg)   {
-                                                                                               if ($swapVersion['t3ver_swapmode']>0 || $TCA[$tN]['ctrl']['versioning_followPages'])    {       // For "Branch" publishing swap ALL, otherwise for "page" publishing, swap only "versioning_followPages" tables
-                       #debug($tN,'SWAPPING pids for subrecords:');
-                                                                                                       $temporaryPid = -($id+1000000);
+                                                                                                       // Collect table names that should be copied along with the tables:
+                                                                                               foreach($TCA as $tN => $tCfg)   {
+                                                                                                       if ($swapVersion['t3ver_swapmode']>0 || $TCA[$tN]['ctrl']['versioning_followPages'])    {       // For "Branch" publishing swap ALL, otherwise for "page" publishing, swap only "versioning_followPages" tables
+                               #debug($tN,'SWAPPING pids for subrecords:');
+                                                                                                               $temporaryPid = -($id+1000000);
 
-                                                                                                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN,'pid='.intval($id),array('pid'=>$temporaryPid));
-                                                                                                       if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
+                                                                                                               $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN,'pid='.intval($id),array('pid'=>$temporaryPid));
+                                                                                                               if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
 
-                                                                                                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN,'pid='.intval($swapWith),array('pid'=>$id));
-                                                                                                       if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
+                                                                                                               $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN,'pid='.intval($swapWith),array('pid'=>$id));
+                                                                                                               if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
 
-                                                                                                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN,'pid='.intval($temporaryPid),array('pid'=>$swapWith));
-                                                                                                       if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
+                                                                                                               $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tN,'pid='.intval($temporaryPid),array('pid'=>$swapWith));
+                                                                                                               if ($GLOBALS['TYPO3_DB']->sql_error())  $sqlErrors[]=$GLOBALS['TYPO3_DB']->sql_error();
 
-                                                                                                       if (count($sqlErrors))  {
-                                                                                                               $this->newlog('During Swapping: SQL errors happend: '.implode('; ',$sqlErrors),2);
+                                                                                                               if (count($sqlErrors))  {
+                                                                                                                       $this->newlog('During Swapping: SQL errors happend: '.implode('; ',$sqlErrors),2);
+                                                                                                               }
                                                                                                        }
                                                                                                }
                                                                                        }
-                                                                               }
-                                                                                       // Clear cache:
-                                                                               $this->clear_cache($table,$id);
-
-                                                                       } else $this->newlog('During Swapping: SQL errors happend: '.implode('; ',$sqlErrors),2);
-                                                               } else $this->newlog('In swap version, either pid was not -1 or the t3ver_oid didn\'t match the id of the online version as it must!',2);
-                                                       } else $this->newlog('Error: A record with a negative UID existed - that indicates some inconsistency in the database from prior versioning actions!',2);
-                                               } else $this->newlog('Workspace #'.$swapVersion['t3ver_wsid'].' does not support swapping.',1);
-                                       } else $this->newlog('You cannot publish a record you do not have edit and show permissions for',1);
+                                                                                               // Clear cache:
+                                                                                       $this->clear_cache($table,$id);
+
+                                                                               } else $this->newlog('During Swapping: SQL errors happend: '.implode('; ',$sqlErrors),2);
+                                                                       } else $this->newlog('In swap version, either pid was not -1 or the t3ver_oid didn\'t match the id of the online version as it must!',2);
+                                                               } else $this->newlog('Error: A record with a negative UID existed - that indicates some inconsistency in the database from prior versioning actions!',2);
+                                                       } else $this->newlog('Workspace #'.$swapVersion['t3ver_wsid'].' does not support swapping.',1);
+                                               } else $this->newlog('You cannot publish a record you do not have edit and show permissions for',1);
+                                       } else $this->newlog('Records in workspace #'.$swapVersion['t3ver_wsid'].' can only be published when in "Publish" stage.',1);
                                } else $this->newlog('User could not publish records from workspace #'.$swapVersion['t3ver_wsid'],1);
                        } else $this->newlog('Error: Either online or swap version could not be selected!',2);
                } else $this->newlog('Error: You cannot swap versions for a record you do not have access to edit!',1);
@@ -3204,6 +3194,12 @@ class t3lib_TCEmain      {
                                if ((int)$liveRec['t3ver_state']===1)   {
                                        $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table,'uid='.intval($liveRec['uid']),$sArray);
                                }
+
+                                       // If "deleted" flag is set for the version that got released it doesn't make sense to keep that "placeholder" anymore and we delete it completly.
+                               $wsRec = t3lib_BEfunc::getRecord($table,$id);
+                               if ((int)$wsRec['t3ver_state']===2)     {
+                                       $this->deleteEl($table, $id, TRUE, TRUE);
+                               }
                        }
                } else $this->newlog('Attempt to reset workspace for record failed because you do not have edit access',1);
        }
@@ -3214,9 +3210,10 @@ class t3lib_TCEmain      {
         * @param       string          Table name
         * @param       integer         Record UID
         * @param       integer         Stage ID to set
+        * @param       string          Comment that goes into log
         * @return      void
         */
-       function version_setStage($table,$id,$stageId)  {
+       function version_setStage($table,$id,$stageId,$comment='')      {
                if ($errorCode = $this->BE_USER->workspaceCannotEditOfflineVersion($table, $id))        {
                        $this->newlog('Attempt to set stage for record failed: '.$errorCode,1);
                } elseif ($this->checkRecordUpdateAccess($table,$id)) {
@@ -3227,7 +3224,13 @@ class t3lib_TCEmain      {
                                $sArray = array();
                                $sArray['t3ver_stage'] = $stageId;
                                $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid='.intval($id), $sArray);
-                               $this->newlog('Stage for record was changed to '.$stageId);
+                               $this->newlog('Stage for record was changed to '.$stageId.'. Comment was: "'.substr($comment,0,100).'"');
+// TEMPORARY:
+$this->log($table,$id,6,0,0,'Stage raised...',-1,array('comment'=>$comment,'stage'=>$stageId));
+
+                               if ((int)$stat['stagechg_notification']>0)      {
+                                       $this->notifyStageChange($stat,$stageId,$table,$id,$comment);
+                               }
                        } else $this->newlog('The member user tried to set a stage value "'.$stageId.'" that was not allowed',1);
                } else $this->newlog('Attempt to set stage for record failed because you do not have edit access',1);
        }
@@ -3252,6 +3255,23 @@ class t3lib_TCEmain      {
         ********************************************/
 
        /**
+        * Delete element from any table
+        *
+        * @param       string          Table name
+        * @param       integer         Record UID
+        * @param       boolean         Flag: If $noRecordCheck is set, then the function does not check permission to delete record
+        * @param       boolean         If TRUE, the "deleted" flag is ignored if applicable for record and the record is deleted COMPLETELY!
+        * @return      void
+        */
+       function deleteEl($table, $uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE)   {
+               if ($table == 'pages')  {
+                       $this->deletePages($uid, $noRecordCheck, $forceHardDelete);
+               } else {
+                       $this->deleteRecord($table, $uid, $noRecordCheck, $forceHardDelete);
+               }
+       }
+
+       /**
         * Deleting a record
         * This function may not be used to delete pages-records unless the underlying records are already deleted
         * Deletes a record regardless of versioning state (live of offline, doesn't matter, the uid decides)
@@ -4995,6 +5015,131 @@ class t3lib_TCEmain     {
                return TRUE;
        }
 
+       /**
+        * Send an email notification to users in workspace
+        *
+        * @param       array           Workspace access array (from t3lib_userauthgroup::checkWorkspace())
+        * @param       integer         New Stage number: 0 = editing, 1= just ready for review, 10 = ready for publication, -1 = rejected!
+        * @param       string          Table name of element
+        * @param       integer         Record uid of element
+        * @param       string          User comment sent along with action
+        * @return      void
+        */
+       function notifyStageChange($stat,$stageId,$table,$id,$comment)  {
+               $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $stat['uid']);
+
+               if (is_array($workspaceRec))    {
+
+                               // Compile label:
+                       switch((int)$stageId)   {
+                               case 1:
+                                       $newStage = 'Ready for review';
+                               break;
+                               case 10:
+                                       $newStage = 'Ready for publishing';
+                               break;
+                               case -1:
+                                       $newStage = 'Element was rejected!';
+                               break;
+                               case 0:
+                                       $newStage = 'Rejected element was noticed and edited';
+                               break;
+                               default:
+                                       $newStage = 'Unknown state change!?';
+                               break;
+                       }
+
+                               // Compile list of recipients:
+                       $emails = array();
+                       switch((int)$stat['stagechg_notification'])     {
+                               case 1:
+                                       switch((int)$stageId)   {
+                                               case 1:
+                                                       $emails = $this->notifyStageChange_getEmails($workspaceRec['reviewers']);
+                                               break;
+                                               case 10:
+                                                       $emails = $this->notifyStageChange_getEmails($workspaceRec['adminusers'], TRUE);
+                                               break;
+                                               case -1:
+                                                       $emails = $this->notifyStageChange_getEmails($workspaceRec['reviewers']);
+                                                       $emails = array_merge($emails,$this->notifyStageChange_getEmails($workspaceRec['members']));
+                                               break;
+                                               case 0:
+                                                       $emails = $this->notifyStageChange_getEmails($workspaceRec['members']);
+                                               break;
+                                               default:
+                                                       $emails = $this->notifyStageChange_getEmails($workspaceRec['adminusers'], TRUE);
+                                               break;
+                                       }
+                               break;
+                               case 10:
+                                       $emails = $this->notifyStageChange_getEmails($workspaceRec['adminusers'], TRUE);
+                                       $emails = array_merge($emails,$this->notifyStageChange_getEmails($workspaceRec['reviewers']));
+                                       $emails = array_merge($emails,$this->notifyStageChange_getEmails($workspaceRec['members']));
+                               break;
+                       }
+                       $emails = array_unique($emails);
+
+                               // Send email:
+                       if (count($emails))     {
+                               $message = sprintf('
+At the TYPO3 site "%s" (%s)
+in workspace "%s" (#%s)
+the stage has changed for the element "%s":
+
+==> %s
+
+User Comment:
+"%s"
+
+State was change by %s (username: %s)
+                               ',
+                               $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
+                               t3lib_div::getIndpEnv('TYPO3_SITE_URL').TYPO3_mainDir,
+                               $workspaceRec['title'],
+                               $workspaceRec['uid'],
+                               $table.':'.$id,
+                               $newStage,
+                               $comment,
+                               $this->BE_USER->user['realName'],
+                               $this->BE_USER->user['username']);
+
+                               t3lib_div::plainMailEncoded(
+                                       implode(',',$emails),
+                                       'TYPO3 Workspace Note: Stage Change for '.$table.':'.$id,
+                                       trim($message)
+                               );
+                       }
+               }
+       }
+
+       /**
+        * Return emails addresses of be_users from input list.
+        *
+        * @param       string          List of backend users, on the form "be_users_10,be_users_2" or "10,2" in case noTablePrefix is set.
+        * @param       boolean         If set, the input list are integers and not strings.
+        * @return      array           Array of emails
+        */
+       function notifyStageChange_getEmails($listOfUsers,$noTablePrefix=FALSE) {
+               $users = t3lib_div::trimExplode(',',$listOfUsers,1);
+               $emails = array();
+               foreach($users as $userIdent)   {
+                       if ($noTablePrefix)     {
+                               $id = intval($userIdent);
+                       } else {
+                               list($table,$id) = t3lib_div::revExplode('_',$userIdent,2);
+                       }
+                       if ($table==='be_users' || $noTablePrefix)      {
+                               if ($userRecord = t3lib_BEfunc::getRecord('be_users', $id, 'email'))    {
+                                       if (strlen(trim($userRecord['email']))) {
+                                               $emails[$id] = $userRecord['email'];
+                                       }
+                               }
+                       }
+               }
+               return $emails;
+       }
+
 
 
 
index 4869722..4ceeaad 100755 (executable)
  *
  *
  *
- *  133: class t3lib_userAuthGroup extends t3lib_userAuth
+ *  134: class t3lib_userAuthGroup extends t3lib_userAuth
  *
  *              SECTION: Permission checking functions:
- *  197:     function isAdmin()
- *  209:     function isMemberOfGroup($groupId)
- *  231:     function doesUserHaveAccess($row,$perms)
- *  248:     function isInWebMount($id,$readPerms='',$exitOnError=0)
- *  275:     function modAccess($conf,$exitOnError)
- *  326:     function getPagePermsClause($perms)
- *  365:     function calcPerms($row)
- *  403:     function isRTE()
- *  437:     function check($type,$value)
- *  454:     function checkAuthMode($table,$field,$value,$authMode)
- *  520:     function checkLanguageAccess($langValue)
- *  541:     function recordEditAccessInternals($table,$idOrRow)
- *  606:     function isPSet($lCP,$table,$type='')
- *  623:     function mayMakeShortcut()
- *  637:     function workspaceCannotEditRecord($table,$rec)
- *  670:     function workspaceAllowLiveRecordsInPID($pid, $table)
- *  691:     function workspaceCreateNewRecord($pid, $table)
- *  710:     function workspaceAllowAutoCreation($table,$id,$recpid)
- *  730:     function workspaceCheckStageForCurrent($stage)
- *  753:     function workspacePublishAccess($wsid)
- *  777:     function workspaceSwapAccess()
- *  789:     function workspaceVersioningTypeAccess($type)
- *  816:     function workspaceVersioningTypeGetClosest($type)
+ *  198:     function isAdmin()
+ *  210:     function isMemberOfGroup($groupId)
+ *  232:     function doesUserHaveAccess($row,$perms)
+ *  249:     function isInWebMount($id,$readPerms='',$exitOnError=0)
+ *  276:     function modAccess($conf,$exitOnError)
+ *  327:     function getPagePermsClause($perms)
+ *  366:     function calcPerms($row)
+ *  404:     function isRTE()
+ *  438:     function check($type,$value)
+ *  455:     function checkAuthMode($table,$field,$value,$authMode)
+ *  521:     function checkLanguageAccess($langValue)
+ *  542:     function recordEditAccessInternals($table,$idOrRow)
+ *  607:     function isPSet($lCP,$table,$type='')
+ *  624:     function mayMakeShortcut()
+ *  638:     function workspaceCannotEditRecord($table,$recData)
+ *  676:     function workspaceCannotEditOfflineVersion($table,$recData)
+ *  699:     function workspaceAllowLiveRecordsInPID($pid, $table)
+ *  720:     function workspaceCreateNewRecord($pid, $table)
+ *  739:     function workspaceAllowAutoCreation($table,$id,$recpid)
+ *  759:     function workspaceCheckStageForCurrent($stage)
+ *  782:     function workspacePublishAccess($wsid)
+ *  806:     function workspaceSwapAccess()
+ *  818:     function workspaceVersioningTypeAccess($type)
+ *  845:     function workspaceVersioningTypeGetClosest($type)
  *
  *              SECTION: Miscellaneous functions
- *  857:     function getTSConfig($objectString,$config='')
- *  883:     function getTSConfigVal($objectString)
- *  895:     function getTSConfigProp($objectString)
- *  907:     function inList($in_list,$item)
- *  918:     function returnWebmounts()
- *  928:     function returnFilemounts()
+ *  886:     function getTSConfig($objectString,$config='')
+ *  912:     function getTSConfigVal($objectString)
+ *  924:     function getTSConfigProp($objectString)
+ *  936:     function inList($in_list,$item)
+ *  947:     function returnWebmounts()
+ *  957:     function returnFilemounts()
  *
  *              SECTION: Authentication methods
- *  959:     function fetchGroupData()
- * 1092:     function fetchGroups($grList,$idList='')
- * 1179:     function setCachedList($cList)
- * 1199:     function addFileMount($title, $altTitle, $path, $webspace, $type)
- * 1246:     function addTScomment($str)
+ *  988:     function fetchGroupData()
+ * 1121:     function fetchGroups($grList,$idList='')
+ * 1208:     function setCachedList($cList)
+ * 1228:     function addFileMount($title, $altTitle, $path, $webspace, $type)
+ * 1275:     function addTScomment($str)
  *
  *              SECTION: Workspaces
- * 1282:     function workspaceInit()
- * 1325:     function checkWorkspace($wsRec,$fields='uid,title,adminusers,members,reviewers')
- * 1399:     function checkWorkspaceCurrent()
- * 1412:     function setWorkspace($workspaceId)
- * 1440:     function setWorkspacePreview($previewState)
- * 1450:     function getDefaultWorkspace()
+ * 1311:     function workspaceInit()
+ * 1354:     function checkWorkspace($wsRec,$fields='uid,title,adminusers,members,reviewers,publish_access,stagechg_notification')
+ * 1428:     function checkWorkspaceCurrent()
+ * 1441:     function setWorkspace($workspaceId)
+ * 1469:     function setWorkspacePreview($previewState)
+ * 1479:     function getDefaultWorkspace()
  *
  *              SECTION: Logging
- * 1501:     function writelog($type,$action,$error,$details_nr,$details,$data,$tablename='',$recuid='',$recpid='',$event_pid=-1,$NEWid='',$userId=0)
- * 1533:     function simplelog($message, $extKey='', $error=0)
- * 1554:     function checkLogFailures($email, $secondsBack=3600, $max=3)
+ * 1530:     function writelog($type,$action,$error,$details_nr,$details,$data,$tablename='',$recuid='',$recpid='',$event_pid=-1,$NEWid='',$userId=0)
+ * 1562:     function simplelog($message, $extKey='', $error=0)
+ * 1583:     function checkLogFailures($email, $secondsBack=3600, $max=3)
  *
- * TOTAL FUNCTIONS: 43
+ * TOTAL FUNCTIONS: 44
  * (This index is automatically created/updated by the extension "extdeveval")
  *
  */
@@ -665,6 +666,14 @@ class t3lib_userAuthGroup extends t3lib_userAuth {
                }
        }
 
+       /**
+        * Evaluates if a user is allowed to edit the offline version
+        *
+        * @param       string          Table of record
+        * @param       array           Integer (record uid) or array where fields are at least: pid, t3ver_wsid, t3ver_stage (if versioningWS is set)
+        * @return      string          String error code, telling the failure state. FALSE=All ok
+        * @see workspaceCannotEditRecord()
+        */
        function workspaceCannotEditOfflineVersion($table,$recData)     {
                if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'])    {
 
@@ -784,7 +793,7 @@ class t3lib_userAuthGroup extends t3lib_userAuth {
                                        return $this->checkWorkspace(0) ? TRUE : FALSE; // If access to Live workspace, no problem.
                                break;
                                default:        // Custom workspace
-                                       return $wsAccess['_ACCESS'] === 'owner' || $this->checkWorkspace(0);    // Either be an adminuser OR have access to online workspace which is OK as well.
+                                       return $wsAccess['_ACCESS'] === 'owner' || ($this->checkWorkspace(0) && !($wsAccess['publish_access']&2));      // Either be an adminuser OR have access to online workspace which is OK as well as long as publishing access is not limited by workspace option.
                                break;
                        }
                } else return FALSE;    // If no access to workspace, of course you cannot publish!
@@ -1340,10 +1349,10 @@ class t3lib_userAuthGroup extends t3lib_userAuth {
         * Checking if a workspace is allowed for backend user
         *
         * @param       mixed           If integer, workspace record is looked up, if array it is seen as a Workspace record with at least uid, title, members and adminusers columns. Can be faked for workspaces uid 0 and -1 (online and offline)
-        * @param       string          List of fields to select. Default fields are: uid,title,adminusers,members,reviewers
+        * @param       string          List of fields to select. Default fields are: uid,title,adminusers,members,reviewers,publish_access,stagechg_notification
         * @return      array           TRUE if access. Output will also show how access was granted. Admin users will have a true output regardless of input.
         */
-       function checkWorkspace($wsRec,$fields='uid,title,adminusers,members,reviewers')        {
+       function checkWorkspace($wsRec,$fields='uid,title,adminusers,members,reviewers,publish_access,stagechg_notification')   {
 
                        // If not array, look up workspace record:
                if (!is_array($wsRec))  {
index e755c31..fdf143b 100755 (executable)
@@ -210,6 +210,8 @@ CREATE TABLE sys_workspace (
   vtypes tinyint(3) DEFAULT '0' NOT NULL,
   disable_autocreate tinyint(3) DEFAULT '0' NOT NULL,
   swap_modes tinyint(3) DEFAULT '0' NOT NULL,
+  publish_access tinyint(3) DEFAULT '0' NOT NULL,
+  stagechg_notification tinyint(3) DEFAULT '0' NOT NULL,
 
   PRIMARY KEY (uid),
   KEY parent (pid)
index 27f6c2c..cf99954 100755 (executable)
@@ -846,9 +846,30 @@ $TCA['sys_workspace'] = Array (
                                ),
                        )
                ),
+               'publish_access' => Array (
+                       'label' => 'Publish access:',
+                       'config' => Array (
+                               'type' => 'check',
+                               'items' => Array (
+                                       Array('Publish only content in publish stage',0),
+                                       Array('Only workspace owner can publish',0),
+                               ),
+                       )
+               ),
+               'stagechg_notification' => Array (
+                       'label' => 'Stage change notification by email:',
+                       'config' => Array (
+                               'type' => 'select',
+                               'items' => Array (
+                                       Array('',0),
+                                       Array('Notify users on next stage only',1),
+                                       Array('Notify all users on any change',10)
+                               ),
+                       )
+               ),
        ),
        'types' => Array (
-               '0' => Array('showitem' => 'title,description,--div--,adminusers,members,reviewers,--div--,db_mountpoints,file_mountpoints,--div--,publish_time,unpublish_time,--div--,freeze,live_edit,disable_autocreate,swap_modes,vtypes')
+               '0' => Array('showitem' => 'title,description,--div--,adminusers,members,reviewers,stagechg_notification,--div--,db_mountpoints,file_mountpoints,--div--,publish_time,unpublish_time,--div--,freeze,live_edit,disable_autocreate,swap_modes,vtypes,publish_access')
        )
 );
 
index bd6bee0..902d101 100755 (executable)
@@ -84,8 +84,17 @@ class wslib {
 
                if ($wsid>=-1 && $wsid!==0)     {
 
+                               // Define stage to select:
+                       $stage = -99;
+                       if ($wsid>0)    {
+                               $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace',$wsid);
+                               if ($workspaceRec['publish_access']&1)  {
+                                       $stage = 10;
+                               }
+                       }
+
                                // Select all versions to swap:
-                       $versions = $this->selectVersionsInWorkspace($wsid);
+                       $versions = $this->selectVersionsInWorkspace($wsid,0,$stage);
 
                                // Traverse the selection to build CMD array:
                        foreach($versions as $table => $records)        {
@@ -112,7 +121,7 @@ class wslib {
         * @param       integer         Lifecycle filter: 1 = select all drafts (never-published), 2 = select all published one or more times (archive/multiple), anything else selects all.
         * @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 REAL pid of the online record is found as "realpid"
         */
-       function selectVersionsInWorkspace($wsid,$filter=0)     {
+       function selectVersionsInWorkspace($wsid,$filter=0,$stage=-99)  {
                global $TCA;
 
                $wsid = intval($wsid);
@@ -131,6 +140,7 @@ class wslib {
                                        'A.pid=-1'.     // Table A is the offline version and pid=-1 defines offline
                                                ($wsid>-98 ? ' AND A.t3ver_wsid='.$wsid : ($wsid===-98 ? ' AND A.t3ver_wsid!=0' : '')). // For "real" workspace numbers, select by that. If = -98, select all that are NOT online (zero). Anything else below -1 will not select on the wsid and therefore select all!
                                                ($filter===1 ? ' AND A.t3ver_count=0' : ($filter===2 ? ' AND A.t3ver_count>0' : '')).   // lifecycle filter: 1 = select all drafts (never-published), 2 = select all published one or more times (archive/multiple)
+                                               ($stage!=-99 ? ' AND A.t3ver_stage='.intval($stage) : '').
                                                ' AND B.pid>=0'.        // Table B (online) must have PID >= 0 to signify being online.
                                                ' AND A.t3ver_oid=B.uid'.       // ... and finally the join between the two tables.
                                                t3lib_BEfunc::deleteClause($table,'A').
index e1d9144..18858d5 100755 (executable)
@@ -121,7 +121,7 @@ class SC_mod_user_ws_index extends t3lib_SCbase {
        var $targets = array();         // Accumulation of online targets.
        var $pageModule = '';
        var $publishAccess = FALSE;
-
+       var $be_user_Array = array();
 
 
        /*********************************
@@ -349,9 +349,9 @@ class SC_mod_user_ws_index extends t3lib_SCbase {
                $actionLinks = '';
                if ($GLOBALS['BE_USER']->workspace!==0) {
                        if ($this->publishAccess)       {
-                               $actionLinks.= '<input type="submit" name="_publish" value="Publish workspace" onclick="return confirm(\'Are you sure you want to publish all content of the workspace?\');"/>';
+                               $actionLinks.= '<input type="submit" name="_publish" value="Publish workspace" onclick="return confirm(\'Are you sure you want to publish all content '.($GLOBALS['BE_USER']->workspaceRec['publish_access']&1 ? 'in &quot;Publish&quot; stage ':'').'from the workspace?\');"/>';
                                if ($GLOBALS['BE_USER']->workspaceSwapAccess()) {
-                                       $actionLinks.= '<input type="submit" name="_swap" value="Swap workspace" onclick="return confirm(\'Are you sure you want to publish (swap) all content of the workspace?\');" />';
+                                       $actionLinks.= '<input type="submit" name="_swap" value="Swap workspace" onclick="return confirm(\'Are you sure you want to publish (swap) all content '.($GLOBALS['BE_USER']->workspaceRec['publish_access']&1 ? 'in &quot;Publish&quot; stage ':'').'from the workspace?\');" />';
                                }
                        } else {
                                $actionLinks.= $this->doc->icons(1).'You are not permitted to publish from this workspace';
@@ -417,6 +417,12 @@ class SC_mod_user_ws_index extends t3lib_SCbase {
                        // Initialize variables:
                $this->showWorkspaceCol = $GLOBALS['BE_USER']->workspace===0 && $this->MOD_SETTINGS['display']<=-98;
 
+                       // Get usernames and groupnames
+               $be_group_Array = t3lib_BEfunc::getListGroupNames('title,uid');
+               $groupArray = array_keys($be_group_Array);
+               $this->be_user_Array = t3lib_BEfunc::getUserNames();
+               if (!$GLOBALS['BE_USER']->isAdmin())            $this->be_user_Array = t3lib_BEfunc::blindUserNames($this->be_user_Array,$groupArray,1);
+
                        // Initialize Workspace ID and filter-value:
                if ($GLOBALS['BE_USER']->workspace===0) {
                        $wsid = $this->MOD_SETTINGS['display'];         // Set wsid to the value from the menu (displaying content of other workspaces)
@@ -633,7 +639,7 @@ class SC_mod_user_ws_index extends t3lib_SCbase {
                                                                        $tableRows[] = '
                                                                                <tr class="bgColor4">
                                                                                        '.$mainCell.$verCell.'
-                                                                                       <td nowrap="nowrap">'.$this->displayWorkspaceOverview_stageCmd($table,$rec_off).'</td>
+                                                                                       <td nowrap="nowrap">'.$this->showStageChangeLog($table,$rec_off['uid'],$this->displayWorkspaceOverview_stageCmd($table,$rec_off)).'</td>
                                                                                        <td nowrap="nowrap" class="'.$swapClass.'">'.
                                                                                                $this->displayWorkspaceOverview_commandLinks($table,$rec_on,$rec_off,$vType).
                                                                                                htmlspecialchars($swapLabel).
@@ -687,11 +693,15 @@ class SC_mod_user_ws_index extends t3lib_SCbase {
                                $sId = 1;
                                $sLabel = 'Editing';
                                $color = '#666666';
+                               $label = 'Comment for Reviewer:';
+                               $titleAttrib = 'Send to Review';
                        break;
                        case 1:
                                $sId = 10;
                                $sLabel = 'Review';
                                $color = '#6666cc';
+                               $label = 'Comment for Publisher:';
+                               $titleAttrib = 'Approve for Publishing';
                        break;
                        case 10:
                                $sLabel = 'Publish';
@@ -701,6 +711,8 @@ class SC_mod_user_ws_index extends t3lib_SCbase {
                                $sLabel = $this->doc->icons(2).'Rejected';
                                $sId = 0;
                                $color = '#ff0000';
+                               $label = 'Comment:';
+                               $titleAttrib = 'Reset stage';
                        break;
                        default:
                                $sLabel = 'Undefined';
@@ -712,13 +724,16 @@ class SC_mod_user_ws_index extends t3lib_SCbase {
 
                $raiseOk = !$GLOBALS['BE_USER']->workspaceCannotEditOfflineVersion($table,$rec_off);
 
-               if ($raiseOk)   {
+               if ($raiseOk && $rec_off['t3ver_stage']!=-1)    {
+                       $onClick = 'var commentTxt=window.prompt("Please explain why you reject:","");
+                                                       if (commentTxt!=null) {document.location="'.$this->doc->issueCommand(
+                                                       '&cmd['.$table.']['.$rec_off['uid'].'][version][action]=setStage'.
+                                                       '&cmd['.$table.']['.$rec_off['uid'].'][version][stageId]=-1'
+                                                       ).'&cmd['.$table.']['.$rec_off['uid'].'][version][comment]="+escape(commentTxt);}'.
+                                                       ' return false;';
                                // Reject:
                        $actionLinks.=
-                               '<a href="'.htmlspecialchars($this->doc->issueCommand(
-                                               '&cmd['.$table.']['.$rec_off['uid'].'][version][action]=setStage'.
-                                               '&cmd['.$table.']['.$rec_off['uid'].'][version][stageId]=-1'
-                                               )).'">'.
+                               '<a href="#" onclick="'.htmlspecialchars($onClick).'">'.
                                '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/down.gif','width="14" height="14"').' alt="" align="top" title="Reject" />'.
                                '</a>';
                } else {
@@ -731,13 +746,16 @@ class SC_mod_user_ws_index extends t3lib_SCbase {
 
                        // Raise
                if ($raiseOk)   {
-                       if ($rec_off['t3ver_stage']!=10)        {
-                               $actionLinks.=
-                                       '<a href="'.htmlspecialchars($this->doc->issueCommand(
+                       $onClick = 'var commentTxt=window.prompt("'.$label.'","");
+                                                       if (commentTxt!=null) {document.location="'.$this->doc->issueCommand(
                                                        '&cmd['.$table.']['.$rec_off['uid'].'][version][action]=setStage'.
                                                        '&cmd['.$table.']['.$rec_off['uid'].'][version][stageId]='.$sId
-                                                       )).'">'.
-                                       '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/up.gif','width="14" height="14"').' alt="" align="top" title="Raise" />'.
+                                                       ).'&cmd['.$table.']['.$rec_off['uid'].'][version][comment]="+escape(commentTxt);}'.
+                                                       ' return false;';
+                       if ($rec_off['t3ver_stage']!=10)        {
+                               $actionLinks.=
+                                       '<a href="#" onclick="'.htmlspecialchars($onClick).'">'.
+                                       '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/up.gif','width="14" height="14"').' alt="" align="top" title="'.htmlspecialchars($titleAttrib).'" />'.
                                        '</a>';
                        }
                }
@@ -754,7 +772,7 @@ class SC_mod_user_ws_index extends t3lib_SCbase {
         * @return      string          HTML content, mainly link tags and images.
         */
        function displayWorkspaceOverview_commandLinks($table,&$rec_on,&$rec_off,$vType)        {
-               if ($this->publishAccess)       {
+               if ($this->publishAccess && (!($GLOBALS['BE_USER']->workspaceRec['publish_access']&1) || (int)$rec_off['t3ver_stage']===10))    {
                        $actionLinks =
                                '<a href="'.htmlspecialchars($this->doc->issueCommand(
                                                '&cmd['.$table.']['.$rec_on['uid'].'][version][action]=swap'.
@@ -1385,6 +1403,46 @@ class SC_mod_user_ws_index extends t3lib_SCbase {
                }
        }
 
+       function showStageChangeLog($table,$id,$stageCommands)  {
+               $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                       'log_data,tstamp,userid',
+                       'sys_log',
+                       'action=6
+                               AND tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($table,'sys_log').'
+                               AND recuid='.intval($id)
+               );
+
+               $entry = array();
+               foreach($rows as $dat)  {
+                       $data = unserialize($dat['log_data']);
+                       $username = $this->be_user_Array[$dat['userid']] ? $this->be_user_Array[$dat['userid']]['username'] : '['.$dat['userid'].']';
+
+                       switch($data['stage'])  {
+                               case 1:
+                                       $text = 'sent element to "Review"';
+                               break;
+                               case 10:
+                                       $text = 'approved for "Publish"';
+                               break;
+                               case -1:
+                                       $text = 'rejected element!';
+                               break;
+                               case 0:
+                                       $text = 'reset to "Editing"';
+                               break;
+                               default:
+                                       $text = '[undefined]';
+                               break;
+                       }
+                       $text = t3lib_BEfunc::dateTime($dat['tstamp']).': "'.$username.'" '.$text;
+                       $text.= ($data['comment']?'<br/>User Comment: <em>'.$data['comment'].'</em>':'');
+
+                       $entry[] = $text;
+               }
+
+               return count($entry) ? '<span onmouseover="document.getElementById(\'log_'.$table.$id.'\').style.visibility = \'visible\';" onmouseout="document.getElementById(\'log_'.$table.$id.'\').style.visibility = \'hidden\';">'.$stageCommands.' ('.count($entry).')</span>'.
+                               '<div class="logLayer" style="visibility: hidden; position: absolute;" id="log_'.$table.$id.'">'.implode('<hr/>',$entry).'</div>' : $stageCommands;
+       }
 
 
 
index 423b6d1..6e1e356 100755 (executable)
@@ -464,7 +464,7 @@ TABLE.ver-subtree A { text-decoration: none; }
 TABLE.ver-subtree TR TD.iconTitle { white-space: nowrap; }
 TABLE.ver-subtree TR TD.diffCell { white-space: normal; }
 TABLE.ver-subtree TABLE.diffTable {background-color: white; border: 1px solid black; }
-TABLE.ver-subtree DIV.diffLayer { background-color: white; width:400px; border: 1px solid black;}
+TABLE.ver-subtree DIV.diffLayer, DIV.logLayer { background-color: white; width:400px; border: 1px solid black;}
 TABLE.ver-subtree TR TD IMG { vertical-align: middle; }
 TABLE.ver-subtree, TABLE.ver-verElement TD.c-diffCell { border: 1px solid #666666; }
 TABLE.ver-verElement { width: 100%;}
index b617f22..7cedb5b 100755 (executable)
                        <label index="reviewers.description">Content have to pass through reviewers approval before it can finally be published by a workspace owner. Reviewers can be backend users or groups and will have access to the workspace just as members have, but can in addition approve content for final publication.</label>
                        <label index="reviewers.details">In case you need no review layer between editors (normally workspace &quot;members&quot;) and the workspace owners what you simply do is to add all editors as reviewers. This give them access to raise content all the way to the workspace owners. Since content is by default raised from editing to review to publish state it even gives the possibility of informal &quot;four-eye&quot; review where workspace owners can require that content has  been raised by action from two different reviewers.</label>
                        <label index="_reviewers.seeAlso">sys_workspace:members</label>
+                       <label index="stagechg_notification.description">When the stage of content changes, users in the workspace can receive a notification by email. Only members/reviewers who are attached to the workspace as users and not through their groups will be notified.</label>
+                       <label index="stagechg_notification.details">&quot;Notify users on next stage only&quot;: When content is raised from &quot;Editing&quot; to &quot;Review&quot;, reviewers are notified. When content is raised to &quot;Publish&quot;, owners are notified. When content is rejected, members and reviewers are notified. When content is raised from rejected state, members are notified.
+
+&quot;Notify all users on any change&quot; : All users in workspace are notified regardless of change.</label>
                        <label index="db_mountpoints.description">If one or more DB mounts are specified the page tree of the backend will be locked into these root points during work in the workspace.</label>
                        <label index="db_mountpoints.details">Any DB mount specified here must be inside the DB mount set for the backend user who logs in. If that is not the case the workspace DB mount will simply not be mounted for the user. If no DB mounts are specified for the workspace the users will access the DB mounts of their user profile.</label>
                        <label index="file_mountpoints.description">Filemounts available for workspace users. Please see details for security information!</label>
@@ -46,6 +50,9 @@ Another mode &quot;Swap-Into-Workspace on Auto-publish&quot; will force the auto
 &quot;Branch&quot; versioning is where a page is versionized and all subpages and content is copied along. This can have quite heavy implications on data duplication and is recommended only in special circumstances.
 
 More information about versioning types can be read in &quot;Inside TYPO3&quot;</label>
+                       <label index="publish_access.description">Refines the rules of publishing</label>
+                       <label index="publish_access.details">&quot;Publish only content in publish stage&quot; : Only when content is in publish stage can it be published. 
+&quot;Only workspace owner can publish&quot; : Only the workspace owner can publish the content in the workspace, even if members or reviewers have access to the Live workspace.</label>
                </languageKey>
                <languageKey index="dk">EXT:csh_dk/lang/dk.locallang_csh_sysws.xml</languageKey>
                <languageKey index="nl">EXT:csh_nl/lang/nl.locallang_csh_sysws.xml</languageKey>
index c5864bc..921db8a 100755 (executable)
@@ -279,10 +279,11 @@ class tx_version_cm1 extends t3lib_SCbase {
                        Label: <input type="text" name="cmd['.$this->table.']['.$this->uid.'][version][label]" /><br/>
                        '.($this->table == 'pages' ? '<select name="cmd['.$this->table.']['.$this->uid.'][version][treeLevels]">
                                <option value="0">Page: Page + content</option>
-                               <option value="1">Branch: 1 level</option>
+<!--                           <option value="1">Branch: 1 level</option>
                                <option value="2">Branch: 2 levels</option>
                                <option value="3">Branch: 3 levels</option>
-                               <option value="4">Branch: 4 levels</option>
+                               <option value="4">Branch: 4 levels</option> -->
+                               <option value="100">Branch: All subpages</option>
                                <option value="-1">Element: Just record</option>
                        </select>' : '').'
                        <br/><input type="hidden" name="cmd['.$this->table.']['.$this->uid.'][version][action]" value="new" />