attachments changes

This commit is contained in:
yuri
2017-11-20 15:19:14 +02:00
parent 3d2ff947e4
commit cfec27eab8
16 changed files with 224 additions and 89 deletions

View File

@@ -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
);
}
}

View File

@@ -167,6 +167,7 @@ return array (
'currencyDecimalPlaces' => null,
'aclStrictMode' => false,
'aclAllowDeleteCreated' => false,
'inlineAttachmentUploadMaxSize' => 20,
'isInstalled' => false
);

View File

@@ -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')));
}
}
}

View File

@@ -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.",

View File

@@ -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": {

View File

@@ -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?",

View File

@@ -26,6 +26,10 @@
"readOnly": true,
"disabled": true
},
"field": {
"type": "varchar",
"disabled": true
},
"createdAt": {
"type": "datetime",
"readOnly": true

View File

@@ -24,7 +24,7 @@
"readOnly": true
},
"attachments": {
"type": "linkMultiple",
"type": "attachmentMultiple",
"view": "views/stream/fields/attachment-multiple"
},
"number": {

View File

@@ -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":[

View File

@@ -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"

View File

@@ -11,6 +11,12 @@
"default":"small",
"options": ["x-small", "small", "medium", "large"]
},
{
"name": "maxFileSize",
"type": "float",
"tooltip": true,
"min": 0
},
{
"name":"audited",
"type":"bool"

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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 () {

View File

@@ -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'));

View File

@@ -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 () {