url-multiple field

This commit is contained in:
Yuri Kuznetsov
2023-04-26 14:25:56 +03:00
parent 4aae685c6e
commit d62d4265a8
11 changed files with 264 additions and 13 deletions
@@ -40,7 +40,7 @@ class ArrayType
{
private const DEFAULT_MAX_ITEM_LENGTH = 100;
public function __construct(private Metadata $metadata, private Defs $defs)
public function __construct(protected Metadata $metadata, private Defs $defs)
{}
public function checkRequired(Entity $entity, string $field): bool
@@ -0,0 +1,55 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Classes\FieldValidators;
use Espo\ORM\Entity;
class UrlMultipleType extends ArrayType
{
private const MAX_ITEM_LENGTH = 255;
public function checkNoEmptyString(Entity $entity, string $field, ?bool $validationValue): bool
{
return parent::checkNoEmptyString($entity, $field, true);
}
public function checkMaxItemLength(Entity $entity, string $field, ?int $validationValue): bool
{
return parent::checkMaxItemLength($entity, $field, self::MAX_ITEM_LENGTH);
}
public function checkPattern(Entity $entity, string $field, ?string $validationValue): bool
{
/** @var string $pattern */
$pattern = $this->metadata->get(['app', 'regExpPatterns', 'uriOptionalProtocol', 'pattern']);
return parent::checkPattern($entity, $field, $pattern);
}
}
+14 -3
View File
@@ -30,20 +30,22 @@
namespace Espo\Repositories;
use Espo\Core\ORM\Entity as CoreEntity;
use Espo\Entities\ArrayValue as ArrayValueEntity;
use Espo\ORM\Entity;
use Espo\Core\Repositories\Database;
use RuntimeException;
use LogicException;
/**
* @extends Database<\Espo\Entities\ArrayValue>
* @extends Database<ArrayValueEntity>
*/
class ArrayValue extends Database
{
protected $hooksDisabled = true;
private const ITEM_MAX_LENGTH = 100;
public function storeEntityAttribute(CoreEntity $entity, string $attribute, bool $populateMode = false): void
{
if ($entity->getAttributeType($attribute) !== Entity::JSON_ARRAY) {
@@ -73,7 +75,6 @@ class ArrayValue extends Database
}
$valueList = array_unique($valueList);
$toSkipValueList = [];
$isTransaction = false;
@@ -104,6 +105,12 @@ class ArrayValue extends Database
}
}
$itemMaxLength = $this->entityManager
->getDefs()
->getEntity(ArrayValueEntity::ENTITY_TYPE)
->getField('value')
->getParam('maxLength') ?? self::ITEM_MAX_LENGTH;
foreach ($valueList as $value) {
if (in_array($value, $toSkipValueList)) {
continue;
@@ -113,6 +120,10 @@ class ArrayValue extends Database
continue;
}
if (strlen($value) > $itemMaxLength) {
$value = substr($value, 0, $itemMaxLength);
}
$arrayValue = $this->getNew();
$arrayValue->set([
@@ -136,6 +136,7 @@
"phone": "Phone",
"text": "Text",
"url": "Url",
"urlMultiple": "Url Multiple",
"varchar": "Varchar",
"file": "File",
"image": "Image",
@@ -144,6 +144,7 @@
"array": "A list of values, similar to Multi-Enum field.",
"address": "An address with street, city, state, postal code and country.",
"url": "For storing links.",
"urlMultiple": "Multiple links.",
"wysiwyg": "A text with HTML support.",
"file": "For file uploading.",
"image": "For image uploading.",
@@ -308,6 +308,7 @@
"fieldNotMatchingPattern$latinLettersDigitsWhitespace": "{field} can contain only latin letters, digits and whitespace",
"fieldNotMatchingPattern$latinLettersWhitespace": "{field} can contain only latin letters and whitespace",
"fieldNotMatchingPattern$digits": "{field} can contain only digits",
"fieldNotMatchingPattern$uriOptionalProtocol": "{field} must be a valid URL",
"fieldInvalid": "{field} is invalid",
"fieldIsRequired": "{field} is required",
"fieldPhoneInvalidCharacters": "Only digits, latin letters and characters `-+_@:#().` are allowed",
@@ -247,6 +247,10 @@
"checklist": {
"view": "views/admin/dynamic-logic/conditions/field-types/multi-enum",
"typeList": ["isEmpty", "isNotEmpty", "has", "notHas"]
},
"urlMultiple": {
"view": "views/admin/dynamic-logic/conditions/field-types/multi-enum",
"typeList": ["isEmpty", "isNotEmpty"]
}
},
"conditionTypes": {
@@ -317,4 +321,4 @@
"valueType": "varchar"
}
}
}
}
@@ -0,0 +1,54 @@
{
"params": [
{
"name": "required",
"type": "bool",
"default": false
},
{
"name": "maxCount",
"type": "int",
"min": 1,
"tooltip": true
},
{
"name": "strip",
"type": "bool",
"default": false,
"tooltip": "urlStrip"
},
{
"name": "audited",
"type": "bool"
},
{
"name": "readOnly",
"type": "bool"
}
],
"validationList": [
"required",
"maxCount"
],
"mandatoryValidationList": [
"array",
"arrayOfString",
"valid",
"maxItemLength",
"pattern",
"noEmptyString"
],
"filter": true,
"notCreatable": false,
"notSortable": true,
"fieldDefs": {
"type": "jsonArray",
"storeArrayValues": true
},
"personalData": true,
"massUpdateActionList": [
"update",
"add",
"remove"
]
}
+3 -2
View File
@@ -386,7 +386,7 @@ function (Dep, RegExpPattern, /** module:ui/multi-select*/MultiSelect) {
this.trigger('change');
},
distance: 5,
cancel: 'input,textarea,button,select,option,a',
cancel: 'input,textarea,button,select,option,a[role="button"]',
cursor: 'grabbing',
});
}
@@ -604,9 +604,10 @@ function (Dep, RegExpPattern, /** module:ui/multi-select*/MultiSelect) {
)
.append(
$('<span>')
.addClass('text')
.text(text)
.append('&nbsp;')
)
.append('')
.get(0)
.outerHTML;
},
+125
View File
@@ -0,0 +1,125 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/fields/url-multiple', ['views/fields/array'], function (Dep) {
/**
* An Url-Multiple field.
*
* @class
* @name Class
* @extends module:views/fields/array.Class
* @memberOf module:views/fields/url-multiple
*/
return Dep.extend(/** @lends module:views/fields/url-multiple.Class# */{
type: 'urlMultiple',
maxItemLength: 255,
displayAsList: true,
defaultProtocol: 'https:',
setup: function () {
Dep.prototype.setup.call(this);
this.noEmptyString = true;
this.params.pattern = '$uriOptionalProtocol';
},
addValueFromUi: function (value) {
value = value.trim();
if (this.params.strip) {
value = this.strip(value);
}
if (value === decodeURI(value)) {
value = encodeURI(value);
}
Dep.prototype.addValueFromUi.call(this, value);
},
/**
* @param {string} value
* @return {string}
*/
strip: function (value) {
if (value.indexOf('//') !== -1) {
value = value.substring(value.indexOf('//') + 2);
}
value = value.replace(/\/+$/, '');
return value;
},
prepareUrl: function (url) {
if (url.indexOf('//') === -1) {
url = this.defaultProtocol + '//' + url;
}
return url;
},
getValueForDisplay: function () {
/** @type {JQuery[]} */
let $list = this.selected.map(value => {
return $('<a>')
.attr('href', this.prepareUrl(value))
.attr('target', '_blank')
.text(decodeURI(value));
});
return $list
.map($item =>
$('<div>')
.addClass('multi-enum-item-container')
.append($item)
.get(0).outerHTML
)
.join('');
},
getItemHtml: function (value) {
let html = Dep.prototype.getItemHtml.call(this, value);
let $item = $(html);
$item.find('span.text').html(
$('<a>')
.attr('href', this.prepareUrl(value))
.css('user-drag', 'none')
.attr('target', '_blank')
.text(decodeURI(value))
);
return $item.get(0).outerHTML;
},
});
});
+4 -6
View File
@@ -1132,8 +1132,6 @@ input.global-search-input {
}
}
.link-container {
> .list-group-item {
.link-item-column {
@@ -1151,18 +1149,18 @@ input.global-search-input {
}
}
.filter .field .link-container .list-group-item a {
.filter .field .link-container .list-group-item a[role="button"] {
margin-top: 1px;
margin-left: 2px;
}
.field .link-container .list-group-item a {
.field .link-container .list-group-item a[role="button"] {
margin-top: 1px;
margin-left: 2px;
}
.field .link-container > .list-group-item.link-with-role > a,
.field .link-container > .list-group-item.link-with-role > div > a {
.field .link-container > .list-group-item.link-with-role > a[role="button"],
.field .link-container > .list-group-item.link-with-role > div > a[role="button"] {
margin-top: 6px;
}