mirror of
https://github.com/espocrm/espocrm.git
synced 2026-06-28 06:56:05 +00:00
attachments changes
This commit is contained in:
@@ -34,16 +34,5 @@ use \Espo\Core\Exceptions\BadRequest;
|
||||
|
||||
class Attachment extends \Espo\Core\Controllers\Record
|
||||
{
|
||||
public function postActionUpload($params, $data, $request)
|
||||
{
|
||||
if (!$this->getAcl()->checkScope('Attachment', 'create')) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$attachment = $this->getRecordService()->upload($data);
|
||||
|
||||
return array(
|
||||
'attachmentId' => $attachment->id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@ return array (
|
||||
'currencyDecimalPlaces' => null,
|
||||
'aclStrictMode' => false,
|
||||
'aclAllowDeleteCreated' => false,
|
||||
'inlineAttachmentUploadMaxSize' => 20,
|
||||
'isInstalled' => false
|
||||
);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ class Attachment extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
if ($entity->isNew()) {
|
||||
if (!$entity->has('size') && $entity->has('contents')) {
|
||||
$entity->set('size', mb_strlen($entity->has('contents')));
|
||||
$entity->set('size', mb_strlen($entity->get('contents')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,8 @@
|
||||
"dynamicLogicRequired": "Conditions making field required",
|
||||
"dynamicLogicOptions": "Conditional options",
|
||||
"probabilityMap": "Stage Probabilities (%)",
|
||||
"readOnly": "Read-only"
|
||||
"readOnly": "Read-only",
|
||||
"maxFileSize": "Max File Size (Mb)"
|
||||
},
|
||||
"messages": {
|
||||
"upgradeVersion": "EspoCRM will be upgraded to version <strong>{version}</strong>. Please be patient as this may take a while.",
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
"maxLength": "Max acceptable length of text.",
|
||||
"before": "The date value should be before the date value of the specified field.",
|
||||
"after": "The date value should be after the date value of the specified field.",
|
||||
"readOnly": "Field value can't be specified by user. But can be calculated by formula."
|
||||
"readOnly": "Field value can't be specified by user. But can be calculated by formula.",
|
||||
"maxFileSize": "If empty or 0 then no limit."
|
||||
},
|
||||
"fieldParts": {
|
||||
"address": {
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"fieldShouldBeLess": "{field} should be less then {value}",
|
||||
"fieldShouldBeGreater": "{field} should be greater then {value}",
|
||||
"fieldBadPasswordConfirm": "{field} not confirmed properly",
|
||||
"fieldMaxFileSizeError": "File should not exceed {max} Mb",
|
||||
"resetPreferencesDone": "Preferences has been reset to defaults",
|
||||
"confirmation": "Are you sure?",
|
||||
"unlinkAllConfirmation": "Are you sure you want to unlink all related records?",
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
"readOnly": true,
|
||||
"disabled": true
|
||||
},
|
||||
"field": {
|
||||
"type": "varchar",
|
||||
"disabled": true
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"readOnly": true
|
||||
},
|
||||
"attachments": {
|
||||
"type": "linkMultiple",
|
||||
"type": "attachmentMultiple",
|
||||
"view": "views/stream/fields/attachment-multiple"
|
||||
},
|
||||
"number": {
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
"name": "sourceList",
|
||||
"type": "multiEnum",
|
||||
"view": "views/admin/field-manager/fields/source-list"
|
||||
},
|
||||
{
|
||||
"name": "maxFileSize",
|
||||
"type": "float",
|
||||
"tooltip": true,
|
||||
"min": 0
|
||||
}
|
||||
],
|
||||
"actualFields":[
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
"type": "multiEnum",
|
||||
"view": "views/admin/field-manager/fields/source-list"
|
||||
},
|
||||
{
|
||||
"name": "maxFileSize",
|
||||
"type": "float",
|
||||
"tooltip": true,
|
||||
"min": 0
|
||||
},
|
||||
{
|
||||
"name":"audited",
|
||||
"type":"bool"
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
"default":"small",
|
||||
"options": ["x-small", "small", "medium", "large"]
|
||||
},
|
||||
{
|
||||
"name": "maxFileSize",
|
||||
"type": "float",
|
||||
"tooltip": true,
|
||||
"min": 0
|
||||
},
|
||||
{
|
||||
"name":"audited",
|
||||
"type":"bool"
|
||||
|
||||
@@ -31,10 +31,18 @@ namespace Espo\Services;
|
||||
|
||||
use \Espo\ORM\Entity;
|
||||
|
||||
use \Espo\Core\Exceptions\BadRequest;
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
class Attachment extends Record
|
||||
{
|
||||
protected $notFilteringAttributeList = ['contents'];
|
||||
|
||||
protected $attachmentFieldTypeList = ['file', 'image', 'attachmentMultiple'];
|
||||
|
||||
protected $inlineAttachmentFieldTypeList = ['text', 'wysiwyg'];
|
||||
|
||||
public function upload($fileData)
|
||||
{
|
||||
if (!$this->getAcl()->checkScope('Attachment', 'create')) {
|
||||
@@ -59,9 +67,79 @@ class Attachment extends Record
|
||||
public function createEntity($data)
|
||||
{
|
||||
if (!empty($data['file'])) {
|
||||
list($prefix, $contents) = explode(',', $data['file']);
|
||||
$arr = explode(',', $data['file']);
|
||||
$contents = '';
|
||||
if (count($arr) > 1) {
|
||||
$contents = $arr[1];
|
||||
}
|
||||
|
||||
$contents = base64_decode($contents);
|
||||
$data['contents'] = $contents;
|
||||
|
||||
$relatedEntityType = null;
|
||||
$field = null;
|
||||
$role = 'Attachment';
|
||||
if (isset($data['parentType'])) {
|
||||
$relatedEntityType = $data['parentType'];
|
||||
} else if (isset($data['relatedType'])) {
|
||||
$relatedEntityType = $data['relatedType'];
|
||||
}
|
||||
if (isset($data['field'])) {
|
||||
$field = $data['field'];
|
||||
}
|
||||
if (isset($data['role'])) {
|
||||
$role = $data['role'];
|
||||
}
|
||||
if (!$relatedEntityType || !$field) {
|
||||
throw new BadRequest("Params 'field' and 'parentType' not passed along with 'file'.");
|
||||
}
|
||||
|
||||
$fieldType = $this->getMetadata()->get(['entityDefs', $relatedEntityType, 'fields', $field, 'type']);
|
||||
if (!$fieldType) {
|
||||
throw new Error("Field '{$field}' does not exist.");
|
||||
}
|
||||
|
||||
if (
|
||||
!$this->getAcl()->checkScope($relatedEntityType, 'create')
|
||||
&&
|
||||
!$this->getAcl()->checkScope($relatedEntityType, 'edit')
|
||||
) {
|
||||
throw new Forbidden("No access to " . $relatedEntityType . ".");
|
||||
}
|
||||
|
||||
if (in_array($field, $this->getAcl()->getScopeForbiddenFieldList($relatedEntityType, 'edit'))) {
|
||||
throw new Forbidden("No access to field '" . $field . "'.");
|
||||
}
|
||||
|
||||
$size = mb_strlen($contents, '8bit');
|
||||
|
||||
if ($role === 'Attachment') {
|
||||
if (!in_array($fieldType, $this->attachmentFieldTypeList)) {
|
||||
throw new Error("Field type '{$fieldType}' is not allowed for attachment.");
|
||||
}
|
||||
$maxSize = $this->getMetadata()->get(['entityDefs', $relatedEntityType, 'fields', $field, 'maxFileSize']);
|
||||
if (!$maxSize) {
|
||||
$maxSize = $this->getConfig()->get('attachmentUploadMaxSize');
|
||||
}
|
||||
if ($maxSize) {
|
||||
if ($size > $maxSize * 1024 * 1024) {
|
||||
throw new Error("File size should not exceed {$maxSize}Mb.");
|
||||
}
|
||||
}
|
||||
|
||||
} else if ($role === 'Inline Attachment') {
|
||||
if (!in_array($fieldType, $this->inlineAttachmentFieldTypeList)) {
|
||||
throw new Error("Field '{$field}' is not allowed to have inline attachment.");
|
||||
}
|
||||
$inlineAttachmentUploadMaxSize = $this->getConfig()->get('inlineAttachmentUploadMaxSize');
|
||||
if ($inlineAttachmentUploadMaxSize) {
|
||||
if ($size > $inlineAttachmentUploadMaxSize * 1024 * 1024) {
|
||||
throw new Error("File size should not exceed {$inlineAttachmentUploadMaxSize}Mb.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new BadRequest("Not supported attachment role.");
|
||||
}
|
||||
}
|
||||
|
||||
$entity = parent::createEntity($data);
|
||||
|
||||
@@ -291,7 +291,7 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D
|
||||
$attachments.append($container);
|
||||
|
||||
if (!id) {
|
||||
var $loading = $('<span class="small">' + this.translate('Uploading...') + '</span>');
|
||||
var $loading = $('<span class="small uploading-message">' + this.translate('Uploading...') + '</span>');
|
||||
$container.append($loading);
|
||||
$att.on('ready', function () {
|
||||
$loading.html(this.translate('Ready'));
|
||||
@@ -303,22 +303,50 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D
|
||||
return $att;
|
||||
},
|
||||
|
||||
showValidationMessage: function (msg, selector) {
|
||||
var $label = this.$el.find('label');
|
||||
var title = $label.attr('title');
|
||||
$label.attr('title', '');
|
||||
Dep.prototype.showValidationMessage.call(this, msg, selector);
|
||||
$label.attr('title', title);
|
||||
},
|
||||
|
||||
uploadFiles: function (files) {
|
||||
var uploadedCount = 0;
|
||||
var totalCount = 0;
|
||||
|
||||
var exceedsMaxFileSize = false;
|
||||
var maxFileSize = this.params.maxFileSize;
|
||||
if (maxFileSize) {
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var file = files[i];
|
||||
if (file.size > maxFileSize * 1024 * 1024) {
|
||||
exceedsMaxFileSize = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exceedsMaxFileSize) {
|
||||
var msg = this.translate('fieldMaxFileSizeError', 'messages')
|
||||
.replace('{field}', this.translate(this.name, 'fields', this.model.name))
|
||||
.replace('{max}', maxFileSize);
|
||||
|
||||
this.showValidationMessage(msg, 'label');
|
||||
return;
|
||||
}
|
||||
|
||||
this.getModelFactory().create('Attachment', function (model) {
|
||||
var canceledList = [];
|
||||
|
||||
var fileList = [];
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
fileList.push(files[i]);
|
||||
totalCount++;
|
||||
}
|
||||
|
||||
fileList.forEach(function (file) {
|
||||
var $att = this.addAttachmentBox(file.name, file.type);
|
||||
var $attachmentBox = this.addAttachmentBox(file.name, file.type);
|
||||
|
||||
$att.find('.remove-attachment').on('click.uploading', function () {
|
||||
$attachmentBox.find('.remove-attachment').on('click.uploading', function () {
|
||||
canceledList.push(attachment.cid);
|
||||
totalCount--;
|
||||
});
|
||||
@@ -327,33 +355,33 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D
|
||||
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function (e) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'Attachment/action/upload',
|
||||
data: e.target.result,
|
||||
contentType: 'multipart/encrypted',
|
||||
timeout: 0,
|
||||
}).done(function (data) {
|
||||
attachment.set('name', file.name);
|
||||
attachment.set('type', file.type || 'text/plain');
|
||||
attachment.set('role', 'Attachment');
|
||||
attachment.set('size', file.size);
|
||||
attachment.set('parentType', this.model.name);
|
||||
attachment.set('file', e.target.result);
|
||||
attachment.set('field', this.name);
|
||||
|
||||
attachment.id = data.attachmentId;
|
||||
attachment.set('name', file.name);
|
||||
attachment.set('type', file.type || 'text/plain');
|
||||
attachment.set('role', 'Attachment');
|
||||
attachment.set('size', file.size);
|
||||
attachment.set('parentType', this.model.name);
|
||||
|
||||
attachment.once('sync', function () {
|
||||
if (canceledList.indexOf(attachment.cid) === -1) {
|
||||
$att.trigger('ready');
|
||||
this.pushAttachment(attachment);
|
||||
$att.attr('data-id', attachment.id);
|
||||
uploadedCount++;
|
||||
if (uploadedCount == totalCount) {
|
||||
afterAttachmentsUploaded.call(this);
|
||||
}
|
||||
attachment.save().then(function () {
|
||||
if (canceledList.indexOf(attachment.cid) === -1) {
|
||||
$attachmentBox.trigger('ready');
|
||||
this.pushAttachment(attachment);
|
||||
$attachmentBox.attr('data-id', attachment.id);
|
||||
uploadedCount++;
|
||||
if (uploadedCount == totalCount) {
|
||||
this.afterAttachmentsUploaded.call(this);
|
||||
}
|
||||
}, this);
|
||||
attachment.save();
|
||||
}
|
||||
}.bind(this)).fail(function () {
|
||||
$attachmentBox.remove();
|
||||
totalCount--;
|
||||
if (!totalCount) {
|
||||
this.$el.find('.uploading-message').remove();
|
||||
}
|
||||
if (uploadedCount == totalCount) {
|
||||
this.afterAttachmentsUploaded.call(this);
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
fileReader.readAsDataURL(file);
|
||||
@@ -522,7 +550,7 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D
|
||||
if (this.isRequired()) {
|
||||
if (this.model.get(this.idsName).length == 0) {
|
||||
var msg = this.translate('fieldIsRequired', 'messages').replace('{field}', this.translate(this.name, 'fields', this.model.name));
|
||||
this.showValidationMessage(msg);
|
||||
this.showValidationMessage(msg, 'label');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,7 +537,7 @@ Espo.define('views/fields/base', 'view', function (Dep) {
|
||||
placement: 'bottom',
|
||||
container: 'body',
|
||||
content: message,
|
||||
trigger: 'manual',
|
||||
trigger: 'manual'
|
||||
}).popover('show');
|
||||
|
||||
$el.closest('.field').one('mousedown click', function () {
|
||||
|
||||
@@ -107,6 +107,14 @@ Espo.define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
return data;
|
||||
},
|
||||
|
||||
showValidationMessage: function (msg, selector) {
|
||||
var $label = this.$el.find('label');
|
||||
var title = $label.attr('title');
|
||||
$label.attr('title', '');
|
||||
Dep.prototype.showValidationMessage.call(this, msg, selector);
|
||||
$label.attr('title', title);
|
||||
},
|
||||
|
||||
validateRequired: function () {
|
||||
if (this.isRequired()) {
|
||||
if (this.model.get(this.idName) == null) {
|
||||
@@ -283,12 +291,27 @@ Espo.define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
uploadFile: function (file) {
|
||||
var isCanceled = false;
|
||||
|
||||
var exceedsMaxFileSize = false;
|
||||
var maxFileSize = this.params.maxFileSize;
|
||||
if (maxFileSize) {
|
||||
if (file.size > maxFileSize * 1024 * 1024) {
|
||||
exceedsMaxFileSize = true;
|
||||
}
|
||||
}
|
||||
if (exceedsMaxFileSize) {
|
||||
var msg = this.translate('fieldMaxFileSizeError', 'messages')
|
||||
.replace('{field}', this.translate(this.name, 'fields', this.model.name))
|
||||
.replace('{max}', maxFileSize);
|
||||
this.showValidationMessage(msg, '.attachment-button label');
|
||||
return;
|
||||
}
|
||||
|
||||
this.getModelFactory().create('Attachment', function (attachment) {
|
||||
var $att = this.addAttachmentBox(file.name, file.type);
|
||||
var $attachmentBox = this.addAttachmentBox(file.name, file.type);
|
||||
|
||||
this.$el.find('.attachment-button').addClass('hidden');
|
||||
|
||||
$att.find('.remove-attachment').on('click.uploading', function () {
|
||||
$attachmentBox.find('.remove-attachment').on('click.uploading', function () {
|
||||
isCanceled = true;
|
||||
this.$el.find('.attachment-button').removeClass('hidden');
|
||||
}.bind(this));
|
||||
@@ -296,27 +319,23 @@ Espo.define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function (e) {
|
||||
this.handleFileUpload(file, e.target.result, function (result, fileParams) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'Attachment/action/upload',
|
||||
data: result,
|
||||
contentType: 'multipart/encrypted',
|
||||
timeout: 0,
|
||||
}).done(function (data) {
|
||||
attachment.id = data.attachmentId;
|
||||
attachment.set('name', fileParams.name);
|
||||
attachment.set('type', fileParams.type || 'text/plain');
|
||||
attachment.set('size', fileParams.size);
|
||||
attachment.set('role', 'Attachment');
|
||||
attachment.set('relatedType', this.model.name);
|
||||
attachment.set('name', fileParams.name);
|
||||
attachment.set('type', fileParams.type || 'text/plain');
|
||||
attachment.set('size', fileParams.size);
|
||||
attachment.set('role', 'Attachment');
|
||||
attachment.set('relatedType', this.model.name);
|
||||
attachment.set('file', e.target.result);
|
||||
attachment.set('field', this.name);
|
||||
|
||||
attachment.once('sync', function () {
|
||||
if (!isCanceled) {
|
||||
$att.trigger('ready');
|
||||
this.setAttachment(attachment);
|
||||
}
|
||||
}, this);
|
||||
attachment.save();
|
||||
attachment.save().then(function () {
|
||||
if (!isCanceled) {
|
||||
$attachmentBox.trigger('ready');
|
||||
this.setAttachment(attachment);
|
||||
}
|
||||
}.bind(this)).fail(function () {
|
||||
$attachmentBox.remove();
|
||||
this.$el.find('.uploading-message').remove();
|
||||
this.$el.find('.attachment-button').removeClass('hidden');
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
@@ -358,7 +377,7 @@ Espo.define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
this.$attachment.append($container);
|
||||
|
||||
if (!id) {
|
||||
var $loading = $('<span class="small">' + this.translate('Uploading...') + '</span>');
|
||||
var $loading = $('<span class="small uploading-message">' + this.translate('Uploading...') + '</span>');
|
||||
$container.append($loading);
|
||||
$att.on('ready', function () {
|
||||
$loading.html(self.translate('Ready'));
|
||||
|
||||
@@ -206,28 +206,23 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
|
||||
this.getModelFactory().create('Attachment', function (attachment) {
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function (e) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: 'Attachment/action/upload',
|
||||
data: e.target.result,
|
||||
contentType: 'multipart/encrypted',
|
||||
}).done(function (data) {
|
||||
attachment.id = data.attachmentId;
|
||||
attachment.set('name', file.name);
|
||||
attachment.set('type', file.type);
|
||||
attachment.set('role', 'Inline Attachment');
|
||||
attachment.set('global', true);
|
||||
attachment.set('size', file.size);
|
||||
attachment.once('sync', function () {
|
||||
var url = '?entryPoint=attachment&id=' + attachment.id;
|
||||
this.$summernote.summernote('insertImage', url);
|
||||
this.notify(false);
|
||||
}, this);
|
||||
attachment.save();
|
||||
}.bind(this));
|
||||
attachment.set('name', file.name);
|
||||
attachment.set('type', file.type);
|
||||
attachment.set('role', 'Inline Attachment');
|
||||
attachment.set('global', true);
|
||||
attachment.set('size', file.size);
|
||||
attachment.set('relatedType', this.model.name);
|
||||
attachment.set('file', e.target.result);
|
||||
attachment.set('field', this.name);
|
||||
|
||||
attachment.once('sync', function () {
|
||||
var url = '?entryPoint=attachment&id=' + attachment.id;
|
||||
this.$summernote.summernote('insertImage', url);
|
||||
this.notify(false);
|
||||
}, this);
|
||||
attachment.save();
|
||||
}.bind(this);
|
||||
fileReader.readAsDataURL(file);
|
||||
|
||||
}, this);
|
||||
}.bind(this),
|
||||
onBlur: function () {
|
||||
|
||||
Reference in New Issue
Block a user