Filemanager no longer has export grant
[tine20] / tine20 / Filemanager / js / Model.js
1 /*
2  * Tine 2.0
3  * 
4  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
5  * @author      Cornelius Weiss <c.weiss@metaways.de>
6  * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8 Ext.ns('Tine.Filemanager.Model');
9     
10 /**
11  * @namespace   Tine.Filemanager.Model
12  * @class       Tine.Filemanager.Model.Node
13  * @extends     Tine.Tinebase.data.Record
14  * Example record definition
15  * 
16  * @author      Cornelius Weiss <c.weiss@metaways.de>
17  */
18 Tine.Filemanager.Model.Node = Tine.Tinebase.data.Record.create(Tine.Tinebase.Model.Tree_NodeArray, {
19     appName: 'Filemanager',
20     modelName: 'Node',
21     idProperty: 'id',
22     titleProperty: 'name',
23     // ngettext('File', 'Files', n); gettext('File');
24     recordName: 'File',
25     recordsName: 'Files',
26     // ngettext('Folder', 'Folders', n); gettext('Folder');
27     containerName: 'Folder',
28     containersName: 'Folders',
29
30     
31     isDropFilesAllowed: function() {
32         var grants = this.get('account_grants');
33         if(!grants) {
34             return false;
35         }
36         else if(!grants.addGrant) {
37             return false;
38         }
39         return true;
40     },
41
42     /**
43      * virtual nodes are part of the tree but don't exists / are editable
44      *
45      * NOTE: only "real" virtual node is node with path "otherUsers". all other nodes exist
46      *
47      * @returns {boolean}
48      */
49     isVirtual: function() {
50         var _ = window.lodash;
51
52         return _.indexOf(['/', Tine.Tinebase.container.getMyFileNodePath(), '/personal', '/shared'], this.get('path')) >= 0;
53     },
54
55     getSystemLink: function() {
56         var _ = window.lodash,
57             encodedPath = _.map(String(this.get('path')).replace(/(^\/|\/$)/, '').split('/'), Ext.ux.util.urlCoder.encodeURIComponent).join('/');
58
59         return [Tine.Tinebase.common.getUrl().replace(/\/$/, ''), '#/Filemanager/showNode', encodedPath].join('/');
60
61
62     }
63 });
64
65 // register grants for nodes
66 Tine.widgets.container.GrantsManager.register('Filemanager_Model_Node', function(container) {
67     // TODO get default grants and remove export
68     // var grants = Tine.widgets.container.GrantsManager.defaultGrants();
69     //grants.push('download', 'publish');
70     var grants = ['read', 'add', 'edit', 'delete', 'sync', 'download', 'publish'];
71
72     return grants;
73 });
74
75 /**
76  * create Node from File
77  * 
78  * @param {File} file
79  */
80 Tine.Filemanager.Model.Node.createFromFile = function(file) {
81     return new Tine.Filemanager.Model.Node({
82         name: file.name ? file.name : file.fileName,  // safari and chrome use the non std. fileX props
83         size: file.size || 0,
84         type: 'file',
85         contenttype: file.type ? file.type : file.fileType, // missing if safari and chrome 
86         revision: 0
87     });
88 };
89
90 // NOTE: atm the activity records are stored as Tinebase_Model_Tree_Node records
91 Tine.widgets.grid.RendererManager.register('Tinebase', 'Tree_Node', 'revision', function(revision, metadata, record) {
92     revision = parseInt(revision, 10);
93     var revisionString = Tine.Tinebase.appMgr.get('Filemanager').i18n._('Revision') + " " + revision,
94         availableRevisions = record.get('available_revisions');
95
96     return Ext.isArray(availableRevisions) && availableRevisions.indexOf(String(revision)) >= 0 ?
97         '<a href="#"; onclick="Tine.Filemanager.downloadFile(\'' + record.get('path')+ '\',' + revision + '); return false;">' + revisionString + '</a>' :
98         revisionString;
99 });
100
101
102 /**
103  * default ExampleRecord backend
104  */
105 Tine.Filemanager.FileRecordBackend = Ext.extend(Tine.Tinebase.data.RecordProxy, {
106     appName: 'Filemanager',
107     modelName: 'Node',
108     recordClass: Tine.Filemanager.Model.Node,
109     
110     /**
111      * creating folder
112      * 
113      * @param name      folder name
114      * @param options   additional options
115      * @returns
116      */
117     createFolder: function(name, options) {
118         
119         options = options || {};
120         var params = {
121                 application : this.appName,
122                 filename : name,
123                 type : 'folder',
124                 method : this.appName + ".createNode"  
125         };
126         
127         options.params = params;
128         
129         options.beforeSuccess = function(response) {
130             var folder = this.recordReader(response);
131             folder.set('client_access_time', new Date());
132             this.postMessage('create', folder.data);
133             return [folder];
134         };
135
136         return this.doXHTTPRequest(options);
137     },
138     
139     /**
140      * is automatically called in generic GridPanel
141      */
142     saveRecord : function(record, request) {
143         if(record.hasOwnProperty('fileRecord')) {
144             return;
145         } else {
146             Tine.Tinebase.data.RecordProxy.prototype.saveRecord.call(this, record, request);
147         }
148     },
149
150     /**
151      * deleting file or folder
152      *
153      * overridden cause json fe works on filenames / not id's
154      *
155      * @param items     files/folders to delete
156      * @param options   additional options
157      * @returns
158      */
159     deleteItems: function(items, options) {
160         options = options || {};
161
162         var _ = window.lodash,
163             me = this,
164             filenames = new Array(),
165             nodeCount = items.length;
166
167         for(var i=0; i<nodeCount; i++) {
168             filenames.push(items[i].data.path );
169         }
170
171         var params = {
172             application: this.appName,
173             filenames: filenames,
174             method: this.appName + ".deleteNodes",
175             timeout: 300000 // 5 minutes
176         };
177
178         options.params = params;
179
180         options.beforeSuccess = function(response) {
181             var folder = this.recordReader(response);
182             folder.set('client_access_time', new Date());
183             return [folder];
184         };
185
186         // announce delte before server delete to improve ux
187         _.each(items, function(record) {
188             me.postMessage('delete', record.data);
189         });
190
191         return this.doXHTTPRequest(options);
192     },
193     
194     /**
195      * copy/move folder/files to a folder
196      *
197      * @param items files/folders to copy
198      * @param targetPath
199      *
200      * @param move
201      */
202     copyNodes : function(items, target, move, params) {
203         
204         var message = '',
205             app = Tine.Tinebase.appMgr.get(this.appName);
206         
207         if(!params) {
208         
209             if(!target || !items || items.length < 1) {
210                 return false;
211             }
212             
213             var sourceFilenames = new Array(),
214                 destinationFilenames = new Array(),
215                 forceOverwrite = false,
216                 treeIsTarget = false,
217                 targetPath = target;
218             
219             if(target.data) {
220                 targetPath = target.data.path + (target.data.type == 'folder' ? '/' : '');
221             }
222             else if (target.attributes) {
223                 targetPath = target.attributes.path + '/';
224                 treeIsTarget = true;
225             }
226
227             for(var i=0; i<items.length; i++) {
228                 var item = items[i];
229                 var itemData = item.data;
230                 if(!itemData) {
231                     itemData = item.attributes;
232                 }
233                 sourceFilenames.push(itemData.path);
234                 
235                 var itemName = itemData.name;
236                 if(typeof itemName == 'object') {
237                     itemName = itemName.name;
238                 }
239
240                 destinationFilenames.push(targetPath + (targetPath.match(/\/$/) ? itemName : ''));
241             }
242             
243             var method = this.appName + ".copyNodes",
244                 message = app.i18n._('Copying data .. {0}');
245             if(move) {
246                 method = this.appName + ".moveNodes";
247                 message = app.i18n._('Moving data .. {0}');
248             }
249             
250             params = {
251                     application: this.appName,
252                     sourceFilenames: sourceFilenames,
253                     destinationFilenames: destinationFilenames,
254                     forceOverwrite: forceOverwrite,
255                     method: method
256             };
257             
258         } else {
259             message = app.i18n._('Copying data .. {0}');
260             if(params.method == this.appName + '.moveNodes') {
261                 message = app.i18n._('Moving data .. {0}');
262             }
263         }
264         
265         this.loadMask = new Ext.LoadMask(app.getMainScreen().getCenterPanel().getEl(), {msg: String.format(i18n._('Please wait')) + '. ' + String.format(message, '' )});
266         app.getMainScreen().getWestPanel().setDisabled(true);
267         app.getMainScreen().getNorthPanel().setDisabled(true);
268         this.loadMask.show();
269         
270         Ext.Ajax.request({
271             params: params,
272             timeout: 300000, // 5 minutes
273             scope: this,
274             success: function(result, request){
275                 
276                 this.loadMask.hide();
277                 app.getMainScreen().getWestPanel().setDisabled(false);
278                 app.getMainScreen().getNorthPanel().setDisabled(false);
279
280                 // send updates
281                 var _ = window.lodash,
282                     me = this,
283                     recordsData = Ext.util.JSON.decode(result.responseText);
284
285                 _.each(recordsData, function(recordData) {
286                     me.postMessage('update', recordData);
287                 });
288
289
290                 // var nodeData = Ext.util.JSON.decode(result.responseText),
291                 //     treePanel = app.getMainScreen().getWestPanel().getContainerTreePanel(),
292                 //     grid = app.getMainScreen().getCenterPanel();
293                 //
294                 // // Tree refresh
295                 // if(treeIsTarget) {
296                 //
297                 //     for(var i=0; i<items.length; i++) {
298                 //
299                 //         var nodeToCopy = items[i];
300                 //
301                 //         if(nodeToCopy.data && nodeToCopy.data.type !== 'folder') {
302                 //             continue;
303                 //         }
304                 //
305                 //         if(move) {
306                 //             var copiedNode = treePanel.cloneTreeNode(nodeToCopy, target),
307                 //                 nodeToCopyId = nodeToCopy.id,
308                 //                 removeNode = treePanel.getNodeById(nodeToCopyId);
309                 //
310                 //             if(removeNode && removeNode.parentNode) {
311                 //                 removeNode.parentNode.removeChild(removeNode);
312                 //             }
313                 //
314                 //             target.appendChild(copiedNode);
315                 //             copiedNode.setId(nodeData[i].id);
316                 //         }
317                 //         else {
318                 //             var copiedNode = treePanel.cloneTreeNode(nodeToCopy, target);
319                 //             target.appendChild(copiedNode);
320                 //             copiedNode.setId(nodeData[i].id);
321                 //
322                 //         }
323                 //     }
324                 // }
325                 //
326                 // // Grid refresh
327                 // grid.getStore().reload();
328             },
329             failure: function(response, request) {
330                 var nodeData = Ext.util.JSON.decode(response.responseText),
331                     request = Ext.util.JSON.decode(request.jsonData);
332                 
333                 this.loadMask.hide();
334                 app.getMainScreen().getWestPanel().setDisabled(false);
335                 app.getMainScreen().getNorthPanel().setDisabled(false);
336                 
337                 Tine.Filemanager.fileRecordBackend.handleRequestException(nodeData.data, request);
338             }
339         });
340     },
341     
342     /**
343      * upload file 
344      * 
345      * @param {} params Request parameters
346      * @param String uploadKey
347      * @param Boolean addToGridStore 
348      */
349     createNode: function(params, uploadKey, addToGridStore) {
350         var app = Tine.Tinebase.appMgr.get(this.appName),
351             me = this,
352             grid = app.getMainScreen().getCenterPanel(),
353             gridStore = grid.getStore();
354         
355         params.application = this.appName;
356         params.method = this.appName + '.createNode';
357         params.uploadKey = uploadKey;
358         params.addToGridStore = addToGridStore;
359         
360         var onSuccess = (function(result, request){
361
362             var nodeData = Ext.util.JSON.decode(response.responseText),
363                 fileRecord = Tine.Tinebase.uploadManager.upload(this.uploadKey);
364
365             fileRecord.on('update', me.onUploadUpdate.createDelegate(me));
366
367             if(addToGridStore) {
368                 var recordToRemove = gridStore.query('name', fileRecord.get('name'));
369                 if(recordToRemove.items[0]) {
370                     gridStore.remove(recordToRemove.items[0]);
371                 }
372                 
373                 fileRecord = Tine.Filemanager.fileRecordBackend.updateNodeRecord(nodeData[i], fileRecord);
374                 var nodeRecord = new Tine.Filemanager.Model.Node(nodeData[i]);
375                 
376                 nodeRecord.fileRecord = fileRecord;
377                 gridStore.add(nodeRecord);
378                 
379             }
380         }).createDelegate({uploadKey: uploadKey, addToGridStore: addToGridStore});
381         
382         var onFailure = (function(response, request) {
383             
384             var nodeData = Ext.util.JSON.decode(response.responseText);
385             request = Ext.util.JSON.decode(request.jsonData);
386             
387             nodeData.data.uploadKey = this.uploadKey;
388             nodeData.data.addToGridStore = this.addToGridStore;
389             Tine.Filemanager.fileRecordBackend.handleRequestException(nodeData.data, request);
390             
391         }).createDelegate({uploadKey: uploadKey, addToGridStore: addToGridStore});
392         
393         Ext.Ajax.request({
394             params: params,
395             timeout: 300000, // 5 minutes
396             scope: this,
397             success: onSuccess || Ext.emptyFn,
398             failure: onFailure || Ext.emptyFn
399         });
400     },
401     
402     /**
403      * upload files
404      * 
405      * @param {} params Request parameters
406      * @param [] uploadKeyArray
407      * @param Boolean addToGridStore
408      */
409     createNodes: function (params, uploadKeyArray, addToGridStore) {
410         var app = Tine.Tinebase.appMgr.get(this.appName),
411             grid = app.getMainScreen().getCenterPanel(),
412             me = this,
413             gridStore = grid.store;
414
415         params.application = this.appName;
416         params.method = this.appName + '.createNodes';
417         params.uploadKeyArray = uploadKeyArray;
418         params.addToGridStore = addToGridStore;
419
420
421         var onSuccess = (function (response, request) {
422
423             var nodeData = Ext.util.JSON.decode(response.responseText);
424
425             for (var i = 0; i < this.uploadKeyArray.length; i++) {
426                 var fileRecord = Tine.Tinebase.uploadManager.upload(this.uploadKeyArray[i]);
427
428                 Tine.Tinebase.uploadManager.getUpload(this.uploadKeyArray[i]).on('update', me.onUploadUpdate.createDelegate(me));
429
430                 if (addToGridStore) {
431                     fileRecord = Tine.Filemanager.fileRecordBackend.updateNodeRecord(nodeData[i], fileRecord);
432                     var nodeRecord = new Tine.Filemanager.Model.Node(nodeData[i]);
433
434                     nodeRecord.fileRecord = fileRecord;
435
436                     var existingRecordIdx = gridStore.find('name', fileRecord.get('name'));
437                     if (existingRecordIdx > -1) {
438                         gridStore.removeAt(existingRecordIdx);
439                         gridStore.insert(existingRecordIdx, nodeRecord);
440                     } else {
441                         gridStore.add(nodeRecord);
442                     }
443                 }
444             }
445
446         }).createDelegate({uploadKeyArray: uploadKeyArray, addToGridStore: addToGridStore});
447
448         var onFailure = (function (response, request) {
449
450             var nodeData = Ext.util.JSON.decode(response.responseText),
451                 request = Ext.util.JSON.decode(request.jsonData);
452
453             nodeData.data.uploadKeyArray = this.uploadKeyArray;
454             nodeData.data.addToGridStore = this.addToGridStore;
455             Tine.Filemanager.fileRecordBackend.handleRequestException(nodeData.data, request);
456
457         }).createDelegate({uploadKeyArray: uploadKeyArray, addToGridStore: addToGridStore});
458
459         Ext.Ajax.request({
460             params: params,
461             timeout: 300000, // 5 minutes
462             scope: this,
463             success: onSuccess || Ext.emptyFn,
464             failure: onFailure || Ext.emptyFn
465         });
466     },
467
468     /**
469      * Is fired if there is any change for an uploading file
470      *
471      * Is bind to the record proxy scope
472      */
473     onUploadUpdate: function (change, upload, fileRecord) {
474         var app = Tine.Tinebase.appMgr.get(this.appName),
475             grid = app.getMainScreen().getCenterPanel(),
476             rowsToUpdate = grid.store.query('name', fileRecord.get('name'));
477
478         if (change === 'uploadstart') {
479             Tine.Tinebase.uploadManager.onUploadStart();
480         } else if (change === 'uploadfailure') {
481             grid.onUploadFail();
482         }
483
484         if (rowsToUpdate.get(0)) {
485             if (change === 'uploadcomplete') {
486                 this.onUploadComplete.call(grid, this, upload, fileRecord);
487             } else if (change === 'uploadfinished') {
488                 rowsToUpdate.get(0).set('size', fileRecord.get('size'));
489                 rowsToUpdate.get(0).set('contenttype', fileRecord.get('contenttype'));
490             }
491
492             rowsToUpdate.get(0).afterEdit();
493             rowsToUpdate.get(0).commit(false);
494         }
495     },
496
497     /**
498      * updating fileRecord after creating node
499      *
500      * @param response
501      * @param request
502      * @param upload
503      */
504     onNodeCreated: function (response, request, upload) {
505         var record = Ext.util.JSON.decode(response.responseText);
506
507         var fileRecord = upload.fileRecord;
508         fileRecord.beginEdit();
509         fileRecord.set('contenttype', record.contenttype);
510         fileRecord.set('created_by', Tine.Tinebase.registry.get('currentAccount'));
511         fileRecord.set('creation_time', record.creation_time);
512         fileRecord.set('revision', record.revision);
513         fileRecord.set('last_modified_by', record.last_modified_by);
514         fileRecord.set('last_modified_time', record.last_modified_time);
515         fileRecord.set('name', record.name);
516         fileRecord.set('path', record.path);
517         fileRecord.set('status', 'complete');
518         fileRecord.set('progress', 100);
519         fileRecord.commit(false);
520
521         upload.fireEvent('update', 'uploadfinished', upload, fileRecord);
522
523         var allRecordsComplete = true;
524         var storeItems = this.getStore().getRange();
525         for (var i = 0; i < storeItems.length; i++) {
526             if (storeItems[i].get('status') && storeItems[i].get('status') !== 'complete') {
527                 allRecordsComplete = false;
528                 break;
529             }
530         }
531
532         if (allRecordsComplete) {
533             this.pagingToolbar.refresh.enable();
534         }
535     },
536
537     /**
538      * copies uploaded temporary file to target location
539      *
540      * @param proxy  {Tine.Filemanager.fileRecordBackend}
541      * @param upload  {Ext.ux.file.Upload}
542      * @param file  {Ext.ux.file.Upload.file}
543      */
544     onUploadComplete: function(proxy, upload, file) {
545         Ext.Ajax.request({
546             timeout: 10*60*1000, // Overriding Ajax timeout - important!
547             params: {
548                 method: proxy.appName + '.createNode',
549                 filename: upload.id,
550                 type: 'file',
551                 tempFileId: file.get('id'),
552                 forceOverwrite: true
553             },
554             success: proxy.onNodeCreated.createDelegate(this, [upload], true),
555             failure: proxy.onNodeCreated.createDelegate(this, [upload], true)
556         });
557
558     },
559
560     /**
561      * exception handler for this proxy
562      * 
563      * @param {Tine.Exception} exception
564      */
565     handleRequestException: function(exception, request) {
566         var _ = window.lodash,
567             appNS = _.get(Tine, this.appName);
568         appNS.handleRequestException(exception, request);
569     },
570     
571     /**
572      * updates given record with nodeData from from response
573      */
574     updateNodeRecord : function(nodeData, nodeRecord) {
575         
576         for(var field in nodeData) {
577             nodeRecord.set(field, nodeData[field]);
578         }
579         
580         return nodeRecord;
581     }
582 });
583 Tine.Filemanager.fileRecordBackend = new Tine.Filemanager.FileRecordBackend({});
584
585 /**
586  * get filtermodel of Node records
587  * 
588  * @namespace Tine.Filemanager.Model
589  * @static
590  * @return {Object} filterModel definition
591  */ 
592 Tine.Filemanager.Model.Node.getFilterModel = function() {
593     var app = Tine.Tinebase.appMgr.get('Filemanager');
594        
595     return [
596         {label : i18n._('Quick Search'), field : 'query', operators : [ 'contains' ]},
597 //        {label: app.i18n._('Type'), field: 'type'}, // -> should be a combo
598         {label: app.i18n._('Contenttype'), field: 'contenttype'},
599         {label: app.i18n._('Creation Time'), field: 'creation_time', valueType: 'date'},
600         {label: app.i18n._('Description'), field: 'description', operators: ['contains', 'notcontains']},
601         {filtertype : 'tine.filemanager.pathfiltermodel', app : app},
602         {filtertype : 'tinebase.tag', app : app} 
603     ].concat(Tine.Tinebase.configManager.get('filesystem.index_content', 'Tinebase') ? [
604         {label : i18n._('File Contents'), field : 'content', operators : [ 'wordstartswith' ]},
605         {label : i18n._('Indexed'), field : 'isIndexed', valueType: 'bool'}
606     ] : []);
607 };
608
609 /**
610  * @namespace   Tine.Filemanager.Model
611  * @class       Tine.Filemanager.Model.DownloadLink
612  * @extends     Tine.Tinebase.data.Record
613  * Example record definition
614  */
615 Tine.Filemanager.Model.DownloadLink = Tine.Tinebase.data.Record.create(Tine.Tinebase.Model.modlogFields.concat([
616     { name: 'id' },
617     { name: 'node_id' },
618     { name: 'url' },
619     { name: 'expiry_time', type: 'datetime' },
620     { name: 'access_count' },
621     { name: 'password' }
622 ]), {
623     appName: 'Filemanager',
624     modelName: 'DownloadLink',
625     idProperty: 'id',
626     titleProperty: 'url',
627     // ngettext('Download Link', 'Download Links', n); gettext('Download Link');
628     recordName: 'Download Link',
629     recordsName: 'Download Links'
630 });
631
632 /**
633  * download link backend
634  */
635 Tine.Filemanager.downloadLinkRecordBackend = new Tine.Tinebase.data.RecordProxy({
636     appName: 'Filemanager',
637     modelName: 'DownloadLink',
638     recordClass: Tine.Filemanager.Model.DownloadLink
639 });