ea9151d473870cfb2d0163a3e0f0c6f90207483a
[tine20] / tine20 / Tinebase / Model / Tree / Node / Path.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Model
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 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  * @todo 0007376: Tinebase_FileSystem / Node model refactoring: move all container related functionality to Filemanager
12  */
13
14 /**
15  * class representing one node path
16  * 
17  * @package     Tinebase
18  * @subpackage  Model
19  * 
20  * @property    string                      containerType
21  * @property    string                      containerOwner
22  * @property    string                      flatpath
23  * @property    string                      statpath
24  * @property    string                      realpath           path without app/type/container stuff 
25  * @property    string                      streamwrapperpath
26  * @property    Tinebase_Model_Application  application
27  * @property    Tinebase_Model_Container    container
28  * @property    Tinebase_Model_FullUser     user
29  * @property    string                      name (last part of path)
30  * @property    Tinebase_Model_Tree_Node_Path parentrecord
31  * 
32  * @todo rename this to Tinebase_Model_Tree_Node_FoldersPath ?
33  * 
34  * exploded flat path should look like this:
35  * 
36  * [0] => app id [required]
37  * [1] => folders [required]
38  * [2] => type [required] (personal|shared)
39  * [3] => container | accountLoginName
40  * [4] => container | directory
41  * [5] => directory
42  * [6] => directory
43  * [...]
44  */
45 class Tinebase_Model_Tree_Node_Path extends Tinebase_Record_Abstract
46 {
47     /**
48      * streamwrapper path prefix
49      */
50     const STREAMWRAPPERPREFIX = 'tine20://';
51     
52     /**
53      * root type
54      */
55     const TYPE_ROOT = 'root';
56     
57     /**
58      * key in $_validators/$_properties array for the field which 
59      * represents the identifier
60      * 
61      * @var string
62      */
63     protected $_identifier = 'flatpath';
64     
65     /**
66      * application the record belongs to
67      *
68      * @var string
69      */
70     protected $_application = 'Tinebase';
71     
72     /**
73      * list of zend validator
74      * 
75      * this validators get used when validating user generated content with Zend_Input_Filter
76      *
77      * @var array
78      */
79     protected $_validators = array (
80         'containerType'     => array(Zend_Filter_Input::ALLOW_EMPTY => true),
81         'containerOwner'    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
82         'flatpath'          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
83         'statpath'          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
84         'realpath'          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
85         'streamwrapperpath' => array(Zend_Filter_Input::ALLOW_EMPTY => true),
86         'application'       => array(Zend_Filter_Input::ALLOW_EMPTY => true),
87         'container'         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
88         'user'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
89         'name'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
90         'parentrecord'      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
91     );
92     
93     /**
94      * (non-PHPdoc)
95      * @see Tinebase/Record/Tinebase_Record_Abstract::__toString()
96      */
97     public function __toString()
98     {
99         return $this->flatpath;
100     }
101     
102     /**
103      * create new path record from given path string
104      * 
105      * @param string|Tinebase_Model_Tree_Node_Path $_path
106      * @return Tinebase_Model_Tree_Node_Path
107      */
108     public static function createFromPath($_path)
109     {
110         $pathRecord = ($_path instanceof Tinebase_Model_Tree_Node_Path) ? $_path : new Tinebase_Model_Tree_Node_Path(array(
111             'flatpath'  => $_path
112         ));
113         
114         return $pathRecord;
115     }
116     
117     /**
118      * create new parent path record from given path string
119      * 
120      * @param string $_path
121      * @return array with (Tinebase_Model_Tree_Node_Path, string)
122      * 
123      * @todo add child to model?
124      */
125     public static function getParentAndChild($_path)
126     {
127         $pathParts = $pathParts = explode('/', trim($_path, '/'));
128         $child = array_pop($pathParts);
129         
130         $pathRecord = new Tinebase_Model_Tree_Node_Path(array(
131             'flatpath'  => '/' . implode('/', $pathParts)
132         ));
133         
134         return array(
135             $pathRecord,
136             $child
137         );
138     }
139     
140     /**
141      * removes app id (and /folders namespace) from a path
142      * 
143      * @param string $_flatpath
144      * @param Tinebase_Model_Application|string $_application
145      * @return string
146      */
147     public static function removeAppIdFromPath($_flatpath, $_application)
148     {
149         $appId = (is_string($_application)) ? Tinebase_Application::getInstance()->getApplicationById($_application)->getId() : $_application->getId();
150         return preg_replace('@^/' . $appId . '/folders@', '', $_flatpath);
151     }
152     
153     /**
154      * get parent path of this record
155      * 
156      * @return Tinebase_Model_Tree_Node_Path
157      */
158     public function getParent()
159     {
160         if (! $this->parentrecord) {
161             list($this->parentrecord, $unused) = self::getParentAndChild($this->flatpath);
162         }
163         return $this->parentrecord;
164     }
165     
166     /**
167      * sets the record related properties from user generated input.
168      * 
169      * if flatpath is set, parse it and set the fields accordingly
170      *
171      * @param array $_data            the new data to set
172      */
173     public function setFromArray(array $_data)
174     {
175         parent::setFromArray($_data);
176         
177         if ((isset($_data['flatpath']) || array_key_exists('flatpath', $_data))) {
178             $this->_parsePath($_data['flatpath']);
179         }
180     }
181     
182     /**
183      * parse given path: check validity, set container type, do replacements
184      * 
185      * @param string $_path
186      */
187     protected function _parsePath($_path = NULL)
188     {
189         if ($_path === NULL) {
190             $_path = $this->flatpath;
191         }
192         
193         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
194             . ' Parsing path: ' . $_path);
195         
196         $pathParts = $this->_getPathParts($_path);
197         
198         $this->name                 = $pathParts[count($pathParts) - 1];
199         $this->containerType        = $this->_getContainerType($pathParts);
200         $this->containerOwner       = $this->_getContainerOwner($pathParts);
201         $this->application          = $this->_getApplication($pathParts);
202         $this->container            = $this->_getContainer($pathParts);
203         $this->statpath             = $this->_getStatPath($pathParts);
204         $this->realpath             = $this->_getRealPath($pathParts);
205         $this->streamwrapperpath    = self::STREAMWRAPPERPREFIX . $this->statpath;
206     }
207     
208     /**
209      * get path parts
210      * 
211      * @param string $_path
212      * @return array
213      * @throws Tinebase_Exception_InvalidArgument
214      */
215     protected function _getPathParts($_path = NULL)
216     {
217         if ($_path === NULL) {
218             $_path = $this->flatpath;
219         }
220         if (! is_string($_path)) {
221             throw new Tinebase_Exception_InvalidArgument('Path needs to be a string!');
222         }
223         $pathParts = explode('/', trim($_path, '/'));
224         if (count($pathParts) < 1) {
225             throw new Tinebase_Exception_InvalidArgument('Invalid path: ' . $_path);
226         }
227         
228         return $pathParts;
229     }
230     
231     /**
232      * get container type from path
233      * 
234      * @param array $_pathParts
235      * @return string
236      * @throws Tinebase_Exception_InvalidArgument
237      */
238     protected function _getContainerType($_pathParts)
239     {
240         $containerType = (isset($_pathParts[2])) ? $_pathParts[2] : self::TYPE_ROOT;
241         
242         if (! in_array($containerType, array(
243             Tinebase_Model_Container::TYPE_PERSONAL,
244             Tinebase_Model_Container::TYPE_SHARED,
245             self::TYPE_ROOT
246         ))) {
247             throw new Tinebase_Exception_InvalidArgument('Invalid type: ' . $containerType);
248         }
249         
250         return $containerType;
251     }
252     
253     /**
254      * get container owner from path
255      * 
256      * @param array $_pathParts
257      * @return string
258      */
259     protected function _getContainerOwner($_pathParts)
260     {
261         $containerOwner = ($this->containerType !== Tinebase_Model_Container::TYPE_SHARED && isset($_pathParts[3])) ? $_pathParts[3] : NULL;
262         
263         return $containerOwner;
264     }
265     
266     /**
267      * get application from path
268      * 
269      * @param array $_pathParts
270      * @return string
271      * @throws Tinebase_Exception_AccessDenied
272      */
273     protected function _getApplication($_pathParts)
274     {
275         $application = Tinebase_Application::getInstance()->getApplicationById($_pathParts[0]);
276         
277         return $application;
278     }
279     
280     /**
281      * get container from path
282      * 
283      * @param array $_pathParts
284      * @return Tinebase_Model_Container
285      */
286     protected function _getContainer($_pathParts)
287     {
288         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . 
289             ' PATH PARTS: ' . print_r($_pathParts, true));
290         
291         $container = NULL;
292         
293         try {
294             switch ($this->containerType) {
295                 case Tinebase_Model_Container::TYPE_SHARED:
296                     if (!empty($_pathParts[3])) {
297                         $container = Tinebase_Container::getInstance()->getContainerByName(
298                             $this->application->name, $_pathParts[3], Tinebase_Model_Container::TYPE_SHARED);
299                     }
300                     break;
301                     
302                 case Tinebase_Model_Container::TYPE_PERSONAL:
303                     if (count($_pathParts) > 4) {
304                         $subPathParts = explode('/', $_pathParts[4], 2);
305                         $owner = ($this->containerOwner) ? Tinebase_User::getInstance()->getUserByLoginName($this->containerOwner) : Tinebase_Core::getUser();
306                         $container = Tinebase_Container::getInstance()->getContainerByName(
307                             $this->application->name, $subPathParts[0], Tinebase_Model_Container::TYPE_PERSONAL, $owner->getId());
308                     }
309                     break;
310             }
311         } catch (Tinebase_Exception_NotFound $tenf) {
312             // container not found
313         }
314         
315         return $container;
316     }
317     
318     /**
319      * do path replacements (container name => container id, account name => account id)
320      * 
321      * @param array $pathParts
322      * @return string
323      */
324     protected function _getStatPath($pathParts = NULL)
325     {
326         if ($pathParts === NULL) {
327             $pathParts = array(
328                 $this->application->getId(),
329                 'folders',
330                 $this->containerType,
331             );
332             
333             if ($this->containerOwner) {
334                 $pathParts[] = $this->containerOwner;
335             }
336             
337             if ($this->container) {
338                 $pathParts[] = $this->container->name;
339             }
340             
341             if ($this->realpath) {
342                 $pathParts += explode('/', $this->realpath);
343             }
344             $this->flatpath = '/' . implode('/', $pathParts);
345         }
346         $result = $this->_createStatPathFromParts($pathParts);
347         
348         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
349             . ' Path to stat: ' . $result);
350         
351         return $result;
352     }
353     
354     /**
355      * create stat path from path parts
356      * 
357      * @param array $pathParts
358      * @return string
359      */
360     protected function _createStatPathFromParts($pathParts)
361     {
362         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($pathParts, TRUE));
363         
364         if (count($pathParts) > 3) {
365             // replace account login name with id
366             if ($this->containerOwner) {
367                 try {
368                     $pathParts[3] = Tinebase_User::getInstance()->getFullUserByLoginName($this->containerOwner)->getId();
369                 } catch (Tinebase_Exception_NotFound $tenf) {
370                     // try again with id
371                     $user = Tinebase_User::getInstance()->getFullUserById($this->containerOwner);
372                     $pathParts[3] = $user->getId();
373                     $this->containerOwner = $user->accountLoginName;
374                 }
375             }
376         
377             // replace container name with id
378             $containerPartIdx = ($this->containerType === Tinebase_Model_Container::TYPE_SHARED) ? 3 : 4;
379             if (isset($pathParts[$containerPartIdx]) && $this->container && $pathParts[$containerPartIdx] === $this->container->name) {
380                 $pathParts[$containerPartIdx] = $this->container->getId();
381             }
382         }
383         
384         $result = '/' . implode('/', $pathParts);
385         return $result;
386     }
387     
388     /**
389      * get real path
390      * 
391      * @param array $pathParts
392      * @return NULL|string
393      */
394     protected function _getRealPath($pathParts)
395     {
396         $result = NULL;
397         $firstRealPartIdx = ($this->containerType === Tinebase_Model_Container::TYPE_SHARED) ? 4 : 5;
398         if (isset($pathParts[$firstRealPartIdx])) {
399             $result = implode('/', array_slice($pathParts, $firstRealPartIdx));
400         }
401         
402         return $result;
403     }
404     
405     /**
406      * check if this path has a matching container (toplevel path) 
407      * 
408      * @return boolean
409      */
410     public function isToplevelPath()
411     {
412         return (! $this->getParent()->container instanceof Tinebase_Model_Container);
413     }
414     
415     /**
416      * set new container / statpath has to be reset
417      * 
418      * @param Tinebase_Model_Container $container
419      */
420     public function setContainer($container)
421     {
422         $this->container            = $container;
423         $this->containerType        = $container->type;
424         $ownerAccountId             = Tinebase_Container::getInstance()->getContainerOwner($container);
425         if ($ownerAccountId) {
426             $this->containerOwner = Tinebase_User::getInstance()->getFullUserById($ownerAccountId)->accountLoginName;
427         } else if ($this->containerType === Tinebase_Model_Container::TYPE_PERSONAL) {
428             throw new Tinebase_Exception_InvalidArgument('Personal container needs an owner!');
429         } else {
430             $this->containerOwner = NULL;
431         }
432         
433         $this->statpath             = $this->_getStatPath();
434         $this->streamwrapperpath    = self::STREAMWRAPPERPREFIX . $this->statpath;
435     }
436
437     /**
438      * validate node/container existance
439      * 
440      * @throws Tinebase_Exception_NotFound
441      */
442     public function validateExistance()
443     {
444         if (! $this->containerType || ! $this->statpath) {
445             $this->_parsePath();
446         }
447         
448         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
449             . ' Validate statpath: ' . $this->statpath);
450         
451         $pathParts = $this->_getPathParts();
452         if (! $this->container) {
453             $containerPart = ($this->containerType === Tinebase_Model_Container::TYPE_PERSONAL) ? 5 : 4;
454             if (count($pathParts) >= $containerPart) {
455                 throw new Tinebase_Exception_NotFound('Container not found');
456             }
457         } else if (! Tinebase_FileSystem::getInstance()->fileExists($this->statpath)) {
458              throw new Tinebase_Exception_NotFound('Node not found');
459         }
460     }
461 }