See changelog.
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_tcemain.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2006 Kasper Skaarhoj (kasperYYYY@typo3.com)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Contains the TYPO3 Core Engine
29 *
30 * $Id$
31 * Revised for TYPO3 3.9 October 2005 by Kasper Skaarhoj
32 *
33 * @author Kasper Skaarhoj <kasperYYYY@typo3.com>
34 */
35 /**
36 * [CLASS/FUNCTION INDEX of SCRIPT]
37 *
38 *
39 *
40 * 237: class t3lib_TCEmain
41 * 358: function start($data,$cmd,$altUserObject='')
42 * 397: function setMirror($mirror)
43 * 422: function setDefaultsFromUserTS($userTS)
44 * 445: function process_uploads($postFiles)
45 * 483: function process_uploads_traverseArray(&$outputArr,$inputArr,$keyToSet)
46 *
47 * SECTION: PROCESSING DATA
48 * 519: function process_datamap()
49 * 869: function placeholderShadowing($table,$id)
50 * 905: function fillInFieldArray($table,$id,$fieldArray,$incomingFieldArray,$realPid,$status,$tscPID)
51 *
52 * SECTION: Evaluation of input values
53 * 1126: function checkValue($table,$field,$value,$id,$status,$realPid,$tscPID)
54 * 1186: function checkValue_SW($res,$value,$tcaFieldConf,$table,$id,$curValue,$status,$realPid,$recFID,$field,$uploadedFiles,$tscPID)
55 * 1235: function checkValue_input($res,$value,$tcaFieldConf,$PP,$field='')
56 * 1273: function checkValue_check($res,$value,$tcaFieldConf,$PP)
57 * 1296: function checkValue_radio($res,$value,$tcaFieldConf,$PP)
58 * 1322: function checkValue_group_select($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field)
59 * 1432: function checkValue_group_select_file($valueArray,$tcaFieldConf,$curValue,$uploadedFileArray,$status,$table,$id,$recFID)
60 * 1601: function checkValue_flex($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field)
61 * 1674: function checkValue_flexArray2Xml($array, $addPrologue=FALSE)
62 * 1686: function _DELETE_FLEX_FORMdata(&$valueArrayToRemoveFrom,$deleteCMDS)
63 * 1708: function _MOVE_FLEX_FORMdata(&$valueArrayToMoveIn, $moveCMDS, $direction)
64 * 1748: function checkValue_inline($res,$value,$tcaFieldConf,$PP,$field)
65 * 1791: function checkValue_checkMax($tcaFieldConf, $valueArray)
66 *
67 * SECTION: Helper functions for evaluation functions.
68 * 1843: function getUnique($table,$field,$value,$id,$newPid=0)
69 * 1881: function checkValue_input_Eval($value,$evalArray,$is_in)
70 * 1978: function checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,$type,$currentTable)
71 * 2022: function checkValue_group_select_explodeSelectGroupValue($value)
72 * 2046: function checkValue_flex_procInData($dataPart,$dataPart_current,$uploadedFiles,$dataStructArray,$pParams,$callBackFunc='')
73 * 2085: function checkValue_flex_procInData_travDS(&$dataValues,$dataValues_current,$uploadedFiles,$DSelements,$pParams,$callBackFunc,$structurePath)
74 *
75 * SECTION: PROCESSING COMMANDS
76 * 2231: function process_cmdmap()
77 *
78 * SECTION: Cmd: Copying
79 * 2371: function copyRecord($table,$uid,$destPid,$first=0,$overrideValues=array(),$excludeFields='')
80 * 2493: function copyPages($uid,$destPid)
81 * 2547: function copySpecificPage($uid,$destPid,$copyTablesArray,$first=0)
82 * 2581: function copyRecord_raw($table,$uid,$pid,$overrideArray=array())
83 * 2645: function rawCopyPageContent($old_pid,$new_pid,$copyTablesArray)
84 * 2669: function insertNewCopyVersion($table,$fieldArray,$realPid)
85 * 2720: function copyRecord_procBasedOnFieldType($table,$uid,$field,$value,$row,$conf)
86 * 2791: function copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
87 * 2819: function copyRecord_procFilesRefs($conf, $uid, $value)
88 *
89 * SECTION: Cmd: Moving, Localizing
90 * 2888: function moveRecord($table,$uid,$destPid)
91 * 3083: function moveRecord_procFields($table,$uid,$destPid)
92 * 3103: function moveRecord_procBasedOnFieldType($table,$uid,$destPid,$field,$value,$conf)
93 * 3136: function localize($table,$uid,$language)
94 *
95 * SECTION: Cmd: Deleting
96 * 3220: function deleteAction($table, $id)
97 * 3267: function deleteEl($table, $uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE)
98 * 3282: function undeleteRecord($table,$uid)
99 * 3299: function deleteRecord($table,$uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE,$undeleteRecord=FALSE)
100 * 3396: function deletePages($uid,$force=FALSE,$forceHardDelete=FALSE)
101 * 3424: function deleteSpecificPage($uid,$forceHardDelete=FALSE)
102 * 3447: function canDeletePage($uid)
103 * 3474: function cannotDeleteRecord($table,$id)
104 * 3493: function deleteRecord_procFields($table, $uid, $undeleteRecord = false)
105 * 3516: function deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf, $undeleteRecord = false)
106 *
107 * SECTION: Cmd: Versioning
108 * 3577: function versionizeRecord($table,$id,$label,$delete=FALSE,$versionizeTree=-1)
109 * 3653: function versionizePages($uid,$label,$versionizeTree)
110 * 3716: function version_swap($table,$id,$swapWith,$swapIntoWS=0)
111 * 3887: function version_clearWSID($table,$id)
112 * 3921: function version_setStage($table,$id,$stageId,$comment='')
113 *
114 * SECTION: Cmd: Helper functions
115 * 3966: function remapListedDBRecords()
116 * 4047: function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
117 * 4074: function remapListedDBRecords_procDBRefs($conf, $value, $MM_localUid, $table)
118 * 4120: function remapListedDBRecords_procInline($conf, $value, $uid, $table)
119 *
120 * SECTION: Access control / Checking functions
121 * 4163: function checkModifyAccessList($table)
122 * 4175: function isRecordInWebMount($table,$id)
123 * 4189: function isInWebMount($pid)
124 * 4203: function checkRecordUpdateAccess($table,$id)
125 * 4227: function checkRecordInsertAccess($insertTable,$pid,$action=1)
126 * 4261: function isTableAllowedForThisPage($page_uid, $checkTable)
127 * 4294: function doesRecordExist($table,$id,$perms)
128 * 4355: function doesRecordExist_pageLookUp($id, $perms)
129 * 4381: function doesBranchExist($inList,$pid,$perms,$recurse)
130 * 4415: function tableReadOnly($table)
131 * 4427: function tableAdminOnly($table)
132 * 4441: function destNotInsideSelf($dest,$id)
133 * 4473: function getExcludeListArray()
134 * 4496: function doesPageHaveUnallowedTables($page_uid,$doktype)
135 *
136 * SECTION: Information lookup
137 * 4545: function pageInfo($id,$field)
138 * 4565: function recordInfo($table,$id,$fieldList)
139 * 4586: function getRecordProperties($table,$id,$noWSOL=FALSE)
140 * 4602: function getRecordPropertiesFromRow($table,$row)
141 *
142 * SECTION: Storing data to Database Layer
143 * 4645: function updateDB($table,$id,$fieldArray)
144 * 4697: function insertDB($table,$id,$fieldArray,$newVersion=FALSE,$suggestedUid=0,$dontSetNewIdIndex=FALSE)
145 * 4770: function checkStoredRecord($table,$id,$fieldArray,$action)
146 * 4807: function setHistory($table,$id,$logId)
147 * 4840: function clearHistory($maxAgeSeconds=604800,$table)
148 * 4854: function updateRefIndex($table,$id)
149 *
150 * SECTION: Misc functions
151 * 4886: function getSortNumber($table,$uid,$pid)
152 * 4959: function resorting($table,$pid,$sortRow, $return_SortNumber_After_This_Uid)
153 * 4990: function setTSconfigPermissions($fieldArray,$TSConfig_p)
154 * 5007: function newFieldArray($table)
155 * 5039: function addDefaultPermittedLanguageIfNotSet($table,&$incomingFieldArray)
156 * 5063: function overrideFieldArray($table,$data)
157 * 5079: function compareFieldArrayWithCurrentAndUnset($table,$id,$fieldArray)
158 * 5125: function assemblePermissions($string)
159 * 5142: function rmComma($input)
160 * 5152: function convNumEntityToByteValue($input)
161 * 5174: function destPathFromUploadFolder($folder)
162 * 5184: function deleteClause($table)
163 * 5200: function getTCEMAIN_TSconfig($tscPID)
164 * 5215: function getTableEntries($table,$TSconfig)
165 * 5228: function getPID($table,$uid)
166 * 5241: function dbAnalysisStoreExec()
167 * 5257: function removeRegisteredFiles()
168 * 5269: function removeCacheFiles()
169 * 5283: function int_pageTreeInfo($CPtable,$pid,$counter, $rootID)
170 * 5304: function compileAdminTables()
171 * 5321: function fixUniqueInPid($table,$uid)
172 * 5357: function fixCopyAfterDuplFields($table,$uid,$prevUid,$update, $newData=array())
173 * 5382: function extFileFields($table)
174 * 5403: function getUniqueFields($table)
175 * 5428: function isReferenceField($conf)
176 * 5439: function getInlineFieldType($conf)
177 * 5462: function getCopyHeader($table,$pid,$field,$value,$count,$prevTitle='')
178 * 5491: function prependLabel($table)
179 * 5508: function resolvePid($table,$pid)
180 * 5538: function clearPrefixFromValue($table,$value)
181 * 5553: function extFileFunctions($table,$field,$filelist,$func)
182 * 5583: function noRecordsFromUnallowedTables($inList)
183 * 5609: function notifyStageChange($stat,$stageId,$table,$id,$comment)
184 * 5704: function notifyStageChange_getEmails($listOfUsers,$noTablePrefix=FALSE)
185 *
186 * SECTION: Clearing cache
187 * 5750: function clear_cache($table,$uid)
188 * 5860: function clear_cacheCmd($cacheCmd)
189 *
190 * SECTION: Logging
191 * 5966: function log($table,$recuid,$action,$recpid,$error,$details,$details_nr=-1,$data=array(),$event_pid=-1,$NEWid='')
192 * 5983: function newlog($message, $error=0)
193 * 5993: function printLogErrorMessages($redirect)
194 *
195 * TOTAL FUNCTIONS: 123
196 * (This index is automatically created/updated by the extension "extdeveval")
197 *
198 */
199
200
201
202
203 // *******************************
204 // Including necessary libraries
205 // *******************************
206 require_once (PATH_t3lib.'class.t3lib_loaddbgroup.php');
207 require_once (PATH_t3lib.'class.t3lib_parsehtml_proc.php');
208 require_once (PATH_t3lib.'class.t3lib_stdgraphic.php');
209 require_once (PATH_t3lib.'class.t3lib_basicfilefunc.php');
210 require_once (PATH_t3lib.'class.t3lib_refindex.php');
211 require_once (PATH_t3lib.'class.t3lib_flexformtools.php');
212
213
214
215
216
217
218
219
220
221
222
223 /**
224 * This is the TYPO3 Core Engine class for manipulation of the database
225 * This class is used by eg. the tce_db.php script which provides an the interface for POST forms to this class.
226 *
227 * Dependencies:
228 * - $GLOBALS['TCA'] must exist
229 * - $GLOBALS['LANG'] (languageobject) may be preferred, but not fatal.
230 *
231 * tce_db.php for further comments and SYNTAX! Also see document 'TYPO3 Core API' for details.
232 *
233 * @author Kasper Skaarhoj <kasperYYYY@typo3.com>
234 * @package TYPO3
235 * @subpackage t3lib
236 */
237 class t3lib_TCEmain {
238
239
240 // *********************
241 // Public variables you can configure before using the class:
242 // *********************
243
244 var $storeLogMessages = TRUE; // Boolean: If true, the default log-messages will be stored. This should not be necessary if the locallang-file for the log-display is properly configured. So disabling this will just save some database-space as the default messages are not saved.
245 var $enableLogging = TRUE; // Boolean: If true, actions are logged to sys_log.
246 var $reverseOrder = FALSE; // Boolean: If true, the datamap array is reversed in the order, which is a nice thing if you're creating a whole new bunch of records.
247 var $checkSimilar = TRUE; // Boolean: If true, only fields which are different from the database values are saved! In fact, if a whole input array is similar, it's not saved then.
248 var $stripslashes_values = TRUE; // Boolean: If true, incoming values in the data-array have their slashes stripped. ALWAYS SET THIS TO ZERO and supply an unescaped data array instead. This switch may totally disappear in future versions of this class!
249 var $checkStoredRecords = TRUE; // Boolean: This will read the record after having updated or inserted it. If anything is not properly submitted an error is written to the log. This feature consumes extra time by selecting records
250 var $checkStoredRecords_loose = TRUE; // Boolean: If set, values '' and 0 will equal each other when the stored records are checked.
251 var $deleteTree = FALSE; // Boolean. If this is set, then a page is deleted by deleting the whole branch under it (user must have deletepermissions to it all). If not set, then the page is deleted ONLY if it has no branch
252 var $neverHideAtCopy = FALSE; // Boolean. If set, then the 'hideAtCopy' flag for tables will be ignored.
253 var $dontProcessTransformations = FALSE; // Boolean: If set, then transformations are NOT performed on the input.
254 var $bypassWorkspaceRestrictions = FALSE; // Boolean: If true, workspace restrictions are bypassed on edit an create actions (process_datamap()). YOU MUST KNOW what you do if you use this feature!
255 var $bypassFileHandling = FALSE; // Boolean: If true, file handling of attached files (addition, deletion etc) is bypassed - the value is saved straight away. YOU MUST KNOW what you are doing with this feature!
256 var $bypassAccessCheckForRecords = FALSE; // Boolean: If true, access check, check for deleted etc. for records is bypassed. YOU MUST KNOW what you are doing if you use this feature!
257
258 var $copyWhichTables = '*'; // String. Comma-list. This list of tables decides which tables will be copied. If empty then none will. If '*' then all will (that the user has permission to of course)
259 var $generalComment = ''; // General comment, eg. for staging in workspaces.
260
261 var $copyTree = 0; // Integer. If 0 then branch is NOT copied. If 1 then pages on the 1st level is copied. If 2 then pages on the second level is copied ... and so on
262
263 var $defaultValues = array(); // Array [table][fields]=value: New records are created with default values and you can set this array on the form $defaultValues[$table][$field] = $value to override the default values fetched from TCA. If ->setDefaultsFromUserTS is called UserTSconfig default values will overrule existing values in this array (thus UserTSconfig overrules externally set defaults which overrules TCA defaults)
264 var $overrideValues = array(); // Array [table][fields]=value: You can set this array on the form $overrideValues[$table][$field] = $value to override the incoming data. You must set this externally. You must make sure the fields in this array are also found in the table, because it's not checked. All columns can be set by this array!
265 var $alternativeFileName = array(); // Array [filename]=alternative_filename: Use this array to force another name onto a file. Eg. if you set ['/tmp/blablabal'] = 'my_file.txt' and '/tmp/blablabal' is set for a certain file-field, then 'my_file.txt' will be used as the name instead.
266 var $data_disableFields=array(); // If entries are set in this array corresponding to fields for update, they are ignored and thus NOT updated. You could set this array from a series of checkboxes with value=0 and hidden fields before the checkbox with 1. Then an empty checkbox will disable the field.
267 var $suggestedInsertUids=array(); // Use this array to validate suggested uids for tables by setting [table]:[uid]. This is a dangerous option since it will force the inserted record to have a certain UID. The value just have to be true, but if you set it to "DELETE" it will make sure any record with that UID will be deleted first (raw delete). The option is used for import of T3D files when synchronizing between two mirrored servers. As a security measure this feature is available only for Admin Users (for now)
268
269 var $callBackObj; // Object. Call back object for flex form traversation. Useful when external classes wants to use the iteration functions inside tcemain for traversing a FlexForm structure.
270
271
272
273
274 // *********************
275 // Internal variables (mapping arrays) which can be used (read-only) from outside
276 // *********************
277 var $autoVersionIdMap = Array(); // Contains mapping of auto-versionized records.
278 var $substNEWwithIDs = Array(); // When new elements are created, this array contains a map between their "NEW..." string IDs and the eventual UID they got when stored in database
279 var $substNEWwithIDs_table = Array(); // Like $substNEWwithIDs, but where each old "NEW..." id is mapped to the table it was from.
280 var $copyMappingArray_merged = Array(); // This array is the sum of all copying operations in this class. May be READ from outside, thus partly public.
281 var $copiedFileMap = Array(); // A map between input file name and final destination for files being attached to records.
282 var $errorLog = Array(); // Errors are collected in this variable.
283
284
285
286 // *********************
287 // Internal Variables, do not touch.
288 // *********************
289
290 // Variables set in init() function:
291 var $BE_USER; // The user-object the script uses. If not set from outside, this is set to the current global $BE_USER.
292 var $userid; // will be set to uid of be_user executing this script
293 var $username; // will be set to username of be_user executing this script
294 var $admin; // will be set if user is admin
295
296 var $defaultPermissions = array( // Can be overridden from $TYPO3_CONF_VARS
297 'user' => 'show,edit,delete,new,editcontent',
298 'group' => 'show,edit,new,editcontent',
299 'everybody' => ''
300 );
301
302 var $exclude_array; // The list of <table>-<fields> that cannot be edited by user. This is compiled from TCA/exclude-flag combined with non_exclude_fields for the user.
303 var $datamap = Array(); // Set with incoming data array
304 var $cmdmap = Array(); // Set with incoming cmd array
305
306 // Internal static:
307 var $pMap = Array( // Permission mapping
308 'show' => 1, // 1st bit
309 'edit' => 2, // 2nd bit
310 'delete' => 4, // 3rd bit
311 'new' => 8, // 4th bit
312 'editcontent' => 16 // 5th bit
313 );
314 var $sortIntervals = 256; // Integer: The interval between sorting numbers used with tables with a 'sorting' field defined. Min 1
315
316 // Internal caching arrays
317 var $recUpdateAccessCache = Array(); // Used by function checkRecordUpdateAccess() to store whether a record is updateable or not.
318 var $recInsertAccessCache = Array(); // User by function checkRecordInsertAccess() to store whether a record can be inserted on a page id
319 var $isRecordInWebMount_Cache=array(); // Caching array for check of whether records are in a webmount
320 var $isInWebMount_Cache=array(); // Caching array for page ids in webmounts
321 var $cachedTSconfig = array(); // Caching for collecting TSconfig for page ids
322 var $pageCache = Array(); // Used for caching page records in pageInfo()
323 var $checkWorkspaceCache = Array(); // Array caching workspace access for BE_USER
324
325 // Other arrays:
326 var $dbAnalysisStore=array(); // For accumulation of MM relations that must be written after new records are created.
327 var $removeFilesStore=array(); // For accumulation of files which must be deleted after processing of all input content
328 var $uploadedFileArray = array(); // Uploaded files, set by process_uploads()
329 var $registerDBList=array(); // Used for tracking references that might need correction after operations
330 var $copyMappingArray = Array(); // Used by the copy action to track the ids of new pages so subpages are correctly inserted! THIS is internally cleared for each executed copy operation! DO NOT USE THIS FROM OUTSIDE! Read from copyMappingArray_merged instead which is accumulating this information.
331 var $remapStack = array(); // array used for remapping uids and values at the end of process_datamap
332 var $updateRefIndexStack = array(); // array used for additional calls to $this->updateRefIndex
333
334 // Various
335 var $fileFunc; // For "singleTon" file-manipulation object
336 var $checkValue_currentRecord=array(); // Set to "currentRecord" during checking of values.
337 var $autoVersioningUpdate = FALSE; // A signal flag used to tell file processing that autoversioning has happend and hence certain action should be applied.
338
339
340
341
342
343
344
345
346
347
348
349
350 /**
351 * Initializing.
352 * For details, see 'TYPO3 Core API' document.
353 * This function does not start the processing of data, but merely initializes the object
354 *
355 * @param array Data to be modified or inserted in the database
356 * @param array Commands to copy, move, delete, localize, versionize records.
357 * @param object An alternative userobject you can set instead of the default, which is $GLOBALS['BE_USER']
358 * @return void
359 */
360 function start($data,$cmd,$altUserObject='') {
361
362 // Initializing BE_USER
363 $this->BE_USER = is_object($altUserObject) ? $altUserObject : $GLOBALS['BE_USER'];
364 $this->userid = $this->BE_USER->user['uid'];
365 $this->username = $this->BE_USER->user['username'];
366 $this->admin = $this->BE_USER->user['admin'];
367
368 if ($GLOBALS['BE_USER']->uc['recursiveDelete']) {
369 $this->deleteTree = 1;
370 }
371
372 // Initializing default permissions for pages
373 $defaultPermissions = $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPermissions'];
374 if (isset($defaultPermissions['user'])) {$this->defaultPermissions['user'] = $defaultPermissions['user'];}
375 if (isset($defaultPermissions['group'])) {$this->defaultPermissions['group'] = $defaultPermissions['group'];}
376 if (isset($defaultPermissions['everybody'])) {$this->defaultPermissions['everybody'] = $defaultPermissions['everybody'];}
377
378 // generates the excludelist, based on TCA/exclude-flag and non_exclude_fields for the user:
379 $this->exclude_array = $this->admin ? array() : $this->getExcludeListArray();
380
381 // Setting the data and cmd arrays
382 if (is_array($data)) {
383 reset($data);
384 $this->datamap = $data;
385 }
386 if (is_array($cmd)) {
387 reset($cmd);
388 $this->cmdmap = $cmd;
389 }
390 }
391
392 /**
393 * Function that can mirror input values in datamap-array to other uid numbers.
394 * Example: $mirror[table][11] = '22,33' will look for content in $this->datamap[table][11] and copy it to $this->datamap[table][22] and $this->datamap[table][33]
395 *
396 * @param array This array has the syntax $mirror[table_name][uid] = [list of uids to copy data-value TO!]
397 * @return void
398 */
399 function setMirror($mirror) {
400 if (is_array($mirror)) {
401 reset($mirror);
402 while(list($table,$uid_array)=each($mirror)) {
403 if (isset($this->datamap[$table])) {
404 reset($uid_array);
405 while (list($id,$uidList) = each($uid_array)) {
406 if (isset($this->datamap[$table][$id])) {
407 $theIdsInArray = t3lib_div::trimExplode(',',$uidList,1);
408 while(list(,$copyToUid)=each($theIdsInArray)) {
409 $this->datamap[$table][$copyToUid] = $this->datamap[$table][$id];
410 }
411 }
412 }
413 }
414 }
415 }
416 }
417
418 /**
419 * Initializes default values coming from User TSconfig
420 *
421 * @param array User TSconfig array
422 * @return void
423 */
424 function setDefaultsFromUserTS($userTS) {
425 global $TCA;
426 if (is_array($userTS)) {
427 foreach($userTS as $k => $v) {
428 $k = substr($k,0,-1);
429 if ($k && is_array($v) && isset($TCA[$k])) {
430 if (is_array($this->defaultValues[$k])) {
431 $this->defaultValues[$k] = array_merge($this->defaultValues[$k],$v);
432 } else {
433 $this->defaultValues[$k] = $v;
434 }
435 }
436 }
437 }
438 }
439
440 /**
441 * Processing of uploaded files.
442 * It turns out that some versions of PHP arranges submitted data for files different if sent in an array. This function will unify this so the internal array $this->uploadedFileArray will always contain files arranged in the same structure.
443 *
444 * @param array $_FILES array
445 * @return void
446 */
447 function process_uploads($postFiles) {
448
449 if (is_array($postFiles)) {
450
451 // Editing frozen:
452 if ($this->BE_USER->workspace!==0 && $this->BE_USER->workspaceRec['freeze']) {
453 $this->newlog('All editing in this workspace has been frozen!',1);
454 return FALSE;
455 }
456
457 reset($postFiles);
458 $subA = current($postFiles);
459 if (is_array($subA)) {
460 if (is_array($subA['name']) && is_array($subA['type']) && is_array($subA['tmp_name']) && is_array($subA['size'])) {
461 // Initialize the uploadedFilesArray:
462 $this->uploadedFileArray=array();
463
464 // For each entry:
465 foreach($subA as $key => $values) {
466 $this->process_uploads_traverseArray($this->uploadedFileArray,$values,$key);
467 }
468 } else {
469 $this->uploadedFileArray=$subA;
470 }
471 }
472 }
473 }
474
475 /**
476 * Traverse the upload array if needed to rearrange values.
477 *
478 * @param array $this->uploadedFileArray passed by reference
479 * @param array Input array ($_FILES parts)
480 * @param string The current $_FILES array key to set on the outermost level.
481 * @return void
482 * @access private
483 * @see process_uploads()
484 */
485 function process_uploads_traverseArray(&$outputArr,$inputArr,$keyToSet) {
486 if (is_array($inputArr)) {
487 foreach($inputArr as $key => $value) {
488 $this->process_uploads_traverseArray($outputArr[$key],$inputArr[$key],$keyToSet);
489 }
490 } else {
491 $outputArr[$keyToSet]=$inputArr;
492 }
493 }
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509 /*********************************************
510 *
511 * PROCESSING DATA
512 *
513 *********************************************/
514
515 /**
516 * Processing the data-array
517 * Call this function to process the data-array set by start()
518 *
519 * @return void
520 */
521 function process_datamap() {
522 global $TCA, $TYPO3_CONF_VARS;
523
524 // Editing frozen:
525 if ($this->BE_USER->workspace!==0 && $this->BE_USER->workspaceRec['freeze']) {
526 $this->newlog('All editing in this workspace has been frozen!',1);
527 return FALSE;
528 }
529
530 // First prepare user defined objects (if any) for hooks which extend this function:
531 $hookObjectsArr = array();
532 if (is_array ($TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'])) {
533 foreach ($TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'] as $classRef) {
534 $hookObjectsArr[] = &t3lib_div::getUserObj($classRef);
535 }
536 }
537
538 // Organize tables so that the pages-table is always processed first. This is required if you want to make sure that content pointing to a new page will be created.
539 $orderOfTables = Array();
540 if (isset($this->datamap['pages'])) { // Set pages first.
541 $orderOfTables[]='pages';
542 }
543 reset($this->datamap);
544 while (list($table,) = each($this->datamap)) {
545 if ($table!='pages') {
546 $orderOfTables[]=$table;
547 }
548 }
549
550 // Process the tables...
551 foreach($orderOfTables as $table) {
552 /* Check if
553 - table is set in $TCA,
554 - table is NOT readOnly
555 - the table is set with content in the data-array (if not, there's nothing to process...)
556 - permissions for tableaccess OK
557 */
558 $modifyAccessList = $this->checkModifyAccessList($table);
559 if (!$modifyAccessList) {
560 $id = 0;
561 $this->log($table,$id,2,0,1,"Attempt to modify table '%s' without permission",1,array($table));
562 }
563 if (isset($TCA[$table]) && !$this->tableReadOnly($table) && is_array($this->datamap[$table]) && $modifyAccessList) {
564 if ($this->reverseOrder) {
565 $this->datamap[$table] = array_reverse($this->datamap[$table], 1);
566 }
567
568 // For each record from the table, do:
569 // $id is the record uid, may be a string if new records...
570 // $incomingFieldArray is the array of fields
571 foreach($this->datamap[$table] as $id => $incomingFieldArray) {
572 if (is_array($incomingFieldArray)) {
573
574 // Hook: processDatamap_preProcessIncomingFieldArray
575 foreach($hookObjectsArr as $hookObj) {
576 if (method_exists($hookObj, 'processDatamap_preProcessFieldArray')) {
577 $hookObj->processDatamap_preProcessFieldArray($incomingFieldArray, $table, $id, $this);
578 }
579 }
580
581 // ******************************
582 // Checking access to the record
583 // ******************************
584 $createNewVersion = FALSE;
585 $recordAccess = FALSE;
586 $old_pid_value = '';
587 $resetRejected = FALSE;
588 $this->autoVersioningUpdate = FALSE;
589
590 if (!t3lib_div::testInt($id)) { // Is it a new record? (Then Id is a string)
591 $fieldArray = $this->newFieldArray($table); // Get a fieldArray with default values
592 if (isset($incomingFieldArray['pid'])) { // A pid must be set for new records.
593 // $value = the pid
594 $pid_value = $incomingFieldArray['pid'];
595
596 // Checking and finding numerical pid, it may be a string-reference to another value
597 $OK = 1;
598 if (strstr($pid_value,'NEW')) { // If a NEW... id
599 if (substr($pid_value,0,1)=='-') {$negFlag=-1;$pid_value=substr($pid_value,1);} else {$negFlag=1;}
600 if (isset($this->substNEWwithIDs[$pid_value])) { // Trying to find the correct numerical value as it should be mapped by earlier processing of another new record.
601 $old_pid_value = $pid_value;
602 $pid_value=intval($negFlag*$this->substNEWwithIDs[$pid_value]);
603 } else {$OK = 0;} // If not found in the substArray we must stop the process...
604 } elseif ($pid_value>=0 && $this->BE_USER->workspace!==0 && $TCA[$table]['ctrl']['versioning_followPages']) { // PID points to page, the workspace is an offline space and the table follows page during versioning: This means we must check if the PID page has a version in the workspace with swapmode set to 0 (zero = page+content) and if so, change the pid to the uid of that version.
605 if ($WSdestPage = t3lib_BEfunc::getWorkspaceVersionOfRecord($this->BE_USER->workspace, 'pages', $pid_value, 'uid,t3ver_swapmode')) { // Looks for workspace version of page.
606 if ($WSdestPage['t3ver_swapmode']==0) { // if swapmode is zero, then change pid value.
607 $pid_value = $WSdestPage['uid'];
608 }
609 }
610 }
611 $pid_value = intval($pid_value);
612
613 // The $pid_value is now the numerical pid at this point
614 if ($OK) {
615 $sortRow = $TCA[$table]['ctrl']['sortby'];
616 if ($pid_value>=0) { // Points to a page on which to insert the element, possibly in the top of the page
617 if ($sortRow) { // If this table is sorted we better find the top sorting number
618 $fieldArray[$sortRow] = $this->getSortNumber($table,0,$pid_value);
619 }
620 $fieldArray['pid'] = $pid_value; // The numerical pid is inserted in the data array
621 } else { // points to another record before ifself
622 if ($sortRow) { // If this table is sorted we better find the top sorting number
623 $tempArray=$this->getSortNumber($table,0,$pid_value); // Because $pid_value is < 0, getSortNumber returns an array
624 $fieldArray['pid'] = $tempArray['pid'];
625 $fieldArray[$sortRow] = $tempArray['sortNumber'];
626 } else { // Here we fetch the PID of the record that we point to...
627 $tempdata = $this->recordInfo($table,abs($pid_value),'pid');
628 $fieldArray['pid']=$tempdata['pid'];
629 }
630 }
631 }
632 }
633 $theRealPid = $fieldArray['pid'];
634
635 // Now, check if we may insert records on this pid.
636 if ($theRealPid>=0) {
637 $recordAccess = $this->checkRecordInsertAccess($table,$theRealPid); // Checks if records can be inserted on this $pid.
638 if ($recordAccess) {
639 $this->addDefaultPermittedLanguageIfNotSet($table,$incomingFieldArray);
640 $recordAccess = $this->BE_USER->recordEditAccessInternals($table,$incomingFieldArray,TRUE);
641 if (!$recordAccess) {
642 $this->newlog("recordEditAccessInternals() check failed. [".$this->BE_USER->errorMsg."]",1);
643 } elseif(!$this->bypassWorkspaceRestrictions) {
644 // Workspace related processing:
645 if ($res = $this->BE_USER->workspaceAllowLiveRecordsInPID($theRealPid,$table)) { // If LIVE records cannot be created in the current PID due to workspace restrictions, prepare creation of placeholder-record
646 if ($res<0) {
647 $recordAccess = FALSE;
648 $this->newlog('Stage for versioning root point and users access level did not allow for editing',1);
649 }
650 } else { // So, if no live records were allowed, we have to create a new version of this record:
651 if ($TCA[$table]['ctrl']['versioningWS']) {
652 $createNewVersion = TRUE;
653 } else {
654 $recordAccess = FALSE;
655 $this->newlog('Record could not be created in this workspace in this branch',1);
656 }
657 }
658 }
659 }
660 } else {
661 debug('Internal ERROR: pid should not be less than zero!');
662 }
663 $status = 'new'; // Yes new record, change $record_status to 'insert'
664 } else { // Nope... $id is a number
665 $fieldArray = array();
666 $recordAccess = $this->checkRecordUpdateAccess($table,$id);
667 if (!$recordAccess) {
668 $propArr = $this->getRecordProperties($table,$id);
669 $this->log($table,$id,2,0,1,"Attempt to modify record '%s' (%s) without permission. Or non-existing page.",2,array($propArr['header'],$table.':'.$id),$propArr['event_pid']);
670 } else { // Next check of the record permissions (internals)
671 $recordAccess = $this->BE_USER->recordEditAccessInternals($table,$id);
672 if (!$recordAccess) {
673 $propArr = $this->getRecordProperties($table,$id);
674 $this->newlog("recordEditAccessInternals() check failed. [".$this->BE_USER->errorMsg."]",1);
675 } else { // Here we fetch the PID of the record that we point to...
676 $tempdata = $this->recordInfo($table,$id,'pid'.($TCA[$table]['ctrl']['versioningWS']?',t3ver_wsid,t3ver_stage':''));
677 $theRealPid = $tempdata['pid'];
678
679 // Prepare the reset of the rejected flag if set:
680 if ($TCA[$table]['ctrl']['versioningWS'] && $tempdata['t3ver_stage']<0) {
681 $resetRejected = TRUE;
682 }
683
684 // Checking access in case of offline workspace:
685 if (!$this->bypassWorkspaceRestrictions && $errorCode = $this->BE_USER->workspaceCannotEditRecord($table,$tempdata)) {
686 $recordAccess = FALSE; // Versioning is required and it must be offline version!
687
688 // Auto-creation of version: In offline workspace, test if versioning is enabled and look for workspace version of input record. If there is no versionized record found we will create one and save to that.
689 if ($this->BE_USER->workspaceAllowAutoCreation($table,$id,$theRealPid)) {
690 $tce = t3lib_div::makeInstance('t3lib_TCEmain');
691 $tce->stripslashes_values = 0;
692
693 // Setting up command for creating a new version of the record:
694 $cmd = array();
695 $cmd[$table][$id]['version'] = array(
696 'action' => 'new',
697 'treeLevels' => -1, // Default is to create a version of the individual records...
698 'label' => 'Auto-created for WS #'.$this->BE_USER->workspace
699 );
700 $tce->start(array(),$cmd);
701 $tce->process_cmdmap();
702 $this->errorLog = array_merge($this->errorLog,$tce->errorLog);
703
704 if ($tce->copyMappingArray[$table][$id]) {
705 $this->uploadedFileArray[$table][$tce->copyMappingArray[$table][$id]] = $this->uploadedFileArray[$table][$id];
706 $id = $this->autoVersionIdMap[$table][$id] = $tce->copyMappingArray[$table][$id];
707 $recordAccess = TRUE;
708 $this->autoVersioningUpdate = TRUE;
709 } else $this->newlog("Could not be edited in offline workspace in the branch where found (failure state: '".$errorCode."'). Auto-creation of version failed!",1);
710 } else $this->newlog("Could not be edited in offline workspace in the branch where found (failure state: '".$errorCode."'). Auto-creation of version not allowed in workspace!",1);
711 }
712 }
713 }
714 $status = 'update'; // the default is 'update'
715 }
716
717 // If access was granted above, proceed to create or update record:
718 if ($recordAccess) {
719
720 list($tscPID) = t3lib_BEfunc::getTSCpid($table,$id,$old_pid_value ? $old_pid_value : $fieldArray['pid']); // Here the "pid" is set IF NOT the old pid was a string pointing to a place in the subst-id array.
721 $TSConfig = $this->getTCEMAIN_TSconfig($tscPID);
722 if ($status=='new' && $table=='pages' && is_array($TSConfig['permissions.'])) {
723 $fieldArray = $this->setTSconfigPermissions($fieldArray,$TSConfig['permissions.']);
724 }
725 if ($createNewVersion) {
726 $newVersion_placeholderFieldArray = $fieldArray;
727 }
728
729 // Processing of all fields in incomingFieldArray and setting them in $fieldArray
730 $fieldArray = $this->fillInFieldArray($table,$id,$fieldArray,$incomingFieldArray,$theRealPid,$status,$tscPID);
731
732 // NOTICE! All manipulation beyond this point bypasses both "excludeFields" AND possible "MM" relations / file uploads to field!
733
734 // Forcing some values unto field array:
735 $fieldArray = $this->overrideFieldArray($table,$fieldArray); // NOTICE: This overriding is potentially dangerous; permissions per field is not checked!!!
736 if ($createNewVersion) {
737 $newVersion_placeholderFieldArray = $this->overrideFieldArray($table,$newVersion_placeholderFieldArray);
738 }
739
740 // Setting system fields
741 if ($status=='new') {
742 if ($TCA[$table]['ctrl']['crdate']) {
743 $fieldArray[$TCA[$table]['ctrl']['crdate']]=time();
744 if ($createNewVersion) $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['crdate']]=time();
745 }
746 if ($TCA[$table]['ctrl']['cruser_id']) {
747 $fieldArray[$TCA[$table]['ctrl']['cruser_id']]=$this->userid;
748 if ($createNewVersion) $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['cruser_id']]=$this->userid;
749 }
750 } elseif ($this->checkSimilar) { // Removing fields which are equal to the current value:
751 $fieldArray = $this->compareFieldArrayWithCurrentAndUnset($table,$id,$fieldArray);
752 }
753 if ($TCA[$table]['ctrl']['tstamp'] && count($fieldArray)) {
754 $fieldArray[$TCA[$table]['ctrl']['tstamp']]=time();
755 if ($createNewVersion) $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['tstamp']]=time();
756 }
757 if ($resetRejected) {
758 $fieldArray['t3ver_stage'] = 0;
759 }
760
761 // Hook: processDatamap_postProcessFieldArray
762 foreach($hookObjectsArr as $hookObj) {
763 if (method_exists($hookObj, 'processDatamap_postProcessFieldArray')) {
764 $hookObj->processDatamap_postProcessFieldArray($status, $table, $id, $fieldArray, $this);
765 }
766 }
767
768 // Performing insert/update. If fieldArray has been unset by some userfunction (see hook above), don't do anything
769 // Kasper: Unsetting the fieldArray is dangerous; MM relations might be saved already and files could have been uploaded that are now "lost"
770 if (is_array($fieldArray)) {
771 if ($status=='new') {
772 if ($createNewVersion) { // This creates a new version of the record with online placeholder and offline version
773 $versioningType = $table==='pages' ? $this->BE_USER->workspaceVersioningTypeGetClosest(t3lib_div::intInRange($TYPO3_CONF_VARS['BE']['newPagesVersioningType'],-1,1)) : -1;
774 if ($this->BE_USER->workspaceVersioningTypeAccess($versioningType)) {
775 $newVersion_placeholderFieldArray['t3ver_label'] = 'INITIAL PLACEHOLDER';
776 $newVersion_placeholderFieldArray['t3ver_state'] = 1; // Setting placeholder state value for temporary record
777 $newVersion_placeholderFieldArray['t3ver_wsid'] = $this->BE_USER->workspace; // Setting workspace - only so display of place holders can filter out those from other workspaces.
778 $newVersion_placeholderFieldArray[$TCA[$table]['ctrl']['label']] = '[PLACEHOLDER, WS#'.$this->BE_USER->workspace.']';
779 $this->insertDB($table,$id,$newVersion_placeholderFieldArray,FALSE); // Saving placeholder as 'original'
780
781 // For the actual new offline version, set versioning values to point to placeholder:
782 $fieldArray['pid'] = -1;
783 $fieldArray['t3ver_oid'] = $this->substNEWwithIDs[$id];
784 $fieldArray['t3ver_id'] = 1;
785 $fieldArray['t3ver_state'] = -1; // Setting placeholder state value for version (so it can know it is currently a new version...)
786 $fieldArray['t3ver_label'] = 'First draft version';
787 $fieldArray['t3ver_wsid'] = $this->BE_USER->workspace;
788 if ($table==='pages') { // Swap mode set to "branch" so we can build branches for pages.
789 $fieldArray['t3ver_swapmode'] = $versioningType;
790 }
791 $phShadowId = $this->insertDB($table,$id,$fieldArray,TRUE,0,TRUE); // When inserted, $this->substNEWwithIDs[$id] will be changed to the uid of THIS version and so the interface will pick it up just nice!
792 if ($phShadowId) {
793 $this->placeholderShadowing($table,$phShadowId);
794 }
795 } else $this->newlog('Versioning type "'.$versioningType.'" was not allowed, so could not create new record.',1);
796 } else {
797 $this->insertDB($table,$id,$fieldArray,FALSE,$incomingFieldArray['uid']);
798 }
799 } else {
800 $this->updateDB($table,$id,$fieldArray);
801 $this->placeholderShadowing($table,$id);
802 }
803 }
804
805 // Hook: processDatamap_afterDatabaseOperations
806 foreach($hookObjectsArr as $hookObj) {
807 if (method_exists($hookObj, 'processDatamap_afterDatabaseOperations')) {
808 $hookObj->processDatamap_afterDatabaseOperations($status, $table, $id, $fieldArray, $this);
809 }
810 }
811 } // if ($recordAccess) {
812 } // if (is_array($incomingFieldArray)) {
813 }
814 }
815 }
816
817 // call_user_func_array
818
819 // Process the stack of relations to remap/correct
820 if(is_array($this->remapStack)) {
821 foreach($this->remapStack as $remapAction) {
822 // if no position index for the arguments was set, skip this remap action
823 if (!is_array($remapAction['pos'])) continue;
824
825 // load values from the argument array in remapAction
826 $field = $remapAction['field'];
827 $id = $remapAction['args'][$remapAction['pos']['id']];
828 $table = $remapAction['args'][$remapAction['pos']['table']];
829 $valueArray = $remapAction['args'][$remapAction['pos']['valueArray']];
830 $tcaFieldConf = $remapAction['args'][$remapAction['pos']['tcaFieldConf']];
831
832 // Replace NEW... IDs with real uids.
833 if(strpos($id, 'NEW') !== false) {
834 $id = $this->substNEWwithIDs[$id];
835 $remapAction['args'][$remapAction['pos']['id']] = $id;
836 }
837
838 // Replace relations to NEW...-IDs in values
839 if(is_array($valueArray)) {
840 foreach($valueArray as $key => $value) {
841 if(strpos($value, 'NEW') !== false) {
842 $valueArray[$key] = $this->substNEWwithIDs[$value];
843 }
844 }
845 $remapAction['args'][$remapAction['pos']['valueArray']] = $valueArray;
846 }
847
848 // process the arguments with the defined function
849 $remapAction['args'][$remapAction['pos']['valueArray']] = call_user_func_array(
850 array($this, $remapAction['func']),
851 $remapAction['args']
852 );
853
854 // @TODO: Add option to disable count-field
855 $newVal = $this->checkValue_checkMax($tcaFieldConf, $remapAction['args'][$remapAction['pos']['valueArray']]);
856 $this->updateDB($table,$id,array($field => implode(',', $newVal)));
857 }
858 }
859
860 $this->dbAnalysisStoreExec();
861 $this->removeRegisteredFiles();
862 }
863
864 /**
865 * Fix shadowing of data in case we are editing a offline version of a live "New" placeholder record:
866 *
867 * @param string Table name
868 * @param integer Record uid
869 * @return void
870 */
871 function placeholderShadowing($table,$id) {
872 global $TCA;
873
874 if ($liveRec = t3lib_BEfunc::getLiveVersionOfRecord($table,$id,'*')) {
875 if ((int)$liveRec['t3ver_state']===1) {
876 $justStoredRecord = t3lib_BEfunc::getRecord($table,$id);
877 $newRecord = array();
878
879 $shadowColumns = t3lib_div::trimExplode(',', $TCA[$table]['ctrl']['shadowColumnsForNewPlaceholders'],1);
880 foreach($shadowColumns as $fieldName) {
881 if (strcmp($justStoredRecord[$fieldName],$liveRec[$fieldName])) {
882 $newRecord[$fieldName] = $justStoredRecord[$fieldName];
883 }
884 }
885
886 if (count($newRecord)) {
887 $this->newlog('Shadowing done on fields '.implode(',',array_keys($newRecord)).' in Placeholder record '.$table.':'.$liveRec['uid'].' (offline version UID='.$id.')');
888 $this->updateDB($table,$liveRec['uid'],$newRecord);
889 }
890 }
891 }
892 }
893
894 /**
895 * Filling in the field array
896 * $this->exclude_array is used to filter fields if needed.
897 *
898 * @param string Table name
899 * @param [type] $id: ...
900 * @param array Default values, Preset $fieldArray with 'pid' maybe (pid and uid will be not be overridden anyway)
901 * @param array $incomingFieldArray is which fields/values you want to set. There are processed and put into $fieldArray if OK
902 * @param integer The real PID value of the record. For updates, this is just the pid of the record. For new records this is the PID of the page where it is inserted.
903 * @param string $status = 'new' or 'update'
904 * @param [type] $tscPID: ...
905 * @return [type] ...
906 */
907 function fillInFieldArray($table,$id,$fieldArray,$incomingFieldArray,$realPid,$status,$tscPID) {
908 global $TCA;
909
910 // Initialize:
911 t3lib_div::loadTCA($table);
912 $originalLanguageRecord = NULL;
913 $originalLanguage_diffStorage = NULL;
914 $diffStorageFlag = FALSE;
915
916 // Setting 'currentRecord' and 'checkValueRecord':
917 if (strstr($id,'NEW')) {
918 $currentRecord = $checkValueRecord = $fieldArray; // must have the 'current' array - not the values after processing below...
919
920 // IF $incomingFieldArray is an array, overlay it.
921 // The point is that when new records are created as copies with flex type fields there might be a field containing information about which DataStructure to use and without that information the flexforms cannot be correctly processed.... This should be OK since the $checkValueRecord is used by the flexform evaluation only anyways...
922 if (is_array($incomingFieldArray) && is_array($checkValueRecord)) {
923 $checkValueRecord = t3lib_div::array_merge_recursive_overrule($checkValueRecord, $incomingFieldArray);
924 }
925 } else {
926 $currentRecord = $checkValueRecord = $this->recordInfo($table,$id,'*'); // We must use the current values as basis for this!
927
928 t3lib_BEfunc::fixVersioningPid($table,$currentRecord); // This is done to make the pid positive for offline versions; Necessary to have diff-view for pages_language_overlay in workspaces.
929
930 // Get original language record if available:
931 if (is_array($currentRecord)
932 && $TCA[$table]['ctrl']['transOrigDiffSourceField']
933 && $TCA[$table]['ctrl']['languageField']
934 && $currentRecord[$TCA[$table]['ctrl']['languageField']] > 0
935 && $TCA[$table]['ctrl']['transOrigPointerField']
936 && intval($currentRecord[$TCA[$table]['ctrl']['transOrigPointerField']]) > 0) {
937
938 $lookUpTable = $TCA[$table]['ctrl']['transOrigPointerTable'] ? $TCA[$table]['ctrl']['transOrigPointerTable'] : $table;
939 $originalLanguageRecord = $this->recordInfo($lookUpTable,$currentRecord[$TCA[$table]['ctrl']['transOrigPointerField']],'*');
940 t3lib_BEfunc::workspaceOL($lookUpTable,$originalLanguageRecord);
941 $originalLanguage_diffStorage = unserialize($currentRecord[$TCA[$table]['ctrl']['transOrigDiffSourceField']]);
942 }
943 }
944 $this->checkValue_currentRecord = $checkValueRecord;
945
946 /*
947 In the following all incoming value-fields are tested:
948 - Are the user allowed to change the field?
949 - Is the field uid/pid (which are already set)
950 - perms-fields for pages-table, then do special things...
951 - If the field is nothing of the above and the field is configured in TCA, the fieldvalues are evaluated by ->checkValue
952
953 If everything is OK, the field is entered into $fieldArray[]
954 */
955 foreach($incomingFieldArray as $field => $fieldValue) {
956 if (!in_array($table.'-'.$field, $this->exclude_array) && !$this->data_disableFields[$table][$id][$field]) { // The field must be editable.
957
958 // Checking if a value for language can be changed:
959 $languageDeny = $TCA[$table]['ctrl']['languageField'] && !strcmp($TCA[$table]['ctrl']['languageField'], $field) && !$this->BE_USER->checkLanguageAccess($fieldValue);
960
961 if (!$languageDeny) {
962 // Stripping slashes - will probably be removed the day $this->stripslashes_values is removed as an option...
963 if ($this->stripslashes_values) {
964 if (is_array($fieldValue)) {
965 t3lib_div::stripSlashesOnArray($fieldValue);
966 } else $fieldValue = stripslashes($fieldValue);
967 }
968
969 switch ($field) {
970 case 'uid':
971 case 'pid':
972 // Nothing happens, already set
973 break;
974 case 'perms_userid':
975 case 'perms_groupid':
976 case 'perms_user':
977 case 'perms_group':
978 case 'perms_everybody':
979 // Permissions can be edited by the owner or the administrator
980 if ($table=='pages' && ($this->admin || $status=='new' || $this->pageInfo($id,'perms_userid')==$this->userid) ) {
981 $value=intval($fieldValue);
982 switch($field) {
983 case 'perms_userid':
984 $fieldArray[$field]=$value;
985 break;
986 case 'perms_groupid':
987 $fieldArray[$field]=$value;
988 break;
989 default:
990 if ($value>=0 && $value<pow(2,5)) {
991 $fieldArray[$field]=$value;
992 }
993 break;
994 }
995 }
996 break;
997 case 't3ver_oid':
998 case 't3ver_id':
999 case 't3ver_wsid':
1000 case 't3ver_state':
1001 case 't3ver_swapmode':
1002 case 't3ver_count':
1003 case 't3ver_stage':
1004 case 't3ver_tstamp':
1005 // t3ver_label is not here because it CAN be edited as a regular field!
1006 break;
1007 default:
1008 if (isset($TCA[$table]['columns'][$field])) {
1009 // Evaluating the value.
1010 $res = $this->checkValue($table,$field,$fieldValue,$id,$status,$realPid,$tscPID);
1011 if (isset($res['value'])) {
1012 $fieldArray[$field]=$res['value'];
1013
1014 // Add the value of the original record to the diff-storage content:
1015 if ($TCA[$table]['ctrl']['transOrigDiffSourceField']) {
1016 $originalLanguage_diffStorage[$field] = $originalLanguageRecord[$field];
1017 $diffStorageFlag = TRUE;
1018 }
1019 }
1020 } elseif ($TCA[$table]['ctrl']['origUid']===$field) { // Allow value for original UID to pass by...
1021 $fieldArray[$field] = $fieldValue;
1022 }
1023 break;
1024 }
1025 } // Checking language.
1026 } // Check exclude fields / disabled fields...
1027 }
1028
1029 // Add diff-storage information:
1030 if ($diffStorageFlag && !isset($fieldArray[$TCA[$table]['ctrl']['transOrigDiffSourceField']])) { // If the field is set it would probably be because of an undo-operation - in which case we should not update the field of course...
1031 $fieldArray[$TCA[$table]['ctrl']['transOrigDiffSourceField']] = serialize($originalLanguage_diffStorage);
1032 }
1033
1034 // Checking for RTE-transformations of fields:
1035 $types_fieldConfig = t3lib_BEfunc::getTCAtypes($table,$currentRecord);
1036 $theTypeString = t3lib_BEfunc::getTCAtypeValue($table,$currentRecord);
1037 if (is_array($types_fieldConfig)) {
1038 reset($types_fieldConfig);
1039 while(list(,$vconf) = each($types_fieldConfig)) {
1040 // Write file configuration:
1041 $eFile = t3lib_parsehtml_proc::evalWriteFile($vconf['spec']['static_write'],array_merge($currentRecord,$fieldArray)); // inserted array_merge($currentRecord,$fieldArray) 170502
1042
1043 // RTE transformations:
1044 if (!$this->dontProcessTransformations) {
1045 if (isset($fieldArray[$vconf['field']])) {
1046 // Look for transformation flag:
1047 switch((string)$incomingFieldArray['_TRANSFORM_'.$vconf['field']]) {
1048 case 'RTE':
1049 $RTEsetup = $this->BE_USER->getTSConfig('RTE',t3lib_BEfunc::getPagesTSconfig($tscPID));
1050 $thisConfig = t3lib_BEfunc::RTEsetup($RTEsetup['properties'],$table,$vconf['field'],$theTypeString);
1051
1052 // Set alternative relative path for RTE images/links:
1053 $RTErelPath = is_array($eFile) ? dirname($eFile['relEditFile']) : '';
1054
1055 // Get RTE object, draw form and set flag:
1056 $RTEobj = &t3lib_BEfunc::RTEgetObj();
1057 if (is_object($RTEobj)) {
1058 $fieldArray[$vconf['field']] = $RTEobj->transformContent('db',$fieldArray[$vconf['field']],$table,$vconf['field'],$currentRecord,$vconf['spec'],$thisConfig,$RTErelPath,$currentRecord['pid']);
1059 } else {
1060 debug('NO RTE OBJECT FOUND!');
1061 }
1062 break;
1063 }
1064 }
1065 }
1066
1067 // Write file configuration:
1068 if (is_array($eFile)) {
1069 $mixedRec = array_merge($currentRecord,$fieldArray);
1070 $SW_fileContent = t3lib_div::getUrl($eFile['editFile']);
1071 $parseHTML = t3lib_div::makeInstance('t3lib_parsehtml_proc');
1072 $parseHTML->init('','');
1073
1074 $eFileMarker = $eFile['markerField']&&trim($mixedRec[$eFile['markerField']]) ? trim($mixedRec[$eFile['markerField']]) : '###TYPO3_STATICFILE_EDIT###';
1075 $insertContent = str_replace($eFileMarker,'',$mixedRec[$eFile['contentField']]); // must replace the marker if present in content!
1076
1077 $SW_fileNewContent = $parseHTML->substituteSubpart($SW_fileContent, $eFileMarker, chr(10).$insertContent.chr(10), 1, 1);
1078 t3lib_div::writeFile($eFile['editFile'],$SW_fileNewContent);
1079
1080 // Write status:
1081 if (!strstr($id,'NEW') && $eFile['statusField']) {
1082 $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
1083 $table,
1084 'uid='.intval($id),
1085 array(
1086 $eFile['statusField'] => $eFile['relEditFile'].' updated '.date('d-m-Y H:i:s').', bytes '.strlen($mixedRec[$eFile['contentField']])
1087 )
1088 );
1089 }
1090 } elseif ($eFile && is_string($eFile)) {
1091 $this->log($table,$id,2,0,1,"Write-file error: '%s'",13,array($eFile),$realPid);
1092 }
1093 }
1094 }
1095 // Return fieldArray
1096 return $fieldArray;
1097 }
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110 /*********************************************
1111 *
1112 * Evaluation of input values
1113 *
1114 ********************************************/
1115
1116 /**
1117 * Evaluates a value according to $table/$field settings.
1118 * This function is for real database fields - NOT FlexForm "pseudo" fields.
1119 * NOTICE: Calling this function expects this: 1) That the data is saved! (files are copied and so on) 2) That files registered for deletion IS deleted at the end (with ->removeRegisteredFiles() )
1120 *
1121 * @param string Table name
1122 * @param string Field name
1123 * @param string Value to be evaluated. Notice, this is the INPUT value from the form. The original value (from any existing record) must be manually looked up inside the function if needed - or taken from $currentRecord array.
1124 * @param string The record-uid, mainly - but not exclusively - used for logging
1125 * @param string 'update' or 'new' flag
1126 * @param integer The real PID value of the record. For updates, this is just the pid of the record. For new records this is the PID of the page where it is inserted. If $realPid is -1 it means that a new version of the record is being inserted.
1127 * @param integer $tscPID
1128 * @return array Returns the evaluated $value as key "value" in this array. Can be checked with isset($res['value']) ...
1129 */
1130 function checkValue($table,$field,$value,$id,$status,$realPid,$tscPID) {
1131 global $TCA, $PAGES_TYPES;
1132 t3lib_div::loadTCA($table);
1133
1134 $res = Array(); // result array
1135 $recFID = $table.':'.$id.':'.$field;
1136
1137 // Processing special case of field pages.doktype
1138 if ($table=='pages' && $field=='doktype') {
1139 // If the user may not use this specific doktype, we issue a warning
1140 if (! ($this->admin || t3lib_div::inList($this->BE_USER->groupData['pagetypes_select'],$value))) {
1141 $propArr = $this->getRecordProperties($table,$id);
1142 $this->log($table,$id,5,0,1,"You cannot change the 'doktype' of page '%s' to the desired value.",1,array($propArr['header']),$propArr['event_pid']);
1143 return $res;
1144 };
1145 if ($status=='update') {
1146 // This checks 1) if we should check for disallowed tables and 2) if there are records from disallowed tables on the current page
1147 $onlyAllowedTables = isset($PAGES_TYPES[$value]['onlyAllowedTables']) ? $PAGES_TYPES[$value]['onlyAllowedTables'] : $PAGES_TYPES['default']['onlyAllowedTables'];
1148 if ($onlyAllowedTables) {
1149 $theWrongTables = $this->doesPageHaveUnallowedTables($id,$value);
1150 if ($theWrongTables) {
1151 $propArr = $this->getRecordProperties($table,$id);
1152 $this->log($table,$id,5,0,1,"'doktype' of page '%s' could not be changed because the page contains records from disallowed tables; %s",2,array($propArr['header'],$theWrongTables),$propArr['event_pid']);
1153 return $res;
1154 }
1155 }
1156 }
1157 }
1158
1159 // Get current value:
1160 $curValueRec = $this->recordInfo($table,$id,$field);
1161 $curValue = $curValueRec[$field];
1162
1163 // Getting config for the field
1164 $tcaFieldConf = $TCA[$table]['columns'][$field]['config'];
1165
1166 // Preform processing:
1167 $res = $this->checkValue_SW($res,$value,$tcaFieldConf,$table,$id,$curValue,$status,$realPid,$recFID,$field,$this->uploadedFileArray[$table][$id][$field],$tscPID);
1168
1169 return $res;
1170 }
1171
1172 /**
1173 * Branches out evaluation of a field value based on its type as configured in TCA
1174 * Can be called for FlexForm pseudo fields as well, BUT must not have $field set if so.
1175 *
1176 * @param array The result array. The processed value (if any!) is set in the "value" key.
1177 * @param string The value to set.
1178 * @param array Field configuration from TCA
1179 * @param string Table name
1180 * @param integer Return UID
1181 * @param [type] $curValue: ...
1182 * @param [type] $status: ...
1183 * @param integer The real PID value of the record. For updates, this is just the pid of the record. For new records this is the PID of the page where it is inserted. If $realPid is -1 it means that a new version of the record is being inserted.
1184 * @param [type] $recFID: ...
1185 * @param string Field name. Must NOT be set if the call is for a flexform field (since flexforms are not allowed within flexforms).
1186 * @param [type] $uploadedFiles: ...
1187 * @param [type] $tscPID: ...
1188 * @return array Returns the evaluated $value as key "value" in this array.
1189 */
1190 function checkValue_SW($res,$value,$tcaFieldConf,$table,$id,$curValue,$status,$realPid,$recFID,$field,$uploadedFiles,$tscPID) {
1191
1192 $PP = array($table,$id,$curValue,$status,$realPid,$recFID,$tscPID);
1193
1194 switch ($tcaFieldConf['type']) {
1195 case 'text':
1196 case 'passthrough':
1197 case 'user':
1198 $res['value'] = $value;
1199 break;
1200 case 'input':
1201 $res = $this->checkValue_input($res,$value,$tcaFieldConf,$PP,$field);
1202 break;
1203 case 'check':
1204 $res = $this->checkValue_check($res,$value,$tcaFieldConf,$PP);
1205 break;
1206 case 'radio':
1207 $res = $this->checkValue_radio($res,$value,$tcaFieldConf,$PP);
1208 break;
1209 case 'group':
1210 case 'select':
1211 $res = $this->checkValue_group_select($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field);
1212 break;
1213 case 'inline':
1214 $res = $this->checkValue_inline($res,$value,$tcaFieldConf,$PP,$field);
1215 break;
1216 case 'flex':
1217 if ($field) { // FlexForms are only allowed for real fields.
1218 $res = $this->checkValue_flex($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field);
1219 }
1220 break;
1221 default:
1222 #debug(array($tcaFieldConf,$res,$value),'NON existing field type:');
1223 break;
1224 }
1225
1226 return $res;
1227 }
1228
1229 /**
1230 * Evaluate "input" type values.
1231 *
1232 * @param array The result array. The processed value (if any!) is set in the "value" key.
1233 * @param string The value to set.
1234 * @param array Field configuration from TCA
1235 * @param array Additional parameters in a numeric array: $table,$id,$curValue,$status,$realPid,$recFID
1236 * @param string Field name
1237 * @return array Modified $res array
1238 */
1239 function checkValue_input($res,$value,$tcaFieldConf,$PP,$field='') {
1240 list($table,$id,$curValue,$status,$realPid,$recFID) = $PP;
1241
1242 // Secures the string-length to be less than max. Will probably make problems with multi-byte strings!
1243 if (intval($tcaFieldConf['max'])>0) {$value = substr($value,0,intval($tcaFieldConf['max']));}
1244
1245 // Checking range of value:
1246 if ($tcaFieldConf['range'] && $value!=$tcaFieldConf['checkbox']) { // If value is not set to the allowed checkbox-value then it is checked against the ranges
1247 if (isset($tcaFieldConf['range']['upper'])&&$value>$tcaFieldConf['range']['upper']) {$value=$tcaFieldConf['range']['upper'];}
1248 if (isset($tcaFieldConf['range']['lower'])&&$value<$tcaFieldConf['range']['lower']) {$value=$tcaFieldConf['range']['lower'];}
1249 }
1250
1251 // Process evaluation settings:
1252 $evalCodesArray = t3lib_div::trimExplode(',',$tcaFieldConf['eval'],1);
1253 $res = $this->checkValue_input_Eval($value,$evalCodesArray,$tcaFieldConf['is_in']);
1254
1255 // Process UNIQUE settings:
1256 if ($field && $realPid>=0) { // Field is NOT set for flexForms - which also means that uniqueInPid and unique is NOT available for flexForm fields! Also getUnique should not be done for versioning and if PID is -1 ($realPid<0) then versioning is happening...
1257 if ($res['value'] && in_array('uniqueInPid',$evalCodesArray)) {
1258 $res['value'] = $this->getUnique($table,$field,$res['value'],$id,$realPid);
1259 }
1260 if ($res['value'] && in_array('unique',$evalCodesArray)) {
1261 $res['value'] = $this->getUnique($table,$field,$res['value'],$id);
1262 }
1263 }
1264
1265 return $res;
1266 }
1267
1268 /**
1269 * Evaluates 'check' type values.
1270 *
1271 * @param array The result array. The processed value (if any!) is set in the 'value' key.
1272 * @param string The value to set.
1273 * @param array Field configuration from TCA
1274 * @param array Additional parameters in a numeric array: $table,$id,$curValue,$status,$realPid,$recFID
1275 * @return array Modified $res array
1276 */
1277 function checkValue_check($res,$value,$tcaFieldConf,$PP) {
1278 list($table,$id,$curValue,$status,$realPid,$recFID) = $PP;
1279
1280 $itemC = count($tcaFieldConf['items']);
1281 if (!$itemC) {$itemC=1;}
1282 $maxV = pow(2,$itemC);
1283
1284 if ($value<0) {$value=0;}
1285 if ($value>$maxV) {$value=$maxV;}
1286 $res['value'] = $value;
1287
1288 return $res;
1289 }
1290
1291 /**
1292 * Evaluates 'radio' type values.
1293 *
1294 * @param array The result array. The processed value (if any!) is set in the 'value' key.
1295 * @param string The value to set.
1296 * @param array Field configuration from TCA
1297 * @param array Additional parameters in a numeric array: $table,$id,$curValue,$status,$realPid,$recFID
1298 * @return array Modified $res array
1299 */
1300 function checkValue_radio($res,$value,$tcaFieldConf,$PP) {
1301 list($table,$id,$curValue,$status,$realPid,$recFID) = $PP;
1302
1303 if (is_array($tcaFieldConf['items'])) {
1304 foreach($tcaFieldConf['items'] as $set) {
1305 if (!strcmp($set[1],$value)) {
1306 $res['value'] = $value;
1307 break;
1308 }
1309 }
1310 }
1311
1312 return $res;
1313 }
1314
1315 /**
1316 * Evaluates 'group' or 'select' type values.
1317 *
1318 * @param array The result array. The processed value (if any!) is set in the 'value' key.
1319 * @param string The value to set.
1320 * @param array Field configuration from TCA
1321 * @param array Additional parameters in a numeric array: $table,$id,$curValue,$status,$realPid,$recFID
1322 * @param [type] $uploadedFiles: ...
1323 * @param string Field name
1324 * @return array Modified $res array
1325 */
1326 function checkValue_group_select($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field) {
1327
1328 list($table,$id,$curValue,$status,$realPid,$recFID) = $PP;
1329
1330 // Detecting if value sent is an array and if so, implode it around a comma:
1331 if (is_array($value)) {
1332 $value = implode(',',$value);
1333 }
1334
1335 // This converts all occurencies of '&#123;' to the byte 123 in the string - this is needed in very rare cases where filenames with special characters (like ???, umlaud etc) gets sent to the server as HTML entities instead of bytes. The error is done only by MSIE, not Mozilla and Opera.
1336 // Anyways, this should NOT disturb anything else:
1337 $value = $this->convNumEntityToByteValue($value);
1338
1339 // When values are sent as group or select they come as comma-separated values which are exploded by this function:
1340 $valueArray = $this->checkValue_group_select_explodeSelectGroupValue($value);
1341
1342 // If not multiple is set, then remove duplicates:
1343 if (!$tcaFieldConf['multiple']) {
1344 $valueArray = array_unique($valueArray);
1345 }
1346
1347 // If an exclusive key is found, discard all others:
1348 if ($tcaFieldConf['type']=='select' && $tcaFieldConf['exclusiveKeys']) {
1349 $exclusiveKeys = t3lib_div::trimExplode(',', $tcaFieldConf['exclusiveKeys']);
1350 foreach($valueArray as $kk => $vv) {
1351 if (in_array($vv, $exclusiveKeys)) { // $vv is the item key!
1352 $valueArray = Array($kk => $vv);
1353 break;
1354 }
1355 }
1356 }
1357
1358 // This could be a good spot for parsing the array through a validation-function which checks if the values are alright (except that database references are not in their final form - but that is the point, isn't it?)
1359 // NOTE!!! Must check max-items of files before the later check because that check would just leave out filenames if there are too many!!
1360
1361 // Checking for select / authMode, removing elements from $valueArray if any of them is not allowed!
1362 if ($tcaFieldConf['type']=='select' && $tcaFieldConf['authMode']) {
1363 $preCount = count($valueArray);
1364 foreach($valueArray as $kk => $vv) {
1365 if (!$this->BE_USER->checkAuthMode($table,$field,$vv,$tcaFieldConf['authMode'])) {
1366 unset($valueArray[$kk]);
1367 }
1368 }
1369
1370 // During the check it turns out that the value / all values were removed - we respond by simply returning an empty array so nothing is written to DB for this field.
1371 if ($preCount && !count($valueArray)) {
1372 return array();
1373 }
1374 }
1375
1376 // For group types:
1377 if ($tcaFieldConf['type']=='group') {
1378 switch($tcaFieldConf['internal_type']) {
1379 case 'file':
1380 $valueArray = $this->checkValue_group_select_file(
1381 $valueArray,
1382 $tcaFieldConf,
1383 $curValue,
1384 $uploadedFiles,
1385 $status,
1386 $table,
1387 $id,
1388 $recFID
1389 );
1390 break;
1391 case 'db':
1392 $valueArray = $this->checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,'group', $table);
1393 break;
1394 }
1395 }
1396 // For select types which has a foreign table attached:
1397 if ($tcaFieldConf['type']=='select' && $tcaFieldConf['foreign_table']) {
1398 // check, if there is a NEW... id in the value, that should be substituded later
1399 if (strpos($value, 'NEW') !== false) {
1400 $this->remapStack[] = array(
1401 'func' => 'checkValue_group_select_processDBdata',
1402 'args' => array($valueArray,$tcaFieldConf,$id,$status,'select',$table),
1403 'pos' => array('valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 5),
1404 'field' => $field
1405 );
1406 $unsetResult = true;
1407 } else {
1408 $valueArray = $this->checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,'select', $table);
1409 }
1410 }
1411
1412 if (!$unsetResult) {
1413 $newVal=$this->checkValue_checkMax($tcaFieldConf, $valueArray);
1414 $res['value'] = implode(',',$newVal);
1415 } else {
1416 unset($res['value']);
1417 }
1418
1419 return $res;
1420 }
1421
1422 /**
1423 * Handling files for group/select function
1424 *
1425 * @param array Array of incoming file references. Keys are numeric, values are files (basically, this is the exploded list of incoming files)
1426 * @param array Configuration array from TCA of the field
1427 * @param string Current value of the field
1428 * @param array Array of uploaded files, if any
1429 * @param string Status ("update" or ?)
1430 * @param string tablename of record
1431 * @param integer UID of record
1432 * @param string Field identifier ([table:uid:field:....more for flexforms?]
1433 * @return array Modified value array
1434 * @see checkValue_group_select()
1435 */
1436 function checkValue_group_select_file($valueArray,$tcaFieldConf,$curValue,$uploadedFileArray,$status,$table,$id,$recFID) {
1437
1438 if (!$this->bypassFileHandling) { // If filehandling should NOT be bypassed, do processing:
1439
1440 // If any files are uploaded, add them to value array
1441 if (is_array($uploadedFileArray) &&
1442 $uploadedFileArray['name'] &&
1443 strcmp($uploadedFileArray['tmp_name'],'none')) {
1444 $valueArray[]=$uploadedFileArray['tmp_name'];
1445 $this->alternativeFileName[$uploadedFileArray['tmp_name']] = $uploadedFileArray['name'];
1446 }
1447
1448 // Creating fileFunc object.
1449 if (!$this->fileFunc) {
1450 $this->fileFunc = t3lib_div::makeInstance('t3lib_basicFileFunctions');
1451 $this->include_filefunctions=1;
1452 }
1453 // Setting permitted extensions.
1454 $all_files = Array();
1455 $all_files['webspace']['allow'] = $tcaFieldConf['allowed'];
1456 $all_files['webspace']['deny'] = $tcaFieldConf['disallowed'] ? $tcaFieldConf['disallowed'] : '*';
1457 $all_files['ftpspace'] = $all_files['webspace'];
1458 $this->fileFunc->init('', $all_files);
1459 }
1460
1461 // If there is an upload folder defined:
1462 if ($tcaFieldConf['uploadfolder']) {
1463 if (!$this->bypassFileHandling) { // If filehandling should NOT be bypassed, do processing:
1464 // For logging..
1465 $propArr = $this->getRecordProperties($table,$id);
1466
1467 // Get destrination path:
1468 $dest = $this->destPathFromUploadFolder($tcaFieldConf['uploadfolder']);
1469
1470 // If we are updating:
1471 if ($status=='update') {
1472
1473 // Traverse the input values and convert to absolute filenames in case the update happens to an autoVersionized record.
1474 // Background: This is a horrible workaround! The problem is that when a record is auto-versionized the files of the record get copied and therefore get new names which is overridden with the names from the original record in the incoming data meaning both lost files and double-references!
1475 // The only solution I could come up with (except removing support for managing files when autoversioning) was to convert all relative files to absolute names so they are copied again (and existing files deleted). This should keep references intact but means that some files are copied, then deleted after being copied _again_.
1476 // Actually, the same problem applies to database references in case auto-versioning would include sub-records since in such a case references are remapped - and they would be overridden due to the same principle then.
1477 // Illustration of the problem comes here:
1478 // We have a record 123 with a file logo.gif. We open and edit the files header in a workspace. So a new version is automatically made.
1479 // The versions uid is 456 and the file is copied to "logo_01.gif". But the form data that we sents was based on uid 123 and hence contains the filename "logo.gif" from the original.
1480 // The file management code below will do two things: First it will blindly accept "logo.gif" as a file attached to the record (thus creating a double reference) and secondly it will find that "logo_01.gif" was not in the incoming filelist and therefore should be deleted.
1481 // If we prefix the incoming file "logo.gif" with its absolute path it will be seen as a new file added. Thus it will be copied to "logo_02.gif". "logo_01.gif" will still be deleted but since the files are the same the difference is zero - only more processing and file copying for no reason. But it will work.
1482 if ($this->autoVersioningUpdate===TRUE) {
1483 foreach($valueArray as $key => $theFile) {
1484 if ($theFile===basename($theFile)) { // If it is an already attached file...
1485 $valueArray[$key] = PATH_site.$tcaFieldConf['uploadfolder'].'/'.$theFile;
1486 }
1487 }
1488 }
1489
1490 // Finding the CURRENT files listed, either from MM or from the current record.
1491 $theFileValues=array();
1492 if ($tcaFieldConf['MM']) { // If MM relations for the files also!
1493 $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
1494 $dbAnalysis->start('','files',$tcaFieldConf['MM'],$id);
1495 reset($dbAnalysis->itemArray);
1496 while (list($somekey,$someval)=each($dbAnalysis->itemArray)) {
1497 if ($someval['id']) {
1498 $theFileValues[]=$someval['id'];
1499 }
1500 }
1501 } else {
1502 $theFileValues=t3lib_div::trimExplode(',',$curValue,1);
1503 }
1504
1505 // DELETE files: If existing files were found, traverse those and register files for deletion which has been removed:
1506 if (count($theFileValues)) {
1507 // Traverse the input values and for all input values which match an EXISTING value, remove the existing from $theFileValues array (this will result in an array of all the existing files which should be deleted!)
1508 foreach($valueArray as $key => $theFile) {
1509 if ($theFile && !strstr(t3lib_div::fixWindowsFilePath($theFile),'/')) {
1510 $theFileValues = t3lib_div::removeArrayEntryByValue($theFileValues,$theFile);
1511 }
1512 }
1513
1514 // This array contains the filenames in the uploadfolder that should be deleted:
1515 foreach($theFileValues as $key => $theFile) {
1516 $theFile = trim($theFile);
1517 if (@is_file($dest.'/'.$theFile)) {
1518 $this->removeFilesStore[]=$dest.'/'.$theFile;
1519 } elseif ($theFile) {
1520 $this->log($table,$id,5,0,1,"Could not delete file '%s' (does not exist). (%s)",10,array($dest.'/'.$theFile, $recFID),$propArr['event_pid']);
1521 }
1522 }
1523 }
1524 }
1525
1526 // Traverse the submitted values:
1527 foreach($valueArray as $key => $theFile) {
1528 // NEW FILES? If the value contains '/' it indicates, that the file is new and should be added to the uploadsdir (whether its absolute or relative does not matter here)
1529 if (strstr(t3lib_div::fixWindowsFilePath($theFile),'/')) {
1530 // Init:
1531 $maxSize = intval($tcaFieldConf['max_size']);
1532 $cmd='';
1533 $theDestFile=''; // Must be cleared. Else a faulty fileref may be inserted if the below code returns an error!
1534
1535 // Check various things before copying file:
1536 if (@is_dir($dest) && (@is_file($theFile) || @is_uploaded_file($theFile))) { // File and destination must exist
1537
1538 // Finding size. For safe_mode we have to rely on the size in the upload array if the file is uploaded.
1539 if (is_uploaded_file($theFile) && $theFile==$uploadedFileArray['tmp_name']) {
1540 $fileSize = $uploadedFileArray['size'];
1541 } else {
1542 $fileSize = filesize($theFile);
1543 }
1544
1545 if (!$maxSize || $fileSize<=($maxSize*1024)) { // Check file size:
1546 // Prepare filename:
1547 $theEndFileName = isset($this->alternativeFileName[$theFile]) ? $this->alternativeFileName[$theFile] : $theFile;
1548 $fI = t3lib_div::split_fileref($theEndFileName);
1549
1550 // Check for allowed extension:
1551 if ($this->fileFunc->checkIfAllowed($fI['fileext'], $dest, $theEndFileName)) {
1552 $theDestFile = $this->fileFunc->getUniqueName($this->fileFunc->cleanFileName($fI['file']), $dest);
1553
1554 // If we have a unique destination filename, then write the file:
1555 if ($theDestFile) {
1556 t3lib_div::upload_copy_move($theFile,$theDestFile);
1557 $this->copiedFileMap[$theFile] = $theDestFile;
1558 clearstatcache();
1559 if (!@is_file($theDestFile)) $this->log($table,$id,5,0,1,"Copying file '%s' failed!: The destination path (%s) may be write protected. Please make it write enabled!. (%s)",16,array($theFile, dirname($theDestFile), $recFID),$propArr['event_pid']);
1560 } else $this->log($table,$id,5,0,1,"Copying file '%s' failed!: No destination file (%s) possible!. (%s)",11,array($theFile, $theDestFile, $recFID),$propArr['event_pid']);
1561 } else $this->log($table,$id,5,0,1,"Fileextension '%s' not allowed. (%s)",12,array($fI['fileext'], $recFID),$propArr['event_pid']);
1562 } else $this->log($table,$id,5,0,1,"Filesize (%s) of file '%s' exceeds limit (%s). (%s)",13,array(t3lib_div::formatSize($fileSize),$theFile,t3lib_div::formatSize($maxSize*1024),$recFID),$propArr['event_pid']);
1563 } else $this->log($table,$id,5,0,1,'The destination (%s) or the source file (%s) does not exist. (%s)',14,array($dest, $theFile, $recFID),$propArr['event_pid']);
1564
1565 // If the destination file was created, we will set the new filename in the value array, otherwise unset the entry in the value array!
1566 if (@is_file($theDestFile)) {
1567 $info = t3lib_div::split_fileref($theDestFile);
1568 $valueArray[$key]=$info['file']; // The value is set to the new filename
1569 } else {
1570 unset($valueArray[$key]); // The value is set to the new filename
1571 }
1572 }
1573 }
1574 }
1575
1576 // If MM relations for the files, we will set the relations as MM records and change the valuearray to contain a single entry with a count of the number of files!
1577 if ($tcaFieldConf['MM']) {
1578 $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
1579 $dbAnalysis->tableArray['files']=array(); // dummy
1580
1581 reset($valueArray);
1582 while (list($key,$theFile)=each($valueArray)) {
1583 // explode files
1584 $dbAnalysis->itemArray[]['id']=$theFile;
1585 }
1586 if ($status=='update') {
1587 $dbAnalysis->writeMM($tcaFieldConf['MM'],$id,0);
1588 } else {
1589 $this->dbAnalysisStore[] = array($dbAnalysis, $tcaFieldConf['MM'], $id, 0); // This will be traversed later to execute the actions
1590 }
1591 $valueArray = $dbAnalysis->countItems();
1592 }
1593 }
1594
1595 return $valueArray;
1596 }
1597
1598 /**
1599 * Evaluates 'flex' type values.
1600 *
1601 * @param array The result array. The processed value (if any!) is set in the 'value' key.
1602 * @param string The value to set.
1603 * @param array Field configuration from TCA
1604 * @param array Additional parameters in a numeric array: $table,$id,$curValue,$status,$realPid,$recFID
1605 * @param array Uploaded files for the field
1606 * @param array Current record array.
1607 * @param string Field name
1608 * @return array Modified $res array
1609 */
1610 function checkValue_flex($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field) {
1611 list($table,$id,$curValue,$status,$realPid,$recFID) = $PP;
1612
1613 if (is_array($value)) {
1614
1615 // This value is necessary for flex form processing to happen on flexform fields in page records when they are copied.
1616 // The problem is, that when copying a page, flexfrom XML comes along in the array for the new record - but since $this->checkValue_currentRecord does not have a uid or pid for that sake, the t3lib_BEfunc::getFlexFormDS() function returns no good DS. For new records we do know the expected PID so therefore we send that with this special parameter. Only active when larger than zero.
1617 $newRecordPidValue = $status=='new' ? $realPid : 0;
1618
1619 // Get current value array:
1620 $dataStructArray = t3lib_BEfunc::getFlexFormDS($tcaFieldConf,$this->checkValue_currentRecord,$table,'',TRUE,$newRecordPidValue);
1621
1622 $currentValueArray = t3lib_div::xml2array($curValue);
1623 if (!is_array($currentValueArray)) $currentValueArray = array();
1624 if (is_array($currentValueArray['meta']['currentLangId'])) unset($currentValueArray['meta']['currentLangId']); // Remove all old meta for languages...
1625
1626 // Evaluation of input values:
1627 $value['data'] = $this->checkValue_flex_procInData($value['data'],$currentValueArray['data'],$uploadedFiles['data'],$dataStructArray,$PP);
1628
1629 // Create XML and convert charsets from input value:
1630 $xmlValue = $this->checkValue_flexArray2Xml($value,TRUE);
1631
1632 // If we wanted to set UTF fixed:
1633 // $storeInCharset='utf-8';
1634 // $currentCharset=$GLOBALS['LANG']->charSet;
1635 // $xmlValue = $GLOBALS['LANG']->csConvObj->conv($xmlValue,$currentCharset,$storeInCharset,1);
1636 $storeInCharset=$GLOBALS['LANG']->charSet;
1637
1638 // Merge them together IF they are both arrays:
1639 // Here we convert the currently submitted values BACK to an array, then merge the two and then BACK to XML again. This is needed to ensure the charsets are the same (provided that the current value was already stored IN the charset that the new value is converted to).
1640 if (is_array($currentValueArray)) {
1641 $arrValue = t3lib_div::xml2array($xmlValue);
1642 $arrValue = t3lib_div::array_merge_recursive_overrule($currentValueArray,$arrValue);
1643 $xmlValue = $this->checkValue_flexArray2Xml($arrValue,TRUE);
1644 }
1645
1646 // Temporary fix to delete flex form elements:
1647 $deleteCMDs = t3lib_div::_GP('_DELETE_FLEX_FORMdata');
1648 if (is_array($deleteCMDs[$table][$id][$field]['data'])) {
1649 $arrValue = t3lib_div::xml2array($xmlValue);
1650 $this->_DELETE_FLEX_FORMdata($arrValue['data'],$deleteCMDs[$table][$id][$field]['data']);
1651 $xmlValue = $this->checkValue_flexArray2Xml($arrValue,TRUE);
1652 }
1653
1654 // Temporary fix to move flex form elements up:
1655 $moveCMDs = t3lib_div::_GP('_MOVEUP_FLEX_FORMdata');
1656 if (is_array($moveCMDs[$table][$id][$field]['data'])) {
1657 $arrValue = t3lib_div::xml2array($xmlValue);
1658 $this->_MOVE_FLEX_FORMdata($arrValue['data'],$moveCMDs[$table][$id][$field]['data'], 'up');
1659 $xmlValue = $this->checkValue_flexArray2Xml($arrValue,TRUE);
1660 }
1661
1662 // Temporary fix to move flex form elements down:
1663 $moveCMDs = t3lib_div::_GP('_MOVEDOWN_FLEX_FORMdata');
1664 if (is_array($moveCMDs[$table][$id][$field]['data'])) {
1665 $arrValue = t3lib_div::xml2array($xmlValue);
1666 $this->_MOVE_FLEX_FORMdata($arrValue['data'],$moveCMDs[$table][$id][$field]['data'], 'down');
1667 $xmlValue = $this->checkValue_flexArray2Xml($arrValue,TRUE);
1668 }
1669
1670 // Create the value XML:
1671 $res['value']='';
1672 $res['value'].=$xmlValue;
1673 } else { // Passthrough...:
1674 $res['value']=$value;
1675 }
1676
1677 return $res;
1678 }
1679
1680 /**
1681 * Converts an array to FlexForm XML
1682 *
1683 * @param array Array with FlexForm data
1684 * @param boolean If set, the XML prologue is returned as well.
1685 * @return string Input array converted to XML
1686 */
1687 function checkValue_flexArray2Xml($array, $addPrologue=FALSE) {
1688 $flexObj = t3lib_div::makeInstance('t3lib_flexformtools');
1689 return $flexObj->flexArray2Xml($array, $addPrologue);
1690 }
1691
1692 /**
1693 * Deletes a flex form element
1694 *
1695 * @param array &$valueArrayToRemoveFrom: by reference
1696 * @param array $deleteCMDS: ... *
1697 * @return void
1698 */
1699 function _DELETE_FLEX_FORMdata(&$valueArrayToRemoveFrom,$deleteCMDS) {
1700 if (is_array($valueArrayToRemoveFrom) && is_array($deleteCMDS)) {
1701 foreach($deleteCMDS as $key => $value) {
1702 if (is_array($deleteCMDS[$key])) {
1703 $this->_DELETE_FLEX_FORMdata($valueArrayToRemoveFrom[$key],$deleteCMDS[$key]);
1704 } else {
1705 unset($valueArrayToRemoveFrom[$key]);
1706 }
1707 }
1708 }
1709 }
1710
1711 /**
1712 * Deletes a flex form element
1713 *
1714 * TODO: Like _DELETE_FLEX_FORMdata, this is only a temporary solution!
1715 *
1716 * @param array &$valueArrayToMoveIn: by reference
1717 * @param array $moveCMDS: ... *
1718 * @param string $direction: 'up' or 'down'
1719 * @return void
1720 */
1721 function _MOVE_FLEX_FORMdata(&$valueArrayToMoveIn, $moveCMDS, $direction) {
1722 if (is_array($valueArrayToMoveIn) && is_array($moveCMDS)) {
1723
1724 // Only execute the first move command:
1725 list ($key, $value) = each ($moveCMDS);
1726
1727 if (is_array($moveCMDS[$key])) {
1728 $this->_MOVE_FLEX_FORMdata($valueArrayToMoveIn[$key],$moveCMDS[$key], $direction);
1729 } else {
1730 switch ($direction) {
1731 case 'up':
1732 if ($key > 1) {
1733 $tmpArr = $valueArrayToMoveIn[$key];
1734 $valueArrayToMoveIn[$key] = $valueArrayToMoveIn[$key-1];
1735 $valueArrayToMoveIn[$key-1] = $tmpArr;
1736 }
1737 break;
1738 case 'down':
1739 if ($key < count($valueArrayToMoveIn)) {
1740 $tmpArr = $valueArrayToMoveIn[$key];
1741 $valueArrayToMoveIn[$key] = $valueArrayToMoveIn[$key+1];
1742 $valueArrayToMoveIn[$key+1] = $tmpArr;
1743 }
1744 break;
1745 }
1746 }
1747 }
1748 }
1749
1750 /**
1751 * Evaluates 'inline' type values.
1752 * (partly copied from the select_group function on this issue)
1753 *
1754 * @param array The result array. The processed value (if any!) is set in the 'value' key.
1755 * @param string The value to set.
1756 * @param array Field configuration from TCA
1757 * @param array Additional parameters in a numeric array: $table,$id,$curValue,$status,$realPid,$recFID
1758 * @param string Field name
1759 * @return array Modified $res array
1760 */
1761 function checkValue_inline($res,$value,$tcaFieldConf,$PP,$field) {
1762 list($table,$id,$curValue,$status,$realPid,$recFID) = $PP;
1763
1764 if (!$tcaFieldConf['foreign_table']) {
1765 return false; // Fatal error, inline fields should always have a foreign_table defined
1766 }
1767
1768 // When values are sent they come as comma-separated values which are exploded by this function:
1769 $valueArray = t3lib_div::trimExplode(',', $value);
1770
1771 // Remove duplicates: (should not be needed)
1772 $valueArray = array_unique($valueArray);
1773
1774 // Example for received data:
1775 // $value = 45,NEW4555fdf59d154,12,123
1776 // We need to decide whether we use the stack or can save the relation directly.
1777 if(strpos($value, 'NEW') !== false || !t3lib_div::testInt($id)) {
1778 $this->remapStack[] = array(
1779 'func' => 'checkValue_group_select_processDBdata',
1780 'args' => array($valueArray,$tcaFieldConf,$id,$status,'inline',$table),
1781 'pos' => array('valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 5),
1782 'field' => $field
1783 );
1784 unset($res['value']);
1785 } elseif($value || t3lib_div::testInt($id)) {
1786 $newValueArray = $this->checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,'inline', $table);
1787
1788 // Checking that the number of items is correct
1789 $newVal = $this->checkValue_checkMax($tcaFieldConf, $newValueArray);
1790 $res['value'] = implode(',',$newVal);
1791 }
1792
1793 return $res;
1794 }
1795
1796 /**
1797 * Checks if a fields has more items than defined via TCA in maxitems.
1798 * If there are more items than allowd, the item list is truncated to the defined number.
1799 *
1800 * @param array $tcaFieldConf: Field configuration from TCA
1801 * @param array $valueArray: Current value array of items
1802 * @return array The truncated value array of items
1803 */
1804 function checkValue_checkMax($tcaFieldConf, $valueArray) {
1805 // BTW, checking for min and max items here does NOT make any sense when MM is used because the above function calls will just return an array with a single item (the count) if MM is used... Why didn't I perform the check before? Probably because we could not evaluate the validity of record uids etc... Hmm...
1806
1807 $valueArrayC = count($valueArray);
1808
1809 // NOTE to the comment: It's not really possible to check for too few items, because you must then determine first, if the field is actual used regarding the CType.
1810 $maxI = isset($tcaFieldConf['maxitems']) ? intval($tcaFieldConf['maxitems']):1;
1811 if ($valueArrayC > $maxI) {$valueArrayC=$maxI;} // Checking for not too many elements
1812
1813 // Dumping array to list
1814 $newVal=array();
1815 foreach($valueArray as $nextVal) {
1816 if ($valueArrayC==0) {break;}
1817 $valueArrayC--;
1818 $newVal[]=$nextVal;
1819 }
1820
1821 return $newVal;
1822 }
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840 /*********************************************
1841 *
1842 * Helper functions for evaluation functions.
1843 *
1844 ********************************************/
1845
1846 /**
1847 * Gets a unique value for $table/$id/$field based on $value
1848 *
1849 * @param string Table name
1850 * @param string Field name for which $value must be unique
1851 * @param string Value string.
1852 * @param integer UID to filter out in the lookup (the record itself...)
1853 * @param integer If set, the value will be unique for this PID
1854 * @return string Modified value (if not-unique). Will be the value appended with a number (until 100, then the function just breaks).
1855 */
1856 function getUnique($table,$field,$value,$id,$newPid=0) {
1857 global $TCA;
1858
1859 // Initialize:
1860 t3lib_div::loadTCA($table);
1861 $whereAdd='';
1862 $newValue='';
1863 if (intval($newPid)) { $whereAdd.=' AND pid='.intval($newPid); } else { $whereAdd.=' AND pid>=0'; } // "AND pid>=0" for versioning
1864 $whereAdd.=$this->deleteClause($table);
1865
1866 // If the field is configured in TCA, proceed:
1867 if (is_array($TCA[$table]) && is_array($TCA[$table]['columns'][$field])) {
1868
1869 // Look for a record which might already have the value:
1870 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, $field.'='.$GLOBALS['TYPO3_DB']->fullQuoteStr($value, $table).' AND uid!='.intval($id).$whereAdd);
1871 $counter = 0;
1872
1873 // For as long as records with the test-value existing, try again (with incremented numbers appended).
1874 while ($GLOBALS['TYPO3_DB']->sql_num_rows($res)) {
1875 $newValue = $value.$counter;
1876 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, $field.'='.$GLOBALS['TYPO3_DB']->fullQuoteStr($newValue, $table).' AND uid!='.intval($id).$whereAdd);
1877 $counter++;
1878 if ($counter>100) { break; } // At "100" it will give up and accept a duplicate - should probably be fixed to a small hash string instead...!
1879 }
1880 // If the new value is there:
1881 $value = strlen($newValue) ? $newValue : $value;
1882 }
1883 return $value;
1884 }
1885
1886 /**
1887 * Evaluation of 'input'-type values based on 'eval' list
1888 *
1889 * @param string Value to evaluate
1890 * @param array Array of evaluations to traverse.
1891 * @param string Is-in string
1892 * @return string Modified $value
1893 */
1894 function checkValue_input_Eval($value,$evalArray,$is_in) {
1895 $res = Array();
1896 $newValue = $value;
1897 $set = true;
1898
1899 foreach($evalArray as $func) {
1900 switch($func) {
1901 case 'int':
1902 case 'year':
1903 case 'date':
1904 case 'datetime':
1905 case 'time':
1906 case 'timesec':
1907 $value = intval($value);
1908 break;
1909 case 'double2':
1910 $theDec = 0;
1911 for ($a=strlen($value); $a>0; $a--) {
1912 if (substr($value,$a-1,1)=='.' || substr($value,$a-1,1)==',') {
1913 $theDec = substr($value,$a);
1914 $value = substr($value,0,$a-1);
1915 break;
1916 }
1917 }
1918 $theDec = ereg_replace('[^0-9]','',$theDec).'00';
1919 $value = intval(str_replace(' ','',$value)).'.'.substr($theDec,0,2);
1920 break;
1921 case 'md5':
1922 if (strlen($value)!=32){$set=false;}
1923 break;
1924 case 'trim':
1925 $value = trim($value);
1926 break;
1927 case 'upper':
1928 $value = strtoupper($value);
1929 # $value = strtr($value, '', ''); // WILL make trouble with other charsets than ISO-8859-1, so what do we do here? PHP-function which can handle this for other charsets? Currently the browsers JavaScript will fix it.
1930 break;
1931 case 'lower':
1932 $value = strtolower($value);
1933 # $value = strtr($value, '', ''); // WILL make trouble with other charsets than ISO-8859-1, so what do we do here? PHP-function which can handle this for other charsets? Currently the browsers JavaScript will fix it.
1934 break;
1935 case 'required':
1936 if (!$value) {$set=0;}
1937 break;
1938 case 'is_in':
1939 $c=strlen($value);
1940 if ($c) {
1941 $newVal = '';
1942 for ($a=0;$a<$c;$a++) {
1943 $char = substr($value,$a,1);
1944 if (strstr($is_in,$char)) {
1945 $newVal.=$char;
1946 }
1947 }
1948 $value = $newVal;
1949 }
1950 break;
1951 case 'nospace':
1952 $value = str_replace(' ','',$value);
1953 break;
1954 case 'alpha':
1955 $value = ereg_replace('[^a-zA-Z]','',$value);
1956 break;
1957 case 'num':
1958 $value = ereg_replace('[^0-9]','',$value);
1959 break;
1960 case 'alphanum':
1961 $value = ereg_replace('[^a-zA-Z0-9]','',$value);
1962 break;
1963 case 'alphanum_x':
1964 $value = ereg_replace('[^a-zA-Z0-9_-]','',$value);
1965 break;
1966 default:
1967 if (substr($func, 0, 3) == 'tx_') {
1968 $evalObj = t3lib_div::getUserObj($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func].':&'.$func);
1969 if(is_object($evalObj) && method_exists($evalObj, 'evaluateFieldValue')) {
1970 $value = $evalObj->evaluateFieldValue($value, $is_in, $set);
1971 }
1972 }
1973 break;
1974 }
1975 }
1976 if ($set) {$res['value'] = $value;}
1977 return $res;
1978 }
1979
1980 /**
1981 * Returns data for group/db and select fields
1982 *
1983 * @param array Current value array
1984 * @param array TCA field config
1985 * @param integer Record id, used for look-up of MM relations (local_uid)
1986 * @param string Status string ('update' or 'new')
1987 * @param string The type, either 'select', 'group' or 'inline'
1988 * @param string Table name, needs to be passed to t3lib_loadDBGroup
1989 * @return array Modified value array
1990 */
1991 function checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,$type,$currentTable) {
1992 $tables = $type=='group'?$tcaFieldConf['allowed']:$tcaFieldConf['foreign_table'].','.$tcaFieldConf['neg_foreign_table'];
1993 $prep = $type=='group'?$tcaFieldConf['prepend_tname']:$tcaFieldConf['neg_foreign_table'];
1994
1995 $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
1996 $dbAnalysis->registerNonTableValues=$tcaFieldConf['allowNonIdValues'] ? 1 : 0;
1997 $dbAnalysis->start(implode(',',$valueArray),$tables, '', 0, $currentTable, $tcaFieldConf);
1998
1999 if ($tcaFieldConf['MM']) {
2000 if ($status=='update') {
2001 $dbAnalysis->writeMM($tcaFieldConf['MM'],$id,$prep);
2002 } else {
2003 $this->dbAnalysisStore[] = array($dbAnalysis,$tcaFieldConf['MM'],$id,$prep); // This will be traversed later to execute the actions
2004 }
2005 $valueArray = $dbAnalysis->countItems();
2006 } elseif ($type == 'inline') {
2007 if ($tcaFieldConf['foreign_field']) {
2008 // update sorting
2009 $dbAnalysis->writeForeignField($tcaFieldConf, $id);
2010 $valueArray = $dbAnalysis->countItems();
2011 } else {
2012 $valueArray = $dbAnalysis->getValueArray($prep);
2013 if ($prep) {
2014 // @TODO: Do we want to support relations to multiple tables in Comma Separated Lists?
2015 $valueArray = $dbAnalysis->convertPosNeg($valueArray,$tcaFieldConf['foreign_table'],$tcaFieldConf['neg_foreign_table']);
2016 }
2017 }
2018 } else {
2019 $valueArray = $dbAnalysis->getValueArray($prep);
2020 if ($type=='select' && $prep) {
2021 $valueArray = $dbAnalysis->convertPosNeg($valueArray,$tcaFieldConf['foreign_table'],$tcaFieldConf['neg_foreign_table']);
2022 }
2023 }
2024
2025 // Here we should see if 1) the records exist anymore, 2) which are new and check if the BE_USER has read-access to the new ones.
2026 return $valueArray;
2027 }
2028
2029 /**
2030 * Explodes the $value, which is a list of files/uids (group select)
2031 *
2032 * @param string Input string, comma separated values. For each part it will also be detected if a '|' is found and the first part will then be used if that is the case. Further the value will be rawurldecoded.
2033 * @return array The value array.
2034 */
2035 function checkValue_group_select_explodeSelectGroupValue($value) {
2036 $valueArray = t3lib_div::trimExplode(',',$value,1);
2037 reset($valueArray);
2038 while(list($key,$newVal)=each($valueArray)) {
2039 $temp=explode('|',$newVal,2);
2040 $valueArray[$key] = str_replace(',','',str_replace('|','',rawurldecode($temp[0])));
2041 }
2042 return $valueArray;
2043 }
2044
2045 /**
2046 * Starts the processing the input data for flexforms. This will traverse all sheets / languages and for each it will traverse the sub-structure.
2047 * See checkValue_flex_procInData_travDS() for more details.
2048 * WARNING: Currently, it traverses based on the actual _data_ array and NOT the _structure_. This means that values for non-valid fields, lKey/vKey/sKeys will be accepted! For traversal of data with a call back function you should rather use class.t3lib_flexformtools.php
2049 *
2050 * @param array The 'data' part of the INPUT flexform data
2051 * @param array The 'data' part of the CURRENT flexform data
2052 * @param array The uploaded files for the 'data' part of the INPUT flexform data
2053 * @param array Data structure for the form (might be sheets or not). Only values in the data array which has a configuration in the data structure will be processed.
2054 * @param array A set of parameters to pass through for the calling of the evaluation functions
2055 * @param string Optional call back function, see checkValue_flex_procInData_travDS() DEPRICATED, use class.t3lib_flexformtools.php instead for traversal!
2056 * @return array The modified 'data' part.
2057 * @see checkValue_flex_procInData_travDS()
2058 */
2059 function checkValue_flex_procInData($dataPart,$dataPart_current,$uploadedFiles,$dataStructArray,$pParams,$callBackFunc='') {
2060 #debug(array($dataPart,$dataPart_current,$dataStructArray));
2061 if (is_array($dataPart)) {
2062 foreach($dataPart as $sKey => $sheetDef) {
2063 list ($dataStruct,$actualSheet) = t3lib_div::resolveSheetDefInDS($dataStructArray,$sKey);
2064 #debug(array($dataStruct,$actualSheet,$sheetDef,$actualSheet,$sKey));
2065 if (is_array($dataStruct) && $actualSheet==$sKey && is_array($sheetDef)) {
2066 foreach($sheetDef as $lKey => $lData) {
2067 $this->checkValue_flex_procInData_travDS(
2068 $dataPart[$sKey][$lKey],
2069 $dataPart_current[$sKey][$lKey],
2070 $uploadedFiles[$sKey][$lKey],
2071 $dataStruct['ROOT']['el'],
2072 $pParams,
2073 $callBackFunc,
2074 $sKey.'/'.$lKey.'/'
2075 );
2076 }
2077 }
2078 }
2079 }
2080
2081 return $dataPart;
2082 }
2083
2084 /**
2085 * Processing of the sheet/language data array
2086 * When it finds a field with a value the processing is done by ->checkValue_SW() by default but if a call back function name is given that method in this class will be called for the processing instead.
2087 *
2088 * @param array New values (those being processed): Multidimensional Data array for sheet/language, passed by reference!
2089 * @param array Current values: Multidimensional Data array. May be empty array() if not needed (for callBackFunctions)
2090 * @param array Uploaded files array for sheet/language. May be empty array() if not needed (for callBackFunctions)
2091 * @param array Data structure which fits the data array
2092 * @param array A set of parameters to pass through for the calling of the evaluation functions / call back function
2093 * @param string Call back function, default is checkValue_SW(). If $this->callBackObj is set to an object, the callback function in that object is called instead.
2094 * @param [type] $structurePath: ...
2095 * @return void
2096 * @see checkValue_flex_procInData()
2097 */
2098 function checkValue_flex_procInData_travDS(&$dataValues,$dataValues_current,$uploadedFiles,$DSelements,$pParams,$callBackFunc,$structurePath) {
2099 if (is_array($DSelements)) {
2100
2101 // For each DS element:
2102 foreach($DSelements as $key => $dsConf) {
2103
2104 // Array/Section:
2105 if ($DSelements[$key]['type']=='array') {
2106 if (is_array($dataValues[$key]['el'])) {
2107 if ($DSelements[$key]['section']) {
2108 foreach($dataValues[$key]['el'] as $ik => $el) {
2109 $theKey = key($el);
2110 if (is_array($dataValues[$key]['el'][$ik][$theKey]['el'])) {
2111 $this->checkValue_flex_procInData_travDS(
2112 $dataValues[$key]['el'][$ik][$theKey]['el'],
2113 $dataValues_current[$key]['el'][$ik][$theKey]['el'],
2114 $uploadedFiles[$key]['el'][$ik][$theKey]['el'],
2115 $DSelements[$key]['el'][$theKey]['el'],
2116 $pParams,
2117 $callBackFunc,
2118 $structurePath.$key.'/el/'.$ik.'/'.$theKey.'/el/'
2119 );
2120 }
2121 }
2122 } else {
2123 if (!isset($dataValues[$key]['el'])) $dataValues[$key]['el']=array();
2124 $this->checkValue_flex_procInData_travDS(
2125 $dataValues[$key]['el'],
2126 $dataValues_current[$key]['el'],
2127 $uploadedFiles[$key]['el'],
2128 $DSelements[$key]['el'],
2129 $pParams,
2130 $callBackFunc,
2131 $structurePath.$key.'/el/'
2132 );
2133 }
2134 }
2135 } else {
2136 if (is_array($dsConf['TCEforms']['config']) && is_array($dataValues[$key])) {
2137 foreach($dataValues[$key] as $vKey => $data) {
2138
2139 if ($callBackFunc) {
2140 if (is_object($this->callBackObj)) {
2141 $res = $this->callBackObj->$callBackFunc(
2142 $pParams,
2143 $dsConf['TCEforms']['config'],
2144 $dataValues[$key][$vKey],
2145 $dataValues_current[$key][$vKey],
2146 $uploadedFiles[$key][$vKey],
2147 $structurePath.$key.'/'.$vKey.'/'
2148 );
2149 } else {
2150 $res = $this->$callBackFunc(
2151 $pParams,
2152 $dsConf['TCEforms']['config'],
2153 $dataValues[$key][$vKey],
2154 $dataValues_current[$key][$vKey],
2155 $uploadedFiles[$key][$vKey]
2156 );
2157 }
2158 } else { // Default
2159 list($CVtable,$CVid,$CVcurValue,$CVstatus,$CVrealPid,$CVrecFID,$CVtscPID) = $pParams;
2160
2161 $res = $this->checkValue_SW(
2162 array(),
2163 $dataValues[$key][$vKey],
2164 $dsConf['TCEforms']['config'],
2165 $CVtable,
2166 $CVid,
2167 $dataValues_current[$key][$vKey],
2168 $CVstatus,
2169 $CVrealPid,
2170 $CVrecFID,
2171 '',
2172 $uploadedFiles[$key][$vKey],
2173 array(),
2174 $CVtscPID
2175 );
2176
2177 // Look for RTE transformation of field:
2178 if ($dataValues[$key]['_TRANSFORM_'.$vKey] == 'RTE' && !$this->dontProcessTransformations) {
2179
2180 // Unsetting trigger field - we absolutely don't want that into the data storage!
2181 unset($dataValues[$key]['_TRANSFORM_'.$vKey]);
2182
2183 if (isset($res['value'])) {
2184
2185 // Calculating/Retrieving some values here:
2186 list(,,$recFieldName) = explode(':', $CVrecFID);
2187 $theTypeString = t3lib_BEfunc::getTCAtypeValue($CVtable,$this->checkValue_currentRecord);
2188 $specConf = t3lib_BEfunc::getSpecConfParts('',$dsConf['TCEforms']['defaultExtras']);
2189
2190 // Find, thisConfig:
2191 $RTEsetup = $this->BE_USER->getTSConfig('RTE',t3lib_BEfunc::getPagesTSconfig($CVtscPID));
2192 $thisConfig = t3lib_BEfunc::RTEsetup($RTEsetup['properties'],$CVtable,$recFieldName,$theTypeString);
2193
2194 // Get RTE object, draw form and set flag:
2195 $RTEobj = &t3lib_BEfunc::RTEgetObj();
2196 if (is_object($RTEobj)) {
2197 $res['value'] = $RTEobj->transformContent('db',$res['value'],$CVtable,$recFieldName,$this->checkValue_currentRecord,$specConf,$thisConfig,'',$CVrealPid);
2198 } else {
2199 debug('NO RTE OBJECT FOUND!');
2200 }
2201 }
2202 }
2203 }
2204
2205 // Adding the value:
2206 if (isset($res['value'])) {
2207 $dataValues[$key][$vKey] = $res['value'];
2208 }
2209 }
2210 }
2211 }
2212 }
2213 }
2214 }
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232 /*********************************************
2233 *
2234 * PROCESSING COMMANDS
2235 *
2236 ********************************************/
2237
2238 /**
2239 * Processing the cmd-array
2240 * See "TYPO3 Core API" for a description of the options.
2241 *
2242 * @return void
2243 */
2244 function process_cmdmap() {
2245 global $TCA, $TYPO3_CONF_VARS;
2246
2247 // Editing frozen:
2248 if ($this->BE_USER->workspace!==0 && $this->BE_USER->workspaceRec['freeze']) {
2249 $this->newlog('All editing in this workspace has been frozen!',1);
2250 return FALSE;
2251 }
2252
2253 // Hook initialization:
2254 $hookObjectsArr = array();
2255 if (is_array ($TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'])) {
2256 foreach ($TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'] as $classRef) {
2257 $hookObjectsArr[] = &t3lib_div::getUserObj($classRef);
2258 }
2259 }
2260
2261 // Traverse command map:
2262 reset($this->cmdmap);
2263 while(list($table,) = each($this->cmdmap)) {
2264
2265 // Check if the table may be modified!
2266 $modifyAccessList = $this->checkModifyAccessList($table);
2267 if (!$modifyAccessList) {
2268 $id = 0;
2269 $this->log($table,$id,2,0,1,"Attempt to modify table '%s' without permission",1,array($table));
2270 } // FIXME: $id not set here (Comment added by Sebastian Kurfuerst)
2271
2272 // Check basic permissions and circumstances:
2273 if (isset($TCA[$table]) && !$this->tableReadOnly($table) && is_array($this->cmdmap[$table]) && $modifyAccessList) {
2274
2275 // Traverse the command map:
2276 foreach($this->cmdmap[$table] as $id => $incomingCmdArray) {
2277 if (is_array($incomingCmdArray)) { // have found a command.
2278
2279 // Get command and value (notice, only one command is observed at a time!):
2280 reset($incomingCmdArray);
2281 $command = key($incomingCmdArray);
2282 $value = current($incomingCmdArray);
2283
2284 foreach($hookObjectsArr as $hookObj) {
2285 if (method_exists($hookObj, 'processCmdmap_preProcess')) {
2286 $hookObj->processCmdmap_preProcess($command, $table, $id, $value, $this);
2287 }
2288 }
2289
2290 // Init copyMapping array:
2291 $this->copyMappingArray = Array(); // Must clear this array before call from here to those functions: Contains mapping information between new and old id numbers.
2292
2293 // Branch, based on command
2294 switch ($command) {
2295 case 'move':
2296 $this->moveRecord($table,$id,$value);
2297 break;
2298 case 'copy':
2299 if ($table === 'pages') {
2300 $this->copyPages($id,$value);
2301 } else {
2302 $this->copyRecord($table,$id,$value,1);
2303 }
2304 break;
2305 case 'localize':
2306 $this->localize($table,$id,$value);
2307 break;
2308 case 'version':
2309 switch ((string)$value['action']) {
2310 case 'new':
2311 $versionizeTree = t3lib_div::intInRange(!isset($value['treeLevels'])?-1:$value['treeLevels'],-1,100);
2312 if ($table == 'pages' && $versionizeTree>=0) {
2313 $this->versionizePages($id,$value['label'],$versionizeTree);
2314 } else {
2315 $this->versionizeRecord($table,$id,$value['label']);
2316 }
2317 break;
2318 case 'swap':
2319 $this->version_swap($table,$id,$value['swapWith'],$value['swapIntoWS']);
2320 break;
2321 case 'clearWSID':
2322 $this->version_clearWSID($table,$id);
2323 break;
2324 case 'setStage':
2325 $idList = t3lib_div::trimExplode(',',$id,1);
2326 foreach($idList as $id) {
2327 $this->version_setStage($table,$id,$value['stageId'],$value['comment']?$value['comment']:$this->generalComment);
2328 }
2329 break;
2330 }
2331 break;
2332 case 'delete':
2333 $this->deleteAction($table, $id);
2334 break;
2335 case 'undelete':
2336 $this->undeleteRecord($table, $id);
2337 break;
2338 }
2339
2340 foreach($hookObjectsArr as $hookObj) {
2341 if (method_exists($hookObj, 'processCmdmap_postProcess')) {
2342 $hookObj->processCmdmap_postProcess($command, $table, $id, $value, $this);
2343 }
2344 }
2345
2346 // Merging the copy-array info together for remapping purposes.
2347 $this->copyMappingArray_merged= t3lib_div::array_merge_recursive_overrule($this->copyMappingArray_merged,$this->copyMappingArray);
2348 }
2349 }
2350 }
2351 }
2352
2353 // Finally, before exit, check if there are ID references to remap. This might be the case if versioning or copying has taken place!
2354 $this->remapListedDBRecords();
2355 }
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367 /*********************************************
2368 *
2369 * Cmd: Copying
2370 *
2371 ********************************************/
2372
2373 /**
2374 * Copying a single record
2375 *
2376 * @param string Element table
2377 * @param integer Element UID
2378 * @param integer $destPid: >=0 then it points to a page-id on which to insert the record (as the first element). <0 then it points to a uid from its own table after which to insert it (works if
2379 * @param boolean $first is a flag set, if the record copied is NOT a 'slave' to another record copied. That is, if this record was asked to be copied in the cmd-array
2380 * @param array Associative array with field/value pairs to override directly. Notice; Fields must exist in the table record and NOT be among excluded fields!
2381 * @param string Commalist of fields to exclude from the copy process (might get default values)
2382 * @return integer ID of new record, if any
2383 */
2384 function copyRecord($table,$uid,$destPid,$first=0,$overrideValues=array(),$excludeFields='') {
2385 global $TCA;
2386
2387 $uid = $origUid = intval($uid);
2388 if ($TCA[$table] && $uid) {
2389 t3lib_div::loadTCA($table);
2390 /*
2391 // In case the record to be moved turns out to be an offline version, we have to find the live version and work on that one (this case happens for pages with "branch" versioning type)
2392 if ($lookForLiveVersion = t3lib_BEfunc::getLiveVersionOfRecord($table,$uid,'uid')) {
2393 $uid = $lookForLiveVersion['uid'];
2394 }
2395 // Get workspace version of the source record, if any: Then we will copy workspace version instead:
2396 if ($WSversion = t3lib_BEfunc::getWorkspaceVersionOfRecord($this->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid')) {
2397 $uid = $WSversion['uid'];
2398 }
2399 // 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.
2400 */
2401 if ($this->doesRecordExist($table,$uid,'show')) { // This checks if the record can be selected which is all that a copy action requires.
2402 $data = Array();
2403
2404 $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));
2405
2406 // $row = $this->recordInfo($table,$uid,'*');
2407 $row = t3lib_BEfunc::getRecordWSOL($table,$uid); // So it copies (and localized) content from workspace...
2408 if (is_array($row)) {
2409
2410 // Initializing:
2411 $theNewID = uniqid('NEW');
2412 $enableField = isset($TCA[$table]['ctrl']['enablecolumns']) ? $TCA[$table]['ctrl']['enablecolumns']['disabled'] : '';
2413 $headerField = $TCA[$table]['ctrl']['label'];
2414
2415 // Getting default data:
2416 $defaultData = $this->newFieldArray($table);
2417
2418 // Getting "copy-after" fields if applicable:
2419 // origDestPid is retrieve before it may possibly be converted to resolvePid if the table is not sorted anyway. In this way, copying records to after another records which are not sorted still lets you use this function in order to copy fields from the one before.
2420 $copyAfterFields = $destPid<0 ? $this->fixCopyAfterDuplFields($table,$uid,abs($destPid),0) : array();
2421
2422 // Page TSconfig related:
2423 $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...
2424 $TSConfig = $this->getTCEMAIN_TSconfig($tscPID);
2425 $tE = $this->getTableEntries($table,$TSConfig);
2426
2427 // Traverse ALL fields of the selected record:
2428 foreach($row as $field => $value) {
2429 if (!in_array($field,$nonFields)) {
2430
2431 // Get TCA configuration for the field:
2432 $conf = $TCA[$table]['columns'][$field]['config'];
2433
2434 // Preparation/Processing of the value:
2435 if ($field=='pid') { // "pid" is hardcoded of course:
2436 $value = $destPid;
2437 } elseif (isset($overrideValues[$field])) { // Override value...
2438 $value = $overrideValues[$field];
2439 } elseif (isset($copyAfterFields[$field])) { // Copy-after value if available:
2440 $value = $copyAfterFields[$field];
2441 } elseif ($TCA[$table]['ctrl']['setToDefaultOnCopy'] && t3lib_div::inList($TCA[$table]['ctrl']['setToDefaultOnCopy'],$field)) { // Revert to default for some fields:
2442 $value = $defaultData[$field];
2443 } else {
2444 // Hide at copy may override:
2445 if ($first && $field==$enableField && $TCA[$table]['ctrl']['hideAtCopy'] && !$this->neverHideAtCopy && !$tE['disableHideAtCopy']) {
2446 $value=1;
2447 }
2448 // Prepend label on copy:
2449 if ($first && $field==$headerField && $TCA[$table]['ctrl']['prependAtCopy'] && !$tE['disablePrependAtCopy']) {
2450 $value = $this->getCopyHeader($table,$this->resolvePid($table,$destPid),$field,$this->clearPrefixFromValue($table,$value),0);
2451 }
2452 // Processing based on the TCA config field type (files, references, flexforms...)
2453 $value = $this->copyRecord_procBasedOnFieldType($table,$uid,$field,$value,$row,$conf);
2454 }
2455
2456 // Add value to array.
2457 $data[$table][$theNewID][$field] = $value;
2458 }
2459 }
2460
2461 // Overriding values:
2462 if ($TCA[$table]['ctrl']['editlock']) {
2463 $data[$table][$theNewID][$TCA[$table]['ctrl']['editlock']] = 0;
2464 }
2465
2466 // Setting original UID:
2467 if ($TCA[$table]['ctrl']['origUid']) {
2468 $data[$table][$theNewID][$TCA[$table]['ctrl']['origUid']] = $uid;
2469 }
2470
2471 // Do the copy by simply submitting the array through TCEmain:
2472 $copyTCE = t3lib_div::makeInstance('t3lib_TCEmain');
2473 $copyTCE->stripslashes_values = 0;
2474 $copyTCE->copyTree = $this->copyTree;
2475 $copyTCE->cachedTSconfig = $this->cachedTSconfig; // Copy forth the cached TSconfig
2476 $copyTCE->dontProcessTransformations=1; // Transformations should NOT be carried out during copy
2477
2478 $copyTCE->start($data,'',$this->BE_USER);
2479 $copyTCE->process_datamap();
2480
2481 // Getting the new UID:
2482 $theNewSQLID = $copyTCE->substNEWwithIDs[$theNewID];
2483 if ($theNewSQLID) {
2484 $this->copyMappingArray[$table][$origUid] = $theNewSQLID;
2485 }
2486
2487 // Copy back the cached TSconfig
2488 $this->cachedTSconfig = $copyTCE->cachedTSconfig;
2489 $this->errorLog = array_merge($this->errorLog,$copyTCE->errorLog);
2490 unset($copyTCE);
2491
2492 return $theNewSQLID;
2493 } else $this->log($table,$uid,3,0,1,'Attempt to copy record that did not exist!');
2494 } else $this->log($table,$uid,3,0,1,'Attempt to copy record without permission');
2495 }
2496 }
2497
2498 /**
2499 * Copying pages
2500 * Main function for copying pages.
2501 *
2502 * @param integer Page UID to copy
2503 * @param integer Destination PID: >=0 then it points to a page-id on which to insert the record (as the first element). <0 then it points to a uid from its own table after which to insert it (works if
2504 * @return void
2505 */
2506 function copyPages($uid,$destPid) {
2507
2508 // Initialize:
2509 $uid = intval($uid);
2510 $destPid = intval($destPid);
2511
2512 // Finding list of tables to copy.
2513 $copyTablesArray = $this->admin ? $this->compileAdminTables() : explode(',',$this->BE_USER->groupData['tables_modify']); // These are the tables, the user may modify
2514 if (!strstr($this->copyWhichTables,'*')) { // If not all tables are allowed then make a list of allowed tables: That is the tables that figure in both allowed tables AND the copyTable-list
2515 foreach($copyTablesArray as $k => $table) {
2516 if (!$table || !t3lib_div::inList($this->copyWhichTables.',pages',$table)) { // pages are always going...
2517 unset($copyTablesArray[$k]);
2518 }
2519 }
2520 }
2521 $copyTablesArray = array_unique($copyTablesArray);
2522
2523 // Begin to copy pages if we're allowed to:
2524 if ($this->admin || in_array('pages',$copyTablesArray)) {
2525
2526 // Copy this page we're on. And set first-flag (this will trigger that the record is hidden if that is configured)!
2527 $theNewRootID = $this->copySpecificPage($uid,$destPid,$copyTablesArray,1);
2528
2529 // If we're going to copy recursively...:
2530 if ($theNewRootID && $this->copyTree) {
2531
2532 // Get ALL subpages to copy (read-permissions are respected!):
2533 $CPtable = $this->int_pageTreeInfo(Array(), $uid, intval($this->copyTree), $theNewRootID);
2534
2535 // Now copying the subpages:
2536 foreach($CPtable as $thePageUid => $thePagePid) {
2537 $newPid = $this->copyMappingArray['pages'][$thePagePid];
2538 if (isset($newPid)) {
2539 $this->copySpecificPage($thePageUid,$newPid,$copyTablesArray);
2540 } else {
2541 $this->log('pages',$uid,5,0,1,'Something went wrong during copying branch');
2542 break;
2543 }
2544 }
2545 } // else the page was not copied. Too bad...
2546 } else {
2547 $this->log('pages',$uid,5,0,1,'Attempt to copy page without permission to this table');
2548 }
2549 }
2550
2551 /**
2552 * Copying a single page ($uid) to $destPid and all tables in the array copyTablesArray.
2553 *
2554 * @param integer Page uid
2555 * @param integer Destination PID: >=0 then it points to a page-id on which to insert the record (as the first element). <0 then it points to a uid from its own table after which to insert it (works if
2556 * @param array Table on pages to copy along with the page.
2557 * @param boolean $first is a flag set, if the record copied is NOT a 'slave' to another record copied. That is, if this record was asked to be copied in the cmd-array
2558 * @return integer The id of the new page, if applicable.
2559 */
2560 function copySpecificPage($uid,$destPid,$copyTablesArray,$first=0) {
2561 global $TCA;
2562
2563 // Copy the page itself:
2564 $theNewRootID = $this->copyRecord('pages',$uid,$destPid,$first);
2565
2566 // If a new page was created upon the copy operation we will proceed with all the tables ON that page:
2567 if ($theNewRootID) {
2568 foreach($copyTablesArray as $table) {
2569 if ($table && is_array($TCA[$table]) && $table!='pages') { // all records under the page is copied.
2570 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'pid='.intval($uid).$this->deleteClause($table), '', ($TCA[$table]['ctrl']['sortby'] ? $TCA[$table]['ctrl']['sortby'].' DESC' : ''));
2571 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
2572 $this->copyRecord($table,$row['uid'], $theNewRootID); // Copying each of the underlying records...
2573 }
2574 }
2575 }
2576 return $theNewRootID;
2577 }
2578 }
2579
2580 /**
2581 * Copying records, but makes a "raw" copy of a record.
2582 * Basically the only thing observed is field processing like the copying of files and correction of ids. All other fields are 1-1 copied.
2583 * Technically the copy is made with THIS instance of the tcemain class contrary to copyRecord() which creates a new instance and uses the processData() function.
2584 * The copy is created by insertNewCopyVersion() which bypasses most of the regular input checking associated with processData() - maybe copyRecord() should even do this as well!?
2585 * This function is used to create new versions of a record.
2586 * NOTICE: DOES NOT CHECK PERMISSIONS to create! And since page permissions are just passed through and not changed to the user who executes the copy we cannot enforce permissions without getting an incomplete copy - unless we change permissions of course.
2587 *
2588 * @param string Element table
2589 * @param integer Element UID
2590 * @param integer Element PID (real PID, not checked)
2591 * @param array Override array - must NOT contain any fields not in the table!
2592 * @return integer Returns the new ID of the record (if applicable)
2593 */
2594 function copyRecord_raw($table,$uid,$pid,$overrideArray=array()) {
2595 global $TCA;
2596
2597 $uid = intval($uid);
2598 if ($TCA[$table] && $uid) {
2599 t3lib_div::loadTCA($table);
2600 if ($this->doesRecordExist($table,$uid,'show')) {
2601
2602 // Set up fields which should not be processed. They are still written - just passed through no-questions-asked!
2603 $nonFields = array('uid','pid','t3ver_id','t3ver_oid','t3ver_wsid','t3ver_label','t3ver_state','t3ver_swapmode','t3ver_count','t3ver_stage','t3ver_tstamp','perms_userid','perms_groupid','perms_user','perms_group','perms_everybody');
2604
2605 // Select main record:
2606 $row = $this->recordInfo($table,$uid,'*');
2607 if (is_array($row)) {
2608
2609 // Merge in override array.
2610 $row = array_merge($row,$overrideArray);
2611
2612 // Traverse ALL fields of the selected record:
2613 foreach($row as $field => $value) {
2614 if (!in_array($field,$nonFields)) {
2615
2616 // Get TCA configuration for the field:
2617 $conf = $TCA[$table]['columns'][$field]['config'];
2618 if (is_array($conf)) {
2619 // Processing based on the TCA config field type (files, references, flexforms...)
2620 $value = $this->copyRecord_procBasedOnFieldType($table,$uid,$field,$value,$row,$conf);
2621 }
2622
2623 // Add value to array.
2624 $row[$field] = $value;
2625 }
2626 }
2627
2628 // Force versioning related fields:
2629 $row['pid'] = $pid;
2630
2631 // Setting original UID:
2632 if ($TCA[$table]['ctrl']['origUid']) {
2633 $row[$TCA[$table]['ctrl']['origUid']] = $uid;
2634 }
2635
2636 // Do the copy by internal function
2637 $theNewSQLID = $this->insertNewCopyVersion($table,$row,$pid);
2638 if ($theNewSQLID) {
2639 $this->dbAnalysisStoreExec();
2640 $this->dbAnalysisStore = array();
2641 return $this->copyMappingArray[$table][$uid] = $theNewSQLID;
2642 }
2643 } else $this->log($table,$uid,3,0,1,'Attempt to rawcopy/versionize record that did not exist!');
2644 } else $this->log($table,$uid,3,0,1,'Attempt to rawcopy/versionize record without copy permission');
2645 }
2646 }
2647
2648 /**
2649 * Copies all records from tables in $copyTablesArray from page with $old_pid to page with $new_pid
2650 * Uses raw-copy for the operation (meant for versioning!)
2651 *
2652 * @param integer Current page id.
2653 * @param integer New page id
2654 * @param array Array of tables from which to copy
2655 * @return void
2656 * @see versionizePages()
2657 */
2658 function rawCopyPageContent($old_pid,$new_pid,$copyTablesArray) {
2659 global $TCA;
2660
2661 if ($new_pid) {
2662 foreach($copyTablesArray as $table) {
2663 if ($table && is_array($TCA[$table]) && $table!='pages') { // all records under the page is copied.
2664 $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'pid='.intval($old_pid).$this->deleteClause($table));
2665 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
2666 $this->copyRecord_raw($table,$row['uid'],$new_pid); // Copying each of the underlying records (method RAW)
2667 }
2668 }
2669 }
2670 }
2671 }
2672
2673 /**
2674 * Inserts a record in the database, passing TCA configuration values through checkValue() but otherwise does NOTHING and checks nothing regarding permissions.
2675 * Passes the "version" parameter to insertDB() so the copy will look like a new version in the log - should probably be changed or modified a bit for more broad usage...
2676 *
2677 * @param string Table name
2678 * @param array Field array to insert as a record
2679 * @param integer The value of PID field. -1 is indication that we are creating a new version!
2680 * @return integer Returns the new ID of the record (if applicable)
2681 */
2682 function insertNewCopyVersion($table,$fieldArray,$realPid) {
2683 global $TCA;
2684
2685 $id = uniqid('NEW');
2686
2687 // $fieldArray is set as current record.
2688 // The point is that when new records are created as copies with flex type fields there might be a field containing information about which DataStructure to use and without that information the flexforms cannot be correctly processed.... This should be OK since the $checkValueRecord is used by the flexform evaluation only anyways...
2689 $this->checkValue_currentRecord = $fieldArray;
2690
2691 // Traverse record and input-process each value:
2692 foreach($fieldArray as $field => $fieldValue) {
2693 if (isset($TCA[$table]['columns'][$field])) {
2694 // Evaluating the value.
2695 $res = $this->checkValue($table,$field,$fieldValue,$id,'new',$realPid,0);
2696 if (isset($res['value'])) {
2697 $fieldArray[$field] = $res['value'];
2698 }
2699 }
2700 }
2701
2702 // System fields being set:
2703 if ($TCA[$table]['ctrl']['crdate']) {
2704 $fieldArray[$TCA[$table]['ctrl']['crdate']]=time();
2705 }
2706 if ($TCA[$table]['ctrl']['cruser_id']) {
2707 $fieldArray[$TCA[$table]['ctrl']['cruser_id']]=$this->userid;
2708 }
2709 if ($TCA[$table]['ctrl']['tstamp']) {
2710 $fieldArray[$TCA[$table]['ctrl']['tstamp']]=time();
2711 }
2712
2713 // Finally, insert record:
2714 $this->insertDB($table,$id,$fieldArray, TRUE);
2715
2716 // Return new id:
2717 return $this->substNEWwithIDs[$id];
2718 }
2719
2720 /**
2721 * Processing/Preparing content for copyRecord() function
2722 *
2723 * @param string Table name
2724 * @param integer Record uid
2725 * @param string Field name being processed
2726 * @param string Input value to be processed.
2727 * @param array Record array
2728 * @param array TCA field configuration
2729 * @return mixed Processed value. Normally a string/integer, but can be an array for flexforms!
2730 * @access private
2731 * @see copyRecord()
2732 */
2733 function copyRecord_procBasedOnFieldType($table,$uid,$field,$value,$row,$conf) {
2734 global $TCA;
2735
2736 // Process references and files, currently that means only the files, prepending absolute paths (so the TCEmain engine will detect the file as new and one that should be made into a copy)
2737 $value = $this->copyRecord_procFilesRefs($conf, $uid, $value);
2738 $inlineSubType = $this->getInlineFieldType($conf);
2739
2740 // Register if there are references to take care of or MM is used on an inline field (no change to value):
2741 if ($this->isReferenceField($conf) || $inlineSubType == 'mm') {
2742 $allowedTables = $conf['type']=='group' ? $conf['allowed'] : $conf['foreign_table'].','.$conf['neg_foreign_table'];
2743 $prependName = $conf['type']=='group' ? $conf['prepend_tname'] : $conf['neg_foreign_table'];
2744 if ($conf['MM']) {
2745 $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
2746 $dbAnalysis->start('', $allowedTables, $conf['MM'], $uid, $table, $conf);
2747 $value = implode(',',$dbAnalysis->getValueArray($prependName));
2748 }
2749 if ($value) { // Setting the value in this array will notify the remapListedDBRecords() function that this field MAY need references to be corrected
2750 $this->registerDBList[$table][$uid][$field] = $value;
2751 }
2752
2753 // if another inline subtype is used (foreign_field, mm with attributes or simply item list)
2754 } elseif ($inlineSubType !== false) {
2755 $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
2756 $dbAnalysis->start($value, $conf['foreign_table'], $conf['MM'], $uid, $table, $conf);
2757
2758 // walk through the items, copy them and remember the new id
2759 foreach ($dbAnalysis->itemArray as $k => $v) {
2760 $newId = $this->copyRecord($v['table'], $v['id'], -$v['id']);
2761 $dbAnalysis->itemArray[$k]['id'] = $newId;
2762 }
2763
2764 // store the new values, we will set up the uids for the subtype later on
2765 $value = implode(',',$dbAnalysis->getValueArray());
2766 $this->registerDBList[$table][$uid][$field] = $value;
2767 }
2768
2769 // For "flex" fieldtypes we need to traverse the structure for two reasons: If there are file references they have to be prepended with absolute paths and if there are database reference they MIGHT need to be remapped (still done in remapListedDBRecords())
2770 if ($conf['type']=='flex') {
2771
2772 // Get current value array:
2773 $dataStructArray = t3lib_BEfunc::getFlexFormDS($conf, $row, $table);
2774 $currentValueArray = t3lib_div::xml2array($value);
2775
2776 // Traversing the XML structure, processing files:
2777 if (is_array($currentValueArray)) {
2778 $currentValueArray['data'] = $this->checkValue_flex_procInData(
2779 $currentValueArray['data'],
2780 array(), // Not used.
2781 array(), // Not used.
2782 $dataStructArray,
2783 array($table,$uid,$field), // Parameters.
2784 'copyRecord_flexFormCallBack'
2785 );
2786 $value = $currentValueArray; // Setting value as an array! -> which means the input will be processed according to the 'flex' type when the new copy is created.
2787 }
2788 }
2789
2790 return $value;
2791 }
2792
2793 /**
2794 * Callback function for traversing the FlexForm structure in relation to creating copied files of file relations inside of flex form structures.
2795 *
2796 * @param array Array of parameters in num-indexes: table, uid, field
2797 * @param array TCA field configuration (from Data Structure XML)
2798 * @param string The value of the flexForm field
2799 * @param string Not used.
2800 * @param string Not used.
2801 * @return array Result array with key "value" containing the value of the processing.
2802 * @see copyRecord(), checkValue_flex_procInData_travDS()
2803 */
2804 function copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2) {
2805
2806 // Extract parameters:
2807 list($table, $uid, $field) = $pParams;
2808
2809 // Process references and files, currently that means only the files, prepending absolute paths:
2810 $dataValue = $this->copyRecord_procFilesRefs($dsConf, $uid, $dataValue);
2811
2812 // If references are set for this field, set flag so they can be corrected later (in ->remapListedDBRecords())
2813 if ($this->isReferenceField($dsConf) && strlen($dataValue)) {
2814 $this->registerDBList[$table][$uid][$field] = 'FlexForm_reference';
2815 }
2816
2817 // Return
2818 return array('value' => $dataValue);
2819 }
2820
2821 /**
2822 * Modifying a field value for any situation regarding files/references:
2823 * For attached files: take current filenames and prepend absolute paths so they get copied.
2824 * For DB references: Nothing done.
2825 *
2826 * @param array TCE field config
2827 * @param integer Record UID
2828 * @param string Field value (eg. list of files)
2829 * @return string The (possibly modified) value
2830 * @see copyRecord(), copyRecord_flexFormCallBack()
2831 */
2832 function copyRecord_procFilesRefs($conf, $uid, $value) {
2833
2834 // Prepend absolute paths to files:
2835 if ($conf['type']=='group' && $conf['internal_type']=='file') {
2836
2837 // Get an array with files as values:
2838 if ($conf['MM']) {
2839 $theFileValues = array();
2840
2841 $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
2842 $dbAnalysis->start('', 'files', $conf['MM'], $uid);
2843
2844 foreach($dbAnalysis->itemArray as $somekey => $someval) {
2845 if ($someval['id']) {
2846 $theFileValues[] = $someval['id'];
2847 }
2848 }
2849 } else {
2850 $theFileValues = t3lib_div::trimExplode(',',$value,1);
2851 }
2852
2853 // Traverse this array of files:
2854 $uploadFolder = $conf['uploadfolder'];
2855 $dest = $this->destPathFromUploadFolder($uploadFolder);
2856 $newValue = array();
2857
2858 foreach($theFileValues as $file) {
2859 if (trim($file)) {
2860 $realFile = $dest.'/'.trim($file);
2861 if (@is_file($realFile)) {
2862 $newValue[] = $realFile;
2863 }
2864 }
2865 }
2866
2867 // Implode the new filelist into the new value (all files have absolute paths now which means they will get copied when entering TCEmain as new values...)
2868 $value = implode(',',$newValue);
2869 }
2870
2871 // Return the new value:
2872 return $value;
2873 }
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887 /*********************************************
2888 *
2889 * Cmd: Moving, Localizing
2890 *
2891 ********************************************/
2892
2893 /**
2894 * Moving single records
2895 *
2896 * @param string Table name to move
2897 * @param integer Record uid to move