0012932: file picker dialog in fileuploadgrid
[tine20] / tine20 / Tinebase / FileSystem / RecordAttachments.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Tinebase
6  * @subpackage  Filesystem
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) 2013-2016 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  */
12
13 /**
14  * filesystem attachments for records
15  *
16  * @package     Tinebase
17  * @subpackage  Filesystem
18  */
19 class Tinebase_FileSystem_RecordAttachments
20 {
21     /**
22      * filesystem controller
23      * 
24      * @var Tinebase_FileSystem
25      */
26     protected $_fsController = NULL;
27     
28     /**
29      * holds the instance of the singleton
30      *
31      * @var Tinebase_FileSystem_RecordAttachments
32      */
33     private static $_instance = NULL;
34     
35     /**
36      * the constructor
37      */
38     public function __construct() 
39     {
40         $this->_fsController  = Tinebase_FileSystem::getInstance();
41     }
42     
43     /**
44      * the singleton pattern
45      *
46      * @return Tinebase_FileSystem_RecordAttachments
47      */
48     public static function getInstance() 
49     {
50         if (self::$_instance === NULL) {
51             self::$_instance = new Tinebase_FileSystem_RecordAttachments();
52         }
53         
54         return self::$_instance;
55     }
56     
57     /**
58      * fetch all file attachments of a record
59      * 
60      * @param Tinebase_Record_Interface $record
61      * @return Tinebase_Record_RecordSet of Tinebase_Model_Tree_Node
62      */
63     public function getRecordAttachments(Tinebase_Record_Interface $record)
64     {
65         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
66             ' Fetching attachments of ' . get_class($record) . ' record with id ' . $record->getId() . ' ...');
67         
68         $parentPath = $this->getRecordAttachmentPath($record);
69         
70         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
71             ' Looking in path ' . $parentPath);
72         
73         try {
74             $record->attachments = $this->_fsController->scanDir($parentPath);
75         } catch (Tinebase_Exception_NotFound $tenf) {
76             $record->attachments = new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node');
77         }
78         
79         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG) && count($record->attachments) > 0) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
80             ' Found ' . count($record->attachments) . ' attachment(s).');
81         
82         return $record->attachments;
83     }
84     
85     /**
86      * fetches attachments for multiple records at once
87      * 
88      * @param Tinebase_Record_RecordSet $records
89      * 
90      * @todo maybe this should be improved
91      */
92     public function getMultipleAttachmentsOfRecords($records)
93     {
94         if ($records instanceof Tinebase_Record_Abstract) {
95             $records = new Tinebase_Record_RecordSet(get_class($records), array($records));
96         }
97
98         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
99             ' Fetching attachments for ' . count($records) . ' record(s)');
100         
101         $parentNodes       = new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node');
102         $recordNodeMapping = array();
103         $typeMap           = array();
104         
105         foreach ($records as $record) {
106             $typeMap[get_class($record)][] = $record->getId();
107             
108             $record->attachments = new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node');
109         }
110         
111         foreach ($typeMap as $className => $recordIds) {
112             $classPathName = $this->_fsController->getApplicationBasePath($record->getApplication(), Tinebase_FileSystem::FOLDER_TYPE_RECORDS) 
113                           . '/' . $className;
114             
115             // top folder for record attachments
116             try {
117                 $classPathNode = $this->_fsController->stat($classPathName);
118             } catch (Tinebase_Exception_NotFound $tenf) {
119                 continue;
120             }
121             
122             // subfolders for all records attachments
123             $searchFilter = new Tinebase_Model_Tree_Node_Filter(array(
124                 array(
125                     'field'     => 'parent_id',
126                     'operator'  => 'equals',
127                     'value'     => $classPathNode->getId()
128                 ),
129                 array(
130                     'field'     => 'name',
131                     'operator'  => 'in',
132                     'value'     => $recordIds
133                 )
134             ));
135             $recordNodes = $this->_fsController->searchNodes($searchFilter);
136             if ($recordNodes->count() === 0) {
137                 // nothing to be done 
138                 continue;
139             }
140             foreach ($recordNodes as $recordNode) {
141                 $recordNodeMapping[$recordNode->getId()] = $recordNode->name;
142             }
143             
144             // get attachments
145             $attachmentNodes = $this->_fsController->getTreeNodeChildren($recordNodes);
146             
147             // add attachments to records
148             foreach ($attachmentNodes as $attachmentNode) {
149                 $record = $records->getById($recordNodeMapping[$attachmentNode->parent_id]);
150                 
151                 $record->attachments->addRecord($attachmentNode);
152             }
153         }
154     }
155     
156     /**
157      * set file attachments of a record
158      * 
159      * @param Tinebase_Record_Interface $record
160      */
161     public function setRecordAttachments(Tinebase_Record_Interface $record)
162     {
163         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
164             ' Record: ' . print_r($record->toArray(), TRUE));
165         
166         $currentAttachments = ($record->getId()) ? $this->getRecordAttachments(clone $record) : new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node');
167         $attachmentsToSet = ($record->attachments instanceof Tinebase_Record_RecordSet) 
168             ? $record->attachments
169             : new Tinebase_Record_RecordSet('Tinebase_Model_Tree_Node', (array)$record->attachments, TRUE);
170         
171         $attachmentDiff = $currentAttachments->diff($attachmentsToSet);
172         
173         foreach ($attachmentDiff->added as $added) {
174             try {
175                 $this->addRecordAttachment($record, $added->name, $added);
176             } catch (Tinebase_Exception_InvalidArgument $teia) {
177                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
178                     ' Could not add new attachment ' . print_r($added->toArray(), TRUE) . ' to record: ' . print_r($record->toArray(), TRUE));
179                 Tinebase_Exception::log($teia);
180             } catch (Tinebase_Exception_NotFound $tenf) {
181                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
182                     ' Could not add new attachment ' . print_r($added->toArray(), TRUE) . ' to record: ' . print_r($record->toArray(), TRUE));
183                 Tinebase_Exception::log($tenf);
184             }
185         }
186         
187         foreach ($attachmentDiff->removed as $removed) {
188             $this->_fsController->deleteFileNode($removed);
189         }
190         
191         foreach ($attachmentDiff->modified as $modified) {
192             $this->_fsController->update($attachmentsToSet->getById($modified->getId()));
193         }
194     }
195     
196     /**
197      * add attachement to record
198      * 
199      * @param  Tinebase_Record_Abstract $record
200      * @param  string $name
201      * @param  mixed $attachment
202          @see Tinebase_FileSystem::copyTempfile
203      * @return null|Tinebase_Model_Tree_Node
204      */
205     public function addRecordAttachment(Tinebase_Record_Abstract $record, $name, $attachment)
206     {
207         // only occurs via unittests
208         if (!$name && isset($attachment->tempFile) && ! is_resource($attachment->tempFile)) {
209             $attachment = Tinebase_TempFile::getInstance()->getTempFile($attachment->tempFile);
210             $name = $attachment->name;
211         }
212
213         // If there is no tempfile, the attachment was added from the filemanager
214         if ($attachment instanceof Tinebase_Model_Tree_Node && !isset($attachment->tempFile) && isset($attachment->path)) {
215             return $this->addRecordAttachmentFromFilemanager($record, $attachment);
216         }
217
218         if ($attachment instanceof Tinebase_Model_Tree_Node && empty($name)) {
219             $name = $attachment->name;
220         }
221
222         if (empty($name)) {
223             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
224                 ' Could not evaluate attachment name.');
225             return null;
226         }
227         
228         $attachmentsDir = $this->getRecordAttachmentPath($record, TRUE);
229         $attachmentPath = $attachmentsDir . '/' . $name;
230         
231         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
232             ' Creating new record attachment ' . $attachmentPath);
233         if ($this->_fsController->fileExists($attachmentPath)) {
234             throw new Tinebase_Exception_InvalidArgument('File already exists');
235         }
236         
237         $this->_fsController->copyTempfile($attachment, $attachmentPath);
238         
239         $node = $this->_fsController->stat($attachmentPath);
240         return $node;
241     }
242
243     /**
244      * Add a filemanager attachment to a given record
245      *
246      * @param Tinebase_Record_Abstract $record
247      * @param Tinebase_Model_Tree_Node $attachment
248      * @return null|Tinebase_Model_Tree_Node
249      */
250     public function addRecordAttachmentFromFilemanager(Tinebase_Record_Abstract $record, Tinebase_Model_Tree_Node $attachment) {
251         if (!$attachment->path || !$attachment->name) {
252             return null;
253         }
254
255         $attachmentsDir = $this->getRecordAttachmentPath($record, true);
256         $attachmentPath = $attachmentsDir . '/' . $attachment->name;
257
258         $nodeController = Filemanager_Controller_Node::getInstance();
259         $path = Tinebase_Model_Tree_Node_Path::createFromPath($nodeController->addBasePath($attachment->path));
260         $attachment = $this->_fsController->copy($path->statpath, $attachmentPath);
261
262         return $attachment;
263     }
264     
265     /**
266      * delete attachments of record
267      * 
268      * @param Tinebase_Record_Abstract $record
269      */
270     public function deleteRecordAttachments($record)
271     {
272         $attachments = ($record->attachments instanceof Tinebase_Record_RecordSet) ? $record->attachments : $this->getRecordAttachments($record);
273         foreach ($attachments as $node) {
274             $this->_fsController->deleteFileNode($node);
275         }
276     }
277     
278     /**
279      * get path for record attachments
280      * 
281      * @param Tinebase_Record_Abstract $record
282      * @param boolean $createDirIfNotExists
283      * @throws Tinebase_Exception_InvalidArgument
284      * @return string
285      */
286     public function getRecordAttachmentPath(Tinebase_Record_Abstract $record, $createDirIfNotExists = FALSE)
287     {
288         if (! $record->getId()) {
289             throw new Tinebase_Exception_InvalidArgument('record needs an identifier');
290         }
291         
292         $parentPath = $this->_fsController->getApplicationBasePath($record->getApplication(), Tinebase_FileSystem::FOLDER_TYPE_RECORDS);
293         $recordPath = $parentPath . '/' . get_class($record) . '/' . $record->getId();
294         if ($createDirIfNotExists && ! $this->_fsController->fileExists($recordPath)) {
295             $this->_fsController->mkdir($recordPath);
296         }
297         
298         return $recordPath;
299     }
300 }