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