Merge branch '2013.10' into 2014.11
[tine20] / tine20 / Filemanager / Controller / Node.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Filemanager
6  * @subpackage  Controller
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * @copyright   Copyright (c) 2011-2012 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  * @todo        add transactions to move/create/delete/copy 
12  */
13
14 /**
15  * Node controller for Filemanager
16  *
17  * @package     Filemanager
18  * @subpackage  Controller
19  */
20 class Filemanager_Controller_Node extends Tinebase_Controller_Record_Abstract
21 {
22     /**
23      * application name (is needed in checkRight())
24      *
25      * @var string
26      */
27     protected $_applicationName = 'Filemanager';
28     
29     /**
30      * Filesystem backend
31      *
32      * @var Tinebase_FileSystem
33      */
34     protected $_backend = NULL;
35     
36     /**
37      * the model handled by this controller
38      * @var string
39      */
40     protected $_modelName = 'Filemanager_Model_Node';
41     
42     /**
43      * TODO handle modlog
44      * @var boolean
45      */
46     protected $_omitModLog = TRUE;
47     
48     /**
49      * holds the total count of the last recursive search
50      * @var integer
51      */
52     protected $_recursiveSearchTotalCount = 0;
53     
54     /**
55      * holds the instance of the singleton
56      *
57      * @var Filemanager_Controller_Node
58      */
59     private static $_instance = NULL;
60     
61     /**
62      * the constructor
63      *
64      * don't use the constructor. use the singleton
65      */
66     private function __construct() 
67     {
68         $this->_backend = Tinebase_FileSystem::getInstance();
69     }
70     
71     /**
72      * don't clone. Use the singleton.
73      *
74      */
75     private function __clone() 
76     {
77     }
78     
79     /**
80      * the singleton pattern
81      *
82      * @return Filemanager_Controller_Node
83      */
84     public static function getInstance() 
85     {
86         if (self::$_instance === NULL) {
87             self::$_instance = new Filemanager_Controller_Node();
88         }
89         
90         return self::$_instance;
91     }
92     
93     /**
94      * (non-PHPdoc)
95      * @see Tinebase_Controller_Record_Abstract::update()
96      */
97     public function update(Tinebase_Record_Interface $_record)
98     {
99         if (! $this->_checkACLContainer($this->_backend->getNodeContainer($_record->getId()), 'update')) {
100             throw new Tinebase_Exception_AccessDenied('No permission to update nodes.');
101         }
102
103         return parent::update($_record);
104     }
105     
106     /**
107      * inspect update of one record (before update)
108      *
109      * @param   Tinebase_Record_Interface $_record      the update record
110      * @param   Tinebase_Record_Interface $_oldRecord   the current persistent record
111      * @return  void
112      */
113     protected function _inspectBeforeUpdate($_record, $_oldRecord)
114     {
115         // protect against file object spoofing
116         foreach (array_keys($_record->toArray()) as $property) {
117             if (! in_array($property, array('name', 'description', 'relations', 'customfields', 'tags', 'notes'))) {
118                 $_record->{$property} = $_oldRecord->{$property};
119             }
120         }
121     }
122     
123     /**
124      * (non-PHPdoc)
125      * @see Tinebase_Controller_Record_Abstract::getMultiple()
126      * 
127      * @return  Tinebase_Record_RecordSet
128      */
129     public function getMultiple($_ids)
130     {
131         $results = $this->_backend->getMultipleTreeNodes($_ids);
132         $this->resolveMultipleTreeNodesPath($results);
133         
134         return $results;
135     }
136     
137     /**
138      * Resolve path of multiple tree nodes
139      * 
140      * @param Tinebase_Record_RecordSet|Tinebase_Model_Tree_Node $_records
141      */
142     public function resolveMultipleTreeNodesPath($_records)
143     {
144         $records = ($_records instanceof Tinebase_Model_Tree_Node)
145             ? new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node', array($_records)) : $_records;
146             
147         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
148             . ' Resolving paths for ' . count($records) .  ' records.');
149             
150         foreach ($records as $record) {
151             $path = $this->_backend->getPathOfNode($record, TRUE);
152             $record->path = Tinebase_Model_Tree_Node_Path::removeAppIdFromPath($path, $this->_applicationName);
153
154             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
155                 . ' Got path ' . $record->path .  ' for node ' . $record->name);
156         }
157     }
158     
159     /**
160      * (non-PHPdoc)
161      * @see Tinebase_Controller_Record_Abstract::get()
162      */
163     public function get($_id, $_containerId = NULL)
164     {
165         if (! $this->_checkACLContainer($this->_backend->getNodeContainer($_id), 'get')) {
166             throw new Tinebase_Exception_AccessDenied('No permission to get node');
167         }
168         $record = parent::get($_id);
169         if($record) {
170             $record->notes = Tinebase_Notes::getInstance()->getNotesOfRecord('Tinebase_Model_Tree_Node', $record->getId());
171         }
172         return $record;
173     }
174     
175     /**
176      * search tree nodes
177      * 
178      * @param Tinebase_Model_Filter_FilterGroup|optional $_filter
179      * @param Tinebase_Model_Pagination|optional $_pagination
180      * @param bool $_getRelations
181      * @param bool $_onlyIds
182      * @param string|optional $_action
183      * @return Tinebase_Record_RecordSet of Tinebase_Model_Tree_Node
184      */
185     public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Record_Interface $_pagination = NULL, $_getRelations = FALSE, $_onlyIds = FALSE, $_action = 'get')
186     {
187         // perform recursive search on recursive filter set
188         if($_filter->getFilter('recursive')) {
189             return $this->_searchNodesRecursive($_filter, $_pagination);
190         } else {
191             $path = $this->_checkFilterACL($_filter, $_action);
192         }
193         
194         if ($path->containerType === Tinebase_Model_Tree_Node_Path::TYPE_ROOT) {
195             $result = $this->_getRootNodes();
196         } else if ($path->containerType === Tinebase_Model_Container::TYPE_PERSONAL && ! $path->containerOwner) {
197             if (! file_exists($path->statpath)) {
198                 $this->_backend->mkdir($path->statpath);
199             }
200             $result = $this->_getOtherUserNodes();
201         } else {
202             try {
203                 $result = $this->_backend->searchNodes($_filter, $_pagination);
204             } catch (Tinebase_Exception_NotFound $tenf) {
205                 // create basic nodes like personal|shared|user root
206                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . 
207                         ' ' . $path->statpath);
208                 if ($path->name === Tinebase_Model_Container::TYPE_SHARED || 
209                     $path->statpath === $this->_backend->getApplicationBasePath(
210                         Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName), 
211                         Tinebase_Model_Container::TYPE_PERSONAL
212                     ) . '/' . Tinebase_Core::getUser()->getId()
213                 ) {
214                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . 
215                         ' Creating new path ' . $path->statpath);
216                     $this->_backend->mkdir($path->statpath);
217                     $result = $this->_backend->searchNodes($_filter, $_pagination);
218                 } else {
219                     throw $tenf;
220                 }
221             }
222             $this->resolveContainerAndAddPath($result, $path);
223             $this->_sortContainerNodes($result, $path, $_pagination);
224         }
225         return $result;
226     }
227     
228     /**
229      * search tree nodes for search combo
230      * 
231      * @param Tinebase_Model_Tree_Node_Filter $_filter
232      * @param Tinebase_Record_Interface $_pagination
233      * 
234      * @return Tinebase_Record_RecordSet of Tinebase_Model_Tree_Node
235      */
236     
237     protected function _searchNodesRecursive($_filter, $_pagination)
238     {
239         $files = new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node');
240         $ret = new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node');
241         $folders = $this->_getRootNodes();
242         $folders->merge($this->_getOtherUserNodes());
243
244         while($folders->count()) {
245             $node = $folders->getFirstRecord();
246             $filter = new Tinebase_Model_Tree_Node_Filter(array(
247                 array('field' => 'path', 'operator' => 'equals', 'value' => $node->path),
248                 ), 'AND');
249             $result = $this->search($filter);
250             $folders->merge($result->filter('type', Tinebase_Model_Tree_Node::TYPE_FOLDER));
251             
252             if($_filter->getFilter('query') && $_filter->getFilter('query')->getValue()) {
253                 $files->merge($result->filter('type', Tinebase_Model_Tree_Node::TYPE_FILE)->filter('name', '/^'.$_filter->getFilter('query')->getValue().'./i', true));
254             } else {
255                 $files->merge($result->filter('type', Tinebase_Model_Tree_Node::TYPE_FILE));
256             }
257             
258             $folders->removeRecord($node);
259         }
260         $this->_recursiveSearchTotalCount = $files->count();
261         $ret = $files->sortByPagination($_pagination)->limitByPagination($_pagination);
262         return $ret;
263     }
264     
265     /**
266      * checks filter acl and adds base path
267      * 
268      * @param Tinebase_Model_Filter_FilterGroup $_filter
269      * @param string $_action get|update
270      * @return Tinebase_Model_Tree_Node_Path
271      * @throws Tinebase_Exception_AccessDenied
272      */
273     protected function _checkFilterACL(Tinebase_Model_Filter_FilterGroup $_filter, $_action = 'get')
274     {
275         if ($_filter === NULL) {
276             $_filter = new Tinebase_Model_Tree_Node_Filter();
277         }
278         
279         $pathFilters = $_filter->getFilter('path', TRUE);
280         if (count($pathFilters) !== 1) {
281             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
282                 . 'Exactly one path filter required.');
283             $pathFilter = (count($pathFilters) > 1) ? $pathFilters[0] : new Tinebase_Model_Tree_Node_PathFilter(array(
284                 'field'     => 'path',
285                 'operator'  => 'equals',
286                 'value'     => '/',)
287             );
288             $_filter->removeFilter('path');
289             $_filter->addFilter($pathFilter);
290         } else {
291             $pathFilter = $pathFilters[0];
292         }
293         
294         // add base path and check grants
295         try {
296             $path = Tinebase_Model_Tree_Node_Path::createFromPath($this->addBasePath($pathFilter->getValue()));
297         } catch (Exception $e) {
298             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
299                 . ' Could not determine path, setting root path (' . $e->getMessage() . ')');
300             $path = Tinebase_Model_Tree_Node_Path::createFromPath($this->addBasePath('/'));
301         }
302         $pathFilter->setValue($path);
303         
304         $this->_checkPathACL($path, $_action);
305         
306         return $path;
307     }
308     
309     /**
310      * get the three root nodes
311      * 
312      * @return Tinebase_Record_RecordSet of Tinebase_Model_Tree_Node
313      */
314     protected function _getRootNodes()
315     {
316         $translate = Tinebase_Translation::getTranslation($this->_applicationName);
317         $result = new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node', array(
318             array(
319                 'name' => $translate->_('My folders'),
320                 'path' => '/' . Tinebase_Model_Container::TYPE_PERSONAL . '/' . Tinebase_Core::getUser()->accountLoginName,
321                 'type' => Tinebase_Model_Tree_Node::TYPE_FOLDER,
322                 'id' => Tinebase_Model_Container::TYPE_PERSONAL,
323
324             ),
325             array(
326                 'name' => $translate->_('Shared folders'),
327                 'path' => '/' . Tinebase_Model_Container::TYPE_SHARED,
328                 'type' => Tinebase_Model_Tree_Node::TYPE_FOLDER,
329                 'id' => Tinebase_Model_Container::TYPE_SHARED,
330             
331             ),
332             array(
333                 'name' => $translate->_('Other users folders'),
334                 'path' => '/' . Tinebase_Model_Container::TYPE_PERSONAL,
335                 'type' => Tinebase_Model_Tree_Node::TYPE_FOLDER,
336                 'id' => Tinebase_Model_Container::TYPE_OTHERUSERS,
337             ),
338         ), TRUE); // bypass validation
339         
340         return $result;
341     }
342
343     /**
344      * get other users nodes
345      * 
346      * @return Tinebase_Record_RecordSet of Tinebase_Model_Tree_Node
347      */
348     protected function _getOtherUserNodes()
349     {
350         $result = new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node');
351         $users = Tinebase_Container::getInstance()->getOtherUsers(Tinebase_Core::getUser(), $this->_applicationName, Tinebase_Model_Grants::GRANT_READ);
352         foreach ($users as $user) {
353             $fullUser = Tinebase_User::getInstance()->getFullUserById($user);
354             $record = new Tinebase_Model_Tree_Node(array(
355                 'name' => $fullUser->accountDisplayName,
356                 'path' => '/' . Tinebase_Model_Container::TYPE_PERSONAL . '/' . $fullUser->accountLoginName,
357                 'type' => Tinebase_Model_Tree_Node::TYPE_FOLDER,
358             ), TRUE);
359             $result->addRecord($record);
360         }
361         
362         return $result;
363     }
364     
365     /**
366      * sort nodes (only checks if we are on the container level and sort by container_name then)
367      * 
368      * @param Tinebase_Record_RecordSet $nodes
369      * @param Tinebase_Model_Tree_Node_Path $path
370      * @param Tinebase_Model_Pagination $pagination
371      */
372     protected function _sortContainerNodes(Tinebase_Record_RecordSet $nodes, Tinebase_Model_Tree_Node_Path $path, Tinebase_Model_Pagination $pagination = NULL)
373     {
374         if ($path->container || ($pagination !== NULL && $pagination->sort && $pagination->sort !== 'name')) {
375             // no toplevel path or no sorting by name -> sorting should be already handled by search()
376             return;
377         }
378         
379         $dir = ($pagination !== NULL && $pagination->dir) ? $pagination->dir : 'ASC';
380         
381         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
382             . ' Sorting container nodes by name (path: ' . $path->flatpath . ') / dir: ' . $dir);
383         
384         $nodes->sort('container_name', $dir);
385     }
386     
387     /**
388      * get file node
389      * 
390      * @param Tinebase_Model_Tree_Node_Path $_path
391      * @return Tinebase_Model_Tree_Node
392      */
393     public function getFileNode(Tinebase_Model_Tree_Node_Path $_path)
394     {
395         $this->_checkPathACL($_path, 'get');
396         
397         if (! $this->_backend->fileExists($_path->statpath)) {
398             throw new Filemanager_Exception('File does not exist,');
399         }
400         
401         if (! $this->_backend->isFile($_path->statpath)) {
402             throw new Filemanager_Exception('Is a directory');
403         }
404         
405         return $this->_backend->stat($_path->statpath);
406     }
407     
408     /**
409      * add base path
410      * 
411      * @param Tinebase_Model_Tree_Node_PathFilter $_pathFilter
412      * @return string
413      */
414     public function addBasePath($_path)
415     {
416         $basePath = $this->_backend->getApplicationBasePath(Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName));
417         $basePath .= '/folders';
418         
419         $path = (strpos($_path, '/') === 0) ? $_path : '/' . $_path;
420         // only add base path once
421         $result = (! preg_match('@^' . preg_quote($basePath) . '@', $path)) ? $basePath . $path : $path;
422         
423         return $result;
424     }
425     
426     /**
427      * check if user has the permissions for the container
428      * 
429      * @param Tinebase_Model_Container $_container
430      * @param string $_action get|update|...
431      * @return boolean
432      */
433     protected function _checkACLContainer($_container, $_action = 'get')
434     {
435         if (Tinebase_Container::getInstance()->hasGrant(Tinebase_Core::getUser(), $_container, Tinebase_Model_Grants::GRANT_ADMIN)) {
436             return TRUE;
437         }
438         
439         switch ($_action) {
440             case 'get':
441                 $requiredGrant = Tinebase_Model_Grants::GRANT_READ;
442                 break;
443             case 'add':
444                 $requiredGrant = Tinebase_Model_Grants::GRANT_ADD;
445                 break;
446             case 'update':
447                 $requiredGrant = Tinebase_Model_Grants::GRANT_EDIT;
448                 break;
449             case 'delete':
450                 $requiredGrant = Tinebase_Model_Grants::GRANT_DELETE;
451                 break;
452             default:
453                 throw new Tinebase_Exception_UnexpectedValue('Unknown action: ' . $_action);
454         }
455         
456         return Tinebase_Container::getInstance()->hasGrant(Tinebase_Core::getUser(), $_container, $requiredGrant);
457     }
458     
459     /**
460      * Gets total count of search with $_filter
461      * 
462      * @param Tinebase_Model_Filter_FilterGroup $_filter
463      * @param string|optional $_action
464      * @return int
465      */
466     public function searchCount(Tinebase_Model_Filter_FilterGroup $_filter, $_action = 'get')
467     {
468         $path = $this->_checkFilterACL($_filter, $_action);
469         
470         if($_filter->getFilter('recursive') && $_filter->getFilter('recursive')->getValue()) {
471             $result = $this->_recursiveSearchTotalCount;
472         } else if ($path->containerType === Tinebase_Model_Tree_Node_Path::TYPE_ROOT) {
473             $result = count($this->_getRootNodes());
474         } else if ($path->containerType === Tinebase_Model_Container::TYPE_PERSONAL && ! $path->containerOwner) {
475             $result = count($this->_getOtherUserNodes());
476         } else {
477             $result = $this->_backend->searchNodesCount($_filter);
478         }
479         
480         return $result;
481     }
482
483     /**
484      * create node(s)
485      * 
486      * @param array $_filenames
487      * @param string $_type directory or file
488      * @param array $_tempFileIds
489      * @param boolean $_forceOverwrite
490      * @return Tinebase_Record_RecordSet of Tinebase_Model_Tree_Node
491      */
492     public function createNodes($_filenames, $_type, $_tempFileIds = array(), $_forceOverwrite = FALSE)
493     {
494         $result = new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node');
495         $nodeExistsException = NULL;
496         
497         foreach ($_filenames as $idx => $filename) {
498             $tempFileId = (isset($_tempFileIds[$idx])) ? $_tempFileIds[$idx] : NULL;
499
500             try {
501                 $node = $this->_createNode($filename, $_type, $tempFileId, $_forceOverwrite);
502                 if ($node) {
503                     $result->addRecord($node);
504                 }
505             } catch (Filemanager_Exception_NodeExists $fene) {
506                 $nodeExistsException = $this->_handleNodeExistsException($fene, $nodeExistsException);
507             }
508         }
509
510         if ($nodeExistsException) {
511             throw $nodeExistsException;
512         }
513         
514         return $result;
515     }
516     
517     /**
518      * collect information of a Filemanager_Exception_NodeExists in a "parent" exception
519      * 
520      * @param Filemanager_Exception_NodeExists $_fene
521      * @param Filemanager_Exception_NodeExists|NULL $_parentNodeExistsException
522      */
523     protected function _handleNodeExistsException($_fene, $_parentNodeExistsException = NULL)
524     {
525         // collect all nodes that already exist and add them to exception info
526         if (! $_parentNodeExistsException) {
527             $_parentNodeExistsException = new Filemanager_Exception_NodeExists();
528         }
529         
530         $nodesInfo = $_fene->getExistingNodesInfo();
531         if (count($nodesInfo) > 0) {
532             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
533                 . ' Adding node info to exception.');
534             $_parentNodeExistsException->addExistingNodeInfo($nodesInfo->getFirstRecord());
535         } else {
536             return $_fene;
537         }
538         
539         return $_parentNodeExistsException;
540     }
541     
542     /**
543      * create new node
544      * 
545      * @param string|Tinebase_Model_Tree_Node_Path $_path
546      * @param string $_type
547      * @param string $_tempFileId
548      * @param boolean $_forceOverwrite
549      * @return Tinebase_Model_Tree_Node
550      * @throws Tinebase_Exception_InvalidArgument
551      */
552     protected function _createNode($_path, $_type, $_tempFileId = NULL, $_forceOverwrite = FALSE)
553     {
554         if (! in_array($_type, array(Tinebase_Model_Tree_Node::TYPE_FILE, Tinebase_Model_Tree_Node::TYPE_FOLDER))) {
555             throw new Tinebase_Exception_InvalidArgument('Type ' . $_type . 'not supported.');
556         } 
557
558         $path = ($_path instanceof Tinebase_Model_Tree_Node_Path) 
559             ? $_path : Tinebase_Model_Tree_Node_Path::createFromPath($this->addBasePath($_path));
560         $parentPathRecord = $path->getParent();
561         
562         // we need to check the parent record existance before commencing node creation
563         $parentPathRecord->validateExistance();
564         
565         try {
566             $this->_checkIfExists($path);
567             $this->_checkPathACL($parentPathRecord, 'add');
568         } catch (Filemanager_Exception_NodeExists $fene) {
569             if ($_forceOverwrite) {
570                 $existingNode = $this->_backend->stat($path->statpath);
571                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
572                     . ' Existing node: ' . print_r($existingNode->toArray(), TRUE));
573                 
574                 if (! $_tempFileId) {
575                     // just return the exisiting node and do not overwrite existing file if no tempfile id was given
576                     $this->_checkPathACL($path, 'get');
577                     $this->resolveContainerAndAddPath($existingNode, $parentPathRecord);
578                     return $existingNode;
579                 } else {
580                     // check if a new (size 0) file is overwritten
581                     // @todo check revision here?
582                     if ($existingNode->size == 0) {
583                         $this->_checkPathACL($parentPathRecord, 'add');
584                     } else {
585                         $this->_checkPathACL($parentPathRecord, 'update');
586                     }
587                 }
588             } else if (! $_forceOverwrite) {
589                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
590                     . ' ' . $fene);
591                 throw $fene;
592             }
593         }
594
595         if (! $parentPathRecord->container && $_type === Tinebase_Model_Tree_Node::TYPE_FOLDER) {
596             $container = $this->_createContainer($path->name, $parentPathRecord->containerType);
597             $newNodePath = $parentPathRecord->statpath . '/' . $container->getId();
598         } else {
599             $container = NULL;
600             $newNodePath = $parentPathRecord->statpath . '/' . $path->name;
601         }
602         
603         $newNode = $this->_createNodeInBackend($newNodePath, $_type, $_tempFileId);
604         
605         $this->resolveContainerAndAddPath($newNode, $parentPathRecord, $container);
606         return $newNode;
607     }
608     
609     /**
610      * create node in backend
611      * 
612      * @param string $_statpath
613      * @param type
614      * @param string $_tempFileId
615      * @return Tinebase_Model_Tree_Node
616      */
617     protected function _createNodeInBackend($_statpath, $_type, $_tempFileId = NULL)
618     {
619         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . 
620             ' Creating new path ' . $_statpath . ' of type ' . $_type);
621         
622         $node = NULL;
623         switch ($_type) {
624             case Tinebase_Model_Tree_Node::TYPE_FILE:
625                  $this->_backend->copyTempfile($_tempFileId, $_statpath);
626                 break;
627             case Tinebase_Model_Tree_Node::TYPE_FOLDER:
628                 $node = $this->_backend->mkdir($_statpath);
629                 break;
630         }
631         
632         return $node ? $node : $this->_backend->stat($_statpath);
633     }
634     
635     /**
636      * check file existance
637      * 
638      * @param Tinebase_Model_Tree_Node_Path $_path
639      * @param Tinebase_Model_Tree_Node $_node
640      * @throws Filemanager_Exception_NodeExists
641      */
642     protected function _checkIfExists(Tinebase_Model_Tree_Node_Path $_path, $_node = NULL)
643     {
644         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
645             . ' Check existance of ' . $_path->statpath);
646
647         if ($this->_backend->fileExists($_path->statpath)) {
648             
649             if (! $_node) {
650                 $_node = $this->_backend->stat($_path->statpath);
651             }
652             
653             if ($_node) {
654                 $existsException = new Filemanager_Exception_NodeExists();
655                 $existsException->addExistingNodeInfo($_node);
656                 throw $existsException;
657             }
658         }
659     }
660         
661     /**
662      * check acl of path
663      * 
664      * @param Tinebase_Model_Tree_Node_Path $_path
665      * @param string $_action
666      * @param boolean $_topLevelAllowed
667      * @throws Tinebase_Exception_AccessDenied
668      */
669     protected function _checkPathACL(Tinebase_Model_Tree_Node_Path $_path, $_action = 'get', $_topLevelAllowed = TRUE)
670     {
671         $hasPermission = FALSE;
672         
673         if ($_path->container) {
674             $hasPermission = $this->_checkACLContainer($_path->container, $_action);
675         } else if ($_topLevelAllowed) {
676             switch ($_path->containerType) {
677                 case Tinebase_Model_Container::TYPE_PERSONAL:
678                     if ($_path->containerOwner) {
679                         $hasPermission = ($_path->containerOwner === Tinebase_Core::getUser()->accountLoginName || $_action === 'get');
680                     } else {
681                         $hasPermission = ($_action === 'get');
682                     }
683                     break;
684                 case Tinebase_Model_Container::TYPE_SHARED:
685                     $hasPermission = ($_action !== 'get') ? $this->checkRight(Tinebase_Acl_Rights::MANAGE_SHARED_FOLDERS, FALSE) : TRUE;
686                     break;
687                 case Tinebase_Model_Tree_Node_Path::TYPE_ROOT:
688                     $hasPermission = ($_action === 'get');
689                     break;
690             }
691         }
692         
693         if (! $hasPermission) {
694             throw new Tinebase_Exception_AccessDenied('No permission to ' . $_action . ' nodes in path ' . $_path->flatpath);
695         }
696     }
697     
698     /**
699      * create new container
700      * 
701      * @param string $_name
702      * @param string $_type
703      * @return Tinebase_Model_Container
704      * @throws Tinebase_Exception_Record_NotAllowed
705      */
706     protected function _createContainer($_name, $_type)
707     {
708         $ownerId = ($_type === Tinebase_Model_Container::TYPE_PERSONAL) ? Tinebase_Core::getUser()->getId() : NULL;
709         try {
710             $existingContainer = Tinebase_Container::getInstance()->getContainerByName(
711                 $this->_applicationName, $_name, $_type, $ownerId);
712             throw new Filemanager_Exception_NodeExists('Container ' . $_name . ' of type ' . $_type . ' already exists.');
713         } catch (Tinebase_Exception_NotFound $tenf) {
714             // go on
715         }
716         
717         $app = Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName);
718         $container = Tinebase_Container::getInstance()->addContainer(new Tinebase_Model_Container(array(
719             'name'           => $_name,
720             'type'           => $_type,
721             'backend'        => 'sql',
722             'application_id' => $app->getId(),
723             'model'          => 'Filemanager_Model_Node'
724         )));
725         
726         return $container;
727     }
728
729     /**
730      * resolve node container and path
731      * 
732      * (1) add path to records 
733      * (2) replace name with container record, if node name is a container id 
734      *     / path is toplevel (shared/personal with useraccount
735      * (3) add account grants of acl container to node
736      * 
737      * @param Tinebase_Record_RecordSet|Tinebase_Model_Tree_Node $_records
738      * @param Tinebase_Model_Tree_Node_Path $_path
739      * @param Tinebase_Model_Container $_container
740      */
741     public function resolveContainerAndAddPath($_records, Tinebase_Model_Tree_Node_Path $_path, Tinebase_Model_Container $_container = NULL)
742     {
743         $records = ($_records instanceof Tinebase_Model_Tree_Node) 
744             ? new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node', array($_records)) : $_records;
745         
746         if (! $_path->container) {
747             // fetch top level container nodes
748             if ($_container === NULL) {
749                 $containerIds = $_records->name;
750                 $containers = Tinebase_Container::getInstance()->getMultiple($containerIds);
751             } else {
752                 $containers = new Tinebase_Record_RecordSet('Tinebase_Model_Container', array($_container));
753             }
754         }
755         
756         $app = Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName);
757         $flatpathWithoutBasepath = Tinebase_Model_Tree_Node_Path::removeAppIdFromPath($_path->flatpath, $app);
758         if ($records) {
759             foreach ($records as $record) {
760                 $record->path = $flatpathWithoutBasepath . '/' . $record->name;
761                 
762                 $aclContainer = NULL;
763                 if (! $_path->container) {
764                     // resolve container
765                     if (! $record->name instanceof Tinebase_Model_Container) {
766                         $idx = $containers->getIndexById($record->name);
767                         if ($idx !== FALSE) {
768                             $aclContainer = $containers[$idx];
769                             $record->name = $aclContainer;
770                             $record->path = $flatpathWithoutBasepath . '/' . $record->name->name;
771                         }
772                     }
773                 } else {
774                     $aclContainer = $_path->container;
775                 }
776                 
777                 if ($aclContainer) {
778                     $record->account_grants = Tinebase_Container::getInstance()->getGrantsOfAccount(
779                         Tinebase_Core::getUser(), 
780                         $aclContainer
781                     )->toArray();
782                     $aclContainer->account_grants = $record->account_grants;
783                     
784                     // needed for sorting
785                     $record->container_name = $aclContainer->name;
786                 }
787             }
788         }
789     }
790     
791     /**
792      * copy nodes
793      * 
794      * @param array $_sourceFilenames array->multiple
795      * @param string|array $_destinationFilenames string->singlefile OR directory, array->multiple files
796      * @param boolean $_forceOverwrite
797      * @return Tinebase_Record_RecordSet of Tinebase_Model_Tree_Node
798      */
799     public function copyNodes($_sourceFilenames, $_destinationFilenames, $_forceOverwrite = FALSE)
800     {
801         return $this->_copyOrMoveNodes($_sourceFilenames, $_destinationFilenames, 'copy', $_forceOverwrite);
802     }
803     
804     /**
805      * copy or move an array of nodes identified by their path
806      * 
807      * @param array $_sourceFilenames array->multiple
808      * @param string|array $_destinationFilenames string->singlefile OR directory, array->multiple files
809      * @param string $_action copy|move
810      * @param boolean $_forceOverwrite
811      * @return Tinebase_Record_RecordSet of Tinebase_Model_Tree_Node
812      */
813     protected function _copyOrMoveNodes($_sourceFilenames, $_destinationFilenames, $_action, $_forceOverwrite = FALSE)
814     {
815         $result = new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node');
816         $nodeExistsException = NULL;
817         
818         foreach ($_sourceFilenames as $idx => $source) {
819             $sourcePathRecord = Tinebase_Model_Tree_Node_Path::createFromPath($this->addBasePath($source));
820             $destinationPathRecord = $this->_getDestinationPath($_destinationFilenames, $idx, $sourcePathRecord);
821             
822             if ($this->_backend->fileExists($destinationPathRecord->statpath) && $sourcePathRecord->flatpath == $destinationPathRecord->flatpath) {
823                 throw new Filemanager_Exception_DestinationIsSameNode();
824             }
825             
826             // test if destination is subfolder of source
827             $dest = explode('/', $destinationPathRecord->statpath);
828             $source = explode('/', $sourcePathRecord->statpath);
829             $isSub = TRUE;
830             
831             for ($i = 0; $i < count($source); $i++) {
832                 
833                 if (! isset($dest[$i])) {
834                     break;
835                 }
836                 
837                 if ($source[$i] != $dest[$i]) {
838                     $isSub = FALSE;
839                 }
840             }
841             if ($isSub) {
842                 throw new Filemanager_Exception_DestinationIsOwnChild();
843             }
844             
845             try {
846                 if ($_action === 'move') {
847                     $node = $this->_moveNode($sourcePathRecord, $destinationPathRecord, $_forceOverwrite);
848                 } else if ($_action === 'copy') {
849                     $node = $this->_copyNode($sourcePathRecord, $destinationPathRecord, $_forceOverwrite);
850                 }
851                 $result->addRecord($node);
852             } catch (Filemanager_Exception_NodeExists $fene) {
853                 $nodeExistsException = $this->_handleNodeExistsException($fene, $nodeExistsException);
854             }
855         }
856         
857         $this->resolveContainerAndAddPath($result, $destinationPathRecord->getParent());
858         
859         if ($nodeExistsException) {
860             // @todo add correctly moved/copied files here?
861             throw $nodeExistsException;
862         }
863         
864         return $result;
865     }
866     
867     /**
868      * get single destination from an array of destinations and an index + $_sourcePathRecord
869      * 
870      * @param string|array $_destinationFilenames
871      * @param int $_idx
872      * @param Tinebase_Model_Tree_Node_Path $_sourcePathRecord
873      * @return Tinebase_Model_Tree_Node_Path
874      * @throws Filemanager_Exception
875      * 
876      * @todo add Tinebase_FileSystem::isDir() check?
877      */
878     protected function _getDestinationPath($_destinationFilenames, $_idx, $_sourcePathRecord)
879     {
880         if (is_array($_destinationFilenames)) {
881             $isdir = FALSE;
882             if (isset($_destinationFilenames[$_idx])) {
883                 $destination = $_destinationFilenames[$_idx];
884             } else {
885                 throw new Filemanager_Exception('No destination path found.');
886             }
887         } else {
888             $isdir = TRUE;
889             $destination = $_destinationFilenames;
890         }
891         
892         if ($isdir) {
893             $destination = $destination . '/' . $_sourcePathRecord->name;
894         }
895         
896         return Tinebase_Model_Tree_Node_Path::createFromPath($this->addBasePath($destination));
897     }
898     
899     /**
900      * copy single node
901      * 
902      * @param Tinebase_Model_Tree_Node_Path $_source
903      * @param Tinebase_Model_Tree_Node_Path $_destination
904      * @param boolean $_forceOverwrite
905      * @return Tinebase_Model_Tree_Node
906      */
907     protected function _copyNode(Tinebase_Model_Tree_Node_Path $_source, Tinebase_Model_Tree_Node_Path $_destination, $_forceOverwrite = FALSE)
908     {
909         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
910             . ' Copy Node ' . $_source->flatpath . ' to ' . $_destination->flatpath);
911                 
912         $newNode = NULL;
913         
914         $this->_checkPathACL($_source, 'get', FALSE);
915         
916         $sourceNode = $this->_backend->stat($_source->statpath);
917         
918         switch ($sourceNode->type) {
919             case Tinebase_Model_Tree_Node::TYPE_FILE:
920                 $newNode = $this->_copyOrMoveFileNode($_source, $_destination, 'copy', $_forceOverwrite);
921                 break;
922             case Tinebase_Model_Tree_Node::TYPE_FOLDER:
923                 $newNode = $this->_copyFolderNode($_source, $_destination);
924                 break;
925         }
926         
927         return $newNode;
928     }
929     
930     /**
931      * copy file node
932      * 
933      * @param Tinebase_Model_Tree_Node_Path $_source
934      * @param Tinebase_Model_Tree_Node_Path $_destination
935      * @param string $_action
936      * @param boolean $_forceOverwrite
937      * @return Tinebase_Model_Tree_Node
938      */
939     protected function _copyOrMoveFileNode(Tinebase_Model_Tree_Node_Path $_source, Tinebase_Model_Tree_Node_Path $_destination, $_action, $_forceOverwrite = FALSE)
940     {
941         $this->_checkPathACL($_destination->getParent(), 'update', FALSE);
942         
943         try {
944             $this->_checkIfExists($_destination);
945         } catch (Filemanager_Exception_NodeExists $fene) {
946             if ($_forceOverwrite && $_source->statpath !== $_destination->statpath) {
947                 // delete old node
948                 $this->_backend->unlink($_destination->statpath);
949             } elseif (! $_forceOverwrite) {
950                 throw $fene;
951             }
952         }
953         
954         switch ($_action) {
955             case 'copy':
956                 $newNode = $this->_backend->copy($_source->statpath, $_destination->statpath);
957                 break;
958             case 'move':
959                 $newNode = $this->_backend->rename($_source->statpath, $_destination->statpath);
960                 break;
961         }
962
963         return $newNode;
964     }
965     
966     /**
967      * copy folder node
968      * 
969      * @param Tinebase_Model_Tree_Node_Path $_source
970      * @param Tinebase_Model_Tree_Node_Path $_destination
971      * @return Tinebase_Model_Tree_Node
972      * @throws Filemanager_Exception_NodeExists
973      * 
974      * @todo add $_forceOverwrite?
975      */
976     protected function _copyFolderNode(Tinebase_Model_Tree_Node_Path $_source, Tinebase_Model_Tree_Node_Path $_destination)
977     {
978         $newNode = $this->_createNode($_destination, Tinebase_Model_Tree_Node::TYPE_FOLDER);
979         
980         // recursive copy for (sub-)folders/files
981         $filter = new Tinebase_Model_Tree_Node_Filter(array(array(
982             'field'    => 'path', 
983             'operator' => 'equals', 
984             'value'    => Tinebase_Model_Tree_Node_Path::removeAppIdFromPath(
985                 $_source->flatpath, 
986                 Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName)
987             ),
988         )));
989         $result = $this->search($filter);
990         if (count($result) > 0) {
991             $this->copyNodes($result->path, $newNode->path);
992         }
993         
994         return $newNode;
995     }
996     
997     /**
998      * move nodes
999      * 
1000      * @param array $_sourceFilenames array->multiple
1001      * @param string|array $_destinationFilenames string->singlefile OR directory, array->multiple files
1002      * @param boolean $_forceOverwrite
1003      * @return Tinebase_Record_RecordSet of Tinebase_Model_Tree_Node
1004      */
1005     public function moveNodes($_sourceFilenames, $_destinationFilenames, $_forceOverwrite = FALSE)
1006     {
1007         return $this->_copyOrMoveNodes($_sourceFilenames, $_destinationFilenames, 'move', $_forceOverwrite);
1008     }
1009     
1010     /**
1011      * move single node
1012      * 
1013      * @param Tinebase_Model_Tree_Node_Path $_source
1014      * @param Tinebase_Model_Tree_Node_Path $_destination
1015      * @param boolean $_forceOverwrite
1016      * @return Tinebase_Model_Tree_Node
1017      */
1018     protected function _moveNode(Tinebase_Model_Tree_Node_Path $_source, Tinebase_Model_Tree_Node_Path $_destination, $_forceOverwrite = FALSE)
1019     {
1020         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1021             . ' Move Node ' . $_source->flatpath . ' to ' . $_destination->flatpath);
1022         
1023         $sourceNode = $this->_backend->stat($_source->statpath);
1024         
1025         switch ($sourceNode->type) {
1026             case Tinebase_Model_Tree_Node::TYPE_FILE:
1027                 $movedNode = $this->_copyOrMoveFileNode($_source, $_destination, 'move', $_forceOverwrite);
1028                 break;
1029             case Tinebase_Model_Tree_Node::TYPE_FOLDER:
1030                 $movedNode = $this->_moveFolderNode($_source, $sourceNode, $_destination, $_forceOverwrite);
1031                 break;
1032         }
1033         
1034         return $movedNode;
1035     }
1036     
1037     /**
1038      * move folder node
1039      * 
1040      * @param Tinebase_Model_Tree_Node_Path $source
1041      * @param Tinebase_Model_Tree_Node $sourceNode [unused]
1042      * @param Tinebase_Model_Tree_Node_Path $destination
1043      * @param boolean $_forceOverwrite
1044      * @return Tinebase_Model_Tree_Node
1045      */
1046     protected function _moveFolderNode($source, $sourceNode, $destination, $_forceOverwrite = FALSE)
1047     {
1048         $this->_checkPathACL($source, 'get', FALSE);
1049         
1050         $destinationParentPathRecord = $destination->getParent();
1051         $destinationNodeName = NULL;
1052         
1053         if ($destination->isToplevelPath()) {
1054             $this->_moveFolderContainer($source, $destination, $_forceOverwrite);
1055             $destinationNodeName = $destination->container->getId();
1056         } else {
1057             $this->_checkPathACL($destinationParentPathRecord, 'update');
1058             if ($source->getParent()->flatpath != $destinationParentPathRecord->flatpath) {
1059                 try {
1060                     $this->_checkIfExists($destination);
1061                 } catch (Filemanager_Exception_NodeExists $fene) {
1062                     if ($_forceOverwrite && $source->statpath !== $destination->statpath) {
1063                         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
1064                             . ' Removing folder node ' . $destination->statpath);
1065                         $this->_backend->rmdir($destination->statpath, TRUE);
1066                     } else if (! $_forceOverwrite) {
1067                         throw $fene;
1068                     }
1069                 }
1070             } else {
1071                 if (! $_forceOverwrite) {
1072                     $this->_checkIfExists($destination);
1073                 }
1074             }
1075         }
1076         
1077         // remove source container if it doesn't have the same parent - otherwise a new one will be created
1078         if ($source->isToplevelPath() && $source->getParent()->getId() !== $destination->getParent()->getId()) {
1079             Tinebase_Container::getInstance()->deleteContainer($source->container->getId());
1080         }
1081         
1082         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
1083             . ' Rename Folder ' . $source->statpath . ' -> ' . $destination->statpath);
1084         
1085         $this->_backend->rename($source->statpath, $destination->statpath);
1086         
1087         $movedNode = $this->_backend->stat($destination->statpath);
1088         if ($destinationNodeName !== NULL) {
1089             $movedNode->name = $destinationNodeName;
1090         }
1091         
1092         return $movedNode;
1093     }
1094     
1095     /**
1096      * move folder container
1097      * 
1098      * @param Tinebase_Model_Tree_Node_Path $source
1099      * @param Tinebase_Model_Tree_Node_Path $destination
1100      * @param boolean $forceOverwrite
1101      * @return Tinebase_Model_Tree_Node
1102      */
1103     protected function _moveFolderContainer($source, $destination, $forceOverwrite = FALSE)
1104     {
1105         if ($source->isToplevelPath()) {
1106             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1107                 . ' Moving container ' . $source->container->name . ' to ' . $destination->flatpath);
1108         
1109             $this->_checkACLContainer($source->container, 'update');
1110             
1111             $container = $source->container;
1112             if ($container->name !== $destination->name) {
1113                 try {
1114                     $existingContainer = Tinebase_Container::getInstance()->getContainerByName(
1115                         $this->_applicationName,
1116                         $destination->name,
1117                         $destination->containerType,
1118                         Tinebase_Core::getUser()
1119                     );
1120                     if (! $forceOverwrite) {
1121                         $fene = new Filemanager_Exception_NodeExists('container exists');
1122                         $fene->addExistingNodeInfo($this->_backend->stat($destination->statpath));
1123                         throw $fene;
1124                     } else {
1125                         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
1126                             . ' Removing existing folder node and container ' . $destination->flatpath);
1127                         $this->_backend->rmdir($destination->statpath, TRUE);
1128                     }
1129                 } catch (Tinebase_Exception_NotFound $tenf) {
1130                     // ok
1131                 }
1132                 
1133                 $container->name = $destination->name;
1134                 $container = Tinebase_Container::getInstance()->update($container);
1135             }
1136         } else {
1137             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1138                 . ' Creating container ' . $destination->name);
1139             $container = $this->_createContainer($destination->name, $destination->containerType);
1140         }
1141         
1142         $destination->setContainer($container);
1143     }
1144     
1145     /**
1146      * delete nodes
1147      * 
1148      * @param array $_filenames string->single file, array->multiple
1149      * @return int delete count
1150      * 
1151      * @todo add recursive param?
1152      */
1153     public function deleteNodes($_filenames)
1154     {
1155         $deleteCount = 0;
1156         foreach ($_filenames as $filename) {
1157             if ($this->_deleteNode($filename)) {
1158                 $deleteCount++;
1159             }
1160         }
1161         
1162         return $deleteCount;
1163     }
1164
1165     /**
1166      * delete node
1167      * 
1168      * @param string $_flatpath
1169      * @return boolean
1170      * @throws Tinebase_Exception_NotFound
1171      */
1172     protected function _deleteNode($_flatpath)
1173     {
1174         $flatpathWithBasepath = $this->addBasePath($_flatpath);
1175         list($parentPathRecord, $nodeName) = Tinebase_Model_Tree_Node_Path::getParentAndChild($flatpathWithBasepath);
1176         $pathRecord = Tinebase_Model_Tree_Node_Path::createFromPath($flatpathWithBasepath);
1177         
1178         $this->_checkPathACL($parentPathRecord, 'delete');
1179         
1180         if (! $parentPathRecord->container) {
1181             // check acl for deleting toplevel container
1182             $this->_checkPathACL($pathRecord, 'delete');
1183         }
1184         
1185         $success = $this->_deleteNodeInBackend($pathRecord);
1186         
1187         if ($success && ! $parentPathRecord->container) {
1188             
1189             if (! is_object($pathRecord->container)) {
1190                 throw new Tinebase_Exception_NotFound('Container not found');
1191             }
1192             
1193             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1194                 . ' Delete container ' . $pathRecord->container->name);
1195             Tinebase_Container::getInstance()->delete($pathRecord->container->getId());
1196         }
1197         
1198         return $success;
1199     }
1200     
1201     /**
1202      * delete node in backend
1203      * 
1204      * @param Tinebase_Model_Tree_Node_Path $_path
1205      * @return boolean
1206      */
1207     protected function _deleteNodeInBackend(Tinebase_Model_Tree_Node_Path $_path)
1208     {
1209         $success = FALSE;
1210         
1211         $node = $this->_backend->stat($_path->statpath);
1212         
1213         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . 
1214             ' Removing path ' . $_path->flatpath . ' of type ' . $node->type);
1215         
1216         switch ($node->type) {
1217             case Tinebase_Model_Tree_Node::TYPE_FILE:
1218                 $success = $this->_backend->unlink($_path->statpath);
1219                 break;
1220             case Tinebase_Model_Tree_Node::TYPE_FOLDER:
1221                 $success = $this->_backend->rmdir($_path->statpath, TRUE);
1222                 break;
1223         }
1224         
1225         return $success;
1226     }
1227     
1228     /**
1229      * Deletes a set of records.
1230      *
1231      * If one of the records could not be deleted, no record is deleted
1232      *
1233      * @param   array array of record identifiers
1234      * @return  Tinebase_Record_RecordSet
1235      */
1236     public function delete($_ids)
1237     {
1238         $nodes = $this->getMultiple($_ids);
1239         foreach ($nodes as $node) {
1240             if ($this->_checkACLContainer($this->_backend->getNodeContainer($node->getId()), 'delete')) {
1241                 $this->_backend->deleteFileNode($node);
1242             } else {
1243                 $nodes->removeRecord($node);
1244             }
1245         }
1246         
1247         return $nodes;
1248     }
1249 }