1 /// crmFile: Manage file attachments
2 (function (angular
, $, _
) {
4 angular
.module('crmAttachment', CRM
.angRequires('crmAttachment'));
6 // crmAttachment manages the list of files which are attached to a given entity
7 angular
.module('crmAttachment').factory('CrmAttachments', function (crmApi
, crmStatus
, FileUploader
, $q
) {
8 // @param target an Object(entity_table:'',entity_id:'') or function which generates an object
9 function CrmAttachments(target
) {
10 var crmAttachments
= this;
11 this._target
= target
;
14 this.uploader
= new FileUploader({
15 url
: CRM
.url('civicrm/ajax/attachment'),
16 onAfterAddingFile
: function onAfterAddingFile(item
) {
21 onSuccessItem
: function onSuccessItem(item
, response
, status
, headers
) {
22 crmAttachments
.files
.push(response
.file
.values
[response
.file
.id
]);
23 crmAttachments
.uploader
.removeFromQueue(item
);
25 onErrorItem
: function onErrorItem(item
, response
, status
, headers
) {
26 var msg
= (response
&& response
.file
&& response
.file
.error_message
) ? response
.file
.error_message
: ts('Unknown error');
27 CRM
.alert(item
.file
.name
+ ' - ' + msg
, ts('Attachment failed'));
28 crmAttachments
.uploader
.removeFromQueue(item
);
33 angular
.extend(CrmAttachments
.prototype, {
34 // @return Object(entity_table:'',entity_id:'')
35 getTarget: function () {
36 return (angular
.isFunction(this._target
) ? this._target() : this._target
);
38 // @return Promise<Attachment>
39 load
: function load() {
40 var target
= this.getTarget();
41 var Attachment
= this;
43 if (target
.entity_id
) {
45 entity_table
: target
.entity_table
,
46 entity_id
: target
.entity_id
48 return crmApi('Attachment', 'get', params
).then(function (apiResult
) {
49 Attachment
.files
= _
.values(apiResult
.values
);
55 Attachment
.files
= [];
56 dfr
.resolve(Attachment
);
61 save
: function save() {
62 var crmAttachments
= this;
63 var target
= this.getTarget();
64 if (!target
.entity_table
|| !target
.entity_id
) {
65 throw "Cannot save attachments: unknown entity_table or entity_id";
68 var params
= _
.extend({}, target
);
69 params
.values
= crmAttachments
.files
;
70 return crmApi('Attachment', 'replace', params
)
74 var newItems
= crmAttachments
.uploader
.getNotUploadedItems();
75 if (newItems
.length
> 0) {
76 _
.each(newItems
, function (item
) {
77 item
.formData
= [_
.extend({crm_attachment_token
: CRM
.crmAttachment
.token
}, target
, item
.crmData
)];
79 crmAttachments
.uploader
.onCompleteAll
= function onCompleteAll() {
80 delete crmAttachments
.uploader
.onCompleteAll
;
81 dfr
.resolve(crmAttachments
);
83 crmAttachments
.uploader
.uploadAll();
86 dfr
.resolve(crmAttachments
);
92 // Compute a digest over the list of files. The signature should change if the attachment list has changed
94 getAutosaveSignature
: function getAutosaveSignature() {
96 // Attachments have a special lifecycle, and attachments.queue is not properly serializable, so
97 // it takes some special effort to figure out a suitable signature. Issues which can cause gratuitous saving:
98 // - Files move from this.uploader.queue to this.files after upload.
99 // - File names are munged after upload.
100 // - Deletes are performed immediately (outside the save process).
101 angular
.forEach(this.files
, function(item
) {
102 sig
.push({f
: item
.name
.replace(/[^a-zA0-Z0-9\.]/, '_'), d
: item
.description
});
104 angular
.forEach(this.uploader
.queue
, function(item
) {
105 sig
.push({f
: item
.file
.name
.replace(/[^a-zA0-Z0-9\.]/, '_'), d
: item
.crmData
.description
});
107 angular
.forEach(this.trash
, function(item
) {
108 sig
.push({f
: item
.name
.replace(/[^a-zA0-Z0-9\.]/, '_'), d
: item
.description
});
110 return _
.sortBy(sig
, 'name');
112 // @param Object file APIv3 attachment record (e.g. id, entity_table, entity_id, description)
113 deleteFile
: function deleteFile(file
) {
114 var crmAttachments
= this;
116 var idx
= _
.indexOf(this.files
, file
);
118 this.files
.splice(idx
, 1);
121 this.trash
.push(file
);
124 var p
= crmApi('Attachment', 'delete', {id
: file
.id
}).then(
125 function () { // success
127 function (response
) { // error; restore the file
128 var msg
= angular
.isObject(response
) ? response
.error_message
: '';
129 CRM
.alert(msg
, ts('Deletion failed'));
130 crmAttachments
.files
.push(file
);
132 var trashIdx
= _
.indexOf(crmAttachments
.trash
, file
);
133 if (trashIdx
!= -1) {
134 crmAttachments
.trash
.splice(trashIdx
, 1);
138 return crmStatus({start
: ts('Deleting...'), success
: ts('Deleted')}, p
);
143 return CrmAttachments
;
147 // $scope.myAttachments = new CrmAttachments({entity_table: 'civicrm_mailing', entity_id: 123});
148 // <div crm-attachments="myAttachments"/>
149 angular
.module('crmAttachment').directive('crmAttachments', function ($parse
, $timeout
) {
154 template
: '<div ng-if="ready" ng-include="inclUrl"></div>',
155 link: function (scope
, elm
, attr
) {
156 var model
= $parse(attr
.crmAttachments
);
157 scope
.att
= model(scope
.$parent
);
158 scope
.ts
= CRM
.ts(null);
159 CRM
.api4('Setting', 'get', {
160 select
: ["maxFileSize"]
161 }).then(function(settings
) {
162 scope
.max_size
= settings
[0].value
;
164 scope
.inclUrl
= '~/crmAttachment/attachments.html';
166 // delay rendering of child tree until after model has been populated
172 })(angular
, CRM
.$, CRM
._
);