[BUGFIX] Fatal error when copying file or folder
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / File / ExtendedFileUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Utility\File;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /**
30 * Extending class to class t3lib_basicFileFunctions
31 *
32 * Revised for TYPO3 3.6 May/2004 by Kasper Skårhøj
33 *
34 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
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 ExtendedFileUtility extends \TYPO3\CMS\Core\Utility\File\BasicFileUtility {
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 // Path to unzip-program (with trailing '/')
70 /**
71 * @todo Define visibility
72 */
73 public $unzipPath = '';
74
75 // If set, the uploaded files will overwrite existing files.
76 /**
77 * @todo Define visibility
78 */
79 public $dontCheckForUnique = 0;
80
81 // This array is self-explaining (look in the class below).
82 // It grants access to the functions. This could be set from outside in order to enabled functions to users.
83 // See also the function init_actionPerms() which takes input directly from the user-record
84 /**
85 * @todo Define visibility
86 */
87 public $actionPerms = array(
88 'deleteFile' => 0,
89 // Deleting files physically
90 'deleteFolder' => 0,
91 // Deleting folders physically
92 'deleteFolderRecursively' => 0,
93 // normally folders are deleted by the PHP-function rmdir(), but with this option a user deletes with 'rm -Rf ....' which is pretty wild!
94 'moveFile' => 0,
95 'moveFolder' => 0,
96 'copyFile' => 0,
97 'copyFolder' => 0,
98 'newFolder' => 0,
99 'newFile' => 0,
100 'editFile' => 0,
101 'unzipFile' => 0,
102 'uploadFile' => 0,
103 'renameFile' => 0,
104 'renameFolder' => 0
105 );
106
107 // This is regarded to be the recycler folder
108 /**
109 * @todo Define visibility
110 */
111 public $recyclerFN = '_recycler_';
112
113 /**
114 * Whether to use recycler (0 = no, 1 = if available, 2 = always)
115 *
116 * @var integer
117 * @deprecated since TYPO3 6.0
118 * @todo Define visibility
119 */
120 public $useRecycler = 1;
121
122 // Internal, dynamic
123 // Will contain map between upload ID and the final filename
124 /**
125 * @todo Define visibility
126 */
127 public $internalUploadMap = array();
128
129 /**
130 * @todo Define visibility
131 */
132 public $lastError = '';
133
134 /**
135 * @var array
136 */
137 protected $fileCmdMap;
138
139 /**
140 * The File Factory
141 *
142 * @var \TYPO3\CMS\Core\Resource\ResourceFactory
143 */
144 protected $fileFactory;
145
146 /**
147 * Initialization of the class
148 *
149 * @param array $fileCmds Array with the commands to execute. See "TYPO3 Core API" document
150 * @return void
151 * @todo Define visibility
152 */
153 public function start($fileCmds) {
154 $unzipPath = trim($GLOBALS['TYPO3_CONF_VARS']['BE']['unzip_path']);
155 if (substr($unzipPath, -1) !== '/' && is_dir($unzipPath)) {
156 // Make sure the path ends with a slash
157 $unzipPath .= '/';
158 }
159 $this->unzipPath = $unzipPath;
160 // Initialize Object Factory
161 $this->fileFactory = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
162 // Initializing file processing commands:
163 $this->fileCmdMap = $fileCmds;
164 }
165
166 /**
167 * Sets up permission to perform file/directory operations.
168 * See below or the be_user-table for the significance of the various bits in $setup.
169 *
170 * @param integer $setup File permission integer from BE_USER OR'ed with permissions of back-end groups this user is a member of
171 * @return void
172 * @todo Define visibility
173 */
174 public function init_actionPerms($setup) {
175 // Files: Upload,Copy,Move,Delete,Rename
176 if (($setup & 1) == 1) {
177 $this->actionPerms['uploadFile'] = 1;
178 $this->actionPerms['copyFile'] = 1;
179 $this->actionPerms['moveFile'] = 1;
180 $this->actionPerms['deleteFile'] = 1;
181 $this->actionPerms['renameFile'] = 1;
182 $this->actionPerms['editFile'] = 1;
183 $this->actionPerms['newFile'] = 1;
184 }
185 // Files: Unzip
186 if (($setup & 2) == 2) {
187 $this->actionPerms['unzipFile'] = 1;
188 }
189 // Directory: Move,Delete,Rename,New
190 if (($setup & 4) == 4) {
191 $this->actionPerms['moveFolder'] = 1;
192 $this->actionPerms['deleteFolder'] = 1;
193 $this->actionPerms['renameFolder'] = 1;
194 $this->actionPerms['newFolder'] = 1;
195 }
196 // Directory: Copy
197 if (($setup & 8) == 8) {
198 $this->actionPerms['copyFolder'] = 1;
199 }
200 // Directory: Delete recursively (rm -Rf)
201 if (($setup & 16) == 16) {
202 $this->actionPerms['deleteFolderRecursively'] = 1;
203 }
204 }
205
206 /**
207 * Processing the command array in $this->fileCmdMap
208 *
209 * @return mixed FALSE, if the file functions were not initialized
210 * @todo Define visibility
211 */
212 public function processData() {
213 $result = array();
214 if (!$this->isInit) {
215 return FALSE;
216 }
217 if (is_array($this->fileCmdMap)) {
218 // Check if there were uploads expected, but no one made
219 if ($this->fileCmdMap['upload']) {
220 $uploads = $this->fileCmdMap['upload'];
221 foreach ($uploads as $upload) {
222 if (!$_FILES[('upload_' . $upload['data'])]['name']) {
223 unset($this->fileCmdMap['upload'][$upload['data']]);
224 }
225 }
226 if (count($this->fileCmdMap['upload']) == 0) {
227 $this->writelog(1, 1, 108, 'No file was uploaded!', '');
228 }
229 }
230 // Traverse each set of actions
231 foreach ($this->fileCmdMap as $action => $actionData) {
232 // Traverse all action data. More than one file might be affected at the same time.
233 if (is_array($actionData)) {
234 $result[$action] = array();
235 foreach ($actionData as $cmdArr) {
236 // Clear file stats
237 clearstatcache();
238 // Branch out based on command:
239 switch ($action) {
240 case 'delete':
241 $result[$action][] = $this->func_delete($cmdArr);
242 break;
243 case 'copy':
244 $result[$action][] = $this->func_copy($cmdArr);
245 break;
246 case 'move':
247 $result[$action][] = $this->func_move($cmdArr);
248 break;
249 case 'rename':
250 $result[$action][] = $this->func_rename($cmdArr);
251 break;
252 case 'newfolder':
253 $result[$action][] = $this->func_newfolder($cmdArr);
254 break;
255 case 'newfile':
256 $result[$action][] = $this->func_newfile($cmdArr);
257 break;
258 case 'editfile':
259 $result[$action][] = $this->func_edit($cmdArr);
260 break;
261 case 'upload':
262 $result[$action][] = $this->func_upload($cmdArr);
263 break;
264 case 'unzip':
265 $result[$action][] = $this->func_unzip($cmdArr);
266 break;
267 }
268 // Hook for post-processing the action
269 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_extfilefunc.php']['processData'])) {
270 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_extfilefunc.php']['processData'] as $classRef) {
271 $hookObject = \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($classRef);
272 if (!$hookObject instanceof \TYPO3\CMS\Core\Utility\File\ExtendedFileUtilityProcessDataHookInterface) {
273 throw new \UnexpectedValueException('$hookObject must implement interface TYPO3\\CMS\\Core\\Utility\\File\\ExtendedFileUtilityProcessDataHookInterface', 1279719168);
274 }
275 $hookObject->processData_postProcessAction($action, $cmdArr, $result[$action], $this);
276 }
277 }
278 }
279 }
280 }
281 }
282 return $result;
283 }
284
285 /**
286 * Adds log error messages from the operations of this script instance to the FlashMessageQueue
287 *
288 * @param string $redirect Redirect URL (for creating link in message)
289 * @return void
290 * @todo Define visibility
291 */
292 public function printLogErrorMessages($redirect = '') {
293 $this->getErrorMessages();
294 }
295
296 /**
297 * Adds log error messages from the previous file operations of this script instance
298 * to the FlashMessageQueue
299 *
300 * @return void
301 * @todo Define visibility
302 */
303 public function getErrorMessages() {
304 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_log', 'type = 2 AND userid = ' . intval($GLOBALS['BE_USER']->user['uid']) . ' AND tstamp=' . intval($GLOBALS['EXEC_TIME']) . ' AND error<>0');
305 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
306 $logData = unserialize($row['log_data']);
307 $msg = $row['error'] . ': ' . sprintf($row['details'], $logData[0], $logData[1], $logData[2], $logData[3], $logData[4]);
308 $flashMessage = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessage', $msg, '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR, TRUE);
309 \TYPO3\CMS\Core\Messaging\FlashMessageQueue::addMessage($flashMessage);
310 }
311 $GLOBALS['TYPO3_DB']->sql_free_result($res);
312 }
313
314 /**
315 * Goes back in the path and checks in each directory if a folder named $this->recyclerFN (usually '_recycler_') is present.
316 * 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
317 *
318 * @param string $theFile Takes a valid Path ($theFile)
319 * @return string Returns the path (without trailing slash) of the closest recycle-folder if found. Else FALSE.
320 * @todo To be put in Storage with a better concept
321 * @todo Define visibility
322 * @deprecated since TYPO3 6.0, use \TYPO3\CMS\Core\Resource\ResourceStorage method instead
323 */
324 public function findRecycler($theFile) {
325 \TYPO3\CMS\Core\Utility\GeneralUtility::logDeprecatedFunction();
326 if ($this->isPathValid($theFile)) {
327 $theFile = $this->cleanDirectoryName($theFile);
328 $fI = \TYPO3\CMS\Core\Utility\GeneralUtility::split_fileref($theFile);
329 $c = 0;
330 // !!! Method has been put in the storage, can be saftely removed
331 $rDir = $fI['path'] . $this->recyclerFN;
332 while ($this->checkPathAgainstMounts($fI['path']) && $c < 20) {
333 if (@is_dir($rDir) && $this->recyclerFN != $fI['file']) {
334 return $rDir;
335 }
336 $theFile = $fI['path'];
337 $theFile = $this->cleanDirectoryName($theFile);
338 $fI = \TYPO3\CMS\Core\Utility\GeneralUtility::split_fileref($theFile);
339 $c++;
340 }
341 }
342 }
343
344 /**
345 * Logging file operations
346 *
347 * @param integer $action The action number. See the functions in the class for a hint. Eg. edit is '9', upload is '1' ...
348 * @param integer $error The severity: 0 = message, 1 = error, 2 = System Error, 3 = security notice (admin)
349 * @param integer $details_nr 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.
350 * @param string $details This is the default, raw error message in english
351 * @param array $data Array with special information that may go into $details by "%s" marks / sprintf() when the log is shown
352 * @return void
353 * @see class.t3lib_beuserauth.php
354 * @todo Define visibility
355 */
356 public function writeLog($action, $error, $details_nr, $details, $data) {
357 // Type value for tce_file.php
358 $type = 2;
359 if (is_object($GLOBALS['BE_USER'])) {
360 $GLOBALS['BE_USER']->writelog($type, $action, $error, $details_nr, $details, $data);
361 }
362 $this->lastError = vsprintf($details, $data);
363 }
364
365 /*************************************
366 *
367 * File operation functions
368 *
369 **************************************/
370 /**
371 * Deleting files and folders (action=4)
372 *
373 * @param array $cmds $cmds['data'] is the file/folder to delete
374 * @return boolean Returns TRUE upon success
375 * @todo Define visibility
376 */
377 public function func_delete($cmds) {
378 $result = FALSE;
379 if (!$this->isInit) {
380 return $result;
381 }
382 // Example indentifier for $cmds['data'] => "4:mypath/tomyfolder/myfile.jpg"
383 // for backwards compatibility: the combined file identifier was the path+filename
384 $fileObject = $this->getFileObject($cmds['data']);
385 // @todo implement the recycler feature which has been removed from the original implementation
386 // checks to delete the file
387 if ($fileObject instanceof \TYPO3\CMS\Core\Resource\File) {
388 $refIndexRecords = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
389 '*',
390 'sys_refindex',
391 'deleted=0 AND ref_table="sys_file" AND ref_uid=' . intval($fileObject->getUid())
392 );
393 // check if the file still has references
394 if (count($refIndexRecords) > 0) {
395 $shortcutContent = array();
396 foreach ($refIndexRecords as $row) {
397 $shortcutRecord = NULL;
398 $shortcutRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($row['tablename'], $row['recuid']);
399 if (is_array($shortcutRecord) && $row['tablename'] !== 'sys_file_reference') {
400 $icon = \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconForRecord($row['tablename'], $shortcutRecord);
401 $onClick = 'showClickmenu("' . $row['tablename'] . '", "' . $row['recuid'] . '", "1", "+info,history,edit,delete", "|", "");return false;';
402 $shortcutContent[] = '<a href="#" oncontextmenu="' . htmlspecialchars($onClick) . '" onclick="' . htmlspecialchars($onClick) . '">' . $icon . '</a>' . htmlspecialchars((\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle($row['tablename'], $shortcutRecord) . ' [' . \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordPath($shortcutRecord['pid'], '', 80) . ']'));
403 }
404 }
405 $out = '<p>The file cannot be deleted since it is still used at the following places:<br />' . implode('<br />', $shortcutContent) . '</p>';
406 $flashMessage = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('t3lib_flashMessage', $out, 'File not deleted', \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING, TRUE);
407 \TYPO3\CMS\Core\Messaging\FlashMessageQueue::addMessage($flashMessage);
408 return;
409 } else {
410 try {
411 $result = $fileObject->delete();
412 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException $e) {
413 $this->writelog(4, 1, 112, 'You are not allowed to access the file', array($fileObject->getIdentifier()));
414 } catch (\TYPO3\CMS\Core\Resource\Exception\NotInMountPointException $e) {
415 $this->writelog(4, 1, 111, 'Target was not within your mountpoints! T="%s"', array($fileObject->getIdentifier()));
416 } catch (\RuntimeException $e) {
417 $this->writelog(4, 1, 110, 'Could not delete file "%s". Write-permission problem?', array($fileObject->getIdentifier()));
418 }
419 // Log success
420 $this->writelog(4, 0, 1, 'File "%s" deleted', array($fileObject->getIdentifier()));
421 }
422 } else {
423 try {
424 /** @var $fileObject \TYPO3\CMS\Core\Resource\FolderInterface */
425 $result = $fileObject->delete(TRUE);
426 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException $e) {
427 $this->writelog(4, 1, 123, 'You are not allowed to access the directory', array($fileObject->getIdentifier()));
428 } catch (\TYPO3\CMS\Core\Resource\Exception\NotInMountPointException $e) {
429 $this->writelog(4, 1, 121, 'Target was not within your mountpoints! T="%s"', array($fileObject->getIdentifier()));
430 } catch (\RuntimeException $e) {
431 $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($fileObject->getIdentifier()));
432 }
433 // Log success
434 $this->writelog(4, 0, 3, 'Directory "%s" deleted', array($fileObject->getIdentifier()));
435 }
436 return $result;
437 }
438
439 /**
440 * Gets a File or a Folder object from an identifier [storage]:[fileId]
441 *
442 * @param string $identifier
443 * @return \TYPO3\CMS\Core\Resource\Folder|\TYPO3\CMS\Core\Resource\File
444 */
445 protected function getFileObject($identifier) {
446 $object = $this->fileFactory->retrieveFileOrFolderObject($identifier);
447 if (!is_object($object)) {
448 throw new \TYPO3\CMS\Core\Resource\Exception\InvalidFileException('The item ' . $identifier . ' was not a file or directory!!', 1320122453);
449 }
450 return $object;
451 }
452
453 /**
454 * Copying files and folders (action=2)
455 *
456 * $cmds['data'] (string): The file/folder to copy
457 * + example "4:mypath/tomyfolder/myfile.jpg")
458 * + for backwards compatibility: the identifier was the path+filename
459 * $cmds['target'] (string): The path where to copy to.
460 * + example "2:targetpath/targetfolder/"
461 * $cmds['altName'] (string): Use an alternative name if the target already exists
462 *
463 * @param array $cmds Command details as described above
464 * @return \TYPO3\CMS\Core\Resource\File
465 */
466 protected function func_copy($cmds) {
467 if (!$this->isInit) {
468 return FALSE;
469 }
470 $sourceFileObject = $this->getFileObject($cmds['data']);
471 /** @var $targetFolderObject \TYPO3\CMS\Core\Resource\Folder */
472 $targetFolderObject = $this->getFileObject($cmds['target']);
473 // Basic check
474 if (!$targetFolderObject instanceof \TYPO3\CMS\Core\Resource\Folder) {
475 $this->writelog(2, 2, 100, 'Destination "%s" was not a directory', array($cmds['target']));
476 return FALSE;
477 }
478 // If this is TRUE, we append _XX to the file name if
479 $appendSuffixOnConflict = (string) $cmds['altName'];
480 $resultObject = NULL;
481 // Copying the file
482 if ($sourceFileObject instanceof \TYPO3\CMS\Core\Resource\File) {
483 try {
484 $conflictMode = $appendSuffixOnConflict !== '' ? 'renameNewFile' : 'cancel';
485 $resultObject = $sourceFileObject->copyTo($targetFolderObject, NULL, $conflictMode);
486 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException $e) {
487 $this->writelog(2, 1, 114, 'You are not allowed to copy files', '');
488 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException $e) {
489 $this->writelog(2, 1, 110, 'Could not access all necessary resources. Source file or destination maybe was not within your mountpoints? T="%s", D="%s"', array($sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()));
490 } catch (\TYPO3\CMS\Core\Resource\Exception\IllegalFileExtensionException $e) {
491 $this->writelog(2, 1, 111, 'Extension of file name "%s" is not allowed in "%s"!', array($sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()));
492 } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException $e) {
493 $this->writelog(2, 1, 112, 'File "%s" already exists in folder "%s"!', array($sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()));
494 } catch (\RuntimeException $e) {
495 $this->writelog(2, 2, 109, 'File "%s" WAS NOT copied to "%s"! Write-permission problem?', array($sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()));
496 }
497 if ($resultObject) {
498 $this->writelog(2, 0, 1, 'File "%s" copied to "%s"', array($sourceFileObject->getIdentifier(), $resultObject->getIdentifier()));
499 }
500 } else {
501 // Else means this is a Folder
502 $sourceFolderObject = $sourceFileObject;
503 try {
504 $conflictMode = $appendSuffixOnConflict !== '' ? 'renameNewFile' : 'cancel';
505 $resultObject = $sourceFolderObject->copyTo($targetFolderObject, NULL, $conflictMode);
506 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException $e) {
507 $this->writelog(2, 1, 125, 'You are not allowed to copy directories', '');
508 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException $e) {
509 $this->writelog(2, 1, 110, 'Could not access all necessary resources. Source file or destination maybe was not within your mountpoints? T="%s", D="%s"', array($sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()));
510 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException $e) {
511 $this->writelog(2, 1, 121, 'You don\'t have full access to the destination directory "%s"!', array($targetFolderObject->getIdentifier()));
512 } catch (\TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException $e) {
513 $this->writelog(2, 1, 122, 'Destination cannot be inside the target! D="%s", T="%s"', array($targetFolderObject->getIdentifier(), $sourceFolderObject->getIdentifier()));
514 } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException $e) {
515 $this->writelog(2, 1, 123, 'Target "%s" already exists!', array($targetFolderObject->getIdentifier()));
516 } catch (\RuntimeException $e) {
517 $this->writelog(2, 2, 119, 'Directory "%s" WAS NOT copied to "%s"! Write-permission problem?', array($sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()));
518 }
519 if ($resultObject) {
520 $this->writelog(2, 0, 2, 'Directory "%s" copied to "%s"', array($sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()));
521 }
522 }
523 return $resultObject;
524 }
525
526 /**
527 * Moving files and folders (action=3)
528 *
529 * $cmds['data'] (string): The file/folder to move
530 * + example "4:mypath/tomyfolder/myfile.jpg")
531 * + for backwards compatibility: the identifier was the path+filename
532 * $cmds['target'] (string): The path where to move to.
533 * + example "2:targetpath/targetfolder/"
534 * $cmds['altName'] (string): Use an alternative name if the target already exists
535 *
536 * @param array $cmds Command details as described above
537 * @return \TYPO3\CMS\Core\Resource\File
538 */
539 protected function func_move($cmds) {
540 if (!$this->isInit) {
541 return FALSE;
542 }
543 $sourceFileObject = $this->getFileObject($cmds['data']);
544 $targetFolderObject = $this->getFileObject($cmds['target']);
545 // Basic check
546 if (!$targetFolderObject instanceof \TYPO3\CMS\Core\Resource\Folder) {
547 $this->writelog(3, 2, 100, 'Destination "%s" was not a directory', array($cmds['target']));
548 return FALSE;
549 }
550 $alternativeName = (string) $cmds['altName'];
551 $resultObject = NULL;
552 // Moving the file
553 if ($sourceFileObject instanceof \TYPO3\CMS\Core\Resource\File) {
554 try {
555 if ($alternativeName !== '') {
556 // Don't allow overwriting existing files, but find a new name
557 $resultObject = $sourceFileObject->moveTo($targetFolderObject, $alternativeName, 'renameNewFile');
558 } else {
559 // Don't allow overwriting existing files
560 $resultObject = $sourceFileObject->moveTo($targetFolderObject, NULL, 'cancel');
561 }
562 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException $e) {
563 $this->writelog(3, 1, 114, 'You are not allowed to move files', '');
564 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException $e) {
565 $this->writelog(3, 1, 110, 'Could not access all necessary resources. Source file or destination maybe was not within your mountpoints? T="%s", D="%s"', array($sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()));
566 } catch (\TYPO3\CMS\Core\Resource\Exception\IllegalFileExtensionException $e) {
567 $this->writelog(3, 1, 111, 'Extension of file name "%s" is not allowed in "%s"!', array($sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()));
568 } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException $e) {
569 $this->writelog(3, 1, 112, 'File "%s" already exists in folder "%s"!', array($sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()));
570 } catch (\RuntimeException $e) {
571 $this->writelog(3, 2, 109, 'File "%s" WAS NOT copied to "%s"! Write-permission problem?', array($sourceFileObject->getIdentifier(), $targetFolderObject->getIdentifier()));
572 }
573 $this->writelog(3, 0, 1, 'File "%s" moved to "%s"', array($sourceFileObject->getIdentifier(), $resultObject->getIdentifier()));
574 } else {
575 // Else means this is a Folder
576 $sourceFolderObject = $sourceFileObject;
577 try {
578 if ($alternativeName !== '') {
579 // Don't allow overwriting existing files, but find a new name
580 $resultObject = $sourceFolderObject->moveTo($targetFolderObject, $alternativeName, 'renameNewFile');
581 } else {
582 // Don't allow overwriting existing files
583 $resultObject = $sourceFolderObject->moveTo($targetFolderObject, NULL, 'renameNewFile');
584 }
585 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException $e) {
586 $this->writelog(3, 1, 125, 'You are not allowed to move directories', '');
587 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException $e) {
588 $this->writelog(3, 1, 110, 'Could not access all necessary resources. Source file or destination maybe was not within your mountpoints? T="%s", D="%s"', array($sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()));
589 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException $e) {
590 $this->writelog(3, 1, 121, 'You don\'t have full access to the destination directory "%s"!', array($targetFolderObject->getIdentifier()));
591 } catch (\TYPO3\CMS\Core\Resource\Exception\InvalidTargetFolderException $e) {
592 $this->writelog(3, 1, 122, 'Destination cannot be inside the target! D="%s", T="%s"', array($targetFolderObject->getIdentifier(), $sourceFolderObject->getIdentifier()));
593 } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException $e) {
594 $this->writelog(3, 1, 123, 'Target "%s" already exists!', array($targetFolderObject->getIdentifier()));
595 } catch (\RuntimeException $e) {
596 $this->writelog(3, 2, 119, 'Directory "%s" WAS NOT moved to "%s"! Write-permission problem?', array($sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()));
597 }
598 $this->writelog(3, 0, 2, 'Directory "%s" moved to "%s"', array($sourceFolderObject->getIdentifier(), $targetFolderObject->getIdentifier()));
599 }
600 return $resultObject;
601 }
602
603 /**
604 * Renaming files or foldes (action=5)
605 *
606 * $cmds['data'] (string): The file/folder to copy
607 * + example "4:mypath/tomyfolder/myfile.jpg")
608 * + for backwards compatibility: the identifier was the path+filename
609 * $cmds['target'] (string): New name of the file/folder
610 *
611 * @param array $cmds Command details as described above
612 * @return \TYPO3\CMS\Core\Resource\File Returns the new file upon success
613 * @todo Define visibility
614 */
615 public function func_rename($cmds) {
616 if (!$this->isInit) {
617 return FALSE;
618 }
619 $sourceFileObject = $this->getFileObject($cmds['data']);
620 $targetFile = $cmds['target'];
621 $resultObject = NULL;
622 if ($sourceFileObject instanceof \TYPO3\CMS\Core\Resource\File) {
623 try {
624 // Try to rename the File
625 $resultObject = $sourceFileObject->rename($targetFile);
626 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException $e) {
627 $this->writelog(5, 1, 102, 'You are not allowed to rename files!', '');
628 } catch (\TYPO3\CMS\Core\Resource\Exception\IllegalFileExtensionException $e) {
629 $this->writelog(5, 1, 101, 'Extension of file name "%s" was not allowed!', array($targetFile));
630 } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException $e) {
631 $this->writelog(5, 1, 120, 'Destination "%s" existed already!', array($targetFile));
632 } catch (\TYPO3\CMS\Core\Resource\Exception\NotInMountPointException $e) {
633 $this->writelog(5, 1, 121, 'Destination path "%s" was not within your mountpoints!', array($targetFile));
634 } catch (\RuntimeException $e) {
635 $this->writelog(5, 1, 100, 'File "%s" was not renamed! Write-permission problem in "%s"?', array($sourceFileObject->getName(), $targetFile));
636 }
637 $this->writelog(5, 0, 1, 'File renamed from "%s" to "%s"', array($sourceFileObject->getName(), $targetFile));
638 } else {
639 // Else means this is a Folder
640 try {
641 // Try to rename the Folder
642 $resultObject = $sourceFileObject->rename($targetFile);
643 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException $e) {
644 $this->writelog(5, 1, 111, 'You are not allowed to rename directories!', '');
645 } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException $e) {
646 $this->writelog(5, 1, 120, 'Destination "%s" existed already!', array($targetFile));
647 } catch (\TYPO3\CMS\Core\Resource\Exception\NotInMountPointException $e) {
648 $this->writelog(5, 1, 121, 'Destination path "%s" was not within your mountpoints!', array($targetFile));
649 } catch (\RuntimeException $e) {
650 $this->writelog(5, 1, 110, 'Directory "%s" was not renamed! Write-permission problem in "%s"?', array($sourceFileObject->getName(), $targetFile));
651 }
652 $this->writelog(5, 0, 2, 'Directory renamed from "%s" to "%s"', array($sourceFileObject->getName(), $targetFile));
653 }
654 return $resultObject;
655 }
656
657 /**
658 * This creates a new folder. (action=6)
659 *
660 * $cmds['data'] (string): The new folder name
661 * $cmds['target'] (string): The path where to copy to.
662 * + example "2:targetpath/targetfolder/"
663 *
664 * @param array $cmds Command details as described above
665 * @return \TYPO3\CMS\Core\Resource\Folder Returns the new foldername upon success
666 * @todo Define visibility
667 */
668 public function func_newfolder($cmds) {
669 if (!$this->isInit) {
670 return FALSE;
671 }
672 $targetFolderObject = $this->getFileObject($cmds['target']);
673 if (!$targetFolderObject instanceof \TYPO3\CMS\Core\Resource\Folder) {
674 $this->writelog(6, 2, 104, 'Destination "%s" was not a directory', array($cmds['target']));
675 return FALSE;
676 }
677 $resultObject = NULL;
678 try {
679 $folderName = $cmds['data'];
680 $resultObject = $targetFolderObject->createFolder($folderName);
681 $this->writelog(6, 0, 1, 'Directory "%s" created in "%s"', array($folderName, $targetFolderObject->getIdentifier() . '/'));
682 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException $e) {
683 $this->writelog(6, 1, 103, 'You are not allowed to create directories!', '');
684 } catch (\TYPO3\CMS\Core\Resource\Exception\NotInMountPointException $e) {
685 $this->writelog(6, 1, 102, 'Destination path "%s" was not within your mountpoints!', array($targetFolderObject->getIdentifier() . '/'));
686 } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException $e) {
687 $this->writelog(6, 1, 101, 'File or directory "%s" existed already!', array($folderName));
688 } catch (\RuntimeException $e) {
689 $this->writelog(6, 1, 100, 'Directory "%s" not created. Write-permission problem in "%s"?', array($folderName, $targetFolderObject->getIdentifier() . '/'));
690 }
691 return $resultObject;
692 }
693
694 /**
695 * This creates a new file. (action=8)
696 * $cmds['data'] (string): The new file name
697 * $cmds['target'] (string): The path where to create it.
698 * + example "2:targetpath/targetfolder/"
699 *
700 * @param array $cmds Command details as described above
701 * @return string Returns the new filename upon success
702 * @todo Define visibility
703 */
704 public function func_newfile($cmds) {
705 if (!$this->isInit) {
706 return FALSE;
707 }
708 $targetFolderObject = $this->getFileObject($cmds['target']);
709 if (!$targetFolderObject instanceof \TYPO3\CMS\Core\Resource\Folder) {
710 $this->writelog(8, 2, 104, 'Destination "%s" was not a directory', array($cmds['target']));
711 return FALSE;
712 }
713 $resultObject = NULL;
714 try {
715 $fileName = $cmds['data'];
716 $resultObject = $targetFolderObject->createFile($fileName);
717 $this->writelog(8, 0, 1, 'File created: "%s"', array($fileName));
718 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException $e) {
719 $this->writelog(8, 1, 103, 'You are not allowed to create files!', '');
720 } catch (\TYPO3\CMS\Core\Resource\Exception\NotInMountPointException $e) {
721 $this->writelog(8, 1, 102, 'Destination path "%s" was not within your mountpoints!', array($targetFolderObject->getIdentifier()));
722 } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException $e) {
723 $this->writelog(8, 1, 101, 'File existed already in "%s"!', array($targetFolderObject->getIdentifier()));
724 } catch (\TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException $e) {
725 $this->writelog(8, 1, 106, 'File name "%s" was not allowed!', $fileName);
726 } catch (\RuntimeException $e) {
727 $this->writelog(8, 1, 100, 'File "%s" was not created! Write-permission problem in "%s"?', array($fileName, $targetFolderObject->getIdentifier()));
728 }
729 return $resultObject;
730 }
731
732 /**
733 * Editing textfiles or folders (action=9)
734 *
735 * @param array $cmds $cmds['data'] is the new content. $cmds['target'] is the target (file or dir)
736 * @return boolean Returns TRUE on success
737 * @todo Define visibility
738 */
739 public function func_edit($cmds) {
740 if (!$this->isInit) {
741 return FALSE;
742 }
743 // Example indentifier for $cmds['target'] => "4:mypath/tomyfolder/myfile.jpg"
744 // for backwards compatibility: the combined file identifier was the path+filename
745 $fileIdentifier = $cmds['target'];
746 $fileObject = $this->getFileObject($fileIdentifier);
747 // Example indentifier for $cmds['target'] => "2:targetpath/targetfolder/"
748 $content = $cmds['data'];
749 if (!$fileObject instanceof \TYPO3\CMS\Core\Resource\File) {
750 $this->writelog(9, 2, 123, 'Target "%s" was not a file!', array($fileIdentifier));
751 return FALSE;
752 }
753 $extList = $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'];
754 if (!\TYPO3\CMS\Core\Utility\GeneralUtility::inList($extList, $fileObject->getExtension())) {
755 $this->writelog(9, 1, 102, 'File extension "%s" is not a textfile format! (%s)', array($fileObject->getExtension(), $extList));
756 return FALSE;
757 }
758 try {
759 $fileObject->setContents($content);
760 clearstatcache();
761 $this->writelog(9, 0, 1, 'File saved to "%s", bytes: %s, MD5: %s ', array($fileObject->getIdentifier(), $fileObject->getSize(), md5($content)));
762 return TRUE;
763 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException $e) {
764 $this->writelog(9, 1, 104, 'You are not allowed to edit files!', '');
765 return FALSE;
766 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFileWritePermissionsException $e) {
767 $this->writelog(9, 1, 100, 'File "%s" was not saved! Write-permission problem?', array($fileObject->getIdentifier()));
768 return FALSE;
769 }
770 }
771
772 /**
773 * Upload of files (action=1)
774 * when having multiple uploads (HTML5-style), the array $_FILES looks like this:
775 * Array(
776 * [upload_1] => Array(
777 * [name] => Array(
778 * [0] => GData - Content-Elements and Media-Gallery.pdf
779 * [1] => CMS Expo 2011.txt
780 * )
781 * [type] => Array(
782 * [0] => application/pdf
783 * [1] => text/plain
784 * )
785 * [tmp_name] => Array(
786 * [0] => /Applications/MAMP/tmp/php/phpNrOB43
787 * [1] => /Applications/MAMP/tmp/php/phpD2HQAK
788 * )
789 * [size] => Array(
790 * [0] => 373079
791 * [1] => 1291
792 * )
793 * )
794 * )
795 * in HTML you'd need sth like this: <input type="file" name="upload_1[]" multiple="true" />
796 *
797 * @param array $cmds $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)
798 * @return string Returns the new filename upon success
799 * @todo Define visibility
800 */
801 public function func_upload($cmds) {
802 if (!$this->isInit) {
803 return FALSE;
804 }
805 $uploadPosition = $cmds['data'];
806 $uploadedFileData = $_FILES['upload_' . $uploadPosition];
807 if (empty($uploadedFileData['name']) || is_array($uploadedFileData['name']) && empty($uploadedFileData['name'][0])) {
808 $this->writelog(1, 2, 108, 'No file was uploaded!', '');
809 return FALSE;
810 }
811 // Example indentifier for $cmds['target'] => "2:targetpath/targetfolder/"
812 $targetFolderObject = $this->getFileObject($cmds['target']);
813 // Uploading with non HTML-5-style, thus, make an array out of it, so we can loop over it
814 if (!is_array($uploadedFileData['name'])) {
815 $uploadedFileData = array(
816 'name' => array($uploadedFileData['name']),
817 'type' => array($uploadedFileData['type']),
818 'tmp_name' => array($uploadedFileData['tmp_name']),
819 'size' => array($uploadedFileData['size'])
820 );
821 }
822 $resultObjects = array();
823 $numberOfUploadedFilesForPosition = count($uploadedFileData['name']);
824 // Loop through all uploaded files
825 for ($i = 0; $i < $numberOfUploadedFilesForPosition; $i++) {
826 $fileInfo = array(
827 'name' => $uploadedFileData['name'][$i],
828 'type' => $uploadedFileData['type'][$i],
829 'tmp_name' => $uploadedFileData['tmp_name'][$i],
830 'size' => $uploadedFileData['size'][$i]
831 );
832 try {
833 // @todo can be improved towards conflict mode naming
834 if ($this->dontCheckForUnique) {
835 $conflictMode = 'replace';
836 } else {
837 $conflictMode = 'cancel';
838 }
839 $resultObjects[] = $targetFolderObject->addUploadedFile($fileInfo, $conflictMode);
840 $this->writelog(1, 0, 1, 'Uploading file "%s" to "%s"', array($fileInfo['name'], $targetFolderObject->getIdentifier()));
841 } catch (\TYPO3\CMS\Core\Resource\Exception\UploadException $e) {
842 $this->writelog(1, 2, 106, 'The upload has failed, no uploaded file found!', '');
843 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException $e) {
844 $this->writelog(1, 1, 105, 'You are not allowed to upload files!', '');
845 } catch (\TYPO3\CMS\Core\Resource\Exception\UploadSizeException $e) {
846 $this->writelog(1, 1, 104, 'The uploaded file "%s" exceeds the size-limit', array($fileInfo['name']));
847 } catch (\TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException $e) {
848 $this->writelog(1, 1, 103, 'Destination path "%s" was not within your mountpoints!', array($targetFolderObject->getIdentifier()));
849 } catch (\TYPO3\CMS\Core\Resource\Exception\IllegalFileExtensionException $e) {
850 $this->writelog(1, 1, 102, 'Extension of file name "%s" is not allowed in "%s"!', array($fileInfo['name'], $targetFolderObject->getIdentifier()));
851 } catch (\TYPO3\CMS\Core\Resource\Exception\ExistingTargetFileNameException $e) {
852 $this->writelog(1, 1, 101, 'No unique filename available in "%s"!', array($targetFolderObject->getIdentifier()));
853 } catch (\RuntimeException $e) {
854 $this->writelog(1, 1, 100, 'Uploaded file could not be moved! Write-permission problem in "%s"?', array($targetFolderObject->getIdentifier()));
855 }
856 }
857 return $resultObjects;
858 }
859
860 /**
861 * Unzipping file (action=7)
862 * This is permitted only if the user has fullAccess or if the file resides
863 *
864 * @param array $cmds $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.
865 * @return boolean Returns TRUE on success
866 * @todo Define visibility
867 */
868 public function func_unzip($cmds) {
869 if (!$this->isInit || $this->dont_use_exec_commands) {
870 return FALSE;
871 }
872 $theFile = $cmds['data'];
873 if (!@is_file($theFile)) {
874 $this->writelog(7, 2, 105, 'The file "%s" did not exist!', array($theFile));
875 return FALSE;
876 }
877 $fI = \TYPO3\CMS\Core\Utility\GeneralUtility::split_fileref($theFile);
878 if (!isset($cmds['target'])) {
879 $cmds['target'] = $fI['path'];
880 }
881 // Clean up destination directory
882 // !!! Method has been put in the local driver, can be saftely removed
883 $theDest = $this->is_directory($cmds['target']);
884 if (!$theDest) {
885 $this->writelog(7, 2, 104, 'Destination "%s" was not a directory', array($cmds['target']));
886 return FALSE;
887 }
888 if (!$this->actionPerms['unzipFile']) {
889 $this->writelog(7, 1, 103, 'You are not allowed to unzip files', '');
890 return FALSE;
891 }
892 if ($fI['fileext'] != 'zip') {
893 $this->writelog(7, 1, 102, 'File extension is not "zip"', '');
894 return FALSE;
895 }
896 if (!$this->checkIfFullAccess($theDest)) {
897 $this->writelog(7, 1, 101, 'You don\'t have full access to the destination directory "%s"!', array($theDest));
898 return FALSE;
899 }
900 // !!! Method has been put in the sotrage driver, can be saftely removed
901 if ($this->checkPathAgainstMounts($theFile) && $this->checkPathAgainstMounts($theDest . '/')) {
902 // No way to do this under windows.
903 $cmd = $this->unzipPath . 'unzip -qq ' . escapeshellarg($theFile) . ' -d ' . escapeshellarg($theDest);
904 \TYPO3\CMS\Core\Utility\CommandUtility::exec($cmd);
905 $this->writelog(7, 0, 1, 'Unzipping file "%s" in "%s"', array($theFile, $theDest));
906 return TRUE;
907 } else {
908 $this->writelog(7, 1, 100, 'File "%s" or destination "%s" was not within your mountpoints!', array($theFile, $theDest));
909 return FALSE;
910 }
911 }
912
913 }
914
915
916 ?>