Index: t3lib/class.t3lib_tcemain.php =================================================================== --- t3lib/class.t3lib_tcemain.php (revision 6108) +++ t3lib/class.t3lib_tcemain.php (working copy) @@ -2731,100 +2731,109 @@ } // Now, the $uid is the actual record we will copy while $origUid is the record we asked to get copied - but that could be a live version. */ + + if ($this->doesRecordExist($table,$uid,'show')) { // This checks if the record can be selected which is all that a copy action requires. - $data = Array(); + if( $this->BE_USER->recordEditAccessInternals($table,$uid, false, false, true ) ) { //Used to check language and general editing rights + $data = Array(); - $nonFields = array_unique(t3lib_div::trimExplode(',','uid,perms_userid,perms_groupid,perms_user,perms_group,perms_everybody,t3ver_oid,t3ver_wsid,t3ver_id,t3ver_label,t3ver_state,t3ver_swapmode,t3ver_count,t3ver_stage,t3ver_tstamp,'.$excludeFields,1)); + $nonFields = array_unique(t3lib_div::trimExplode(',','uid,perms_userid,perms_groupid,perms_user,perms_group,perms_everybody,t3ver_oid,t3ver_wsid,t3ver_id,t3ver_label,t3ver_state,t3ver_swapmode,t3ver_count,t3ver_stage,t3ver_tstamp,'.$excludeFields,1)); - // $row = $this->recordInfo($table,$uid,'*'); - $row = t3lib_BEfunc::getRecordWSOL($table,$uid); // So it copies (and localized) content from workspace... - if (is_array($row)) { + // $row = $this->recordInfo($table,$uid,'*'); + $row = t3lib_BEfunc::getRecordWSOL($table,$uid); // So it copies (and localized) content from workspace... + if (is_array($row)) { - // Initializing: - $theNewID = uniqid('NEW'); - $enableField = isset($TCA[$table]['ctrl']['enablecolumns']) ? $TCA[$table]['ctrl']['enablecolumns']['disabled'] : ''; - $headerField = $TCA[$table]['ctrl']['label']; + // Initializing: + $theNewID = uniqid('NEW'); + $enableField = isset($TCA[$table]['ctrl']['enablecolumns']) ? $TCA[$table]['ctrl']['enablecolumns']['disabled'] : ''; + $headerField = $TCA[$table]['ctrl']['label']; - // Getting default data: - $defaultData = $this->newFieldArray($table); + // Getting default data: + $defaultData = $this->newFieldArray($table); - // Getting "copy-after" fields if applicable: - $copyAfterFields = $destPid<0 ? $this->fixCopyAfterDuplFields($table,$uid,abs($destPid),0) : array(); + // Getting "copy-after" fields if applicable: + $copyAfterFields = $destPid<0 ? $this->fixCopyAfterDuplFields($table,$uid,abs($destPid),0) : array(); - // Page TSconfig related: - $tscPID = t3lib_BEfunc::getTSconfig_pidValue($table,$uid,$destPid); // NOT using t3lib_BEfunc::getTSCpid() because we need the real pid - not the ID of a page, if the input is a page... - $TSConfig = $this->getTCEMAIN_TSconfig($tscPID); - $tE = $this->getTableEntries($table,$TSConfig); + // Page TSconfig related: + $tscPID = t3lib_BEfunc::getTSconfig_pidValue($table,$uid,$destPid); // NOT using t3lib_BEfunc::getTSCpid() because we need the real pid - not the ID of a page, if the input is a page... + $TSConfig = $this->getTCEMAIN_TSconfig($tscPID); + $tE = $this->getTableEntries($table,$TSConfig); - // Traverse ALL fields of the selected record: - foreach($row as $field => $value) { - if (!in_array($field,$nonFields)) { + // Traverse ALL fields of the selected record: + foreach($row as $field => $value) { + if (!in_array($field,$nonFields)) { - // Get TCA configuration for the field: - $conf = $TCA[$table]['columns'][$field]['config']; + // Get TCA configuration for the field: + $conf = $TCA[$table]['columns'][$field]['config']; - // Preparation/Processing of the value: - if ($field=='pid') { // "pid" is hardcoded of course: - $value = $destPid; - } elseif (isset($overrideValues[$field])) { // Override value... - $value = $overrideValues[$field]; - } elseif (isset($copyAfterFields[$field])) { // Copy-after value if available: - $value = $copyAfterFields[$field]; - } elseif ($TCA[$table]['ctrl']['setToDefaultOnCopy'] && t3lib_div::inList($TCA[$table]['ctrl']['setToDefaultOnCopy'],$field)) { // Revert to default for some fields: - $value = $defaultData[$field]; - } else { - // Hide at copy may override: - if ($first && $field==$enableField && $TCA[$table]['ctrl']['hideAtCopy'] && !$this->neverHideAtCopy && !$tE['disableHideAtCopy']) { - $value=1; + // Preparation/Processing of the value: + if ($field=='pid') { // "pid" is hardcoded of course: + $value = $destPid; + } elseif (isset($overrideValues[$field])) { // Override value... + $value = $overrideValues[$field]; + } elseif (isset($copyAfterFields[$field])) { // Copy-after value if available: + $value = $copyAfterFields[$field]; + } elseif ($TCA[$table]['ctrl']['setToDefaultOnCopy'] && t3lib_div::inList($TCA[$table]['ctrl']['setToDefaultOnCopy'],$field)) { // Revert to default for some fields: + $value = $defaultData[$field]; + } else { + // Hide at copy may override: + if ($first && $field==$enableField && $TCA[$table]['ctrl']['hideAtCopy'] && !$this->neverHideAtCopy && !$tE['disableHideAtCopy']) { + $value=1; + } + // Prepend label on copy: + if ($first && $field==$headerField && $TCA[$table]['ctrl']['prependAtCopy'] && !$tE['disablePrependAtCopy']) { + $value = $this->getCopyHeader($table,$this->resolvePid($table,$destPid),$field,$this->clearPrefixFromValue($table,$value),0); + } + // Processing based on the TCA config field type (files, references, flexforms...) + $value = $this->copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $tscPID, $language); } - // Prepend label on copy: - if ($first && $field==$headerField && $TCA[$table]['ctrl']['prependAtCopy'] && !$tE['disablePrependAtCopy']) { - $value = $this->getCopyHeader($table,$this->resolvePid($table,$destPid),$field,$this->clearPrefixFromValue($table,$value),0); - } - // Processing based on the TCA config field type (files, references, flexforms...) - $value = $this->copyRecord_procBasedOnFieldType($table, $uid, $field, $value, $row, $conf, $tscPID, $language); + + // Add value to array. + $data[$table][$theNewID][$field] = $value; } + } + // Overriding values: + if ($TCA[$table]['ctrl']['editlock']) { + $data[$table][$theNewID][$TCA[$table]['ctrl']['editlock']] = 0; + } - // Add value to array. - $data[$table][$theNewID][$field] = $value; + // Setting original UID: + if ($TCA[$table]['ctrl']['origUid']) { + $data[$table][$theNewID][$TCA[$table]['ctrl']['origUid']] = $uid; } - } - // Overriding values: - if ($TCA[$table]['ctrl']['editlock']) { - $data[$table][$theNewID][$TCA[$table]['ctrl']['editlock']] = 0; - } + // Do the copy by simply submitting the array through TCEmain: + $copyTCE = t3lib_div::makeInstance('t3lib_TCEmain'); + /* @var $copyTCE t3lib_TCEmain */ + $copyTCE->stripslashes_values = 0; + $copyTCE->copyTree = $this->copyTree; + $copyTCE->cachedTSconfig = $this->cachedTSconfig; // Copy forth the cached TSconfig + $copyTCE->dontProcessTransformations=1; // Transformations should NOT be carried out during copy - // Setting original UID: - if ($TCA[$table]['ctrl']['origUid']) { - $data[$table][$theNewID][$TCA[$table]['ctrl']['origUid']] = $uid; - } + $copyTCE->start($data,'',$this->BE_USER); + $copyTCE->process_datamap(); - // Do the copy by simply submitting the array through TCEmain: - $copyTCE = t3lib_div::makeInstance('t3lib_TCEmain'); - /* @var $copyTCE t3lib_TCEmain */ - $copyTCE->stripslashes_values = 0; - $copyTCE->copyTree = $this->copyTree; - $copyTCE->cachedTSconfig = $this->cachedTSconfig; // Copy forth the cached TSconfig - $copyTCE->dontProcessTransformations=1; // Transformations should NOT be carried out during copy + // Getting the new UID: + $theNewSQLID = $copyTCE->substNEWwithIDs[$theNewID]; + if ($theNewSQLID) { + $this->copyRecord_fixRTEmagicImages($table,t3lib_BEfunc::wsMapId($table,$theNewSQLID)); + $this->copyMappingArray[$table][$origUid] = $theNewSQLID; + } - $copyTCE->start($data,'',$this->BE_USER); - $copyTCE->process_datamap(); + // Copy back the cached TSconfig + $this->cachedTSconfig = $copyTCE->cachedTSconfig; + $this->errorLog = array_merge($this->errorLog,$copyTCE->errorLog); + unset($copyTCE); - // Getting the new UID: - $theNewSQLID = $copyTCE->substNEWwithIDs[$theNewID]; - if ($theNewSQLID) { - $this->copyRecord_fixRTEmagicImages($table,t3lib_BEfunc::wsMapId($table,$theNewSQLID)); - $this->copyMappingArray[$table][$origUid] = $theNewSQLID; - } + if($language == 0) { + //repointing the new translation records to the parent record we just created + $overrideValues[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $theNewSQLID; + $this->copyL10nOverlayRecords($table, $uid, $destPid, $first, $overrideValues, $excludeFields=''); + } - // Copy back the cached TSconfig - $this->cachedTSconfig = $copyTCE->cachedTSconfig; - $this->errorLog = array_merge($this->errorLog,$copyTCE->errorLog); - unset($copyTCE); - - return $theNewSQLID; - } else $this->log($table,$uid,3,0,1,'Attempt to copy record that did not exist!'); + return $theNewSQLID; + } else $this->log($table,$uid,3,0,1,'Attempt to copy record that did not exist!'); + } else $this->log($table,$uid,3,0,1,'Attempt to copy record without having permissions to do so. ['.$this->BE_USER->errorMsg.'].'); } else $this->log($table,$uid,3,0,1,'Attempt to copy record without permission'); } } @@ -3367,7 +3376,26 @@ + /** + * Find l10n-overlay records and perform the requested move action for these records. + * + * @param string $table: Record Table + * @param string $uid: Record UID + * @param string $destPid: Position to move to + * @return void + */ + function copyL10nOverlayRecords($table, $uid, $destPid, $first=0, $overrideValues=array(), $excludeFields='') { + //there's no need to perform this for page-records + if( $table == 'pages' ) return; + t3lib_div::loadTCA( $table ); + $l10nRecords = t3lib_BEfunc::getRecordsByField( $table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], $uid ); + if( is_array($l10nRecords) ) { + foreach( $l10nRecords as $record ) { + $this->copyRecord($table, $record['uid'], $destPid, $first, $overrideValues, $excludeFields, $record[$GLOBALS['TCA'][$table]['ctrl']['languageField']]); + } + } + } @@ -3377,6 +3405,7 @@ + /********************************************* * * Cmd: Moving, Localizing @@ -3424,7 +3453,7 @@ } // Checking if there is anything else disallowing moving the record by checking if editing is allowed - $mayEditAccess = $this->BE_USER->recordEditAccessInternals($table,$uid); + $mayEditAccess = $this->BE_USER->recordEditAccessInternals($table,$uid, false, false, true ); // If moving is allowed, begin the processing: if ($mayEditAccess) { @@ -3539,6 +3568,14 @@ $newVersion_placeholderFieldArray['t3ver_wsid'] = $this->BE_USER->workspace; // Setting workspace - only so display of place holders can filter out those from other workspaces. $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['label']] = '[MOVE-TO PLACEHOLDER for #'.$uid.', WS#'.$this->BE_USER->workspace.']'; + // moving localized records requires to keep localization-settings for the placeholder too + if( array_key_exists('languageField', $GLOBALS['TCA'][$table]['ctrl']) && array_key_exists('transOrigPointerField', $GLOBALS['TCA'][$table]['ctrl']) ) { + $l10nParentRec = t3lib_BEfunc::getRecord($table,$uid); + $newVersion_placeholderFieldArray[ $GLOBALS['TCA'][$table]['ctrl']['languageField'] ] = $l10nParentRec[ $GLOBALS['TCA'][$table]['ctrl']['languageField'] ]; + $newVersion_placeholderFieldArray[ $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ] = $l10nParentRec[ $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ]; + unset($l10nParentRec); + } + $newVersion_placeholderFieldArray['pid'] = 0; // Initially, create at root level. $id = 'NEW_MOVE_PLH'; $this->insertDB($table,$id,$newVersion_placeholderFieldArray,FALSE); // Saving placeholder as 'original' @@ -3551,6 +3588,9 @@ $updateFields['t3ver_state'] = 4; // Setting placeholder state value for version (so it can know it is currently a new version...) $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid='.intval($wsUid), $updateFields); } + + //check for the localizations of that element and move them as well + $this->moveL10nOverlayRecords($table,$uid,$destPid); } /** @@ -3608,6 +3648,8 @@ $this->moveRecord_procFields($table,$uid,$destPid); // Create query for update: $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid='.intval($uid), $updateFields); + // check for the localizations of that element + $this->moveL10nOverlayRecords($table,$uid,$destPid); // Call post processing hooks: foreach($hookObjectsArr as $hookObj) { @@ -3651,6 +3693,8 @@ $this->moveRecord_procFields($table,$uid,$destPid); // Create query for update: $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid='.intval($uid), $updateFields); + // check for the localizations of that element + $this->moveL10nOverlayRecords($table,$uid,$destPid); // Call post processing hooks: foreach($hookObjectsArr as $hookObj) { @@ -3749,6 +3793,27 @@ } /** + * Find l10n-overlay records and perform the requested move action for these records. + * + * @param string $table: Record Table + * @param string $uid: Record UID + * @param string $destPid: Position to move to + * @return void + */ + function moveL10nOverlayRecords($table,$uid,$destPid) { + //there's no need to perform this for page-records + if( $table == 'pages' ) return; + t3lib_div::loadTCA( $table ); + + $l10nRecords = t3lib_BEfunc::getRecordsByField( $table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], $uid ); + if( is_array($l10nRecords) ) { + foreach( $l10nRecords as $record ) { + $this->moveRecord( $table, $record['uid'], $destPid ); + } + } + } + + /** * Localizes a record to another system language * In reality it only works if transOrigPointerTable is not set. For "pages" the implementation is hardcoded * @@ -4013,6 +4078,7 @@ } else { // Otherwise, try to delete by versioning: $this->versionizeRecord($table,$id,'DELETED!',TRUE); + $this->deleteL10nOverlayRecords($table,$id); } } } @@ -4088,7 +4154,7 @@ global $TCA; // Checking if there is anything else disallowing deleting the record by checking if editing is allowed - $mayEditAccess = $this->BE_USER->recordEditAccessInternals($table, $uid, FALSE, $undeleteRecord); + $mayEditAccess = $this->BE_USER->recordEditAccessInternals($table, $uid, FALSE, $undeleteRecord, TRUE ); $uid = intval($uid); if ($TCA[$table] && $uid) { @@ -4114,10 +4180,14 @@ if ($TCA[$table]['ctrl']['sortby'] && !$undeleteRecord) { $updateFields[$TCA[$table]['ctrl']['sortby']] = 1000000000; } - // before (un-)deleting this record, check for child records or references $this->deleteRecord_procFields($table, $uid, $undeleteRecord); $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid='.intval($uid), $updateFields); + + // delete all l10n records aswell, impossible during undelete because it might bring too many records back to life + if( !$undeleteRecord ) { + $this->deleteL10nOverlayRecords($table, $uid); + } } else { // Fetches all fields with flexforms and look for files to delete: @@ -4149,6 +4219,8 @@ // Delete the hard way...: $GLOBALS['TYPO3_DB']->exec_DELETEquery($table, 'uid='.intval($uid)); + + $this->deleteL10nOverlayRecords($table, $uid); } $state = $undeleteRecord ? 1 : 3; // 1 means insert, 3 means delete @@ -4413,13 +4485,33 @@ } } + /** + * Find l10n-overlay records and perform the requested delete action for these records. + * + * @param string $table: Record Table + * @param string $uid: Record UID + * @param string $destPid: Position to move to + * @return void + */ + function deleteL10nOverlayRecords($table, $uid) { + if( $table == 'pages' ) return; + t3lib_div::loadTCA( $table ); + $l10nRecords = t3lib_BEfunc::getRecordsByField( $table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], $uid ); + if( is_array($l10nRecords) ) { + foreach( $l10nRecords as $record ) { + $this->deleteAction($table, intval($record['t3ver_oid'])>0?intval($record['t3ver_oid']):intval($record['uid'])); + } + } + } + + /********************************************* * * Cmd: Versioning @@ -4620,6 +4712,10 @@ if ($TCA[$table]['ctrl']['sortby']) { $keepFields[] = $TCA[$table]['ctrl']['sortby']; } + // l10n-fields must be kept otherwise the localization will be lost during the publishing + if ( $TCA[$table]['ctrl']['transOrigPointerField'] ) { + $keepFields[] = $TCA[$table]['ctrl']['transOrigPointerField']; + } // Swap "keepfields" foreach($keepFields as $fN) { Index: t3lib/class.t3lib_userauthgroup.php =================================================================== --- t3lib/class.t3lib_userauthgroup.php (revision 6108) +++ t3lib/class.t3lib_userauthgroup.php (working copy) @@ -538,6 +538,39 @@ } /** + * Check if user has access to all existing localizations for a certain record + * + * @param array $record + * @return boolean + */ + function checkFullLanguagesAccess( $table, $record ) { + $recordLocalizationAccess = $this->checkLanguageAccess(0); + if ($recordLocalizationAccess && t3lib_BEfunc::isTableLocalizable($table) ) { + + $pointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']; + + $recordLocalizations = t3lib_BEfunc::getRecordsByField( + $table, + $pointerField, + $record[$pointerField]>0 ? $record[$pointerField] : $record['uid'], + '', //AND '.$GLOBALS['TCA'][$table]['ctrl']['languageField'].'>0 '.($andWhereClause ? ' '.$andWhereClause : ''), + '', + '', + '1' + ); + + if(is_array($recordLocalizations)) { + foreach($recordLocalizations as $localization) { + $recordLocalizationAccess = $recordLocalizationAccess && $this->checkLanguageAccess( $localization[$GLOBALS['TCA'][$table]['ctrl']['languageField']] ); + if(!$recordLocalizationAccess) break; + } + } + + } + return $recordLocalizationAccess; + } + + /** * Checking if a user has editing access to a record from a $TCA table. * The checks does not take page permissions and other "environmental" things into account. It only deal with record internals; If any values in the record fields disallows it. * For instance languages settings, authMode selector boxes are evaluated (and maybe more in the future). @@ -550,7 +583,7 @@ * @param boolean Set, if testing a deleted record array. * @return boolean True if OK, otherwise false */ - function recordEditAccessInternals($table, $idOrRow, $newRecord = FALSE, $deletedRecord = FALSE) { + function recordEditAccessInternals($table, $idOrRow, $newRecord = FALSE, $deletedRecord = FALSE, $checkFullLanguageAccess = FALSE) { global $TCA; if (isset($TCA[$table])) { @@ -578,6 +611,9 @@ if (!$this->checkLanguageAccess($idOrRow[$TCA[$table]['ctrl']['languageField']])) { $this->errorMsg = 'ERROR: Language was not allowed.'; return FALSE; + } elseif( $checkFullLanguageAccess && !$this->checkFullLanguagesAccess($table, $idOrRow ) ) { + $this->errorMsg = 'ERROR: Related/affected language was not allowed.'; + return FALSE; } } else { $this->errorMsg = 'ERROR: The "languageField" field named "'.$TCA[$table]['ctrl']['languageField'].'" was not found in testing record!'; Index: typo3/class.db_list_extra.inc =================================================================== --- typo3/class.db_list_extra.inc (revision 6108) +++ typo3/class.db_list_extra.inc (working copy) @@ -1322,17 +1322,17 @@ $cells=array(); $cells['pasteAfter'] = $cells['pasteInto'] = $this->spaceIcon; - + $isL10nOverlay = $this->localizationView && $row[$TCA[$table]['ctrl']['transOrigPointerField']]!=0; // Return blank, if disabled: // Whether a numeric clipboard pad is active or the normal pad we will see different content of the panel: if ($this->clipObj->current=='normal') { // For the "Normal" pad: // Show copy/cut icons: $isSel = (string)$this->clipObj->isSelected($table,$row['uid']); - $cells['copy']=''. + $cells['copy']= $isL10nOverlay ? $this->spaceIcon : ''. 'backPath,'gfx/clip_copy'.($isSel=='copy'?'_h':'').'.gif','width="12" height="12"').' title="'.$LANG->sL('LLL:EXT:lang/locallang_core.php:cm.copy',1).'" alt="" />'. ''; - $cells['cut']=''. + $cells['cut']= $isL10nOverlay ? $this->spaceIcon : ''. 'backPath,'gfx/clip_cut'.($isSel=='cut'?'_h':'').'.gif','width="12" height="12"').' title="'.$LANG->sL('LLL:EXT:lang/locallang_core.php:cm.cut',1).'" alt="" />'. ''; @@ -1355,7 +1355,7 @@ } // Adding the checkbox to the panel: - $cells['select']=''; + $cells['select']= $isL10nOverlay ? $this->spaceIcon : ''; } // Now, looking for selected elements from the current table: