[BUGFIX] Some methods in t3lib_TSparser should be static
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_extfilefunc.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2011 Kasper Skårhøj (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 * Extending class to class t3lib_basicFileFunctions
29 *
30 * Revised for TYPO3 3.6 May/2004 by Kasper Skårhøj
31 *
32 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
33 */
34
35
36 /**
37 * Contains functions for performing file operations like copying, pasting, uploading, moving, deleting etc. through the TCE
38 * Extending class to class t3lib_basicFileFunctions.
39 *
40 * see basicFileFunctions
41 * see document "TYPO3 Core API" for syntax
42 *
43 * This class contains functions primarily used by tce_file.php (TYPO3 Core Engine for file manipulation)
44 * Functions include copying, moving, deleting, uploading and so on...
45 *
46 * Important internal variables:
47 *
48 * $filemounts (see basicFileFunctions)
49 * $f_ext (see basicFileFunctions)
50 * ... All fileoperations must be within the filemount-paths. Further the fileextension MUST validate TRUE with the f_ext array
51 *
52 * The unzip-function allows unzip only if the destination path has it's f_ext[]['allow'] set to '*'!!
53 * You are allowed to copy/move folders within the same 'space' (web/ftp).
54 * You are allowed to copy/move folders between spaces (web/ftp) IF the destination has it's f_ext[]['allow'] set to '*'!
55 *
56 * Advice:
57 * You should always exclude php-files from the webspace. This will keep people from uploading, copy/moving and renaming files to become executable php scripts.
58 * You should never mount a ftp_space 'below' the webspace so that it reaches into the webspace. This is because if somebody unzips a zip-file in the ftp-space so that it reaches out into the webspace this will be a violation of the safety
59 * For example this is a bad idea: you have an ftp-space that is '/www/' and a web-space that is '/www/htdocs/'
60 *
61 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
62 * @package TYPO3
63 * @subpackage t3lib
64 */
65 class t3lib_extFileFunctions extends t3lib_basicFileFunctions {
66
67 // External static variables:
68 // Notice; some of these are overridden in the start() method with values from $GLOBALS['TYPO3_CONF_VARS']['BE']
69 var $maxCopyFileSize = 10000; // max copy size (kb) for files
70 var $maxMoveFileSize = 10000; // max move size (kb) for files
71 var $maxUploadFileSize = 10000; // max upload size (kb) for files. Remember that PHP has an inner limit often set to 2 MB
72 var $unzipPath = ''; // Path to unzip-program (with trailing '/')
73 var $dontCheckForUnique = 0; // If set, the uploaded files will overwrite existing files.
74
75 var $actionPerms = array( // This array is self-explaning (look in the class below). It grants access to the functions. This could be set from outside in order to enabled functions to users. See also the function init_actionPerms() which takes input directly from the user-record
76 'deleteFile' => 0, // Deleting files physically
77 'deleteFolder' => 0, // Deleting foldes physically
78 'deleteFolderRecursively' => 0, // normally folders are deleted by the PHP-function rmdir(), but with this option a user deletes with 'rm -Rf ....' which is pretty wild!
79 'moveFile' => 0,
80 'moveFolder' => 0,
81 'copyFile' => 0,
82 'copyFolder' => 0,
83 'newFolder' => 0,
84 'newFile' => 0,
85 'editFile' => 0,
86 'unzipFile' => 0,
87 'uploadFile' => 0,
88 'renameFile' => 0,
89 'renameFolder' => 0
90 );
91
92 var $recyclerFN = '_recycler_'; // This is regarded to be the recycler folder
93 var $useRecycler = 1; // 0 = no, 1 = if available, 2 = always
94
95 // Internal, static:
96 var $PHPFileFunctions = 0; // If set, all fileoperations are done by the default PHP-functions. This is necessary under windows! On UNIX the system commands by exec() can be used
97 var $dont_use_exec_commands = 0; // This is necessary under windows!
98
99 // Internal, dynamic:
100 var $internalUploadMap = array(); // Will contain map between upload ID and the final filename
101
102 var $lastError = '';
103
104
105 /**
106 * Initialization of the class
107 *
108 * @param array The $file array with the commands to execute. See "TYPO3 Core API" document
109 * @return void
110 */
111 function start($fileCmds) {
112
113 // Configure settings from TYPO3_CONF_VARS:
114 if (TYPO3_OS == 'WIN' || $GLOBALS['TYPO3_CONF_VARS']['BE']['disable_exec_function']) {
115 $this->PHPFileFunctions = 1;
116 $this->dont_use_exec_commands = 1;
117 } else {
118 $this->PHPFileFunctions = $GLOBALS['TYPO3_CONF_VARS']['BE']['usePHPFileFunctions'];
119 }
120
121 $unzipPath = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['unzip_path']);
122 if (substr($unzipPath, -1) !== '/' && is_dir($unzipPath)) {
123 // Make sure the path ends with a slash
124 $unzipPath.= '/';
125 }
126 $this->unzipPath = $unzipPath;
127
128 $maxFileSize = intval($GLOBALS['TYPO3_CONF_VARS']['BE']['maxFileSize']);
129 if ($maxFileSize > 0) {
130 $this->maxCopyFileSize = $maxFileSize;
131 $this->maxMoveFileSize = $maxFileSize;
132 }
133 $this->maxUploadFileSize = t3lib_div::getMaxUploadFileSize();
134
135 // Initializing file processing commands:
136 $this->fileCmdMap = $fileCmds;
137 }
138
139 /**
140 * Sets up permission to perform file/directory operations.
141 * See below or the be_user-table for the significance of the various bits in $setup.
142 *
143 * @param integer File permission integer from BE_USER OR'ed with permissions of back-end groups this user is a member of
144 * @return void
145 */
146 function init_actionPerms($setup) {
147 if (($setup & 1) == 1) { // Files: Upload,Copy,Move,Delete,Rename
148 $this->actionPerms['uploadFile'] = 1;
149 $this->actionPerms['copyFile'] = 1;
150 $this->actionPerms['moveFile'] = 1;
151 $this->actionPerms['deleteFile'] = 1;
152 $this->actionPerms['renameFile'] = 1;
153 $this->actionPerms['editFile'] = 1;
154 $this->actionPerms['newFile'] = 1;
155 }
156 if (($setup & 2) == 2) { // Files: Unzip
157 $this->actionPerms['unzipFile'] = 1;
158 }
159 if (($setup & 4) == 4) { // Directory: Move,Delete,Rename,New
160 $this->actionPerms['moveFolder'] = 1;
161 $this->actionPerms['deleteFolder'] = 1;
162 $this->actionPerms['renameFolder'] = 1;
163 $this->actionPerms['newFolder'] = 1;
164 }
165 if (($setup & 8) == 8) { // Directory: Copy
166 $this->actionPerms['copyFolder'] = 1;
167 }
168 if (($setup & 16) == 16) { // Directory: Delete recursively (rm -Rf)
169 $this->actionPerms['deleteFolderRecursively'] = 1;
170 }
171 }
172
173 /**
174 * Processing the command array in $this->fileCmdMap
175 *
176 * @return mixed FALSE, if the file functions were not initialized
177 * otherwise returns an array of all the results that are returned
178 * from each command, separated in each action.
179 */
180 function processData() {
181 $result = array();
182 if (!$this->isInit) {
183 return FALSE;
184 }
185
186 if (is_array($this->fileCmdMap)) {
187
188 // Check if there were uploads expected, but no one made
189 if ($this->fileCmdMap['upload']) {
190 $uploads = $this->fileCmdMap['upload'];
191 foreach ($uploads as $upload) {
192 if (!$_FILES['upload_' . $upload['data']]['name']) {
193 unset($this->fileCmdMap['upload'][$upload['data']]);
194 }
195 }
196 if (count($this->fileCmdMap['upload']) == 0) {
197 $this->writelog(1, 1, 108, 'No file was uploaded!', '');
198 }
199 }
200
201 // Traverse each set of actions
202 foreach ($this->fileCmdMap as $action => $actionData) {
203
204 // Traverse all action data. More than one file might be affected at the same time.
205 if (is_array($actionData)) {
206 $result[$action] = array();
207 foreach ($actionData as $cmdArr) {
208
209 // Clear file stats
210 clearstatcache();
211
212 // Branch out based on command:
213 switch ($action) {
214 case 'delete':
215 $result[$action][] = $this->func_delete($cmdArr);
216 break;
217 case 'copy':
218 $result[$action][] = $this->func_copy($cmdArr);
219 break;
220 case 'move':
221 $result[$action][] = $this->func_move($cmdArr);
222 break;
223 case 'rename':
224 $result[$action][] = $this->func_rename($cmdArr);
225 break;
226 case 'newfolder':
227 $result[$action][] = $this->func_newfolder($cmdArr);
228 break;
229 case 'newfile':
230 $result[$action][] = $this->func_newfile($cmdArr);
231 break;
232 case 'editfile':
233 $result[$action][] = $this->func_edit($cmdArr);
234 break;
235 case 'upload':
236 $result[$action][] = $this->func_upload($cmdArr);
237 break;
238 case 'unzip':
239 $result[$action][] = $this->func_unzip($cmdArr);
240 break;
241 }
242
243 // Hook for post-processing the action
244 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_extfilefunc.php']['processData'])) {
245 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_extfilefunc.php']['processData'] as $classRef) {
246 $hookObject = t3lib_div::getUserObj($classRef);
247
248 if (!($hookObject instanceof t3lib_extFileFunctions_processDataHook)) {
249 throw new UnexpectedValueException('$hookObject must implement interface t3lib_extFileFunctions_processDataHook', 1279719168);
250 }
251
252 $hookObject->processData_postProcessAction($action, $cmdArr, $result[$action], $this);
253 }
254 }
255 }
256 }
257 }
258 }
259 return $result;
260 }
261
262 /**
263 * Adds log error messages from the operations of this script instance to the FlashMessageQueue
264 *
265 * @param string Redirect URL (for creating link in message)
266 * @return void
267 */
268 function printLogErrorMessages($redirect = '') {
269 $this->getErrorMessages();
270 }
271
272
273 /**
274 * Adds log error messages from the previous file operations of this script instance
275 * to the FlashMessageQueue
276 *
277 * @return void
278 */
279 function getErrorMessages() {
280 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
281 '*',
282 'sys_log',
283 'type = 2 AND userid = ' . intval($GLOBALS['BE_USER']->user['uid'])
284 . ' AND tstamp=' . intval($GLOBALS['EXEC_TIME'])
285 . ' AND error != 0'
286 );
287 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
288 $logData = unserialize($row['log_data']);
289 $msg = $row['error'] . ': ' . sprintf($row['details'], $logData[0], $logData[1], $logData[2], $logData[3], $logData[4]);
290 $flashMessage = t3lib_div::makeInstance(
291 't3lib_FlashMessage',
292 $msg,
293 '',
294 t3lib_FlashMessage::ERROR,
295 TRUE
296 );
297 t3lib_FlashMessageQueue::addMessage($flashMessage);
298 }
299 $GLOBALS['TYPO3_DB']->sql_free_result($res);
300 }
301
302
303 /**
304 * Goes back in the path and checks in each directory if a folder named $this->recyclerFN (usually '_recycler_') is present.
305 * If a folder in the tree happens to be a _recycler_-folder (which means that we're deleting something inside a _recycler_-folder) this is ignored
306 *
307 * @param string Takes a valid Path ($theFile)
308 * @return string Returns the path (without trailing slash) of the closest recycle-folder if found. Else FALSE.
309 */
310 function findRecycler($theFile) {
311 if ($this->isPathValid($theFile)) {
312 $theFile = $this->cleanDirectoryName($theFile);
313 $fI = t3lib_div::split_fileref($theFile);
314 $c = 0;
315 while ($this->checkPathAgainstMounts($fI['path']) && $c < 20) {
316 $rDir = $fI['path'] . $this->recyclerFN;
317 if (@is_dir($rDir) && $this->recyclerFN != $fI['file']) {
318 return $rDir;
319 }
320 $theFile = $fI['path'];
321 $theFile = $this->cleanDirectoryName($theFile);
322 $fI = t3lib_div::split_fileref($theFile);
323 $c++;
324 }
325 }
326 }
327
328 /**
329 * Logging file operations
330 *
331 * @param integer The action number. See the functions in the class for a hint. Eg. edit is '9', upload is '1' ...
332 * @param integer The severity: 0 = message, 1 = error, 2 = System Error, 3 = security notice (admin)
333 * @param integer This number is unique for every combination of $type and $action. This is the error-message number, which can later be used to translate error messages.
334 * @param string This is the default, raw error message in english
335 * @param array Array with special information that may go into $details by "%s" marks / sprintf() when the log is shown
336 * @return void
337 * @see class.t3lib_userauthgroup.php
338 */
339 function writeLog($action, $error, $details_nr, $details, $data) {
340 $type = 2; // Type value for tce_file.php
341 if (is_object($GLOBALS['BE_USER'])) {
342 $GLOBALS['BE_USER']->writelog($type, $action, $error, $details_nr, $details, $data);
343 }
344 $this->lastError = vsprintf($details, $data);
345 }
346
347
348 /*************************************
349 *
350 * File operation functions
351 *
352 **************************************/
353
354 /**
355 * Deleting files and folders (action=4)
356 *
357 * @param array $cmds['data'] is the file/folder to delete
358 * @return boolean Returns TRUE upon success
359 */
360 function func_delete($cmds) {
361 if (!$this->isInit) {
362 return FALSE;
363 }
364
365 // Checking path:
366 $theFile = $cmds['data'];
367 if (!$this->isPathValid($theFile)) {
368 $this->writelog(4, 2, 101, 'Target "%s" had invalid path (".." and "//" is not allowed in path).', array($theFile));
369 return FALSE;
370 }
371
372 // Recycler moving or not?
373 if ($this->useRecycler && $recyclerPath = $this->findRecycler($theFile)) {
374 // If a recycler is found, the deleted items is moved to the recycler and not just deleted.
375 $newCmds = array();
376 $newCmds['data'] = $theFile;
377 $newCmds['target'] = $recyclerPath;
378 $newCmds['altName'] = 1;
379 $this->func_move($newCmds);
380 $this->writelog(4, 0, 4, 'Item "%s" moved to recycler at "%s"', array($theFile, $recyclerPath));
381 return TRUE;
382 } elseif ($this->useRecycler != 2) { // if $this->useRecycler==2 then we cannot delete for real!!
383 if (@is_file($theFile)) { // If we are deleting a file...
384 if (!$this->actionPerms['deleteFile']) {
385 $this->writelog(4, 1, 112, 'You are not allowed to delete files', '');
386 return FALSE;
387 }
388 if (!$this->checkPathAgainstMounts($theFile)) {
389 $this->writelog(4, 1, 111, 'Target was not within your mountpoints! T="%s"', array($theFile));
390 return FALSE;
391 }
392 if (@unlink($theFile)) {
393 $this->writelog(4, 0, 1, 'File "%s" deleted', array($theFile));
394 return TRUE;
395 } else {
396 $this->writelog(4, 1, 110, 'Could not delete file "%s". Write-permission problem?', array($theFile));
397 }
398 // FINISHED deleting file
399
400 } elseif (@is_dir($theFile)) { // if we're deleting a folder
401 if (!$this->actionPerms['deleteFolder']) {
402 $this->writelog(4, 1, 123, 'You are not allowed to delete directories', '');
403 return FALSE;
404 }
405
406 $theFile = $this->is_directory($theFile);
407 if (!$theFile) {
408 $this->writelog(4, 2, 122, 'Target seemed not to be a directory! (Shouldn\'t happen here!)', '');
409 return FALSE;
410 }
411 if (!$this->checkPathAgainstMounts($theFile)) {
412 $this->writelog(4, 1, 121, 'Target was not within your mountpoints! T="%s"', array($theFile));
413 return FALSE;
414 }
415 // I choose not to append '/' to $theFile here as this will prevent us from deleting mounts!! (which makes sense to me...)
416 if ($this->actionPerms['deleteFolderRecursively']) {
417 if (t3lib_div::rmdir($theFile, TRUE)) {
418 $this->writelog(4, 0, 2, 'Directory "%s" deleted recursively!', array($theFile));
419 return TRUE;
420 } else {
421 $this->writelog(4, 2, 119, 'Directory "%s" WAS NOT deleted recursively! Write-permission problem?', array($theFile));
422 }
423 } else {
424 if (@rmdir($theFile)) {
425 $this->writelog(4, 0, 3, 'Directory "%s" deleted', array($theFile));
426 return TRUE;
427 } else {
428 $this->writelog(4, 1, 120, 'Could not delete directory! Write-permission problem? Is directory "%s" empty? (You are not allowed to delete directories recursively).', array($theFile));
429 }
430 }
431 // FINISHED copying directory
432
433 } else {
434 $this->writelog(4, 2, 130, 'The item was not a file or directory! "%s"', array($theFile));
435 }
436 } else {
437 $this->writelog(4, 1, 131, 'No recycler found!', '');
438 }
439 }
440
441 /**
442 * Copying files and folders (action=2)
443 *
444 * @param array $cmds['data'] is the file/folder to copy. $cmds['target'] is the path where to copy to. $cmds['altName'] (boolean): If set, another filename is found in case the target already exists
445 * @return string Returns the new filename upon success
446 */
447 function func_copy($cmds) {
448 if (!$this->isInit) {
449 return FALSE;
450 }
451
452 // Initialize and check basic conditions:
453 $theFile = $cmds['data'];
454 $theDest = $this->is_directory($cmds['target']); // Clean up destination directory
455 $altName = $cmds['altName'];
456 if (!$theDest) {
457 $this->writelog(2, 2, 100, 'Destination "%s" was not a directory', array($cmds['target']));
458 return FALSE;
459 }
460 if (!$this->isPathValid($theFile) || !$this->isPathValid($theDest)) {
461 $this->writelog(2, 2, 101, 'Target or destination had invalid path (".." and "//" is not allowed in path). T="%s", D="%s"', array($theFile, $theDest));
462 return FALSE;
463 }
464
465 // Processing of file or directory.
466 if (@is_file($theFile)) { // If we are copying a file...
467 if (!$this->actionPerms['copyFile']) {
468 $this->writelog(2, 1, 114, 'You are not allowed to copy files', '');
469 return FALSE;
470 }
471 if (filesize($theFile) >= ($this->maxCopyFileSize * 1024)) {
472 $this->writelog(2, 1, 113, 'File "%s" exceeds the size-limit of %s bytes', array($theFile, $this->maxCopyFileSize * 1024));
473 return FALSE;
474 }
475 $fI = t3lib_div::split_fileref($theFile);
476 if ($altName) { // If altName is set, we're allowed to create a new filename if the file already existed
477 $theDestFile = $this->getUniqueName($fI['file'], $theDest);
478 $fI = t3lib_div::split_fileref($theDestFile);
479 } else {
480 $theDestFile = $theDest . '/' . $fI['file'];
481 }
482 if (!$theDestFile || file_exists($theDestFile)) {
483 $this->writelog(2, 1, 112, 'File "%s" already exists!', array($theDestFile));
484 return FALSE;
485 }
486 if (!$this->checkIfAllowed($fI['fileext'], $theDest, $fI['file'])) {
487 $this->writelog(2, 1, 111, 'Extension of file name "%s" is not allowed in "%s"!', array($fI['file'], $theDest . '/'));
488 return FALSE;
489 }
490 if (!$this->checkPathAgainstMounts($theDestFile) || !$this->checkPathAgainstMounts($theFile)) {
491 $this->writelog(2, 1, 110, 'Target or destination was not within your mountpoints! T="%s", D="%s"', array($theFile, $theDestFile));
492 return FALSE;
493 }
494 if ($this->PHPFileFunctions) {
495 copy($theFile, $theDestFile);
496 } else {
497 $cmd = 'cp "' . $theFile . '" "' . $theDestFile . '"';
498 t3lib_utility_Command::exec($cmd);
499 }
500 t3lib_div::fixPermissions($theDestFile);
501 clearstatcache();
502 if (@is_file($theDestFile)) {
503 $this->writelog(2, 0, 1, 'File "%s" copied to "%s"', array($theFile, $theDestFile));
504 return $theDestFile;
505 } else {
506 $this->writelog(2, 2, 109, 'File "%s" WAS NOT copied to "%s"! Write-permission problem?', array($theFile, $theDestFile));
507 return FALSE;
508 }
509 // FINISHED copying file
510
511 } elseif (@is_dir($theFile) && !$this->dont_use_exec_commands) { // if we're copying a folder
512 if (!$this->actionPerms['copyFolder']) {
513 $this->writelog(2, 1, 125, 'You are not allowed to copy directories', '');
514 return FALSE;
515 }
516 $theFile = $this->is_directory($theFile);
517 if (!$theFile) {
518 $this->writelog(2, 2, 124, 'Target seemed not to be a directory! (Shouldn\'t happen here!)', '');
519 return FALSE;
520 }
521 $fI = t3lib_div::split_fileref($theFile);
522 if ($altName) { // If altName is set, we're allowed to create a new filename if the file already existed
523 $theDestFile = $this->getUniqueName($fI['file'], $theDest);
524 $fI = t3lib_div::split_fileref($theDestFile);
525 } else {
526 $theDestFile = $theDest . '/' . $fI['file'];
527 }
528 if (!$theDestFile || file_exists($theDestFile)) {
529 $this->writelog(2, 1, 123, 'Target "%s" already exists!', array($theDestFile));
530 return FALSE;
531 }
532 if (!t3lib_div::isFirstPartOfStr($theDestFile . '/', $theFile . '/')) {
533 $this->writelog(2, 1, 122, 'Destination cannot be inside the target! D="%s", T="%s"', array($theDestFile . '/', $theFile . '/'));
534 return FALSE;
535 }
536 // Check if the one folder is inside the other or on the same level... to target/dest is the same?
537 if ($this->checkIfFullAccess($theDest) || $this->is_webPath($theDestFile) == $this->is_webPath($theFile)) { // no copy of folders between spaces
538 $this->writelog(2, 1, 121, 'You don\'t have full access to the destination directory "%s"!', array($theDest . '/'));
539 return FALSE;
540 }
541 if (!$this->checkPathAgainstMounts($theDestFile) || !$this->checkPathAgainstMounts($theFile)) {
542 $this->writelog(2, 1, 120, 'Target or destination was not within your mountpoints! T="%s", D="%s"', array($theFile, $theDestFile));
543 return FALSE;
544 }
545 // No way to do this under windows!
546 $cmd = 'cp -R "' . $theFile . '" "' . $theDestFile . '"';
547 t3lib_utility_Command::exec($cmd);
548 clearstatcache();
549 if (@is_dir($theDestFile)) {
550 $this->writelog(2, 0, 2, 'Directory "%s" copied to "%s"', array($theFile, $theDestFile));
551 return $theDestFile;
552 } else {
553 $this->writelog(2, 2, 119, 'Directory "%s" WAS NOT copied to "%s"! Write-permission problem?', array($theFile, $theDestFile));
554 return FALSE;
555 }
556 // FINISHED copying directory
557
558 } else {
559 $this->writelog(2, 2, 130, 'The item "%s" was not a file or directory!', array($theFile));
560 }
561 }
562
563 /**
564 * Moving files and folders (action=3)
565 *
566 * @param array $cmds['data'] is the file/folder to move. $cmds['target'] is the path where to move to. $cmds['altName'] (boolean): If set, another filename is found in case the target already exists
567 * @return string Returns the new filename upon success
568 */
569 function func_move($cmds) {
570 if (!$this->isInit) {
571 return FALSE;
572 }
573
574 // Initialize and check basic conditions:
575 $theFile = $cmds['data'];
576 $theDest = $this->is_directory($cmds['target']); // Clean up destination directory
577 $altName = $cmds['altName'];
578 if (!$theDest) {
579 $this->writelog(3, 2, 100, 'Destination "%s" was not a directory', array($cmds['target']));
580 return FALSE;
581 }
582 if (!$this->isPathValid($theFile) || !$this->isPathValid($theDest)) {
583 $this->writelog(3, 2, 101, 'Target or destination had invalid path (".." and "//" is not allowed in path). T="%s", D="%s"', array($theFile, $theDest));
584 return FALSE;
585 }
586
587 // Processing of file or directory:
588 if (@is_file($theFile)) { // If we are moving a file...
589 if (!$this->actionPerms['moveFile']) {
590 $this->writelog(3, 1, 114, 'You are not allowed to move files', '');
591 return FALSE;
592 }
593 if (filesize($theFile) >= ($this->maxMoveFileSize * 1024)) {
594 $this->writelog(3, 1, 113, 'File "%s" exceeds the size-limit of %s bytes', array($theFile, $this->maxMoveFileSize * 1024));
595 return FALSE;
596 }
597 $fI = t3lib_div::split_fileref($theFile);
598 if ($altName) { // If altName is set, we're allowed to create a new filename if the file already existed
599 $theDestFile = $this->getUniqueName($fI['file'], $theDest);
600 $fI = t3lib_div::split_fileref($theDestFile);
601 } else {
602 $theDestFile = $theDest . '/' . $fI['file'];
603 }
604 if (!$theDestFile || file_exists($theDestFile)) {
605 $this->writelog(3, 1, 112, 'File "%s" already exists!', array($theDestFile));
606 return FALSE;
607 }
608 if (!$this->checkIfAllowed($fI['fileext'], $theDest, $fI['file'])) {
609 $this->writelog(3, 1, 111, 'Extension of file name "%s" is not allowed in "%s"!', array($fI['file'], $theDest . '/'));
610 return FALSE;
611 }
612 if (!$this->checkPathAgainstMounts($theDestFile) || !$this->checkPathAgainstMounts($theFile)) {
613 $this->writelog(3, 1, 110, 'Target or destination was not within your mountpoints! T="%s", D="%s"', array($theFile, $theDestFile));
614 return FALSE;
615 }
616 if ($this->PHPFileFunctions) {
617 @rename($theFile, $theDestFile);
618 } else {
619 $cmd = 'mv "' . $theFile . '" "' . $theDestFile . '"';
620 t3lib_utility_Command::exec($cmd);
621 }
622 clearstatcache();
623 if (@is_file($theDestFile)) {
624 $this->writelog(3, 0, 1, 'File "%s" moved to "%s"', array($theFile, $theDestFile));
625 return $theDestFile;
626 } else {
627 $this->writelog(3, 2, 109, 'File "%s" WAS NOT moved to "%s"! Write-permission problem?', array($theFile, $theDestFile));
628 return FALSE;
629 }
630 // FINISHED moving file
631
632 } elseif (@is_dir($theFile)) { // if we're moving a folder
633 if (!$this->actionPerms['moveFolder']) {
634 $this->writelog(3, 1, 125, 'You are not allowed to move directories', '');
635 return FALSE;
636 }
637 $theFile = $this->is_directory($theFile);
638 if (!$theFile) {
639 $this->writelog(3, 2, 124, 'Target seemed not to be a directory! (Shouldn\'t happen here!)', '');
640 return FALSE;
641 }
642 $fI = t3lib_div::split_fileref($theFile);
643 if ($altName) { // If altName is set, we're allowed to create a new filename if the file already existed
644 $theDestFile = $this->getUniqueName($fI['file'], $theDest);
645 $fI = t3lib_div::split_fileref($theDestFile);
646 } else {
647 $theDestFile = $theDest . '/' . $fI['file'];
648 }
649 if (!$theDestFile || file_exists($theDestFile)) {
650 $this->writelog(3, 1, 123, 'Target "%s" already exists!', array($theDestFile));
651 return FALSE;
652 }
653 if (t3lib_div::isFirstPartOfStr($theDestFile . '/', $theFile . '/')) { // Check if the one folder is inside the other or on the same level... to target/dest is the same?
654 $this->writelog(3, 1, 122, 'Destination cannot be inside the target! D="%s", T="%s"', array($theDestFile . '/', $theFile . '/'));
655 return FALSE;
656 }
657 if (!$this->checkIfFullAccess($theDest) && $this->is_webPath($theDestFile) != $this->is_webPath($theFile)) { // // no moving of folders between spaces
658 $this->writelog(3, 1, 121, 'You don\'t have full access to the destination directory "%s"!', array($theDest . '/'));
659 return FALSE;
660 }
661 if (!$this->checkPathAgainstMounts($theDestFile) || !$this->checkPathAgainstMounts($theFile)) {
662 $this->writelog(3, 1, 120, 'Target or destination was not within your mountpoints! T="%s", D="%s"', array($theFile, $theDestFile));
663 return FALSE;
664 }
665 if ($this->PHPFileFunctions) {
666 @rename($theFile, $theDestFile);
667 } else {
668 $cmd = 'mv "' . $theFile . '" "' . $theDestFile . '"';
669 $errArr = array();
670 t3lib_utility_Command::exec($cmd, $errArr);
671 }
672 clearstatcache();
673 if (@is_dir($theDestFile)) {
674 $this->writelog(3, 0, 2, 'Directory "%s" moved to "%s"', array($theFile, $theDestFile));
675 return $theDestFile;
676 } else {
677 $this->writelog(3, 2, 119, 'Directory "%s" WAS NOT moved to "%s"! Write-permission problem?', array($theFile, $theDestFile));
678 return FALSE;
679 }
680 // FINISHED moving directory
681
682 } else {
683 $this->writelog(3, 2, 130, 'The item "%s" was not a file or directory!', array($theFile));
684 }
685 }
686
687 /**
688 * Renaming files or foldes (action=5)
689 *
690 * @param array $cmds['data'] is the new name. $cmds['target'] is the target (file or dir).
691 * @return string Returns the new filename upon success
692 */
693 function func_rename($cmds) {
694 if (!$this->isInit) {
695 return FALSE;
696 }
697
698 $theNewName = $this->cleanFileName($cmds['data']);
699 if (!$theNewName) {
700 return FALSE;
701 }
702 if (!$this->checkFileNameLen($theNewName)) {
703 $this->writelog(5, 1, 124, 'New name "%s" was too long (max %s characters)', array($theNewName, $this->maxInputNameLen));
704 return FALSE;
705 }
706 $theTarget = $cmds['target'];
707 $type = filetype($theTarget);
708 if ($type != 'file' && $type != 'dir') { // $type MUST BE file or dir
709 $this->writelog(5, 2, 123, 'Target "%s" was neither a directory nor a file!', array($theTarget));
710 return FALSE;
711 }
712 $fileInfo = t3lib_div::split_fileref($theTarget); // Fetches info about path, name, extention of $theTarget
713 if ($fileInfo['file'] == $theNewName) { // The name should be different from the current. And the filetype must be allowed
714 $this->writelog(5, 1, 122, 'Old and new name is the same (%s)', array($theNewName));
715 return FALSE;
716 }
717 $theRenameName = $fileInfo['path'] . $theNewName;
718 if (!$this->checkPathAgainstMounts($fileInfo['path'])) {
719 $this->writelog(5, 1, 121, 'Destination path "%s" was not within your mountpoints!', array($fileInfo['path']));
720 return FALSE;
721 }
722 if (file_exists($theRenameName)) {
723 $this->writelog(5, 1, 120, 'Destination "%s" existed already!', array($theRenameName));
724 return FALSE;
725 }
726 switch ($type) {
727 case 'file':
728 if (!$this->actionPerms['renameFile']) {
729 $this->writelog(5, 1, 102, 'You are not allowed to rename files!', '');
730 return FALSE;
731 }
732 $fI = t3lib_div::split_fileref($theRenameName);
733 if (!$this->checkIfAllowed($fI['fileext'], $fileInfo['path'], $fI['file'])) {
734 $this->writelog(5, 1, 101, 'Extension of file name "%s" was not allowed!', array($fI['file']));
735 return FALSE;
736 }
737 if (@rename($theTarget, $theRenameName)) {
738 $this->writelog(5, 0, 1, 'File renamed from "%s" to "%s"', array($fileInfo['file'], $theNewName));
739 return $theRenameName;
740 } else {
741 $this->writelog(5, 1, 100, 'File "%s" was not renamed! Write-permission problem in "%s"?', array($theTarget, $fileInfo['path']));
742 return FALSE;
743 }
744
745 break;
746
747 case 'dir':
748 if (!$this->actionPerms['renameFolder']) {
749 $this->writelog(5, 1, 111, 'You are not allowed to rename directories!', '');
750 return FALSE;
751 }
752 if (@rename($theTarget, $theRenameName)) {
753 $this->writelog(5, 0, 2, 'Directory renamed from "%s" to "%s"', array($fileInfo['file'], $theNewName));
754 return $theRenameName;
755 } else {
756 $this->writelog(5, 1, 110, 'Directory "%s" was not renamed! Write-permission problem in "%s"?', array($theTarget, $fileInfo['path']));
757 return FALSE;
758 }
759
760 break;
761 }
762 }
763
764 /**
765 * This creates a new folder. (action=6)
766 *
767 * @param array $cmds['data'] is the foldername. $cmds['target'] is the path where to create it.
768 * @return string Returns the new foldername upon success
769 */
770 function func_newfolder($cmds) {
771 if (!$this->isInit) {
772 return FALSE;
773 }
774
775 $theFolder = $this->cleanFileName($cmds['data']);
776 if (!isset($theFolder) || trim($theFolder) == '') {
777 return FALSE;
778 }
779 if (!$this->checkFileNameLen($theFolder)) {
780 $this->writelog(6, 1, 105, 'New name "%s" was too long (max %s characters)', array($theFolder, $this->maxInputNameLen));
781 return FALSE;
782 }
783 $theTarget = $this->is_directory($cmds['target']); // Check the target dir
784 if (!$theTarget) {
785 $this->writelog(6, 2, 104, 'Destination "%s" was not a directory', array($cmds['target']));
786 return FALSE;
787 }
788 if (!$this->actionPerms['newFolder']) {
789 $this->writelog(6, 1, 103, 'You are not allowed to create directories!', '');
790 return FALSE;
791 }
792 $theNewFolder = $theTarget . '/' . $theFolder;
793 if (!$this->checkPathAgainstMounts($theNewFolder)) {
794 $this->writelog(6, 1, 102, 'Destination path "%s" was not within your mountpoints!', array($theTarget . '/'));
795 return FALSE;
796 }
797 if (file_exists($theNewFolder)) {
798 $this->writelog(6, 1, 101, 'File or directory "%s" existed already!', array($theNewFolder));
799 return FALSE;
800 }
801 if (t3lib_div::mkdir($theNewFolder)) {
802 $this->writelog(6, 0, 1, 'Directory "%s" created in "%s"', array($theFolder, $theTarget . '/'));
803 return $theNewFolder;
804 } else {
805 $this->writelog(6, 1, 100, 'Directory "%s" not created. Write-permission problem in "%s"?', array($theFolder, $theTarget . '/'));
806 return FALSE;
807 }
808 }
809
810 /**
811 * This creates a new file. (action=8)
812 *
813 * @param array $cmds['data'] is the new filename. $cmds['target'] is the path where to create it
814 * @return string Returns the new filename upon success
815 */
816 function func_newfile($cmds) {
817 $extList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'];
818 if (!$this->isInit) {
819 return FALSE;
820 }
821 $newName = $this->cleanFileName($cmds['data']);
822 if (!$newName) {
823 return FALSE;
824 }
825 if (!$this->checkFileNameLen($newName)) {
826 $this->writelog(8, 1, 105, 'New name "%s" was too long (max %s characters)', array($newName, $this->maxInputNameLen));
827 return FALSE;
828 }
829 $theTarget = $this->is_directory($cmds['target']); // Check the target dir
830 $fileInfo = t3lib_div::split_fileref($theTarget); // Fetches info about path, name, extention of $theTarget
831 if (!$theTarget) {
832 $this->writelog(8, 2, 104, 'Destination "%s" was not a directory', array($cmds['target']));
833 return FALSE;
834 }
835 if (!$this->actionPerms['newFile']) {
836 $this->writelog(8, 1, 103, 'You are not allowed to create files!', '');
837 return FALSE;
838 }
839 $theNewFile = $theTarget . '/' . $newName;
840 if (!$this->checkPathAgainstMounts($theNewFile)) {
841 $this->writelog(8, 1, 102, 'Destination path "%s" was not within your mountpoints!', array($theTarget . '/'));
842 return FALSE;
843 }
844 if (file_exists($theNewFile)) {
845 $this->writelog(8, 1, 101, 'File "%s" existed already!', array($theNewFile));
846 return FALSE;
847 }
848 $fI = t3lib_div::split_fileref($theNewFile);
849 if (!$this->checkIfAllowed($fI['fileext'], $fileInfo['path'], $fI['file'])) {
850 $this->writelog(8, 1, 106, 'Extension of file name "%s" was not allowed!', array($fI['file']));
851 return FALSE;
852 }
853 if (!t3lib_div::inList($extList, $fI['fileext'])) {
854 $this->writelog(8, 1, 107, 'File extension "%s" is not a textfile format! (%s)', array($fI['fileext'], $extList));
855 return FALSE;
856 }
857 if (t3lib_div::writeFile($theNewFile, '')) {
858 clearstatcache();
859 $this->writelog(8, 0, 1, 'File created: "%s"', array($fI['file']));
860 return $theNewFile;
861 } else {
862 $this->writelog(8, 1, 100, 'File "%s" was not created! Write-permission problem in "%s"?', array($fI['file'], $theTarget));
863 return FALSE;
864 }
865 }
866
867 /**
868 * Editing textfiles or folders (action=9)
869 *
870 * @param array $cmds['data'] is the new content. $cmds['target'] is the target (file or dir)
871 * @return boolean Returns TRUE on success
872 */
873 function func_edit($cmds) {
874 if (!$this->isInit) {
875 return FALSE;
876 }
877 $theTarget = $cmds['target'];
878 $content = $cmds['data'];
879 $extList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'];
880 $type = filetype($theTarget);
881 if ($type != 'file') { // $type MUST BE file
882 $this->writelog(9, 2, 123, 'Target "%s" was not a file!', array($theTarget));
883 return FALSE;
884 }
885 $fileInfo = t3lib_div::split_fileref($theTarget); // Fetches info about path, name, extention of $theTarget
886 $fI = $fileInfo;
887 if (!$this->checkPathAgainstMounts($fileInfo['path'])) {
888 $this->writelog(9, 1, 121, 'Destination path "%s" was not within your mountpoints!', array($fileInfo['path']));
889 return FALSE;
890 }
891 if (!$this->actionPerms['editFile']) {
892 $this->writelog(9, 1, 104, 'You are not allowed to edit files!', '');
893 return FALSE;
894 }
895 $fI = t3lib_div::split_fileref($theTarget);
896 if (!$this->checkIfAllowed($fI['fileext'], $fileInfo['path'], $fI['file'])) {
897 $this->writelog(9, 1, 103, 'Extension of file name "%s" was not allowed!', array($fI['file']));
898 return FALSE;
899 }
900 if (!t3lib_div::inList($extList, $fileInfo['fileext'])) {
901 $this->writelog(9, 1, 102, 'File extension "%s" is not a textfile format! (%s)', array($fI['fileext'], $extList));
902 return FALSE;
903 }
904 if (t3lib_div::writeFile($theTarget, $content)) {
905 clearstatcache();
906 $this->writelog(9, 0, 1, 'File saved to "%s", bytes: %s, MD5: %s ', array($fileInfo['file'], @filesize($theTarget), md5($content)));
907 return TRUE;
908 } else {
909 $this->writelog(9, 1, 100, 'File "%s" was not saved! Write-permission problem in "%s"?', array($theTarget, $fileInfo['path']));
910 return FALSE;
911 }
912 }
913
914 /**
915 * Upload of files (action=1)
916 *
917 * @param array $cmds['data'] is the ID-number (points to the global var that holds the filename-ref ($_FILES['upload_'.$id]['name']). $cmds['target'] is the target directory, $cmds['charset'] is the the character set of the file name (utf-8 is needed for JS-interaction)
918 * @return string Returns the new filename upon success
919 */
920 function func_upload($cmds) {
921 if (!$this->isInit) {
922 return FALSE;
923 }
924 $id = $cmds['data'];
925 if (!$_FILES['upload_' . $id]['name']) {
926 $this->writelog(1, 2, 108, 'No file was uploaded!', '');
927 return FALSE;
928 }
929 $theFile = $_FILES['upload_' . $id]['tmp_name']; // filename of the uploaded file
930 $theFileSize = $_FILES['upload_' . $id]['size']; // filesize of the uploaded file
931 $theName = $this->cleanFileName(stripslashes($_FILES['upload_' . $id]['name']), (isset($cmds['charset']) ? $cmds['charset'] : '')); // The original filename
932 if (!is_uploaded_file($theFile) || !$theName) { // Check the file
933 $this->writelog(1, 2, 106, 'The upload has failed, no uploaded file found!', '');
934 return FALSE;
935 }
936 if (!$this->actionPerms['uploadFile']) {
937 $this->writelog(1, 1, 105, 'You are not allowed to upload files!', '');
938 return FALSE;
939 }
940 if ($theFileSize >= ($this->maxUploadFileSize * 1024)) {
941 $this->writelog(1, 1, 104, 'The uploaded file exceeds the size-limit of %s bytes', array($this->maxUploadFileSize * 1024));
942 return FALSE;
943 }
944 $fI = t3lib_div::split_fileref($theName);
945 $theTarget = $this->is_directory($cmds['target']); // Check the target dir
946 if (!$theTarget || !$this->checkPathAgainstMounts($theTarget . '/')) {
947 $this->writelog(1, 1, 103, 'Destination path "%s" was not within your mountpoints!', array($theTarget . '/'));
948 return FALSE;
949 }
950 if (!$this->checkIfAllowed($fI['fileext'], $theTarget, $fI['file'])) {
951 $this->writelog(1, 1, 102, 'Extension of file name "%s" is not allowed in "%s"!', array($fI['file'], $theTarget . '/'));
952 return FALSE;
953 }
954 $theNewFile = $this->getUniqueName($theName, $theTarget, $this->dontCheckForUnique);
955 if (!$theNewFile) {
956 $this->writelog(1, 1, 101, 'No unique filename available in "%s"!', array($theTarget . '/'));
957 return FALSE;
958 }
959 t3lib_div::upload_copy_move($theFile, $theNewFile);
960 clearstatcache();
961 if (@is_file($theNewFile)) {
962 $this->internalUploadMap[$id] = $theNewFile;
963 $this->writelog(1, 0, 1, 'Uploading file "%s" to "%s"', array($theName, $theNewFile, $id));
964 return $theNewFile;
965 } else {
966 $this->writelog(1, 1, 100, 'Uploaded file could not be moved! Write-permission problem in "%s"?', array($theTarget . '/'));
967 return FALSE;
968 }
969 }
970
971 /**
972 * Unzipping file (action=7)
973 * This is permitted only if the user has fullAccess or if the file resides
974 *
975 * @param array $cmds['data'] is the zip-file. $cmds['target'] is the target directory. If not set we'll default to the same directory as the file is in.
976 * @return boolean Returns TRUE on success
977 */
978 function func_unzip($cmds) {
979 if (!$this->isInit || $this->dont_use_exec_commands) {
980 return FALSE;
981 }
982
983 $theFile = $cmds['data'];
984 if (!@is_file($theFile)) {
985 $this->writelog(7, 2, 105, 'The file "%s" did not exist!', array($theFile));
986 return FALSE;
987 }
988 $fI = t3lib_div::split_fileref($theFile);
989 if (!isset($cmds['target'])) {
990 $cmds['target'] = $fI['path'];
991 }
992 // Clean up destination directory
993 $theDest = $this->is_directory($cmds['target']);
994 if (!$theDest) {
995 $this->writelog(7, 2, 104, 'Destination "%s" was not a directory', array($cmds['target']));
996 return FALSE;
997 }
998 if (!$this->actionPerms['unzipFile']) {
999 $this->writelog(7, 1, 103, 'You are not allowed to unzip files', '');
1000 return FALSE;
1001 }
1002 if ($fI['fileext'] != 'zip') {
1003 $this->writelog(7, 1, 102, 'File extension is not "zip"', '');
1004 return FALSE;
1005 }
1006 if (!$this->checkIfFullAccess($theDest)) {
1007 $this->writelog(7, 1, 101, 'You don\'t have full access to the destination directory "%s"!', array($theDest));
1008 return FALSE;
1009 }
1010 if ($this->checkPathAgainstMounts($theFile) && $this->checkPathAgainstMounts($theDest . '/')) {
1011 // No way to do this under windows.
1012 $cmd = $this->unzipPath . 'unzip -qq "' . $theFile . '" -d "' . $theDest . '"';
1013 t3lib_utility_Command::exec($cmd);
1014 $this->writelog(7, 0, 1, 'Unzipping file "%s" in "%s"', array($theFile, $theDest));
1015 return TRUE;
1016 } else {
1017 $this->writelog(7, 1, 100, 'File "%s" or destination "%s" was not within your mountpoints!', array($theFile, $theDest));
1018 return FALSE;
1019 }
1020 }
1021 }
1022
1023 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_extfilefunc.php'])) {
1024 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_extfilefunc.php']);
1025 }
1026
1027 ?>