Merge branch 'fix'
This commit is contained in:
@@ -165,7 +165,7 @@ class GoogleMaps implements Helper
|
||||
|
||||
$url = "https://maps.googleapis.com/maps/api/staticmap?" .
|
||||
'center=' . $addressEncoded .
|
||||
'format=' . $format .
|
||||
'&format=' . $format .
|
||||
'&size=' . $size .
|
||||
'&key=' . $apiKey;
|
||||
|
||||
|
||||
@@ -106,7 +106,10 @@ class EmailReminder
|
||||
return;
|
||||
}
|
||||
|
||||
if ($entity->hasLinkMultipleField('users')) {
|
||||
if (
|
||||
$entity->hasLinkMultipleField('users') &&
|
||||
$entity->hasAttribute('usersColumns')
|
||||
) {
|
||||
$entity->loadLinkMultipleField('users', ['status' => 'acceptanceStatus']);
|
||||
$status = $entity->getLinkMultipleColumn('users', 'status', $user->getId());
|
||||
|
||||
|
||||
@@ -106,7 +106,8 @@ class SubmitPopupReminders implements JobDataLess
|
||||
|
||||
if (
|
||||
$entity instanceof CoreEntity &&
|
||||
$entity->hasLinkMultipleField('users')
|
||||
$entity->hasLinkMultipleField('users') &&
|
||||
$entity->hasAttribute('usersColumns')
|
||||
) {
|
||||
$entity->loadLinkMultipleField('users', ['status' => 'acceptanceStatus']);
|
||||
|
||||
|
||||
@@ -113,7 +113,8 @@ class PopupNotificationsProvider implements Provider
|
||||
|
||||
if (
|
||||
$entity instanceof CoreEntity &&
|
||||
$entity->hasLinkMultipleField('users')
|
||||
$entity->hasLinkMultipleField('users') &&
|
||||
$entity->hasAttribute('usersColumns')
|
||||
) {
|
||||
$entity->loadLinkMultipleField('users', ['status' => 'acceptanceStatus']);
|
||||
|
||||
|
||||
@@ -126,7 +126,9 @@ class LinkManager
|
||||
$params['relationName'] :
|
||||
lcfirst($entity) . $entityForeign;
|
||||
|
||||
$relationName = $this->nameUtil->addCustomPrefix($relationName);
|
||||
if ($relationName[0] !== 'c' || !preg_match('/[A-Z]/', $relationName[1])) {
|
||||
$relationName = $this->nameUtil->addCustomPrefix($relationName);
|
||||
}
|
||||
|
||||
if ($this->isNameTooLong($relationName)) {
|
||||
throw new Error("Relation name is too long.");
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#layout ul.panels > li {
|
||||
padding: 5px 10px;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 14px;
|
||||
min-height: 80px;
|
||||
border: 1px solid var(--select-item-border);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
@@ -45,7 +45,9 @@
|
||||
{{#if hasCustomLabel}}
|
||||
data-custom-label="{{customLabel}}"
|
||||
{{/if}}
|
||||
data-no-label="{{noLabel}}" >
|
||||
data-no-label="{{noLabel}}"
|
||||
title="{{label}}"
|
||||
>
|
||||
<div class="left" style="width: calc(100% - 14px);">{{label}}</div>
|
||||
<div class="right" style="width: 14px;">
|
||||
<a
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<header>{{translate 'Available Fields' scope='Admin'}}</header>
|
||||
<ul class="disabled cells clearfix">
|
||||
{{#each disabledFields}}
|
||||
<li class="cell" data-name="{{./this}}">
|
||||
<li class="cell" data-name="{{./this}}" title="{{translate this scope=../scope category='fields'}}">
|
||||
<div class="left" style="width: calc(100% - 14px);">
|
||||
{{translate this scope=../scope category='fields'}}
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
<header>{{translate 'Enabled' scope='Admin'}}</header>
|
||||
<ul class="enabled connected">
|
||||
{{#each layout}}
|
||||
<li class="cell" draggable="true" {{#each ../dataAttributeList}}data-{{toDom this}}="{{prop ../this this}}" {{/each}}>
|
||||
<li
|
||||
class="cell"
|
||||
draggable="true"
|
||||
{{#each ../dataAttributeList}}data-{{toDom this}}="{{prop ../this this}}" {{/each}}
|
||||
title="{{labelText}}"
|
||||
>
|
||||
<div class="left" style="width: calc(100% - 17px);">
|
||||
<span>{{labelText}}</span>
|
||||
</div>
|
||||
@@ -40,6 +45,7 @@
|
||||
class="cell"
|
||||
draggable="true"
|
||||
{{#each ../dataAttributeList}}data-{{toDom this}}="{{prop ../this this}}" {{/each}}
|
||||
title="{{labelText}}"
|
||||
>
|
||||
<div class="left" style="width: calc(100% - 17px);">
|
||||
<span>{{labelText}}</span>
|
||||
|
||||
@@ -32,11 +32,12 @@ class UserAcl extends Acl {
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
checkModelRead(model, data, precise) {
|
||||
if (model.isPortal()) {
|
||||
if (this.get('portalPermission') === 'yes') {
|
||||
// @todo Support getPermissionLevel.
|
||||
/*if (model.isPortal()) {
|
||||
if (this.getPermissionLevel('portalPermission') === 'yes') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
return this.checkModel(model, data, 'read', precise);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,19 @@ class LinkManagerEditModalView extends ModalView {
|
||||
|
||||
const allEntityList = this.getMetadata().getScopeEntityList()
|
||||
.filter(item => {
|
||||
return this.getMetadata().get(['scopes', item, 'customizable']);
|
||||
const defs = /** @type {Record} */this.getMetadata().get(['scopes', item]) || {};
|
||||
|
||||
if (!defs.customizable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const emDefs = /** @type {Record} */defs.entityManager || {};
|
||||
|
||||
if (emDefs.relationships === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.sort((v1, v2) => {
|
||||
const t1 = this.translate(v1, 'scopeNames');
|
||||
@@ -196,9 +208,19 @@ class LinkManagerEditModalView extends ModalView {
|
||||
|
||||
const entityList = (Object.keys(scopes) || [])
|
||||
.filter(item => {
|
||||
const d = scopes[item];
|
||||
const defs = /** @type {Record} */scopes[item] || {};
|
||||
|
||||
return d.customizable && d.entity;
|
||||
if (!defs.entity || !defs.customizable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const emDefs = /** @type {Record} */defs.entityManager || {};
|
||||
|
||||
if (emDefs.relationships === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.sort((v1, v2) => {
|
||||
const t1 = this.translate(v1, 'scopeNames');
|
||||
@@ -665,10 +687,6 @@ class LinkManagerEditModalView extends ModalView {
|
||||
Espo.Utils.lowerCaseFirst(this.scope) + entityForeign :
|
||||
Espo.Utils.lowerCaseFirst(entityForeign) + this.scope;
|
||||
|
||||
if (relationName[0] !== 'c' || !/[A-Z]/.test(relationName[1])) {
|
||||
relationName = 'c' + Espo.Utils.upperCaseFirst(relationName);
|
||||
}
|
||||
|
||||
this.model.set('relationName', relationName);
|
||||
|
||||
break;
|
||||
|
||||
@@ -110,6 +110,7 @@ class FileFieldView extends LinkFieldView {
|
||||
},
|
||||
}
|
||||
|
||||
// noinspection JSCheckFunctionSignatures
|
||||
data() {
|
||||
const data = {
|
||||
...super.data(),
|
||||
@@ -123,6 +124,7 @@ class FileFieldView extends LinkFieldView {
|
||||
|
||||
data.valueIsSet = this.model.has(this.idName);
|
||||
|
||||
// noinspection JSValidateTypes
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -523,6 +525,9 @@ class FileFieldView extends LinkFieldView {
|
||||
return maxFileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {File} file
|
||||
*/
|
||||
uploadFile(file) {
|
||||
let isCanceled = false;
|
||||
|
||||
@@ -627,6 +632,11 @@ class FileFieldView extends LinkFieldView {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @param {File} file
|
||||
* @return {Promise<unknown>}
|
||||
*/
|
||||
handleUploadingFile(file) {
|
||||
return new Promise(resolve => resolve(file));
|
||||
}
|
||||
|
||||
@@ -26,113 +26,128 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/user/fields/avatar', ['views/fields/image'], function (Dep) {
|
||||
import ImageFieldView from 'views/fields/image';
|
||||
|
||||
return Dep.extend({
|
||||
class UserAvatarFieldView extends ImageFieldView {
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
getAttributeList() {
|
||||
if (this.isEditMode()) {
|
||||
return super.getAttributeList();
|
||||
}
|
||||
|
||||
this.on('after:inline-save', () => {
|
||||
this.suspendCache = true;
|
||||
return [];
|
||||
}
|
||||
|
||||
this.reRender();
|
||||
});
|
||||
},
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
handleUploadingFile: function (file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let fileReader = new FileReader();
|
||||
this.on('after:inline-save', () => {
|
||||
this.suspendCache = true;
|
||||
|
||||
fileReader.onload = (e) => {
|
||||
this.createView('crop', 'views/modals/image-crop', {contents: e.target.result})
|
||||
.then(view => {
|
||||
view.render();
|
||||
this.reRender();
|
||||
});
|
||||
}
|
||||
|
||||
let cropped = false;
|
||||
/**
|
||||
* @protected
|
||||
* @param {File} file
|
||||
* @return {Promise<unknown>}
|
||||
*/
|
||||
handleUploadingFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
|
||||
this.listenToOnce(view, 'crop', (dataUrl) => {
|
||||
cropped = true;
|
||||
fileReader.onload = (e) => {
|
||||
this.createView('crop', 'views/modals/image-crop', {contents: e.target.result})
|
||||
.then(view => {
|
||||
view.render();
|
||||
|
||||
setTimeout(() => {
|
||||
fetch(dataUrl)
|
||||
.then(result => result.blob())
|
||||
.then(blob => {
|
||||
resolve(
|
||||
new File([blob], 'avatar.jpg', {type: 'image/jpeg'})
|
||||
);
|
||||
});
|
||||
}, 10);
|
||||
});
|
||||
let cropped = false;
|
||||
|
||||
this.listenToOnce(view, 'remove', () => {
|
||||
if (!cropped) {
|
||||
setTimeout(() => this.render(), 10);
|
||||
this.listenToOnce(view, 'crop', dataUrl => {
|
||||
cropped = true;
|
||||
|
||||
reject();
|
||||
}
|
||||
|
||||
this.clearView('crop');
|
||||
});
|
||||
setTimeout(() => {
|
||||
fetch(dataUrl)
|
||||
.then(result => result.blob())
|
||||
.then(blob => {
|
||||
resolve(
|
||||
new File([blob], 'avatar.jpg', {type: 'image/jpeg'})
|
||||
);
|
||||
});
|
||||
}, 10);
|
||||
});
|
||||
};
|
||||
|
||||
fileReader.readAsDataURL(file);
|
||||
this.listenToOnce(view, 'remove', () => {
|
||||
if (!cropped) {
|
||||
setTimeout(() => this.render(), 10);
|
||||
|
||||
reject();
|
||||
}
|
||||
|
||||
this.clearView('crop');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
fileReader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
getValueForDisplay() {
|
||||
if (!this.isReadMode()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const id = this.model.get(this.idName);
|
||||
const userId = this.model.id;
|
||||
|
||||
let t = this.cacheTimestamp = this.cacheTimestamp || Date.now();
|
||||
|
||||
if (this.suspendCache) {
|
||||
t = Date.now();
|
||||
}
|
||||
|
||||
const src = this.getBasePath() +
|
||||
'?entryPoint=avatar&size=' + this.previewSize + '&id=' + userId +
|
||||
'&t=' + t + '&attachmentId=' + (id || 'false');
|
||||
|
||||
// noinspection HtmlRequiredAltAttribute,RequiredAttributes
|
||||
const $img = $('<img>')
|
||||
.attr('src', src)
|
||||
.attr('alt', this.labelText)
|
||||
.css({
|
||||
maxWidth: (this.imageSizes[this.previewSize] || {})[0],
|
||||
maxHeight: (this.imageSizes[this.previewSize] || {})[1],
|
||||
});
|
||||
},
|
||||
|
||||
getValueForDisplay: function () {
|
||||
if (!this.isReadMode()) {
|
||||
return '';
|
||||
if (!this.isDetailMode()) {
|
||||
if (this.getCache()) {
|
||||
t = this.getCache().get('app', 'timestamp');
|
||||
}
|
||||
|
||||
let id = this.model.get(this.idName);
|
||||
let userId = this.model.id;
|
||||
const src = `${this.getBasePath()}?entryPoint=avatar&size=${this.previewSize}&id=${userId}&t=${t}`;
|
||||
|
||||
let t = this.cacheTimestamp = this.cacheTimestamp || Date.now();
|
||||
|
||||
if (this.suspendCache) {
|
||||
t = Date.now();
|
||||
}
|
||||
|
||||
let src = this.getBasePath() +
|
||||
'?entryPoint=avatar&size=' + this.previewSize + '&id=' + userId +
|
||||
'&t=' + t + '&attachmentId=' + (id || 'false');
|
||||
|
||||
let $img = $('<img>')
|
||||
$img
|
||||
.attr('width', '16')
|
||||
.attr('src', src)
|
||||
.css({
|
||||
maxWidth: (this.imageSizes[this.previewSize] || {})[0],
|
||||
maxHeight: (this.imageSizes[this.previewSize] || {})[1],
|
||||
});
|
||||
.css('maxWidth', '16px');
|
||||
}
|
||||
|
||||
if (!this.isDetailMode()) {
|
||||
if (this.getCache()) {
|
||||
t = this.getCache().get('app', 'timestamp');
|
||||
}
|
||||
|
||||
let src = this.getBasePath() + '?entryPoint=avatar&size=' +
|
||||
this.previewSize + '&id=' + userId + '&t=' + t;
|
||||
|
||||
$img
|
||||
.attr('width', '16')
|
||||
.attr('src', src)
|
||||
.css('maxWidth', '16px');
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
return $img
|
||||
.get(0)
|
||||
.outerHTML;
|
||||
}
|
||||
|
||||
return $('<a>')
|
||||
.attr('data-id', id)
|
||||
.attr('data-action', 'showImagePreview')
|
||||
.attr('href', this.getBasePath() + '?entryPoint=image&id=' + id)
|
||||
.append($img)
|
||||
if (!id) {
|
||||
return $img
|
||||
.get(0)
|
||||
.outerHTML;
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return $('<a>')
|
||||
.attr('data-id', id)
|
||||
.attr('data-action', 'showImagePreview')
|
||||
.attr('href', this.getBasePath() + '?entryPoint=image&id=' + id)
|
||||
.append($img)
|
||||
.get(0)
|
||||
.outerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
export default UserAvatarFieldView;
|
||||
|
||||
@@ -26,140 +26,153 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/user/fields/generate-password', ['views/fields/base'], function (Dep) {
|
||||
import BaseFieldView from 'views/fields/base';
|
||||
|
||||
return Dep.extend({
|
||||
class UserGeneratePasswordFieldView extends BaseFieldView {
|
||||
|
||||
templateContent: '<button type="button" class="btn btn-default" data-action="generatePassword">' +
|
||||
'{{translate \'Generate\' scope=\'User\'}}</button>',
|
||||
templateContent = `
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
data-action="generatePassword"
|
||||
>{{translate 'Generate' scope='User'}}</button>`
|
||||
|
||||
events: {
|
||||
'click [data-action="generatePassword"]': function () {
|
||||
this.actionGeneratePassword();
|
||||
},
|
||||
events = {
|
||||
/** @this {UserGeneratePasswordFieldView} */
|
||||
'click [data-action="generatePassword"]': function () {
|
||||
this.actionGeneratePassword();
|
||||
},
|
||||
}
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
this.listenTo(this.model, 'change:password', (model, value, o) => {
|
||||
if (o.isGenerated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.set({
|
||||
passwordPreview: '',
|
||||
});
|
||||
});
|
||||
|
||||
this.strengthParams = this.options.strengthParams || {};
|
||||
|
||||
this.passwordStrengthLength = this.strengthParams.passwordStrengthLength ||
|
||||
this.getConfig().get('passwordStrengthLength');
|
||||
|
||||
this.passwordStrengthLetterCount = this.strengthParams.passwordStrengthLetterCount ||
|
||||
this.getConfig().get('passwordStrengthLetterCount');
|
||||
|
||||
this.passwordStrengthNumberCount = this.strengthParams.passwordStrengthNumberCount ||
|
||||
this.getConfig().get('passwordStrengthNumberCount');
|
||||
|
||||
this.passwordGenerateLength = this.strengthParams.passwordGenerateLength ||
|
||||
this.getConfig().get('passwordGenerateLength');
|
||||
|
||||
this.passwordGenerateLetterCount = this.strengthParams.passwordGenerateLetterCount ||
|
||||
this.getConfig().get('passwordGenerateLetterCount');
|
||||
|
||||
this.passwordGenerateNumberCount = this.strengthParams.passwordGenerateNumberCount ||
|
||||
this.getConfig().get('passwordGenerateNumberCount');
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
return {};
|
||||
},
|
||||
|
||||
actionGeneratePassword: function () {
|
||||
var length = this.passwordStrengthLength;
|
||||
var letterCount = this.passwordStrengthLetterCount;
|
||||
var numberCount = this.passwordStrengthNumberCount;
|
||||
|
||||
var generateLength = this.passwordGenerateLength || 10;
|
||||
var generateLetterCount = this.passwordGenerateLetterCount || 4;
|
||||
var generateNumberCount = this.passwordGenerateNumberCount || 2;
|
||||
|
||||
length = (typeof length === 'undefined') ? generateLength : length;
|
||||
letterCount = (typeof letterCount === 'undefined') ? generateLetterCount : letterCount;
|
||||
numberCount = (typeof numberCount === 'undefined') ? generateNumberCount : numberCount;
|
||||
|
||||
if (length < generateLength) length = generateLength;
|
||||
if (letterCount < generateLetterCount) letterCount = generateLetterCount;
|
||||
if (numberCount < generateNumberCount) numberCount = generateNumberCount;
|
||||
|
||||
var password = this.generatePassword(length, letterCount, numberCount, true);
|
||||
|
||||
this.model.set({
|
||||
password: password,
|
||||
passwordConfirm: password,
|
||||
passwordPreview: password,
|
||||
}, {isGenerated: true});
|
||||
},
|
||||
|
||||
generatePassword: function (length, letters, numbers, bothCases) {
|
||||
var chars = [
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
|
||||
'0123456789',
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
'abcdefghijklmnopqrstuvwxyz',
|
||||
];
|
||||
|
||||
var upperCase = 0;
|
||||
var lowerCase = 0;
|
||||
|
||||
if (bothCases) {
|
||||
upperCase = 1;
|
||||
lowerCase = 1;
|
||||
|
||||
if (letters >= 2) {
|
||||
letters = letters - 2;
|
||||
} else {
|
||||
letters = 0;
|
||||
}
|
||||
this.listenTo(this.model, 'change:password', (model, value, o) => {
|
||||
if (o.isGenerated) {
|
||||
return;
|
||||
}
|
||||
|
||||
var either = length - (letters + numbers + upperCase + lowerCase);
|
||||
if (value !== undefined) {
|
||||
this.model.set('passwordPreview', null);
|
||||
|
||||
if (either < 0) {
|
||||
either = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var setList = [letters, numbers, either, upperCase, lowerCase];
|
||||
this.model.unset('passwordPreview');
|
||||
});
|
||||
|
||||
var shuffle = function (array) {
|
||||
var currentIndex = array.length, temporaryValue, randomIndex;
|
||||
this.strengthParams = this.options.strengthParams || {};
|
||||
|
||||
while (0 !== currentIndex) {
|
||||
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex -= 1;
|
||||
temporaryValue = array[currentIndex];
|
||||
array[currentIndex] = array[randomIndex];
|
||||
array[randomIndex] = temporaryValue;
|
||||
}
|
||||
this.passwordStrengthLength = this.strengthParams.passwordStrengthLength ||
|
||||
this.getConfig().get('passwordStrengthLength');
|
||||
|
||||
return array;
|
||||
};
|
||||
this.passwordStrengthLetterCount = this.strengthParams.passwordStrengthLetterCount ||
|
||||
this.getConfig().get('passwordStrengthLetterCount');
|
||||
|
||||
var array = setList.map(
|
||||
function (len, i) {
|
||||
return Array(len).fill(chars[i]).map(
|
||||
function (x) {
|
||||
return x[Math.floor(Math.random() * x.length)];
|
||||
}
|
||||
).join('');
|
||||
}
|
||||
).concat();
|
||||
this.passwordStrengthNumberCount = this.strengthParams.passwordStrengthNumberCount ||
|
||||
this.getConfig().get('passwordStrengthNumberCount');
|
||||
|
||||
return shuffle(array).join('');
|
||||
},
|
||||
this.passwordGenerateLength = this.strengthParams.passwordGenerateLength ||
|
||||
this.getConfig().get('passwordGenerateLength');
|
||||
|
||||
});
|
||||
});
|
||||
this.passwordGenerateLetterCount = this.strengthParams.passwordGenerateLetterCount ||
|
||||
this.getConfig().get('passwordGenerateLetterCount');
|
||||
|
||||
this.passwordGenerateNumberCount = this.strengthParams.passwordGenerateNumberCount ||
|
||||
this.getConfig().get('passwordGenerateNumberCount');
|
||||
}
|
||||
|
||||
fetch() {
|
||||
return {};
|
||||
}
|
||||
|
||||
actionGeneratePassword() {
|
||||
let length = this.passwordStrengthLength;
|
||||
let letterCount = this.passwordStrengthLetterCount;
|
||||
let numberCount = this.passwordStrengthNumberCount;
|
||||
|
||||
const generateLength = this.passwordGenerateLength || 10;
|
||||
const generateLetterCount = this.passwordGenerateLetterCount || 4;
|
||||
const generateNumberCount = this.passwordGenerateNumberCount || 2;
|
||||
|
||||
length = (typeof length === 'undefined') ? generateLength : length;
|
||||
letterCount = (typeof letterCount === 'undefined') ? generateLetterCount : letterCount;
|
||||
numberCount = (typeof numberCount === 'undefined') ? generateNumberCount : numberCount;
|
||||
|
||||
if (length < generateLength) {
|
||||
length = generateLength;
|
||||
}
|
||||
|
||||
if (letterCount < generateLetterCount) {
|
||||
letterCount = generateLetterCount;
|
||||
}
|
||||
|
||||
if (numberCount < generateNumberCount) {
|
||||
numberCount = generateNumberCount;
|
||||
}
|
||||
|
||||
const password = this.generatePassword(length, letterCount, numberCount, true);
|
||||
|
||||
this.model.set({
|
||||
password: password,
|
||||
passwordConfirm: password,
|
||||
passwordPreview: password,
|
||||
}, {isGenerated: true});
|
||||
}
|
||||
|
||||
generatePassword(length, letters, numbers, bothCases) {
|
||||
const chars = [
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
|
||||
'0123456789',
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
'abcdefghijklmnopqrstuvwxyz',
|
||||
];
|
||||
|
||||
let upperCase = 0;
|
||||
let lowerCase = 0;
|
||||
|
||||
if (bothCases) {
|
||||
upperCase = 1;
|
||||
lowerCase = 1;
|
||||
|
||||
if (letters >= 2) {
|
||||
letters = letters - 2;
|
||||
} else {
|
||||
letters = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let either = length - (letters + numbers + upperCase + lowerCase);
|
||||
|
||||
if (either < 0) {
|
||||
either = 0;
|
||||
}
|
||||
|
||||
const setList = [letters, numbers, either, upperCase, lowerCase];
|
||||
|
||||
const shuffle = function (array) {
|
||||
let currentIndex = array.length, temporaryValue, randomIndex;
|
||||
|
||||
while (0 !== currentIndex) {
|
||||
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex -= 1;
|
||||
temporaryValue = array[currentIndex];
|
||||
array[currentIndex] = array[randomIndex];
|
||||
array[randomIndex] = temporaryValue;
|
||||
}
|
||||
|
||||
return array;
|
||||
};
|
||||
|
||||
const array = setList.map(
|
||||
(len, i) => Array(len).fill(chars[i]).map(
|
||||
x => x[Math.floor(Math.random() * x.length)]
|
||||
).join('')
|
||||
).concat();
|
||||
|
||||
return shuffle(array).join('');
|
||||
}
|
||||
}
|
||||
|
||||
export default UserGeneratePasswordFieldView;
|
||||
|
||||
@@ -26,122 +26,159 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
define('views/user/fields/password', ['views/fields/password'], function (Dep) {
|
||||
import PasswordFieldView from 'views/fields/password';
|
||||
|
||||
return Dep.extend({
|
||||
class UserPasswordFieldView extends PasswordFieldView {
|
||||
|
||||
validations: ['required', 'strength', 'confirm'],
|
||||
validations = [
|
||||
'required',
|
||||
'strength',
|
||||
'confirm',
|
||||
]
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
},
|
||||
init() {
|
||||
const tooltipItemList = [];
|
||||
|
||||
init: function () {
|
||||
var tooltipItemList = [];
|
||||
this.strengthParams = this.options.strengthParams || {
|
||||
passwordStrengthLength: this.getConfig().get('passwordStrengthLength'),
|
||||
passwordStrengthLetterCount: this.getConfig().get('passwordStrengthLetterCount'),
|
||||
passwordStrengthNumberCount: this.getConfig().get('passwordStrengthNumberCount'),
|
||||
passwordStrengthBothCases: this.getConfig().get('passwordStrengthBothCases'),
|
||||
};
|
||||
|
||||
this.strengthParams = this.options.strengthParams || {
|
||||
passwordStrengthLength: this.getConfig().get('passwordStrengthLength'),
|
||||
passwordStrengthLetterCount: this.getConfig().get('passwordStrengthLetterCount'),
|
||||
passwordStrengthNumberCount: this.getConfig().get('passwordStrengthNumberCount'),
|
||||
passwordStrengthBothCases: this.getConfig().get('passwordStrengthBothCases'),
|
||||
};
|
||||
const minLength = this.strengthParams.passwordStrengthLength;
|
||||
|
||||
var minLength = this.strengthParams.passwordStrengthLength;
|
||||
if (minLength) {
|
||||
tooltipItemList.push(
|
||||
'* ' + this.translate('passwordStrengthLength', 'messages', 'User').replace('{length}', minLength.toString())
|
||||
);
|
||||
if (minLength) {
|
||||
tooltipItemList.push(
|
||||
'* ' + this.translate('passwordStrengthLength', 'messages', 'User')
|
||||
.replace('{length}', minLength.toString())
|
||||
);
|
||||
}
|
||||
|
||||
const requiredLetterCount = this.strengthParams.passwordStrengthLetterCount;
|
||||
|
||||
if (requiredLetterCount) {
|
||||
tooltipItemList.push(
|
||||
'* ' + this.translate('passwordStrengthLetterCount', 'messages', 'User')
|
||||
.replace('{count}', requiredLetterCount.toString())
|
||||
);
|
||||
}
|
||||
|
||||
const requiredNumberCount = this.strengthParams.passwordStrengthNumberCount;
|
||||
|
||||
if (requiredNumberCount) {
|
||||
tooltipItemList.push(
|
||||
'* ' + this.translate('passwordStrengthNumberCount', 'messages', 'User')
|
||||
.replace('{count}', requiredNumberCount.toString())
|
||||
);
|
||||
}
|
||||
|
||||
const bothCases = this.strengthParams.passwordStrengthBothCases;
|
||||
|
||||
if (bothCases) {
|
||||
tooltipItemList.push(
|
||||
'* ' + this.translate('passwordStrengthBothCases', 'messages', 'User')
|
||||
);
|
||||
}
|
||||
|
||||
if (tooltipItemList.length) {
|
||||
this.tooltip = true;
|
||||
this.tooltipText = this.translate('Requirements', 'labels', 'User') + ':\n' + tooltipItemList.join('\n');
|
||||
}
|
||||
|
||||
super.init();
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
validateStrength() {
|
||||
if (!this.model.get(this.name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const password = this.model.get(this.name);
|
||||
const minLength = this.strengthParams.passwordStrengthLength;
|
||||
|
||||
if (minLength) {
|
||||
if (password.length < minLength) {
|
||||
const msg = this.translate('passwordStrengthLength', 'messages', 'User')
|
||||
.replace('{length}', minLength.toString());
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var requiredLetterCount = this.strengthParams.passwordStrengthLetterCount;
|
||||
if (requiredLetterCount) {
|
||||
tooltipItemList.push(
|
||||
'* ' + this.translate('passwordStrengthLetterCount', 'messages', 'User').replace('{count}', requiredLetterCount.toString())
|
||||
);
|
||||
}
|
||||
const requiredLetterCount = this.strengthParams.passwordStrengthLetterCount;
|
||||
|
||||
var requiredNumberCount = this.strengthParams.passwordStrengthNumberCount;
|
||||
if (requiredNumberCount) {
|
||||
tooltipItemList.push(
|
||||
'* ' + this.translate('passwordStrengthNumberCount', 'messages', 'User').replace('{count}', requiredNumberCount.toString())
|
||||
);
|
||||
}
|
||||
if (requiredLetterCount) {
|
||||
let letterCount = 0;
|
||||
|
||||
var bothCases = this.strengthParams.passwordStrengthBothCases;
|
||||
if (bothCases) {
|
||||
tooltipItemList.push(
|
||||
'* ' + this.translate('passwordStrengthBothCases', 'messages', 'User')
|
||||
);
|
||||
}
|
||||
|
||||
if (tooltipItemList.length) {
|
||||
this.tooltip = true;
|
||||
this.tooltipText = this.translate('Requirements', 'labels', 'User') + ':\n' + tooltipItemList.join('\n');
|
||||
}
|
||||
|
||||
Dep.prototype.init.call(this);
|
||||
},
|
||||
|
||||
validateStrength: function () {
|
||||
if (!this.model.get(this.name)) return;
|
||||
|
||||
var password = this.model.get(this.name);
|
||||
|
||||
var minLength = this.strengthParams.passwordStrengthLength;
|
||||
if (minLength) {
|
||||
if (password.length < minLength) {
|
||||
var msg = this.translate('passwordStrengthLength', 'messages', 'User').replace('{length}', minLength.toString());
|
||||
this.showValidationMessage(msg);
|
||||
return true;;
|
||||
password.split('').forEach(c => {
|
||||
if (c.toLowerCase() !== c.toUpperCase()) {
|
||||
letterCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (letterCount < requiredLetterCount) {
|
||||
const msg = this.translate('passwordStrengthLetterCount', 'messages', 'User')
|
||||
.replace('{count}', requiredLetterCount.toString());
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var requiredLetterCount = this.strengthParams.passwordStrengthLetterCount;
|
||||
if (requiredLetterCount) {
|
||||
var letterCount = 0;
|
||||
password.split('').forEach(function (c) {
|
||||
if (c.toLowerCase() !== c.toUpperCase()) letterCount++;
|
||||
}, this);
|
||||
const requiredNumberCount = this.strengthParams.passwordStrengthNumberCount;
|
||||
|
||||
if (letterCount < requiredLetterCount) {
|
||||
var msg = this.translate('passwordStrengthLetterCount', 'messages', 'User').replace('{count}', requiredLetterCount.toString());
|
||||
this.showValidationMessage(msg);
|
||||
return true;;
|
||||
if (requiredNumberCount) {
|
||||
let numberCount = 0;
|
||||
|
||||
password.split('').forEach((c) => {
|
||||
if (c >= '0' && c <= '9') {
|
||||
numberCount++;
|
||||
}
|
||||
});
|
||||
|
||||
if (numberCount < requiredNumberCount) {
|
||||
const msg = this.translate('passwordStrengthNumberCount', 'messages', 'User')
|
||||
.replace('{count}', requiredNumberCount.toString());
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var requiredNumberCount = this.strengthParams.passwordStrengthNumberCount;
|
||||
if (requiredNumberCount) {
|
||||
var numberCount = 0;
|
||||
password.split('').forEach(function (c) {
|
||||
if (c >= '0' && c <= '9') numberCount++;
|
||||
}, this);
|
||||
const bothCases = this.strengthParams.passwordStrengthBothCases;
|
||||
|
||||
if (numberCount < requiredNumberCount) {
|
||||
var msg = this.translate('passwordStrengthNumberCount', 'messages', 'User').replace('{count}', requiredNumberCount.toString());
|
||||
this.showValidationMessage(msg);
|
||||
return true;;
|
||||
if (bothCases) {
|
||||
let ucCount = 0;
|
||||
|
||||
password.split('').forEach((c) => {
|
||||
if (c.toLowerCase() !== c.toUpperCase() && c === c.toUpperCase()) {
|
||||
ucCount++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var bothCases = this.strengthParams.passwordStrengthBothCases;
|
||||
if (bothCases) {
|
||||
var ucCount = 0;
|
||||
password.split('').forEach(function (c) {
|
||||
if (c.toLowerCase() !== c.toUpperCase() && c === c.toUpperCase()) ucCount++;
|
||||
}, this);
|
||||
var lcCount = 0;
|
||||
password.split('').forEach(function (c) {
|
||||
if (c.toLowerCase() !== c.toUpperCase() && c === c.toLowerCase()) lcCount++;
|
||||
}, this);
|
||||
let lcCount = 0;
|
||||
|
||||
if (!ucCount || !lcCount) {
|
||||
var msg = this.translate('passwordStrengthBothCases', 'messages', 'User');
|
||||
this.showValidationMessage(msg);
|
||||
return true;
|
||||
password.split('').forEach(c => {
|
||||
if (c.toLowerCase() !== c.toUpperCase() && c === c.toLowerCase()) {
|
||||
lcCount++;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
if (!ucCount || !lcCount) {
|
||||
const msg = this.translate('passwordStrengthBothCases', 'messages', 'User');
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default UserPasswordFieldView;
|
||||
|
||||
@@ -124,11 +124,15 @@ class UserEditRecordView extends EditRecordView {
|
||||
|
||||
this.hideField('sendAccessInfo');
|
||||
|
||||
if (!this.model.has('sendAccessInfo')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.set('sendAccessInfo', false);
|
||||
}
|
||||
|
||||
controlSendAccessInfoFieldForNew() {
|
||||
let skipSettingTrue = this.recordHelper.getFieldStateParam('sendAccessInfo', 'hidden') === false;
|
||||
const skipSettingTrue = this.recordHelper.getFieldStateParam('sendAccessInfo', 'hidden') === false;
|
||||
|
||||
if (this.isPasswordSendable()) {
|
||||
this.showField('sendAccessInfo');
|
||||
@@ -173,7 +177,7 @@ class UserEditRecordView extends EditRecordView {
|
||||
getGridLayout(callback) {
|
||||
this.getHelper().layoutManager
|
||||
.get(this.model.entityType, this.options.layoutName || this.layoutName, simpleLayout => {
|
||||
let layout = Espo.Utils.cloneDeep(simpleLayout);
|
||||
const layout = Espo.Utils.cloneDeep(simpleLayout);
|
||||
|
||||
layout.push({
|
||||
"label": "Teams and Access Control",
|
||||
@@ -279,7 +283,7 @@ class UserEditRecordView extends EditRecordView {
|
||||
});
|
||||
}
|
||||
|
||||
let gridLayout = {
|
||||
const gridLayout = {
|
||||
type: 'record',
|
||||
layout: this.convertDetailLayout(layout),
|
||||
};
|
||||
@@ -302,7 +306,7 @@ class UserEditRecordView extends EditRecordView {
|
||||
}
|
||||
|
||||
fetch() {
|
||||
let data = super.fetch();
|
||||
const data = super.fetch();
|
||||
|
||||
if (!this.isNew) {
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user