Index: t3lib/class.t3lib_tcemain.php =================================================================== --- t3lib/class.t3lib_tcemain.php (revision 6145) +++ t3lib/class.t3lib_tcemain.php (working copy) @@ -2733,99 +2733,106 @@ // 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'); } } @@ -3368,7 +3375,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']]); + } + } + } @@ -3378,6 +3404,7 @@ + /********************************************* * * Cmd: Moving, Localizing @@ -3425,7 +3452,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) { @@ -3540,6 +3567,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' @@ -3552,6 +3587,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 ); } /** @@ -3609,6 +3647,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) { @@ -3652,6 +3692,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) { @@ -3750,6 +3792,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 * @@ -4014,6 +4077,7 @@ } else { // Otherwise, try to delete by versioning: $this->versionizeRecord($table,$id,'DELETED!',TRUE); + $this->deleteL10nOverlayRecords( $table, $id ); } } } @@ -4089,7 +4153,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) { @@ -4119,6 +4183,11 @@ // 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: @@ -4150,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 @@ -4414,13 +4485,32 @@ } } + /** + * Find l10n-overlay records and perform the requested delete action for these records. + * + * @param string $table: Record Table + * @param string $uid: Record UID + * @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 @@ -4621,6 +4711,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 6145) +++ 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). @@ -548,9 +581,10 @@ * @param mixed If integer, then this is the ID of the record. If Array this just represents fields in the record. * @param boolean Set, if testing a new (non-existing) record array. Will disable certain checks that doesn't make much sense in that context. * @param boolean Set, if testing a deleted record array. + * @param boolean Set, whenever access to all translations of the record is required * @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 +612,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 6145) +++ typo3/class.db_list_extra.inc (working copy) @@ -551,6 +551,11 @@ // For each available translation, render the record: if (is_array($this->translations)) { foreach ($this->translations as $lRow) { + // $lRow isn't always what we want - if record was moved we've to work with the placeholder records otherwise the list is messed up a bit + if($row['t3ver_state'] == 3 && $row['t3ver_wsid'] != 0) { + $tmpRow = t3lib_BEfunc::getRecordRaw($table, 't3ver_state=3 and t3ver_move_id="'.intval( $lRow['uid']).'"', $selFieldList); + $lRow = is_array($tmpRow)?$tmpRow:$lRow; + } // In offline workspace, look for alternative record: t3lib_BEfunc::workspaceOL($table, $lRow, $GLOBALS['BE_USER']->workspace); if (is_array($lRow) && $GLOBALS['BE_USER']->checkLanguageAccess($lRow[$TCA[$table]['ctrl']['languageField']])) { @@ -1322,17 +1327,18 @@ $cells=array(); $cells['pasteAfter'] = $cells['pasteInto'] = $this->spaceIcon; - + //enables to hide the copy, cut and paste icons for localized records - doesn't make much sense to perform these options for them + $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,13 +1361,13 @@ } // Adding the checkbox to the panel: - $cells['select']=''; + $cells['select']= $isL10nOverlay ? $this->spaceIcon : ''; } // Now, looking for selected elements from the current table: $elFromTable = $this->clipObj->elFromTable($table); if (count($elFromTable) && $TCA[$table]['ctrl']['sortby']) { // IF elements are found and they can be individually ordered, then add a "paste after" icon: - $cells['pasteAfter']=''. + $cells['pasteAfter']= $isL10nOverlay ? $this->spaceIcon : ''. 'backPath,'gfx/clip_pasteafter.gif','width="12" height="12"').' title="'.$LANG->getLL('clip_pasteAfter',1).'" alt="" />'. ''; }