Compare commits
126 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e7f61b46d | |||
| c5e41faa0f | |||
| 465377c67b | |||
| f41cc85cba | |||
| fdf08624cb | |||
| 5c89f4f389 | |||
| acce8f1b1f | |||
| f1382e802e | |||
| f64e59b0de | |||
| 5b41abe76f | |||
| f286d2277e | |||
| b38c983e73 | |||
| a70d133471 | |||
| bc3d488bfe | |||
| e9578d8d44 | |||
| 4c38d96730 | |||
| b962d1572a | |||
| 3f1f686bd1 | |||
| 611bbb8fb2 | |||
| b89b39cd44 | |||
| 9a6e2d6578 | |||
| 9f63c00f5c | |||
| f11b9c0bbc | |||
| 6ea109f712 | |||
| 1c3dc61264 | |||
| c415ce677d | |||
| 927a580dce | |||
| 8e3b44c2e1 | |||
| 4048b7207b | |||
| a87231552b | |||
| a234f503a1 | |||
| 55be5b12b2 | |||
| 1f400649f5 | |||
| e6f65440f2 | |||
| def8455d78 | |||
| 1ad2144432 | |||
| c2828e3273 | |||
| b1f272215a | |||
| 032199d9c7 | |||
| cce78ac258 | |||
| 67074f2b0a | |||
| ea3189ebc3 | |||
| 83e7e0b517 | |||
| 2c1fed8adf | |||
| 45e6f3bb34 | |||
| f53708c38e | |||
| d1aa3f721d | |||
| 0afe715d9a | |||
| 9cbe31300a | |||
| dde139f0a1 | |||
| 5fb8b2c71c | |||
| 63c07a1f17 | |||
| a338bbca59 | |||
| 157bf87d40 | |||
| 911a9f80de | |||
| c9bbbeb2c4 | |||
| 57803c0828 | |||
| 3d8be61fbf | |||
| 690e87a3fb | |||
| cd45f07e54 | |||
| a35f9625af | |||
| f5245ef3eb | |||
| 4ad9ef770b | |||
| acbc74d858 | |||
| 8da3350523 | |||
| 496879dd79 | |||
| 84cda80fd8 | |||
| 50a81b1247 | |||
| f0f225f349 | |||
| d49fff1289 | |||
| abaa1b302b | |||
| 0eaa7825ea | |||
| 1028b7c5b3 | |||
| f319e5219d | |||
| 0daae5ced5 | |||
| abee63d269 | |||
| be33d90986 | |||
| 3a7cac824d | |||
| 7d616b075f | |||
| b718765138 | |||
| 3104be7c99 | |||
| a8d182b4cd | |||
| 3bfc99c88b | |||
| ac3300a5cf | |||
| 636587a22c | |||
| 4ed2c71dfd | |||
| 723229d8e6 | |||
| 080ab488fa | |||
| fd32696c5a | |||
| 9a56858f12 | |||
| 5923757810 | |||
| 62948a7740 | |||
| 821d57ce00 | |||
| efd0505ead | |||
| 91c55965a7 | |||
| 974305e152 | |||
| 016abaf7b6 | |||
| f3ec50dcbd | |||
| baeee7caf5 | |||
| 56280224f9 | |||
| 4b3e341606 | |||
| 58fad48d5e | |||
| 841a73b886 | |||
| f74ae46ec9 | |||
| 542d78425c | |||
| 7b241b90a0 | |||
| de77b1b1d0 | |||
| 8199692df7 | |||
| 1d3a340d3a | |||
| d664f29388 | |||
| 4d0e1af000 | |||
| 97ea0c71e1 | |||
| d9fbcda231 | |||
| f4b5cfa5b6 | |||
| 4391c7a7ac | |||
| 6c14f390f6 | |||
| 4a6829cf10 | |||
| 2b6c493be5 | |||
| 597406f70d | |||
| 110e2fbc37 | |||
| 3e1fab487a | |||
| c76e34fe81 | |||
| eb922103a8 | |||
| 9b946c6a1f | |||
| e2df819e57 | |||
| 9b00d50079 |
@@ -39,6 +39,16 @@ class User extends \Espo\Core\Acl\Base
|
||||
return $user->id === $entity->id;
|
||||
}
|
||||
|
||||
public function checkEntityRead(EntityUser $user, Entity $entity, $data)
|
||||
{
|
||||
if (!$user->isAdmin() && $entity->isPortal()) {
|
||||
if ($this->getAclManager()->get($user, 'portalPermission') === 'yes') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return $this->checkEntity($user, $entity, $data, 'read');
|
||||
}
|
||||
|
||||
public function checkEntityCreate(EntityUser $user, Entity $entity, $data)
|
||||
{
|
||||
if (!$user->isAdmin()) {
|
||||
|
||||
@@ -75,7 +75,7 @@ class Email extends \Espo\Core\Controllers\Record
|
||||
throw new NotFound();
|
||||
}
|
||||
if (!$this->getUser()->isAdmin()) {
|
||||
if ($emailAccount->get('assigniedUserId') !== $this->getUser()->id) {
|
||||
if ($emailAccount->get('assignedUserId') !== $this->getUser()->id) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,8 +91,10 @@ class DataManager
|
||||
*/
|
||||
public function rebuildDatabase($entityList = null)
|
||||
{
|
||||
$schema = $this->getContainer()->get('schema');
|
||||
|
||||
try {
|
||||
$result = $this->getContainer()->get('schema')->rebuild($entityList);
|
||||
$result = $schema->rebuild($entityList);
|
||||
} catch (\Exception $e) {
|
||||
$result = false;
|
||||
$GLOBALS['log']->error('Fault to rebuild database schema'.'. Details: '.$e->getMessage());
|
||||
@@ -102,6 +104,18 @@ class DataManager
|
||||
throw new Exceptions\Error("Error while rebuilding database. See log file for details.");
|
||||
}
|
||||
|
||||
$config = $this->getContainer()->get('config');
|
||||
|
||||
$databaseType = strtolower($schema->getDatabaseHelper()->getDatabaseType());
|
||||
if (!$config->get('actualDatabaseType') || $config->get('actualDatabaseType') != $databaseType) {
|
||||
$config->set('actualDatabaseType', $databaseType);
|
||||
}
|
||||
|
||||
$databaseVersion = $schema->getDatabaseHelper()->getDatabaseVersion();
|
||||
if (!$config->get('actualDatabaseVersion') || $config->get('actualDatabaseVersion') != $databaseVersion) {
|
||||
$config->set('actualDatabaseVersion', $databaseVersion);
|
||||
}
|
||||
|
||||
$this->updateCacheTimestamp();
|
||||
|
||||
return $result;
|
||||
@@ -180,7 +194,7 @@ class DataManager
|
||||
public function updateCacheTimestamp()
|
||||
{
|
||||
$this->getContainer()->get('config')->updateCacheTimestamp();
|
||||
$this->getContainer()->get('config')->save();
|
||||
$this->getContainer()->get('config')->save(); /* correct rebuildDatabase() method when remove this line */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ class SumRelatedType extends \Espo\Core\Formula\Functions\Base
|
||||
$foreignSelectManager = $this->getInjection('selectManagerFactory')->create($foreignEntityType);
|
||||
|
||||
$foreignLink = $entity->getRelationParam($link, 'foreign');
|
||||
$foreignLinkAlias = $foreignLink . 'SumRelated';
|
||||
|
||||
if (empty($foreignLink)) {
|
||||
throw new Error("No foreign link for link {$link}.");
|
||||
@@ -95,29 +96,49 @@ class SumRelatedType extends \Espo\Core\Formula\Functions\Base
|
||||
$foreignSelectManager->applyFilter($filter, $selectParams);
|
||||
}
|
||||
|
||||
$selectParams['select'] = [[$foreignLink . '.id', 'foreignId'], 'SUM:' . $field];
|
||||
$selectParams['select'] = [[$foreignLinkAlias . '.id', 'foreignId'], 'SUM:' . $field];
|
||||
|
||||
if ($entity->getRelationType($link) === 'hasChildren') {
|
||||
$foreignSelectManager->addJoin([
|
||||
$entity->getEntityType(),
|
||||
$foreignLink,
|
||||
$foreignLinkAlias,
|
||||
[
|
||||
$foreignLink . '.id:' => $foreignLink . 'Id',
|
||||
$foreignLinkAlias . '.id:' => $foreignLink . 'Id',
|
||||
'deleted' => false,
|
||||
$foreignLink . '.id!=' => null,
|
||||
$foreignLinkAlias . '.id!=' => null,
|
||||
]
|
||||
], $selectParams);
|
||||
$selectParams['whereClause'][] = [$foreignLink . 'Type' => $entity->getEntityType()];
|
||||
|
||||
} else {
|
||||
$foreignSelectManager->addJoin($foreignLink, $selectParams);
|
||||
$foreignSelectManager->addJoin([$foreignLink, $foreignLinkAlias], $selectParams);
|
||||
}
|
||||
|
||||
$selectParams['groupBy'] = [$foreignLink . '.id'];
|
||||
if (!empty($selectParams['distinct'])) {
|
||||
$sqSelectParams = $selectParams;
|
||||
|
||||
$selectParams['whereClause'][] = [
|
||||
$foreignLink . '.id' => $entity->id
|
||||
];
|
||||
$sqSelectParams['whereClause'][] = [
|
||||
$foreignLinkAlias . '.id' => $entity->id
|
||||
];
|
||||
|
||||
$sqSelectParams['select'] = ['id'];
|
||||
unset($sqSelectParams['distinct']);
|
||||
unset($sqSelectParams['orderBy']);
|
||||
unset($sqSelectParams['order']);
|
||||
|
||||
$selectParams['whereClause'][] = [
|
||||
'id=s' => [
|
||||
'entityType' => $foreignEntityType,
|
||||
'selectParams' => $sqSelectParams,
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$selectParams['whereClause'][] = [
|
||||
$foreignLinkAlias . '.id' => $entity->id
|
||||
];
|
||||
}
|
||||
|
||||
$selectParams['groupBy'] = [$foreignLinkAlias . '.id'];
|
||||
|
||||
$entityManager->getRepository($foreignEntityType)->handleSelectParams($selectParams);
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class RelateType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
}
|
||||
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $item->value ?? [];
|
||||
|
||||
if (count($args) < 4) throw new Error("Formula: record\\relate: Not enough arguments.");
|
||||
|
||||
$entityType = $this->evaluate($args[0]);
|
||||
$id = $this->evaluate($args[1]);
|
||||
$link = $this->evaluate($item->value[2]);
|
||||
$foreignId = $this->evaluate($item->value[3]);
|
||||
|
||||
if (!$entityType) throw new Error("Formula record\\relate: Empty entityType.");
|
||||
if (!$id) return null;
|
||||
if (!$link) throw new Error("Formula record\\relate: Empty link.");
|
||||
if (!$foreignId) return null;
|
||||
|
||||
$em = $this->getInjection('entityManager');
|
||||
|
||||
if (!$em->hasRepository($entityType)) throw new Error("Formula: record\\relate: Repository does not exist.");
|
||||
|
||||
$entity = $em->getEntity($entityType, $id);
|
||||
if (!$entity) return null;
|
||||
|
||||
if ($em->getRepository($entityType)->isRelated($entity, $link, $foreignId))
|
||||
return true;
|
||||
|
||||
return $em->getRepository($entityType)->relate($entity, $link, $foreignId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Core\Formula\Functions\RecordGroup;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
class UnrelateType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
}
|
||||
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $item->value ?? [];
|
||||
|
||||
if (count($args) < 4) throw new Error("Formula: record\\unrelate: Not enough arguments.");
|
||||
|
||||
$entityType = $this->evaluate($args[0]);
|
||||
$id = $this->evaluate($args[1]);
|
||||
$link = $this->evaluate($item->value[2]);
|
||||
$foreignId = $this->evaluate($item->value[3]);
|
||||
|
||||
if (!$entityType) throw new Error("Formula record\\unrelate: Empty entityType.");
|
||||
if (!$id) return null;
|
||||
if (!$link) throw new Error("Formula record\\unrelate: Empty link.");
|
||||
if (!$foreignId) return null;
|
||||
|
||||
$em = $this->getInjection('entityManager');
|
||||
|
||||
if (!$em->hasRepository($entityType)) throw new Error("Formula: record\\unrelate: Repository does not exist.");
|
||||
|
||||
$entity = $em->getEntity($entityType, $id);
|
||||
if (!$entity) return null;
|
||||
|
||||
if (!$em->getRepository($entityType)->isRelated($entity, $link, $foreignId))
|
||||
return true;
|
||||
|
||||
return $em->getRepository($entityType)->unrelate($entity, $link, $foreignId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Core\Formula\Functions\StringGroup;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
class PosType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
$args = $item->value ?? [];
|
||||
|
||||
if (count($args) < 2) throw new Error("Bad arguments passed to function string\\pos.");
|
||||
|
||||
$string = $this->evaluate($args[0]);
|
||||
$needle = $this->evaluate($args[1]);
|
||||
|
||||
if (!is_string($string)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mb_strpos($string, $needle);
|
||||
}
|
||||
}
|
||||
@@ -115,10 +115,12 @@ class Parser
|
||||
$braceCounter = 0;
|
||||
|
||||
for ($i = 0; $i < strlen($string); $i++) {
|
||||
$isStringStart = false;
|
||||
if ($string[$i] === "'" && ($i === 0 || $string[$i - 1] !== "\\")) {
|
||||
if (!$isString) {
|
||||
$isString = true;
|
||||
$isSingleQuote = true;
|
||||
$isStringStart = true;
|
||||
} else {
|
||||
if ($isSingleQuote) {
|
||||
$isString = false;
|
||||
@@ -127,6 +129,7 @@ class Parser
|
||||
} else if ($string[$i] === "\"" && ($i === 0 || $string[$i - 1] !== "\\")) {
|
||||
if (!$isString) {
|
||||
$isString = true;
|
||||
$isStringStart = true;
|
||||
$isSingleQuote = false;
|
||||
} else {
|
||||
if (!$isSingleQuote) {
|
||||
@@ -137,6 +140,8 @@ class Parser
|
||||
if ($isString) {
|
||||
if ($string[$i] === '(' || $string[$i] === ')') {
|
||||
$modifiedString[$i] = '_';
|
||||
} else if (!$isStringStart) {
|
||||
$modifiedString[$i] = ' ';
|
||||
}
|
||||
} else {
|
||||
if ($string[$i] === '(') {
|
||||
@@ -176,6 +181,16 @@ class Parser
|
||||
|
||||
$this->processStrings($expression, $modifiedExpression, $splitterIndexList, true);
|
||||
|
||||
$this->stripComments($expression, $modifiedExpression);
|
||||
|
||||
foreach ($splitterIndexList as $i => $index) {
|
||||
if ($expression[$index] !== ';') {
|
||||
unset($splitterIndexList[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
$splitterIndexList = array_values($splitterIndexList);
|
||||
|
||||
$expressionOutOfBraceList = [];
|
||||
|
||||
for ($i = 0; $i < strlen($modifiedExpression); $i++) {
|
||||
@@ -402,6 +417,43 @@ class Parser
|
||||
}
|
||||
}
|
||||
|
||||
protected function stripComments(&$expression, &$modifiedExpression)
|
||||
{
|
||||
$commentIndexStart = null;
|
||||
|
||||
for ($i = 0; $i < strlen($modifiedExpression); $i++) {
|
||||
if (is_null($commentIndexStart)) {
|
||||
if ($modifiedExpression[$i] === '/' && $i < strlen($modifiedExpression) - 1 && $modifiedExpression[$i + 1] === '/') {
|
||||
$commentIndexStart = $i;
|
||||
}
|
||||
} else {
|
||||
if ($modifiedExpression[$i] === "\n" || $i === strlen($modifiedExpression) - 1) {
|
||||
for ($j = $commentIndexStart; $j <= $i; $j++) {
|
||||
$modifiedExpression[$j] = ' ';
|
||||
$expression[$j] = ' ';
|
||||
}
|
||||
$commentIndexStart = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 0; $i < strlen($modifiedExpression) - 1; $i++) {
|
||||
if (is_null($commentIndexStart)) {
|
||||
if ($modifiedExpression[$i] === '/' && $modifiedExpression[$i + 1] === '*') {
|
||||
$commentIndexStart = $i;
|
||||
}
|
||||
} else {
|
||||
if ($modifiedExpression[$i] === '*' && $modifiedExpression[$i + 1] === '/') {
|
||||
for ($j = $commentIndexStart; $j <= $i + 1; $j++) {
|
||||
$modifiedExpression[$j] = ' ';
|
||||
$expression[$j] = ' ';
|
||||
}
|
||||
$commentIndexStart = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseArgumentListFromFunctionContent($functionContent)
|
||||
{
|
||||
$functionContent = trim($functionContent);
|
||||
|
||||
@@ -35,26 +35,33 @@ use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Utils\File\Manager as FileManager;
|
||||
use Espo\Core\Utils\DateTime;
|
||||
use Espo\Core\Utils\NumberUtil;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
require('vendor/zordius/lightncandy/src/lightncandy.php');
|
||||
|
||||
class Htmlizer
|
||||
{
|
||||
protected $fileManager;
|
||||
|
||||
protected $dateTime;
|
||||
|
||||
protected $config;
|
||||
|
||||
protected $acl;
|
||||
|
||||
protected $entityManager;
|
||||
|
||||
protected $metadata;
|
||||
|
||||
protected $language;
|
||||
|
||||
public function __construct(FileManager $fileManager, DateTime $dateTime, NumberUtil $number, $acl = null, $entityManager = null, $metadata = null, $language = null)
|
||||
public function __construct(
|
||||
FileManager $fileManager,
|
||||
DateTime $dateTime,
|
||||
NumberUtil $number,
|
||||
$acl = null,
|
||||
?EntityManager $entityManager = null,
|
||||
?Metadata $metadata = null,
|
||||
?Language $language = null,
|
||||
?Config $config = null
|
||||
)
|
||||
{
|
||||
$this->fileManager = $fileManager;
|
||||
$this->dateTime = $dateTime;
|
||||
@@ -63,6 +70,7 @@ class Htmlizer
|
||||
$this->entityManager = $entityManager;
|
||||
$this->metadata = $metadata;
|
||||
$this->language = $language;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
protected function getAcl()
|
||||
@@ -92,7 +100,7 @@ class Htmlizer
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function getDataFromEntity(Entity $entity, $skipLinks = false, $level = 0)
|
||||
protected function getDataFromEntity(Entity $entity, $skipLinks = false, $level = 0, ?string $template = null)
|
||||
{
|
||||
$data = $entity->toArray();
|
||||
|
||||
@@ -118,15 +126,32 @@ class Htmlizer
|
||||
|
||||
if (!$skipLinks && $level === 0) {
|
||||
foreach ($relationList as $relation) {
|
||||
if (!$entity->hasLinkMultipleField($relation)) continue;
|
||||
$collection = null;
|
||||
|
||||
$collection = $entity->getLinkMultipleCollection($relation);
|
||||
$data[$relation] = $collection;
|
||||
if ($entity->hasLinkMultipleField($relation)) {
|
||||
$toLoad = true;
|
||||
$collection = $entity->getLinkCollection($relation);
|
||||
} else {
|
||||
if (
|
||||
$template && $entity->getRelationType($relation, ['hasMany', 'manyMany', 'hasChildren']) &&
|
||||
mb_stripos($template, '{{#each '.$relation.'}}') !== false
|
||||
) {
|
||||
$limit = 100;
|
||||
if ($this->config) {
|
||||
$limit = $this->config->get('htmlizerLinkLimit') ?? $limit;
|
||||
}
|
||||
$collection = $entity->getLinkCollection($relation, ['limit' => $limit]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($collection) {
|
||||
$data[$relation] = $collection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if ($value instanceof \Espo\ORM\EntityCollection) {
|
||||
if ($value instanceof \Espo\ORM\ICollection) {
|
||||
$skipAttributeList[] = $key;
|
||||
$collection = $value;
|
||||
$list = [];
|
||||
@@ -280,7 +305,7 @@ class Htmlizer
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
],
|
||||
'hbhelpers' => [
|
||||
'ifEqual' => function () {
|
||||
@@ -300,7 +325,19 @@ class Htmlizer
|
||||
} else {
|
||||
return $context['inverse'] ? $context['inverse']() : '';
|
||||
}
|
||||
}
|
||||
},
|
||||
'ifInArray' => function () {
|
||||
$args = func_get_args();
|
||||
$context = $args[count($args) - 1];
|
||||
|
||||
$array = $args[1] ?? [];
|
||||
|
||||
if (in_array($args[0], $array)) {
|
||||
return $context['fn']();
|
||||
} else {
|
||||
return $context['inverse'] ? $context['inverse']() : '';
|
||||
}
|
||||
},
|
||||
]
|
||||
]);
|
||||
|
||||
@@ -319,7 +356,7 @@ class Htmlizer
|
||||
$this->fileManager->removeFile($fileName);
|
||||
}
|
||||
|
||||
$data = $this->getDataFromEntity($entity, $skipLinks);
|
||||
$data = $this->getDataFromEntity($entity, $skipLinks, 0, $template);
|
||||
|
||||
if (!array_key_exists('today', $data)) {
|
||||
$data['today'] = $this->dateTime->getTodayString();
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Core\Loaders;
|
||||
|
||||
class AuthenticationFactory extends Base
|
||||
{
|
||||
public function load()
|
||||
{
|
||||
$obj = new \Espo\Core\Utils\Authentication\Utils\AuthenticationFactory(
|
||||
$this->getContainer()
|
||||
);
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
@@ -212,56 +212,70 @@ class Importer
|
||||
if ($parser->checkMessageAttribute($message, 'in-Reply-To') && $parser->getMessageAttribute($message, 'in-Reply-To')) {
|
||||
$arr = explode(' ', $parser->getMessageAttribute($message, 'in-Reply-To'));
|
||||
$inReplyTo = $arr[0];
|
||||
$replied = $this->getEntityManager()->getRepository('Email')->where(array(
|
||||
'messageId' => $inReplyTo
|
||||
))->findOne();
|
||||
if ($replied) {
|
||||
$email->set('repliedId', $replied->id);
|
||||
$repliedTeamIdList = $replied->getLinkMultipleIdList('teams');
|
||||
foreach ($repliedTeamIdList as $repliedTeamId) {
|
||||
$email->addLinkMultipleId('teams', $repliedTeamId);
|
||||
|
||||
if ($inReplyTo) {
|
||||
if ($inReplyTo[0] !== '<') $inReplyTo = '<' . $inReplyTo . '>';
|
||||
$replied = $this->getEntityManager()->getRepository('Email')->where(array(
|
||||
'messageId' => $inReplyTo
|
||||
))->findOne();
|
||||
if ($replied) {
|
||||
$email->set('repliedId', $replied->id);
|
||||
$repliedTeamIdList = $replied->getLinkMultipleIdList('teams');
|
||||
foreach ($repliedTeamIdList as $repliedTeamId) {
|
||||
$email->addLinkMultipleId('teams', $repliedTeamId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($parser->checkMessageAttribute($message, 'references') && $parser->getMessageAttribute($message, 'references')) {
|
||||
$arr = explode(' ', $parser->getMessageAttribute($message, 'references'));
|
||||
$reference = $arr[0];
|
||||
$reference = str_replace(array('/', '@'), " ", trim($reference, '<>'));
|
||||
$parentType = $parentId = null;
|
||||
$emailSent = PHP_INT_MAX;
|
||||
$number = null;
|
||||
$n = sscanf($reference, '%s %s %d %d espo', $parentType, $parentId, $emailSent, $number);
|
||||
if ($n != 4) {
|
||||
$n = sscanf($reference, '%s %s %d %d espo-system', $parentType, $parentId, $emailSent, $number);
|
||||
$references = $parser->getMessageAttribute($message, 'references');
|
||||
$delimiter = ' ';
|
||||
if (strpos($references, '>,')) {
|
||||
$delimiter = ',';
|
||||
}
|
||||
if ($n == 4 && $emailSent < time()) {
|
||||
if (!empty($parentType) && !empty($parentId)) {
|
||||
if ($parentType == 'Lead') {
|
||||
$parent = $this->getEntityManager()->getEntity('Lead', $parentId);
|
||||
if ($parent && $parent->get('status') == 'Converted') {
|
||||
if ($parent->get('createdAccountId')) {
|
||||
$account = $this->getEntityManager()->getEntity('Account', $parent->get('createdAccountId'));
|
||||
if ($account) {
|
||||
$parentType = 'Account';
|
||||
$parentId = $account->id;
|
||||
}
|
||||
} else {
|
||||
if ($this->getConfig()->get('b2cMode')) {
|
||||
if ($parent->get('createdContactId')) {
|
||||
$contact = $this->getEntityManager()->getEntity('Contact', $parent->get('createdContactId'));
|
||||
if ($contact) {
|
||||
$parentType = 'Contact';
|
||||
$parentId = $contact->id;
|
||||
|
||||
$arr = explode($delimiter, $references);
|
||||
|
||||
foreach ($arr as $reference) {
|
||||
$reference = trim($reference);
|
||||
$reference = str_replace(['/', '@'], " ", trim($reference, '<>'));
|
||||
$parentType = $parentId = null;
|
||||
$emailSent = PHP_INT_MAX;
|
||||
$number = null;
|
||||
$n = sscanf($reference, '%s %s %d %d espo', $parentType, $parentId, $emailSent, $number);
|
||||
if ($n != 4) {
|
||||
$n = sscanf($reference, '%s %s %d %d espo-system', $parentType, $parentId, $emailSent, $number);
|
||||
}
|
||||
|
||||
if ($n == 4 && $emailSent < time()) {
|
||||
if (!empty($parentType) && !empty($parentId)) {
|
||||
if ($parentType == 'Lead') {
|
||||
$parent = $this->getEntityManager()->getEntity('Lead', $parentId);
|
||||
if ($parent && $parent->get('status') == 'Converted') {
|
||||
if ($parent->get('createdAccountId')) {
|
||||
$account = $this->getEntityManager()->getEntity('Account', $parent->get('createdAccountId'));
|
||||
if ($account) {
|
||||
$parentType = 'Account';
|
||||
$parentId = $account->id;
|
||||
}
|
||||
} else {
|
||||
if ($this->getConfig()->get('b2cMode')) {
|
||||
if ($parent->get('createdContactId')) {
|
||||
$contact = $this->getEntityManager()->getEntity('Contact', $parent->get('createdContactId'));
|
||||
if ($contact) {
|
||||
$parentType = 'Contact';
|
||||
$parentId = $contact->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$email->set('parentType', $parentType);
|
||||
$email->set('parentId', $parentId);
|
||||
$parentFound = true;
|
||||
}
|
||||
$email->set('parentType', $parentType);
|
||||
$email->set('parentId', $parentId);
|
||||
$parentFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,13 @@ class MailMimeParser
|
||||
|
||||
public function getMessageMessageId($message)
|
||||
{
|
||||
return $this->getMessageAttribute($message, 'Message-ID');
|
||||
$messageId = $this->getMessageAttribute($message, 'Message-ID');
|
||||
|
||||
if ($messageId && strlen($messageId) && $messageId[0] !== '<') {
|
||||
$messageId = '<' . $messageId . '>';
|
||||
}
|
||||
|
||||
return $messageId;
|
||||
}
|
||||
|
||||
public function getAddressNameMap($message)
|
||||
|
||||
@@ -74,22 +74,33 @@ class Entity extends \Espo\ORM\Entity
|
||||
}
|
||||
}
|
||||
|
||||
public function getLinkMultipleCollection($field)
|
||||
public function getLinkCollection(string $link, ?array $selectParams = null)
|
||||
{
|
||||
if (!$this->hasLinkMultipleField($field)) return;
|
||||
if (!$selectParams) $selectParams = [];
|
||||
|
||||
$defs = $this->getRelationSelectParams($field);
|
||||
$relSelectParams = $this->getRelationSelectParams($link);
|
||||
|
||||
$columnAttribute = $field . 'Columns';
|
||||
$selectParams = array_merge($selectParams, $relSelectParams);
|
||||
|
||||
$selectParams['returnSthCollection'] = true;
|
||||
|
||||
$columnAttribute = $link . 'Columns';
|
||||
if ($this->hasAttribute($columnAttribute) && $this->getAttributeParam($columnAttribute, 'columns')) {
|
||||
$defs['additionalColumns'] = $this->getAttributeParam($columnAttribute, 'columns');
|
||||
$selectParams['additionalColumns'] = $this->getAttributeParam($columnAttribute, 'columns');
|
||||
}
|
||||
|
||||
$collection = $this->get($field, $defs);
|
||||
$collection = $this->get($link, $selectParams);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function getLinkMultipleCollection(string $link, ?array $selectParams = null)
|
||||
{
|
||||
if (!$this->hasLinkMultipleField($link)) return;
|
||||
|
||||
return $this->getLinkCollection($link, $selectParams);
|
||||
}
|
||||
|
||||
protected function getRelationSelectParams($link)
|
||||
{
|
||||
$field = $link;
|
||||
|
||||
@@ -240,4 +240,33 @@ class Tcpdf extends \TCPDF
|
||||
$this->_out($out);
|
||||
}
|
||||
|
||||
public function Output($name = 'doc.pdf', $dest = 'I')
|
||||
{
|
||||
if ($dest === 'I' && !$this->sign && php_sapi_name() != 'cli') {
|
||||
if ($this->state < 3) {
|
||||
$this->Close();
|
||||
}
|
||||
$name = preg_replace('/[\s]+/', '_', $name);
|
||||
$name = \Espo\Core\Utils\Util::sanitizeFileName($name);
|
||||
|
||||
if (ob_get_contents()) {
|
||||
$this->Error('Some data has already been output, can\'t send PDF file');
|
||||
}
|
||||
|
||||
header('Content-Type: application/pdf');
|
||||
if (headers_sent()) {
|
||||
$this->Error('Some data has already been output to browser, can\'t send PDF file');
|
||||
}
|
||||
header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
|
||||
header('Pragma: public');
|
||||
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
|
||||
header('Content-Disposition: inline; filename="'.$name.'"');
|
||||
TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return parent::Output($name, $dest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,12 +70,19 @@ class Base
|
||||
|
||||
protected $selectAttributesDependancyMap = [];
|
||||
|
||||
protected $fullTextSearchForceOrderOnlyByRelevance = false;
|
||||
protected $fullTextOrderType = self::FT_ORDER_COMBINTED;
|
||||
|
||||
protected $fullTextRelevanceThreshold = null;
|
||||
|
||||
const FT_ORDER_COMBINTED = 0;
|
||||
const FT_ORDER_RELEVANCE = 1;
|
||||
const FT_ORDER_ORIGINAL = 3;
|
||||
|
||||
const MIN_LENGTH_FOR_CONTENT_SEARCH = 4;
|
||||
|
||||
const MIN_LENGTH_FOR_FULL_TEXT_SEARCH = 4;
|
||||
|
||||
protected $fullTextOrderRelevanceDivider = 5;
|
||||
|
||||
protected $fullTextSearchDataCacheHash = [];
|
||||
|
||||
public function __construct(EntityManager $entityManager, \Espo\Entities\User $user, Acl $acl, AclManager $aclManager, Metadata $metadata, Config $config, FieldManagerUtil $fieldManagerUtil, InjectableFactory $injectableFactory)
|
||||
@@ -186,6 +193,12 @@ class Base
|
||||
$result['orderBy'] = 'LIST:' . $sortBy . ':' . implode(',', $list);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (strpos($sortBy, '.') === false && strpos($sortBy, ':') === false) {
|
||||
if (!$this->getSeed()->hasAttribute($sortBy)) {
|
||||
throw new Error("Order by non-existing field '{$sortBy}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$desc) {
|
||||
@@ -356,12 +369,16 @@ class Base
|
||||
if ($relationType == 'belongsTo') {
|
||||
$key = $seed->getRelationParam($link, 'key');
|
||||
|
||||
$aliasName = 'usersTeams' . ucfirst($link);
|
||||
$aliasName = 'usersTeams' . ucfirst($link) . strval(rand(10000, 99999));
|
||||
|
||||
$result['customJoin'] .= "
|
||||
JOIN team_user AS {$aliasName}Middle ON {$aliasName}Middle.user_id = ".$query->toDb($seed->getEntityType()).".".$query->toDb($key)." AND {$aliasName}Middle.deleted = 0
|
||||
JOIN team AS {$aliasName} ON {$aliasName}.deleted = 0 AND {$aliasName}Middle.team_id = {$aliasName}.id
|
||||
";
|
||||
$this->addLeftJoin([
|
||||
'TeamUser',
|
||||
$aliasName . 'Middle',
|
||||
[
|
||||
$aliasName . 'Middle.userId:' => $key,
|
||||
$aliasName . 'Middle.deleted' => false,
|
||||
]
|
||||
], $result);
|
||||
|
||||
$result['whereClause'][] = [
|
||||
$aliasName . 'Middle.teamId' => $idsValue
|
||||
@@ -1728,6 +1745,7 @@ class Base
|
||||
case 'arrayNoneOf':
|
||||
case 'arrayIsEmpty':
|
||||
case 'arrayIsNotEmpty':
|
||||
case 'arrayAllOf':
|
||||
if (!$result) break;
|
||||
|
||||
$arrayValueAlias = 'arrayFilter' . strval(rand(10000, 99999));
|
||||
@@ -1759,6 +1777,8 @@ class Base
|
||||
$arrayValueAlias . '.attribute' => $arrayAttribute
|
||||
]], $result);
|
||||
$part[$arrayValueAlias . '.value'] = $value;
|
||||
|
||||
$this->setDistinct(true, $result);
|
||||
} else if ($type === 'arrayNoneOf') {
|
||||
if (is_null($value) || !$value && !is_array($value)) break;
|
||||
$this->addLeftJoin(['ArrayValue', $arrayValueAlias, [
|
||||
@@ -1768,6 +1788,8 @@ class Base
|
||||
$arrayValueAlias . '.value=' => $value
|
||||
]], $result);
|
||||
$part[$arrayValueAlias . '.id'] = null;
|
||||
|
||||
$this->setDistinct(true, $result);
|
||||
} else if ($type === 'arrayIsEmpty') {
|
||||
$this->addLeftJoin(['ArrayValue', $arrayValueAlias, [
|
||||
$arrayValueAlias . '.entityId:' => $idPart,
|
||||
@@ -1775,6 +1797,8 @@ class Base
|
||||
$arrayValueAlias . '.attribute' => $arrayAttribute
|
||||
]], $result);
|
||||
$part[$arrayValueAlias . '.id'] = null;
|
||||
|
||||
$this->setDistinct(true, $result);
|
||||
} else if ($type === 'arrayIsNotEmpty') {
|
||||
$this->addLeftJoin(['ArrayValue', $arrayValueAlias, [
|
||||
$arrayValueAlias . '.entityId:' => $idPart,
|
||||
@@ -1782,9 +1806,31 @@ class Base
|
||||
$arrayValueAlias . '.attribute' => $arrayAttribute
|
||||
]], $result);
|
||||
$part[$arrayValueAlias . '.id!='] = null;
|
||||
}
|
||||
|
||||
$this->setDistinct(true, $result);
|
||||
$this->setDistinct(true, $result);
|
||||
} else if ($type === 'arrayAllOf') {
|
||||
if (is_null($value) || !$value && !is_array($value)) break;
|
||||
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
foreach ($value as $arrayValue) {
|
||||
$part[] = [
|
||||
$idPart .'=s' => [
|
||||
'entityType' => 'ArrayValue',
|
||||
'selectParams' => [
|
||||
'select' => ['entityId'],
|
||||
'whereClause' => [
|
||||
'value' => $arrayValue,
|
||||
'attribute' => $arrayAttribute,
|
||||
'entityType' => $arrayEntityType,
|
||||
],
|
||||
],
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2133,7 +2179,7 @@ class Base
|
||||
$fullTextSearchColumnSanitizedList[$i] = $query->sanitize($query->toDb($field));
|
||||
}
|
||||
|
||||
$where = $function . ':' . implode(',', $fullTextSearchColumnSanitizedList) . ':' . $textFilter;
|
||||
$where = $function . ':(' . implode(',', $fullTextSearchColumnSanitizedList) . ',' . $textFilter . ')';
|
||||
|
||||
$result = [
|
||||
'where' => $where,
|
||||
@@ -2200,21 +2246,36 @@ class Base
|
||||
|
||||
$fullTextSearchFieldList = [];
|
||||
if ($fullTextSearchData) {
|
||||
$fullTextGroup[] = $fullTextSearchData['where'];
|
||||
if ($this->fullTextRelevanceThreshold) {
|
||||
$fullTextGroup[] = [$fullTextSearchData['where'] . '>=' => $this->fullTextRelevanceThreshold];
|
||||
} else {
|
||||
$fullTextGroup[] = $fullTextSearchData['where'];
|
||||
}
|
||||
|
||||
$fullTextSearchFieldList = $fullTextSearchData['fieldList'];
|
||||
|
||||
if (isset($result['orderBy']) && !$this->fullTextSearchForceOrderOnlyByRelevance) {
|
||||
if (is_string($result['orderBy'])) {
|
||||
$result['orderBy'] = [
|
||||
[$fullTextSearchData['where'], 'desc'],
|
||||
[$result['orderBy'], $result['order'] ?? 'asc'],
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$result['orderBy'] = [[$fullTextSearchData['where'], 'desc']];
|
||||
$relevanceExpression = $fullTextSearchData['where'];
|
||||
|
||||
if (!isset($result['orderBy']) || $this->fullTextOrderType === self::FT_ORDER_RELEVANCE) {
|
||||
$result['orderBy'] = [[$relevanceExpression, 'desc']];
|
||||
$result['order'] = null;
|
||||
} else {
|
||||
if ($this->fullTextOrderType === self::FT_ORDER_COMBINTED) {
|
||||
$relevanceExpression =
|
||||
'ROUND:(DIV:(' . $fullTextSearchData['where'] . ','.$this->fullTextOrderRelevanceDivider.'))';
|
||||
|
||||
if (is_string($result['orderBy'])) {
|
||||
$result['orderBy'] = [
|
||||
[$relevanceExpression, 'desc'],
|
||||
[$result['orderBy'], $result['order'] ?? 'asc'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result['additionalSelect'] = $result['additionalSelect'] ?? [];
|
||||
$result['additionalSelect'][] = $relevanceExpression;
|
||||
|
||||
$result['hasFullTextSearch'] = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,12 @@
|
||||
"entity": "Task",
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"emails": {
|
||||
"type": "hasChildren",
|
||||
"entity": "Email",
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
|
||||
@@ -245,7 +245,7 @@ abstract class Base
|
||||
$isInRange = false;
|
||||
try {
|
||||
$isInRange = Semver::satisfies($currentVersion, $version);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$GLOBALS['log']->error('SemVer: Version identification error: '.$e->getMessage().'.');
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ abstract class Base
|
||||
|
||||
try {
|
||||
$script->run($this->getContainer(), $this->scriptParams);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->throwErrorAndRemovePackage($e->getMessage());
|
||||
}
|
||||
}
|
||||
@@ -494,7 +494,7 @@ abstract class Base
|
||||
{
|
||||
try {
|
||||
$res = $this->getFileManager()->copy($sourcePath, $destPath, $recursively, $fileList, $copyOnlyFiles);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->throwErrorAndRemovePackage($e->getMessage());
|
||||
}
|
||||
|
||||
@@ -706,7 +706,7 @@ abstract class Base
|
||||
{
|
||||
try {
|
||||
return $this->getContainer()->get('dataManager')->rebuild();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$GLOBALS['log']->error('Database rebuild failure, details: '.$e->getMessage().'.');
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
|
||||
|
||||
if (!isset($data['skipSystemRebuild']) || !$data['skipSystemRebuild']) {
|
||||
if (!$this->systemRebuild()) {
|
||||
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
|
||||
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild. Please see the log for more detail.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
|
||||
|
||||
if (!isset($data['skipSystemRebuild']) || !$data['skipSystemRebuild']) {
|
||||
if (!$this->systemRebuild()) {
|
||||
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
|
||||
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild. Please see the log for more detail.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ class Output
|
||||
echo $data;
|
||||
}
|
||||
|
||||
public function processError(string $message = 'Error', int $statusCode = 500, bool $toPrint = false, $exception = null)
|
||||
public function processError(string $message = 'Error', $statusCode = 500, bool $toPrint = false, $exception = null)
|
||||
{
|
||||
$currentRoute = $this->getSlim()->router()->getCurrentRoute();
|
||||
|
||||
@@ -99,7 +99,7 @@ class Output
|
||||
$this->displayError($message, $statusCode, $toPrint, $exception);
|
||||
}
|
||||
|
||||
public function displayError(string $text, int $statusCode = 500, bool $toPrint = false, $exception = null)
|
||||
public function displayError(string $text, $statusCode = 500, bool $toPrint = false, $exception = null)
|
||||
{
|
||||
$logLevel = 'error';
|
||||
$messageLineFile = null;
|
||||
|
||||
@@ -77,20 +77,7 @@ class Auth
|
||||
|
||||
protected function getAuthenticationImpl(string $method) : \Espo\Core\Utils\Authentication\Base
|
||||
{
|
||||
$className = $this->getMetadata()->get([
|
||||
'authenticationMethods', $method, 'implementationClassName'
|
||||
]);
|
||||
|
||||
if (!$className) {
|
||||
$sanitizedName = preg_replace('/[^a-zA-Z0-9]+/', '', $method);
|
||||
|
||||
$className = "\\Espo\\Custom\\Core\\Utils\\Authentication\\" . $sanitizedName;
|
||||
if (!class_exists($className)) {
|
||||
$className = "\\Espo\\Core\\Utils\\Authentication\\" . $sanitizedName;
|
||||
}
|
||||
}
|
||||
|
||||
return new $className($this->getConfig(), $this->getEntityManager(), $this, $this->getContainer());
|
||||
return $this->getContainer()->get('authenticationFactory')->create($method);
|
||||
}
|
||||
|
||||
protected function get2FAImpl(string $method) : \Espo\Core\Utils\Authentication\TwoFA\Base
|
||||
|
||||
@@ -29,11 +29,13 @@
|
||||
|
||||
namespace Espo\Core\Utils\Authentication;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Entities\AuthToken;
|
||||
|
||||
class ApiKey extends Base
|
||||
{
|
||||
public function login($username, $password, $authToken = null, $params = [], $request)
|
||||
public function login(string $username, $password, ?AuthToken $authToken = null, array $params = [], $request = null)
|
||||
{
|
||||
$apiKey = $username;
|
||||
|
||||
@@ -41,7 +43,7 @@ class ApiKey extends Base
|
||||
'whereClause' => [
|
||||
'type' => 'api',
|
||||
'apiKey' => $apiKey,
|
||||
'authMethod' => 'ApiKey'
|
||||
'authMethod' => 'ApiKey',
|
||||
]
|
||||
]);
|
||||
|
||||
|
||||
@@ -35,19 +35,16 @@ use \Espo\Core\Utils\Auth;
|
||||
|
||||
abstract class Base
|
||||
{
|
||||
private $config;
|
||||
protected $config;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
private $auth;
|
||||
protected $entityManager;
|
||||
|
||||
private $passwordHash;
|
||||
|
||||
public function __construct(Config $config, EntityManager $entityManager, Auth $auth)
|
||||
public function __construct(Config $config, EntityManager $entityManager)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->auth = $auth;
|
||||
}
|
||||
|
||||
protected function getConfig()
|
||||
@@ -60,11 +57,6 @@ abstract class Base
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
protected function getAuth()
|
||||
{
|
||||
return $this->auth;
|
||||
}
|
||||
|
||||
protected function getPasswordHash()
|
||||
{
|
||||
if (!isset($this->passwordHash)) {
|
||||
@@ -74,4 +66,3 @@ abstract class Base
|
||||
return $this->passwordHash;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,11 +29,13 @@
|
||||
|
||||
namespace Espo\Core\Utils\Authentication;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Entities\AuthToken;
|
||||
|
||||
class Espo extends Base
|
||||
{
|
||||
public function login($username, $password, \Espo\Entities\AuthToken $authToken = null, $params = [], $request)
|
||||
public function login(string $username, $password, ?AuthToken $authToken = null, array $params = [], $request = null)
|
||||
{
|
||||
if (!$password) return;
|
||||
|
||||
@@ -47,7 +49,7 @@ class Espo extends Base
|
||||
'whereClause' => [
|
||||
'userName' => $username,
|
||||
'password' => $hash,
|
||||
'type!=' => ['api', 'system']
|
||||
'type!=' => ['api', 'system'],
|
||||
]
|
||||
]);
|
||||
|
||||
|
||||
@@ -29,11 +29,13 @@
|
||||
|
||||
namespace Espo\Core\Utils\Authentication;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Entities\AuthToken;
|
||||
|
||||
class Hmac extends Base
|
||||
{
|
||||
public function login($username, $password, $authToken = null, $params = [], $request)
|
||||
public function login(string $username, $password, ?AuthToken $authToken = null, array $params = [], $request)
|
||||
{
|
||||
$apiKey = $username;
|
||||
$hash = $password;
|
||||
|
||||
@@ -33,6 +33,7 @@ use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\ORM\EntityManager;
|
||||
use Espo\Core\Utils\Auth;
|
||||
use Espo\Entities\AuthToken;
|
||||
|
||||
class LDAP extends Espo
|
||||
{
|
||||
@@ -74,9 +75,9 @@ class LDAP extends Espo
|
||||
'portalRolesIds' => 'portalUserRolesIds',
|
||||
);
|
||||
|
||||
public function __construct(Config $config, EntityManager $entityManager, Auth $auth)
|
||||
public function __construct(Config $config, EntityManager $entityManager)
|
||||
{
|
||||
parent::__construct($config, $entityManager, $auth);
|
||||
parent::__construct($config, $entityManager);
|
||||
|
||||
$this->utils = new LDAP\Utils($config);
|
||||
}
|
||||
@@ -101,16 +102,7 @@ class LDAP extends Espo
|
||||
return $this->ldapClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP login
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param \Espo\Entities\AuthToken $authToken
|
||||
*
|
||||
* @return \Espo\Entities\User | null
|
||||
*/
|
||||
public function login($username, $password, \Espo\Entities\AuthToken $authToken = null, $params = [], $request)
|
||||
public function login(string $username, $password, ?AuthToken $authToken = null, array $params = [], $request = null)
|
||||
{
|
||||
if (!$password) return;
|
||||
|
||||
@@ -279,9 +271,17 @@ class LDAP extends Espo
|
||||
$data[$fieldName] = $fieldValue;
|
||||
}
|
||||
|
||||
$this->getAuth()->useNoAuth();
|
||||
$entityManager = $this->entityManager;
|
||||
|
||||
$user = $this->getEntityManager()->getEntity('User');
|
||||
$systemUser = $entityManager->getRepository('User')->get('system');
|
||||
if (!$systemUser) {
|
||||
throw new Error("System user is not found");
|
||||
}
|
||||
$systemUser->set('isAdmin', true);
|
||||
$systemUser->set('ipAddress', $_SERVER['REMOTE_ADDR']);
|
||||
$entityManager->setUser($systemUser);
|
||||
|
||||
$user = $entityManager->getEntity('User');
|
||||
$user->set($data);
|
||||
|
||||
$this->getEntityManager()->saveEntity($user);
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\Core\Utils\Authentication\Utils;
|
||||
|
||||
use Espo\Core\Container;
|
||||
|
||||
class AuthenticationFactory
|
||||
{
|
||||
protected $container;
|
||||
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function create(string $method) : \Espo\Core\Utils\Authentication\Base
|
||||
{
|
||||
$metadata = $this->container->get('metadata');
|
||||
|
||||
$className = $metadata->get(['authenticationMethods', $method, 'implementationClassName']);
|
||||
$dependencyList = $metadata->get(['authenticationMethods', $method, 'dependencyList']) ?? [];
|
||||
|
||||
if (!$className) {
|
||||
$sanitizedName = preg_replace('/[^a-zA-Z0-9]+/', '', $method);
|
||||
|
||||
$className = "\\Espo\\Custom\\Core\\Utils\\Authentication\\" . $sanitizedName;
|
||||
if (!class_exists($className)) {
|
||||
$className = "\\Espo\\Core\\Utils\\Authentication\\" . $sanitizedName;
|
||||
}
|
||||
}
|
||||
|
||||
$config = $this->container->get('config');
|
||||
$entityManager = $this->container->get('entityManager');
|
||||
|
||||
$impl = new $className($config, $entityManager);
|
||||
|
||||
foreach ($dependencyList as $item) {
|
||||
$impl->inject($item, $this->container->get($item));
|
||||
}
|
||||
|
||||
return $impl;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class Autoload
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
return Utill::getValueByKey($this->data, $key, $returns);
|
||||
return Util::getValueByKey($this->data, $key, $returns);
|
||||
}
|
||||
|
||||
public function getAll()
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Schema\TableDiff;
|
||||
use Doctrine\DBAL\Schema\ColumnDiff;
|
||||
|
||||
class Comparator extends \Doctrine\DBAL\Schema\Comparator
|
||||
{
|
||||
@@ -136,4 +139,130 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator
|
||||
return $changedProperties;
|
||||
}
|
||||
|
||||
public function diffTable(Table $table1, Table $table2)
|
||||
{
|
||||
$changes = 0;
|
||||
$tableDifferences = new TableDiff($table1->getName());
|
||||
$tableDifferences->fromTable = $table1;
|
||||
|
||||
$table1Columns = $table1->getColumns();
|
||||
$table2Columns = $table2->getColumns();
|
||||
|
||||
/* See if all the fields in table 1 exist in table 2 */
|
||||
foreach ( $table2Columns as $columnName => $column ) {
|
||||
if ( !$table1->hasColumn($columnName) ) {
|
||||
$tableDifferences->addedColumns[$columnName] = $column;
|
||||
$changes++;
|
||||
}
|
||||
}
|
||||
/* See if there are any removed fields in table 2 */
|
||||
foreach ( $table1Columns as $columnName => $column ) {
|
||||
if ( !$table2->hasColumn($columnName) ) {
|
||||
$tableDifferences->removedColumns[$columnName] = $column;
|
||||
$changes++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $table1Columns as $columnName => $column ) {
|
||||
if ( $table2->hasColumn($columnName) ) {
|
||||
$changedProperties = $this->diffColumn( $column, $table2->getColumn($columnName) );
|
||||
if (count($changedProperties) ) {
|
||||
$columnDiff = new ColumnDiff($column->getName(), $table2->getColumn($columnName), $changedProperties);
|
||||
$columnDiff->fromColumn = $column;
|
||||
$tableDifferences->changedColumns[$column->getName()] = $columnDiff;
|
||||
$changes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->detectColumnRenamings($tableDifferences);
|
||||
|
||||
$table1Indexes = $table1->getIndexes();
|
||||
$table2Indexes = $table2->getIndexes();
|
||||
|
||||
foreach ($table2Indexes as $index2Name => $index2Definition) {
|
||||
foreach ($table1Indexes as $index1Name => $index1Definition) {
|
||||
if ($this->diffIndex($index1Definition, $index2Definition) === false) {
|
||||
unset($table1Indexes[$index1Name]);
|
||||
unset($table2Indexes[$index2Name]);
|
||||
} else {
|
||||
if ($index1Name == $index2Name) {
|
||||
/*espo*/ if (isset($table2Indexes[$index2Name])) { /*espo*/
|
||||
$tableDifferences->changedIndexes[$index2Name] = $table2Indexes[$index2Name];
|
||||
unset($table1Indexes[$index1Name]);
|
||||
unset($table2Indexes[$index2Name]);
|
||||
$changes++;
|
||||
/*espo*/ } /*espo*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($table1Indexes as $index1Name => $index1Definition) {
|
||||
$tableDifferences->removedIndexes[$index1Name] = $index1Definition;
|
||||
$changes++;
|
||||
}
|
||||
|
||||
foreach ($table2Indexes as $index2Name => $index2Definition) {
|
||||
$tableDifferences->addedIndexes[$index2Name] = $index2Definition;
|
||||
$changes++;
|
||||
}
|
||||
|
||||
$fromFkeys = $table1->getForeignKeys();
|
||||
$toFkeys = $table2->getForeignKeys();
|
||||
|
||||
foreach ($fromFkeys as $key1 => $constraint1) {
|
||||
foreach ($toFkeys as $key2 => $constraint2) {
|
||||
if($this->diffForeignKey($constraint1, $constraint2) === false) {
|
||||
unset($fromFkeys[$key1]);
|
||||
unset($toFkeys[$key2]);
|
||||
} else {
|
||||
if (strtolower($constraint1->getName()) == strtolower($constraint2->getName())) {
|
||||
$tableDifferences->changedForeignKeys[] = $constraint2;
|
||||
$changes++;
|
||||
unset($fromFkeys[$key1]);
|
||||
unset($toFkeys[$key2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($fromFkeys as $constraint1) {
|
||||
$tableDifferences->removedForeignKeys[] = $constraint1;
|
||||
$changes++;
|
||||
}
|
||||
|
||||
foreach ($toFkeys as $constraint2) {
|
||||
$tableDifferences->addedForeignKeys[] = $constraint2;
|
||||
$changes++;
|
||||
}
|
||||
|
||||
return $changes ? $tableDifferences : false;
|
||||
}
|
||||
|
||||
private function detectColumnRenamings(TableDiff $tableDifferences)
|
||||
{
|
||||
$renameCandidates = array();
|
||||
foreach ($tableDifferences->addedColumns as $addedColumnName => $addedColumn) {
|
||||
foreach ($tableDifferences->removedColumns as $removedColumn) {
|
||||
if (count($this->diffColumn($addedColumn, $removedColumn)) == 0) {
|
||||
$renameCandidates[$addedColumn->getName()][] = array($removedColumn, $addedColumn, $addedColumnName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($renameCandidates as $candidateColumns) {
|
||||
if (count($candidateColumns) == 1) {
|
||||
list($removedColumn, $addedColumn) = $candidateColumns[0];
|
||||
$removedColumnName = strtolower($removedColumn->getName());
|
||||
$addedColumnName = strtolower($addedColumn->getName());
|
||||
|
||||
if ( ! isset($tableDifferences->renamedColumns[$removedColumnName])) {
|
||||
$tableDifferences->renamedColumns[$removedColumnName] = $addedColumn;
|
||||
unset($tableDifferences->addedColumns[$addedColumnName]);
|
||||
unset($tableDifferences->removedColumns[$removedColumnName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,19 +332,6 @@ class Converter
|
||||
|
||||
$table->setPrimaryKey(array("id"));
|
||||
|
||||
//add unique indexes
|
||||
if (!empty($relationParams['conditions'])) {
|
||||
foreach ($relationParams['conditions'] as $fieldName => $fieldParams) {
|
||||
$uniqueIndex[] = Util::toUnderScore($fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($uniqueIndex)) {
|
||||
$uniqueIndexName = implode('_', $uniqueIndex);
|
||||
$table->addUniqueIndex($uniqueIndex, SchemaUtils::generateIndexName($uniqueIndexName, 'unique'));
|
||||
}
|
||||
//END: add unique indexes
|
||||
|
||||
//add defined indexes
|
||||
if (!empty($relationParams['indexes'])) {
|
||||
$normalizedIndexes = SchemaUtils::getIndexList([
|
||||
@@ -358,6 +345,19 @@ class Converter
|
||||
}
|
||||
//END: add defined indexes
|
||||
|
||||
//add unique indexes
|
||||
if (!empty($relationParams['conditions'])) {
|
||||
foreach ($relationParams['conditions'] as $fieldName => $fieldParams) {
|
||||
$uniqueIndex[] = Util::toUnderScore($fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($uniqueIndex)) {
|
||||
$uniqueIndexName = implode('_', $uniqueIndex);
|
||||
$table->addUniqueIndex($uniqueIndex, SchemaUtils::generateIndexName($uniqueIndexName, 'unique'));
|
||||
}
|
||||
//END: add unique indexes
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,18 @@ class EntityManager
|
||||
|
||||
private $linkForbiddenNameList = ['posts', 'stream', 'subscription', 'followers', 'action', 'null', 'false', 'true'];
|
||||
|
||||
private $forbiddenEntityTypeNameList = ['Common', 'PortalUser', 'ApiUser', 'Timeline', 'About', 'Admin', 'Null', 'False', 'True'];
|
||||
private $forbiddenEntityTypeNameList = [
|
||||
'Common',
|
||||
'PortalUser',
|
||||
'ApiUser',
|
||||
'Timeline',
|
||||
'About',
|
||||
'Admin',
|
||||
'Null',
|
||||
'False',
|
||||
'True',
|
||||
'Base',
|
||||
];
|
||||
|
||||
public function __construct(Metadata $metadata, Language $language, File\Manager $fileManager, Config $config, Container $container = null)
|
||||
{
|
||||
|
||||
@@ -427,7 +427,11 @@ class FieldManager
|
||||
|
||||
protected function getFieldDefs($scope, $name, $default = null)
|
||||
{
|
||||
return $this->getMetadata()->get('entityDefs'.'.'.$scope.'.fields.'.$name, $default);
|
||||
$defs = $this->getMetadata()->getObjects(['entityDefs', $scope, 'fields', $name], $default);
|
||||
if (is_object($defs)) {
|
||||
return get_object_vars($defs);
|
||||
}
|
||||
return $defs;
|
||||
}
|
||||
|
||||
protected function getCustomFieldDefs($scope, $name, $default = null)
|
||||
@@ -532,7 +536,7 @@ class FieldManager
|
||||
}
|
||||
|
||||
$actualCustomFieldDefs = $this->getCustomFieldDefs($scope, $name, []);
|
||||
$actualFieldDefs = $this->getFieldDefs($scope, $name, []);
|
||||
$actualFieldDefs = $this->getFieldDefs($scope, $name, (object) []);
|
||||
$permittedParamList = array_keys($params);
|
||||
|
||||
$filteredFieldDefs = !empty($actualCustomFieldDefs) ? $actualCustomFieldDefs : [];
|
||||
|
||||
@@ -127,6 +127,8 @@ return [
|
||||
'webSocketUseSecureServer',
|
||||
'webSocketPort',
|
||||
'aclStrictMode',
|
||||
'actualDatabaseType',
|
||||
'actualDatabaseVersion',
|
||||
],
|
||||
'adminItems' => [
|
||||
'devMode',
|
||||
|
||||
@@ -39,7 +39,6 @@ class OauthCallback extends \Espo\Core\EntryPoints\Base
|
||||
|
||||
public function run()
|
||||
{
|
||||
echo "EspoCRM rocks !!!";
|
||||
echo "EspoCRM rocks! If this window is not closed automatically, it's probable that URL you use to access EspoCRM doesn't match URL specified at Administration > Settings > Site URL.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -371,48 +371,13 @@ class Stream extends \Espo\Core\Hooks\Base
|
||||
|
||||
if (
|
||||
$this->getMetadata()->get(['entityDefs', $entityType, 'links', $link, 'audited'])
|
||||
|
||||
) {
|
||||
$n = $this->getEntityManager()->getRepository('Note')->where(array(
|
||||
'type' => 'Relate',
|
||||
'parentId' => $entity->id,
|
||||
'parentType' => $entityType,
|
||||
'relatedId' => $foreignEntity->id,
|
||||
'relatedType' => $foreignEntity->getEntityType()
|
||||
))->findOne();
|
||||
if (!$n) {
|
||||
$note = $this->getEntityManager()->getEntity('Note');
|
||||
$note->set(array(
|
||||
'type' => 'Relate',
|
||||
'parentId' => $entity->id,
|
||||
'parentType' => $entityType,
|
||||
'relatedId' => $foreignEntity->id,
|
||||
'relatedType' => $foreignEntity->getEntityType()
|
||||
));
|
||||
$this->getEntityManager()->saveEntity($note);
|
||||
}
|
||||
$this->getStreamService()->noteRelate($foreignEntity, $entityType, $entity->id);
|
||||
}
|
||||
|
||||
$foreignLink = $entity->getRelationParam($link, 'foreign');
|
||||
if ($this->getMetadata()->get(['entityDefs', $foreignEntity->getEntityType(), 'links', $foreignLink, 'audited'])) {
|
||||
$n = $this->getEntityManager()->getRepository('Note')->where(array(
|
||||
'type' => 'Relate',
|
||||
'parentId' => $foreignEntity->id,
|
||||
'parentType' => $foreignEntity->getEntityType(),
|
||||
'relatedId' => $entity->id,
|
||||
'relatedType' => $entityType
|
||||
))->findOne();
|
||||
if (!$n) {
|
||||
$note = $this->getEntityManager()->getEntity('Note');
|
||||
$note->set(array(
|
||||
'type' => 'Relate',
|
||||
'parentId' => $foreignEntity->id,
|
||||
'parentType' => $foreignEntity->getEntityType(),
|
||||
'relatedId' => $entity->id,
|
||||
'relatedType' => $entityType
|
||||
));
|
||||
$this->getEntityManager()->saveEntity($note);
|
||||
}
|
||||
$this->getStreamService()->noteRelate($entity, $foreignEntity->getEntityType(), $foreignEntity->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,39 +32,41 @@ namespace Espo\Modules\Crm\Business\Distribution\CaseObj;
|
||||
class LeastBusy
|
||||
{
|
||||
protected $entityManager;
|
||||
protected $metadata;
|
||||
|
||||
public function __construct($entityManager)
|
||||
public function __construct($entityManager, $metadata)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
protected function getEntityManager()
|
||||
{
|
||||
return $this->entityManager;
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
public function getUser($team, $targetUserPosition = null)
|
||||
{
|
||||
$params = array();
|
||||
$selectParams = [
|
||||
'whereClause' => ['isActive' => true],
|
||||
'orderBy' => 'id',
|
||||
];
|
||||
|
||||
if (!empty($targetUserPosition)) {
|
||||
$params['additionalColumnsConditions'] = array(
|
||||
'role' => $targetUserPosition
|
||||
);
|
||||
$selectParams['additionalColumnsConditions'] = ['role' => $targetUserPosition];
|
||||
}
|
||||
|
||||
$userList = $team->get('users', $params);
|
||||
$userList = $team->get('users', $selectParams);
|
||||
|
||||
if (count($userList) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$countHash = array();
|
||||
$countHash = [];
|
||||
|
||||
$notActualStatusList =
|
||||
$this->metadata->get(['entityDefs', 'Case', 'fields', 'status', 'notActualOptions']) ?? [];
|
||||
|
||||
foreach ($userList as $user) {
|
||||
$count = $this->getEntityManager()->getRepository('Case')->where(array(
|
||||
$count = $this->entityManager->getRepository('Case')->where([
|
||||
'assignedUserId' => $user->id,
|
||||
'status<>' => ['Closed', 'Rejected', 'Duplicated']
|
||||
))->count();
|
||||
'status!=' => $notActualStatusList,
|
||||
])->count();
|
||||
$countHash[$user->id] = $count;
|
||||
}
|
||||
|
||||
@@ -83,8 +85,7 @@ class LeastBusy
|
||||
}
|
||||
|
||||
if ($foundUserId !== false) {
|
||||
return $this->getEntityManager()->getEntity('User', $foundUserId);
|
||||
return $this->entityManager->getEntity('User', $foundUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,29 +45,30 @@ class RoundRobin
|
||||
|
||||
public function getUser($team, $targetUserPosition = null)
|
||||
{
|
||||
$params = array();
|
||||
$selectParams = [
|
||||
'whereClause' => ['isActive' => true],
|
||||
'orderBy' => 'id',
|
||||
];
|
||||
|
||||
if (!empty($targetUserPosition)) {
|
||||
$params['additionalColumnsConditions'] = array(
|
||||
'role' => $targetUserPosition
|
||||
);
|
||||
$selectParams['additionalColumnsConditions'] = ['role' => $targetUserPosition];
|
||||
}
|
||||
|
||||
$userList = $team->get('users', $params);
|
||||
$userList = $team->get('users', $selectParams);
|
||||
|
||||
if (count($userList) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$userIdList = array();
|
||||
$userIdList = [];
|
||||
|
||||
foreach ($userList as $user) {
|
||||
$userIdList[] = $user->id;
|
||||
}
|
||||
|
||||
|
||||
$case = $this->getEntityManager()->getRepository('Case')->where(array(
|
||||
$case = $this->getEntityManager()->getRepository('Case')->where([
|
||||
'assignedUserId' => $userIdList,
|
||||
))->order('createdAt', 'DESC')->findOne();
|
||||
])->order('number', 'DESC')->findOne();
|
||||
|
||||
if (empty($case)) {
|
||||
$num = 0;
|
||||
@@ -80,7 +81,8 @@ class RoundRobin
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getEntityManager()->getEntity('User', $userIdList[$num]);
|
||||
$id = $userIdList[$num];
|
||||
|
||||
return $this->getEntityManager()->getEntity('User', $id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"aclDependencies": {
|
||||
"Lead.options.source": {
|
||||
"scope": "Opportunity",
|
||||
"field": "leadSource"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"aclDependencies": {
|
||||
"entityDefs.Lead.fields.source.options": {
|
||||
"scope": "Opportunity",
|
||||
"field": "leadSource"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,8 +310,7 @@
|
||||
"campaign": {
|
||||
"type": "belongsTo",
|
||||
"entity": "Campaign",
|
||||
"foreign": "accounts",
|
||||
"noJoin": true
|
||||
"foreign": "accounts"
|
||||
},
|
||||
"campaignLogRecords": {
|
||||
"type": "hasChildren",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"dateEnd": {
|
||||
"type": "datetime",
|
||||
"required": true,
|
||||
"view": "crm:views/call/fields/date-end",
|
||||
"after": "dateStart"
|
||||
},
|
||||
"duration": {
|
||||
|
||||
@@ -268,26 +268,22 @@
|
||||
"createdAccount": {
|
||||
"type": "belongsTo",
|
||||
"entity": "Account",
|
||||
"noJoin": true,
|
||||
"foreign": "originalLead"
|
||||
},
|
||||
"createdContact": {
|
||||
"type": "belongsTo",
|
||||
"entity": "Contact",
|
||||
"noJoin": true,
|
||||
"foreign": "originalLead"
|
||||
},
|
||||
"createdOpportunity": {
|
||||
"type": "belongsTo",
|
||||
"entity": "Opportunity",
|
||||
"noJoin": true,
|
||||
"foreign": "originalLead"
|
||||
},
|
||||
"campaign": {
|
||||
"type": "belongsTo",
|
||||
"entity": "Campaign",
|
||||
"foreign": "leads",
|
||||
"noJoin": true
|
||||
"foreign": "leads"
|
||||
},
|
||||
"campaignLogRecords": {
|
||||
"type": "hasChildren",
|
||||
|
||||
@@ -334,8 +334,7 @@
|
||||
"campaign": {
|
||||
"type": "belongsTo",
|
||||
"entity": "Campaign",
|
||||
"foreign": "opportunities",
|
||||
"noJoin": true
|
||||
"foreign": "opportunities"
|
||||
},
|
||||
"originalLead": {
|
||||
"type": "hasOne",
|
||||
|
||||
@@ -754,6 +754,13 @@ class Activities extends \Espo\Core\Services\Base
|
||||
$service = $this->getServiceFactory()->create($entityType);
|
||||
$selectManager = $this->getSelectManagerFactory()->create($entityType);
|
||||
|
||||
|
||||
if ($entityType === 'Email') {
|
||||
if ($params['orderBy'] ?? null === 'dateStart') {
|
||||
$params['orderBy'] = 'dateSent';
|
||||
}
|
||||
}
|
||||
|
||||
$selectParams = $selectManager->getSelectParams($params, false, true);
|
||||
|
||||
$selectAttributeList = $service->getSelectAttributeList($params);
|
||||
@@ -778,13 +785,6 @@ class Activities extends \Espo\Core\Services\Base
|
||||
unset($selectParams['order']);
|
||||
unset($selectParams['orderBy']);
|
||||
|
||||
if ($entityType === 'Email') {
|
||||
if ($orderBy === 'dateStart') {
|
||||
$orderBy = 'dateSent';
|
||||
$order = 'desc';
|
||||
}
|
||||
}
|
||||
|
||||
$sql = $this->getActivitiesQuery($entity, $entityType, $statusList, $isHistory, $selectParams);
|
||||
|
||||
$query = $this->getEntityManager()->getQuery();
|
||||
|
||||
@@ -163,6 +163,13 @@ abstract class Base
|
||||
'IN',
|
||||
'NOT_IN',
|
||||
'BINARY',
|
||||
'UNIX_TIMESTAMP',
|
||||
'TIMESTAMPDIFF_DAY',
|
||||
'TIMESTAMPDIFF_MONTH',
|
||||
'TIMESTAMPDIFF_YEAR',
|
||||
'TIMESTAMPDIFF_WEEK',
|
||||
'TIMESTAMPDIFF_HOUR',
|
||||
'TIMESTAMPDIFF_MINUTE',
|
||||
];
|
||||
|
||||
protected $multipleArgumentsFunctionList = [
|
||||
@@ -188,6 +195,12 @@ abstract class Base
|
||||
'MUL',
|
||||
'DIV',
|
||||
'MOD',
|
||||
'TIMESTAMPDIFF_DAY',
|
||||
'TIMESTAMPDIFF_MONTH',
|
||||
'TIMESTAMPDIFF_YEAR',
|
||||
'TIMESTAMPDIFF_WEEK',
|
||||
'TIMESTAMPDIFF_HOUR',
|
||||
'TIMESTAMPDIFF_MINUTE',
|
||||
];
|
||||
|
||||
protected $comparisonFunctionList = [
|
||||
@@ -553,6 +566,18 @@ abstract class Base
|
||||
break;
|
||||
case 'NOT':
|
||||
return 'NOT ' . $part;
|
||||
case 'TIMESTAMPDIFF_YEAR':
|
||||
return 'TIMESTAMPDIFF(YEAR, ' . implode(', ', $argumentPartList) . ')';
|
||||
case 'TIMESTAMPDIFF_MONTH':
|
||||
return 'TIMESTAMPDIFF(MONTH, ' . implode(', ', $argumentPartList) . ')';
|
||||
case 'TIMESTAMPDIFF_WEEK':
|
||||
return 'TIMESTAMPDIFF(WEEK, ' . implode(', ', $argumentPartList) . ')';
|
||||
case 'TIMESTAMPDIFF_DAY':
|
||||
return 'TIMESTAMPDIFF(DAY, ' . implode(', ', $argumentPartList) . ')';
|
||||
case 'TIMESTAMPDIFF_HOUR':
|
||||
return 'TIMESTAMPDIFF(HOUR, ' . implode(', ', $argumentPartList) . ')';
|
||||
case 'TIMESTAMPDIFF_MINUTE':
|
||||
return 'TIMESTAMPDIFF(MINUTE, ' . implode(', ', $argumentPartList) . ')';
|
||||
}
|
||||
|
||||
if ($distinct) {
|
||||
@@ -605,16 +630,31 @@ abstract class Base
|
||||
throw new \Exception("Empty MATCH parameters.");
|
||||
}
|
||||
|
||||
$delimiterPosition = strpos($rest, ':');
|
||||
if ($delimiterPosition === false) {
|
||||
throw new \Exception("Bad MATCH usage.");
|
||||
if (substr($rest, 0, 1) === '(' && substr($rest, -1) === ')') {
|
||||
$rest = substr($rest, 1, -1);
|
||||
|
||||
$argumentList = self::parseArgumentListFromFunctionContent($rest);
|
||||
if (count($argumentList) < 2) {
|
||||
throw new \Exception("Bad MATCH usage.");
|
||||
}
|
||||
|
||||
$columnList = [];
|
||||
for ($i = 0; $i < count($argumentList) - 1; $i++) {
|
||||
$columnList[] = $argumentList[$i];
|
||||
}
|
||||
$query = $argumentList[count($argumentList) - 1];
|
||||
} else {
|
||||
$delimiterPosition = strpos($rest, ':');
|
||||
if ($delimiterPosition === false) {
|
||||
throw new \Exception("Bad MATCH usage.");
|
||||
}
|
||||
|
||||
$columns = substr($rest, 0, $delimiterPosition);
|
||||
$query = mb_substr($rest, $delimiterPosition + 1);
|
||||
|
||||
$columnList = explode(',', $columns);
|
||||
}
|
||||
|
||||
$columns = substr($rest, 0, $delimiterPosition);
|
||||
$query = mb_substr($rest, $delimiterPosition + 1);
|
||||
|
||||
$columnList = explode(',', $columns);
|
||||
|
||||
$tableName = $this->toDb($entity->getEntityType());
|
||||
|
||||
foreach ($columnList as $i => $column) {
|
||||
@@ -929,15 +969,22 @@ abstract class Base
|
||||
$attributeList = array_keys($entity->fields);
|
||||
} else {
|
||||
$attributeList = $itemList;
|
||||
foreach ($attributeList as $i => $attribute) {
|
||||
if (is_string($attribute)) {
|
||||
if (strpos($attribute, ':')) {
|
||||
$attributeList[$i] = [
|
||||
$attribute,
|
||||
$attribute
|
||||
];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($params && isset($params['additionalSelect'])) {
|
||||
foreach ($params['additionalSelect'] as $item) {
|
||||
$attributeList[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($attributeList as $i => $attribute) {
|
||||
if (is_string($attribute)) {
|
||||
if (strpos($attribute, ':')) {
|
||||
$attributeList[$i] = [
|
||||
$attribute,
|
||||
$attribute
|
||||
];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,6 +448,15 @@ abstract class Entity implements IEntity
|
||||
|
||||
public function setFetched($name, $value)
|
||||
{
|
||||
if ($value) {
|
||||
$type = $this->getAttributeType($name);
|
||||
if ($type === self::JSON_OBJECT) {
|
||||
$value = self::cloneObject($value);
|
||||
} else if ($type === self::JSON_ARRAY) {
|
||||
$value = self::cloneArray($value);
|
||||
}
|
||||
}
|
||||
|
||||
$this->fetchedValuesContainer[$name] = $value;
|
||||
}
|
||||
|
||||
@@ -478,12 +487,16 @@ abstract class Entity implements IEntity
|
||||
public function updateFetchedValues()
|
||||
{
|
||||
$this->fetchedValuesContainer = $this->valuesContainer;
|
||||
|
||||
foreach ($this->fetchedValuesContainer as $attribute => $value) {
|
||||
$this->setFetched($attribute, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function setAsFetched()
|
||||
{
|
||||
$this->isFetched = true;
|
||||
$this->fetchedValuesContainer = $this->valuesContainer;
|
||||
$this->updateFetchedValues();
|
||||
}
|
||||
|
||||
public function setAsNotFetched()
|
||||
@@ -520,4 +533,45 @@ abstract class Entity implements IEntity
|
||||
{
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
protected function cloneArray($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$copy = [];
|
||||
foreach ($value as $v) {
|
||||
if (is_object($v)) {
|
||||
$v = clone $v;
|
||||
}
|
||||
$copy[] = $v;
|
||||
}
|
||||
return $copy;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function cloneObject($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$copy = [];
|
||||
foreach ($value as $v) {
|
||||
$copy[] = self::cloneObject($v);
|
||||
}
|
||||
return $copy;
|
||||
}
|
||||
|
||||
if (is_object($value)) {
|
||||
$copy = (object) [];
|
||||
foreach (get_object_vars($value) as $k => $v) {
|
||||
$key = $k;
|
||||
if (!is_string($key)) {
|
||||
$key = strval($key);
|
||||
}
|
||||
$copy->$key = self::cloneObject($v);
|
||||
}
|
||||
return $copy;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\ORM;
|
||||
|
||||
class EntityCollection implements \Iterator, \Countable, \ArrayAccess, \SeekableIterator
|
||||
class EntityCollection implements \Iterator, \Countable, \ArrayAccess, \SeekableIterator, ICollection
|
||||
{
|
||||
private $entityFactory = null;
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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\ORM;
|
||||
|
||||
interface ICollection
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ class RDB extends \Espo\ORM\Repository
|
||||
*/
|
||||
protected $listParams = [];
|
||||
|
||||
private $isTableLocked = false;
|
||||
|
||||
public function __construct($entityType, EntityManager $entityManager, EntityFactory $entityFactory)
|
||||
{
|
||||
$this->entityType = $entityType;
|
||||
@@ -643,9 +645,27 @@ class RDB extends \Espo\ORM\Repository
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
protected function getPDO()
|
||||
{
|
||||
return $this->getEntityManager()->getPDO();
|
||||
}
|
||||
|
||||
protected function lockTable()
|
||||
{
|
||||
$tableName = $this->getEntityManager()->getQuery()->toDb($this->entityType);
|
||||
|
||||
$this->getPDO()->query("LOCK TABLES `{$tableName}` WRITE");
|
||||
$this->isTableLocked = true;
|
||||
}
|
||||
|
||||
protected function unlockTable()
|
||||
{
|
||||
$this->getPDO()->query("UNLOCK TABLES");
|
||||
$this->isTableLocked = false;
|
||||
}
|
||||
|
||||
protected function isTableLocked()
|
||||
{
|
||||
return $this->isTableLocked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\ORM;
|
||||
|
||||
class SthCollection implements \IteratorAggregate
|
||||
class SthCollection implements \IteratorAggregate, ICollection
|
||||
{
|
||||
protected $entityManager = null;
|
||||
|
||||
|
||||
@@ -87,9 +87,21 @@ class Email extends \Espo\Core\ORM\Repositories\RDB
|
||||
{
|
||||
if ($entity->get('fromEmailAddressName')) {
|
||||
$entity->set('from', $entity->get('fromEmailAddressName'));
|
||||
} else {
|
||||
$entity->set('from', null);
|
||||
return;
|
||||
}
|
||||
if ($entity->get('fromEmailAddressId')) {
|
||||
$ea = $this->getEntityManager()->getRepository('EmailAddress')->get($entity->get('fromEmailAddressId'));
|
||||
if ($ea) {
|
||||
$entity->set('from', $ea->get('name'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$entity->has('fromEmailAddressId')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity->set('from', null);
|
||||
}
|
||||
|
||||
public function loadToField(Entity $entity)
|
||||
|
||||
@@ -85,36 +85,6 @@ class User extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
parent::beforeSave($entity, $options);
|
||||
|
||||
if ($entity->isNew()) {
|
||||
$userName = $entity->get('userName');
|
||||
if (empty($userName)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$user = $this->where([
|
||||
'userName' => $userName
|
||||
])->findOne();
|
||||
|
||||
if ($user) {
|
||||
throw new Conflict(json_encode(['reason' => 'userNameExists']));
|
||||
}
|
||||
} else {
|
||||
if ($entity->isAttributeChanged('userName')) {
|
||||
$userName = $entity->get('userName');
|
||||
if (empty($userName)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$user = $this->where(array(
|
||||
'userName' => $userName,
|
||||
'id!=' => $entity->id
|
||||
))->findOne();
|
||||
if ($user) {
|
||||
throw new Conflict(json_encode(['reason' => 'userNameExists']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->has('type') && !$entity->isPortal()) {
|
||||
$entity->set('portalRolesIds', []);
|
||||
$entity->set('portalRolesNames', (object)[]);
|
||||
@@ -130,10 +100,51 @@ class User extends \Espo\Core\ORM\Repositories\RDB
|
||||
$entity->set('defaultTeamId', null);
|
||||
$entity->set('defaultTeamName', null);
|
||||
}
|
||||
|
||||
if ($entity->isNew()) {
|
||||
$userName = $entity->get('userName');
|
||||
if (empty($userName)) {
|
||||
throw new Error("Username can't be empty.");
|
||||
}
|
||||
|
||||
$this->lockTable();
|
||||
|
||||
$user = $this->select(['id'])->where([
|
||||
'userName' => $userName
|
||||
])->findOne();
|
||||
|
||||
if ($user) {
|
||||
$this->unlockTable();
|
||||
throw new Conflict(json_encode(['reason' => 'userNameExists']));
|
||||
}
|
||||
} else {
|
||||
if ($entity->isAttributeChanged('userName')) {
|
||||
$userName = $entity->get('userName');
|
||||
if (empty($userName)) {
|
||||
throw new Error("Username can't be empty.");
|
||||
}
|
||||
|
||||
$this->lockTable();
|
||||
|
||||
$user = $this->select(['id'])->where(array(
|
||||
'userName' => $userName,
|
||||
'id!=' => $entity->id
|
||||
))->findOne();
|
||||
|
||||
if ($user) {
|
||||
$this->unlockTable();
|
||||
throw new Conflict(json_encode(['reason' => 'userNameExists']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function afterSave(Entity $entity, array $options = [])
|
||||
{
|
||||
if ($this->isTableLocked()) {
|
||||
$this->unlockTable();
|
||||
}
|
||||
|
||||
parent::afterSave($entity, $options);
|
||||
|
||||
if ($entity->isApi()) {
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"View Users": "View Users"
|
||||
},
|
||||
"messages": {
|
||||
"noSmtpSetup": "No SMTP setup. {link}.",
|
||||
"noSmtpSetup": "SMTP is not configured: {link}",
|
||||
"testEmailSent": "Test email has been sent",
|
||||
"emailSent": "Email has been sent",
|
||||
"savedAsDraft": "Saved as draft",
|
||||
|
||||
@@ -608,7 +608,9 @@
|
||||
"isNot": "Is Not",
|
||||
"isNotOneOf": "None Of",
|
||||
"anyOf": "Any Of",
|
||||
"noneOf": "None Of"
|
||||
"allOf": "All Of",
|
||||
"noneOf": "None Of",
|
||||
"any": "Any"
|
||||
},
|
||||
"varcharSearchRanges": {
|
||||
"equals": "Equals",
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"removeDuplicates": "This will permanently remove all imported records that were recognized as duplicates.",
|
||||
"confirmRevert": "This will remove all imported records permanently. Are you sure?",
|
||||
"confirmRemoveDuplicates": "This will permanently remove all imported records that were recognized as duplicates. Are you sure?",
|
||||
"confirmRemoveImportLog" : "This will remove the import log. All imported records will be kept. You wan't be able to revert import results. Are you sure you?",
|
||||
"confirmRemoveImportLog" : "This will remove the import log. All imported records will be kept. You won't be able to revert import results. Are you sure you?",
|
||||
"removeImportLog": "This will remove the import log. All imported records will be kept. Use it if you are sure that import is fine."
|
||||
},
|
||||
"fields": {
|
||||
|
||||
@@ -56,7 +56,20 @@
|
||||
"Portal Users": "Usuarios del portal",
|
||||
"Action History": "Histórico",
|
||||
"Label Manager": "Etiquetas",
|
||||
"Permissions": "Permisos"
|
||||
"Auth Log": "Registros de autenticación",
|
||||
"Lead Capture": "Captura de Posible cliente",
|
||||
"Attachments": "Adjuntos",
|
||||
"API Users": "Usuarios de API",
|
||||
"Template Manager": "Gestor de plantillas",
|
||||
"System Requirements": "Requerimientos del sistema",
|
||||
"PHP Settings": "Configuraciones PHP",
|
||||
"Database Settings": "Configuraciones de la Base de Datos",
|
||||
"Permissions": "Permisos",
|
||||
"Success": "Éxito",
|
||||
"Fail": "Falló",
|
||||
"is recommended": "es recomendado",
|
||||
"extension is missing": "no se encuentra la extensión",
|
||||
"PDF Templates": "Plantillas PDF"
|
||||
},
|
||||
"layouts": {
|
||||
"list": "Lista",
|
||||
@@ -159,8 +172,7 @@
|
||||
"installExtension": "La extensión {name} {version} está lista para ser instalada.",
|
||||
"upgradeBackup": "Recomendamos hacer una copia de seguridad de los archivos y datos de EspoCRM antes de actualizar.",
|
||||
"thousandSeparatorEqualsDecimalMark": "El símbolo de separador de miles no puede ser el mismo que el de punto decimal.",
|
||||
"userHasNoEmailAddress": "El usuario no tiene dirección de correo electrónico.",
|
||||
"newVersionIsAvailable": "La nueva versión de EspoCRM {latestVersion} está disponible."
|
||||
"userHasNoEmailAddress": "El usuario no tiene dirección de correo electrónico."
|
||||
},
|
||||
"descriptions": {
|
||||
"settings": "Ajustes generales del sistema.",
|
||||
@@ -191,7 +203,17 @@
|
||||
"entityManager": "Crear y editar entidades personalizadas. Administrar campos y relaciones.",
|
||||
"emailFilters": "Filtros para los correos entrantes.",
|
||||
"actionHistory": "Registro de las acciones del usuario.",
|
||||
"labelManager": "Personaliza las etiquetas de las aplicaciones."
|
||||
"labelManager": "Personaliza las etiquetas de las aplicaciones.",
|
||||
"authLog": "Historial de acceso.",
|
||||
"leadCapture": "Puntos de acceso al API para Web-to-Lead.",
|
||||
"attachments": "Todos los archivos adjuntos almacenados en el sistema.",
|
||||
"templateManager": "Personalice las plantillas de mensajes.",
|
||||
"systemRequirements": "Requerimientos del sistema para EspoCRM.",
|
||||
"apiUsers": "Usuarios separados para propósitos de integraciones.",
|
||||
"jobs": "Los trabajos que ejecutan tareas en segundo plano.",
|
||||
"pdfTemplates": "Plantillas para impresión en PDF.",
|
||||
"webhooks": "Administre webhooks.",
|
||||
"dashboardTemplates": "Implemente paneles para los usuarios."
|
||||
},
|
||||
"options": {
|
||||
"previewSize": {
|
||||
|
||||
@@ -328,14 +328,14 @@
|
||||
},
|
||||
"notificationMessages": {
|
||||
"assign": "{entityType} {entity} ha sido asignado a usted",
|
||||
"emailReceived": "Correo recibido de: <strong>{from}</strong>.",
|
||||
"entityRemoved": "{user} ha eliminado: <strong>[{entityType}]</strong> {entity}"
|
||||
"emailReceived": "Correo recibido de: {from}",
|
||||
"entityRemoved": "{user} ha eliminado: {entityType} {entity}"
|
||||
},
|
||||
"streamMessages": {
|
||||
"post": "{user} ha publicado en: <strong>[{entityType}]</strong> {entity}",
|
||||
"attach": "{user} ha añadido un archivo adjunto en: <strong>[{entityType}]</strong> {entity}",
|
||||
"status": "{user} ha actualizado el campo <strong>{field}</strong> en: <strong>[{entityType}]</strong> {entity}",
|
||||
"update": "{user} ha actualizado: <strong>[{entityType}]</strong> {entity}",
|
||||
"post": "{user} ha publicado en: {entityType} {entity}",
|
||||
"attach": "{user} ha añadido un archivo adjunto en: {entityType} {entity}",
|
||||
"status": "{user} ha actualizado el campo {field} en: {entityType} {entity}",
|
||||
"update": "{user} ha actualizado: {entityType} {entity}",
|
||||
"postTargetTeam": "{user} ha publicado en el equipo {target}",
|
||||
"postTargetTeams": "{user} ha publicado en los equipos {target}",
|
||||
"postTargetPortal": "{user} ha publicado en el portal {target}",
|
||||
@@ -344,47 +344,47 @@
|
||||
"postTargetYou": "{user} ha publicado pora usted",
|
||||
"postTargetYouAndOthers": "{user} ha publicado para {target} y para usted",
|
||||
"postTargetAll": "{user} ha publicado para todos",
|
||||
"mentionInPost": "{user} ha mencionado a {mentioned} en: <strong>[{entityType}]</strong> {entity}",
|
||||
"mentionYouInPost": "{user} te ha mencionado en: <strong>[{entityType}]</strong> {entity}",
|
||||
"mentionInPost": "{user} ha mencionado a {mentioned} en: {entityType} {entity}",
|
||||
"mentionYouInPost": "{user} te ha mencionado en: {entityType} {entity}",
|
||||
"mentionInPostTarget": "{user} ha mencionado a {mentioned} en la publicación",
|
||||
"mentionYouInPostTarget": "{user} te ha mencionado en la publicación para {target}",
|
||||
"mentionYouInPostTargetAll": "{user} te ha mencionado en una publicación para todos",
|
||||
"mentionYouInPostTargetNoTarget": "{user} te ha mencionado en una publicación",
|
||||
"create": "{user} ha creado: <strong>[{entityType}]</strong> {entity}",
|
||||
"createThis": "{user} ha creado: <strong>{entityType}</strong>",
|
||||
"create": "{user} ha creado: {entityType} {entity}",
|
||||
"createThis": "{user} ha creado: {entityType}",
|
||||
"createAssignedThis": "{user} ha creado y se lo ha asignado a {assignee}: {entityType}",
|
||||
"createAssigned": "{user} ha creado y se lo ha asignado a {assignee}: <strong>[{entityType}]</strong> {entity}",
|
||||
"assign": "{user} ha asignado: <strong>[{entityType}]</strong> {entity} a {assignee}",
|
||||
"createAssigned": "{user} ha creado y se lo ha asignado a {assignee}: {entityType} {entity}",
|
||||
"assign": "{user} ha asignado: {entityType} {entity} a {assignee}",
|
||||
"assignThis": "{user} ha asignado {entityType} a {assignee}",
|
||||
"postThis": "{user} ha publicado",
|
||||
"attachThis": "{user} ha adjuntado",
|
||||
"statusThis": "{user} ha actualizado el campo <strong>{field}</strong>",
|
||||
"statusThis": "{user} ha actualizado el campo {field}",
|
||||
"updateThis": "{user} ha actualizado: {entityType}",
|
||||
"createRelatedThis": "{user} ha creado: <strong>[{relatedEntityType}]</strong> {relatedEntity}, enlazado a {entityType}",
|
||||
"createRelated": "{user} ha creado: <strong>[{relatedEntityType}]</strong> {relatedEntity} enlazado a <strong>[{entityType}]</strong> {entity}",
|
||||
"relate": "{user} ha enlazado <strong>[{relatedEntityType}]</strong> {relatedEntity} a <strong>[{entityType}] {entity}",
|
||||
"createRelatedThis": "{user} ha creado: {relatedEntityType} {relatedEntity}, enlazado a {entityType}",
|
||||
"createRelated": "{user} ha creado: {relatedEntityType} {relatedEntity} enlazado a {entityType} {entity}",
|
||||
"relate": "{user} ha enlazado {relatedEntityType} {relatedEntity} a {entityType} {entity}",
|
||||
"relateThis": "{user} vinculado {relatedEntityType} {relatedEntity} con este {entityType}",
|
||||
"emailReceivedFromThis": "Correo recibido de: <strong>{from}</strong>.",
|
||||
"emailReceivedFromThis": "Correo recibido de: {from}",
|
||||
"emailReceivedInitialFromThis": "Correo recibido de {from}, se ha creado: {entityType}",
|
||||
"emailReceivedThis": "Correo recibido",
|
||||
"emailReceivedInitialThis": "Correo recibido, se ha creado: {entityType}",
|
||||
"emailReceivedFrom": "Correo recibido de {from}, relacionado a: <strong>[{entityType}]</strong> {entity}",
|
||||
"emailReceivedFromInitial": "Correo recibido de {from}, se ha creado: <strong>[{entityType}]<strong>\n {entityType}",
|
||||
"emailReceivedInitialFrom": "Correo recibido de {from}, se ha creado: <strong>[{entityType}]<strong>\n {entityType}",
|
||||
"emailReceivedFrom": "Correo recibido de {from}, relacionado a: {entityType} {entity}",
|
||||
"emailReceivedFromInitial": "Correo recibido de {from}, se ha creado: {entityType} {entityType}",
|
||||
"emailReceivedInitialFrom": "Correo recibido de {from}, se ha creado: {entityType} {entityType}",
|
||||
"emailReceived": "El correo {email} ha sido recibido para el {entityType} {entity}",
|
||||
"emailReceivedInitial": "Correo recibido, se ha creado: <strong>[{entityType}]<strong>\n {entityType}",
|
||||
"emailSent": "{by} ha enviado un correo relacionado a: <strong>[{entityType}]</strong> {entity}",
|
||||
"emailReceivedInitial": "Correo recibido, se ha creado: {entityType} {entityType}",
|
||||
"emailSent": "{by} ha enviado un correo relacionado a: {entityType} {entity}",
|
||||
"emailSentThis": "{by} ha enviado un correo",
|
||||
"postTargetSelf": "{user} se ha enviado un mensaje a sí mismo",
|
||||
"postTargetSelfAndOthers": "{user} ha publicado para {target} y para sí mismo",
|
||||
"createAssignedYou": "{user} ha creado y te lo ha asignado: <strong>[{entityType}]</strong> {entity}",
|
||||
"createAssignedYou": "{user} ha creado y te lo ha asignado: {entityType} {entity}",
|
||||
"createAssignedThisSelf": "{user} ha creado y se ha asignado a sí mismo: {entityType}",
|
||||
"createAssignedSelf": "{user} ha creado y se ha asignado a sí mismo: <strong>[{entityType}]</strong> {entity}",
|
||||
"assignYou": "{user} te ha asignado <strong>[{entityType}]<strong> {entity} a ti",
|
||||
"createAssignedSelf": "{user} ha creado y se ha asignado a sí mismo: {entityType} {entity}",
|
||||
"assignYou": "{user} te ha asignado {entityType} {entity} a ti",
|
||||
"assignThisVoid": "{user} ha desasignado: {entityType}",
|
||||
"assignVoid": "{user} ha desasignado: <strong>[{entityType}]</strong> {entity}",
|
||||
"assignVoid": "{user} ha desasignado: {entityType} {entity}",
|
||||
"assignThisSelf": "{user} se ha asignado así mismo: {entityType}",
|
||||
"assignSelf": "{user} se ha asignado así mismo: <strong>[{entityType}]</strong> {entity}"
|
||||
"assignSelf": "{user} se ha asignado así mismo: {entityType} {entity}"
|
||||
},
|
||||
"lists": {
|
||||
"monthNames": [
|
||||
|
||||
@@ -147,7 +147,6 @@
|
||||
"upgradeBackup": "We adviseren om eerst een backup te maken van uw EspoCRM bestanden en data, alvorens te upgraden.",
|
||||
"thousandSeparatorEqualsDecimalMark": "Het duizendtal scheidingsteken mag niet hetzelfde zijn als het decimaalteken.",
|
||||
"userHasNoEmailAddress": "Gebruiker heeft geen emailadres.",
|
||||
"newVersionIsAvailable": "Nieuwe EspoCRM-versie {latestVersion} is beschikbaar.",
|
||||
"uninstallConfirmation": "Weet je zeker dat je de extensie wilt verwijderen?",
|
||||
"cronIsNotConfigured": "Geplande taken zijn niet actief. Vandaar dat inkomende e-mails, meldingen en herinneringen niet werken. Volg de [instructies] (https://www.espocrm.com/documentation/administration/server-configuration/#user-content-setup-a-crontab) om cron-taak in te stellen.",
|
||||
"newExtensionVersionIsAvailable": "Nieuwe versie {extensionName} {latestVersion} is beschikbaar.",
|
||||
|
||||
@@ -184,10 +184,9 @@
|
||||
"upgradeBackup": "Перед обновлением рекомендуется сделать резервную копию ваших файлов и данных EspoCRM.",
|
||||
"thousandSeparatorEqualsDecimalMark": "Разделитель тысячных не может быть таким же, как разделитель десятичных.",
|
||||
"userHasNoEmailAddress": "У пользователя нет адреса эл. почты.",
|
||||
"newVersionIsAvailable": "Новая версия EspoCRM {latestVersion} доступна.",
|
||||
"uninstallConfirmation": "Вы действительно хотите удалить расширение?",
|
||||
"cronIsNotConfigured": "Запланированные задания не выполняются. Следовательно, входящие письма, уведомления и напоминания не работают. Пожалуйста, следуйте инструкциям [https://www.espocrm.com/documentation/administration/server-configuration/#user-content-setup-a-crontab) для установки cron job.",
|
||||
"newExtensionVersionIsAvailable": "Новая {extensionName} версия {lastVersion} доступна."
|
||||
"newExtensionVersionIsAvailable": "Новая {extensionName} версия {latestVersion} доступна."
|
||||
},
|
||||
"descriptions": {
|
||||
"settings": "Системные настройки.",
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
[
|
||||
{
|
||||
"label":"",
|
||||
"rows":[
|
||||
"columns": [
|
||||
[
|
||||
{
|
||||
"name":"from",
|
||||
"view": "views/email/fields/compose-from-address"
|
||||
},
|
||||
{"name":"cc"}
|
||||
{"name":"to"}
|
||||
],
|
||||
[
|
||||
{"name":"to"},
|
||||
{"name":"cc"},
|
||||
{"name":"bcc"}
|
||||
],
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"name": "parent"
|
||||
@@ -22,9 +25,9 @@
|
||||
"view":"views/email/fields/select-template"
|
||||
}
|
||||
],
|
||||
[{"name":"subject","fullWidth":true}],
|
||||
[{"name":"body","fullWidth":true}],
|
||||
[{"name":"attachments"},{"name":"isHtml"}]
|
||||
[{"name":"subject","fullWidth": true}],
|
||||
[{"name":"body","fullWidth": true}],
|
||||
[{"name":"attachments"}, {"name":"isHtml"} ]
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
"account",
|
||||
"dateSent",
|
||||
"emailAddress",
|
||||
"from",
|
||||
"to",
|
||||
"isNotRead",
|
||||
"isImportant",
|
||||
"isNotReplied",
|
||||
"status",
|
||||
"parent",
|
||||
|
||||
@@ -163,5 +163,103 @@
|
||||
"ZAR":"R",
|
||||
"ZWD":"Z$",
|
||||
"BTC":"฿"
|
||||
}
|
||||
},
|
||||
"list": [
|
||||
"AFN",
|
||||
"AED",
|
||||
"ALL",
|
||||
"ANG",
|
||||
"ARS",
|
||||
"AUD",
|
||||
"BAM",
|
||||
"BGN",
|
||||
"BHD",
|
||||
"BND",
|
||||
"BOB",
|
||||
"BRL",
|
||||
"BWP",
|
||||
"CAD",
|
||||
"CHF",
|
||||
"CLP",
|
||||
"CNY",
|
||||
"COP",
|
||||
"CRC",
|
||||
"CZK",
|
||||
"DKK",
|
||||
"DOP",
|
||||
"DZD",
|
||||
"EEK",
|
||||
"EGP",
|
||||
"EUR",
|
||||
"FJD",
|
||||
"GBP",
|
||||
"GNF",
|
||||
"HKD",
|
||||
"HNL",
|
||||
"HRK",
|
||||
"HUF",
|
||||
"IDR",
|
||||
"ILS",
|
||||
"INR",
|
||||
"IRR",
|
||||
"JMD",
|
||||
"JOD",
|
||||
"JPY",
|
||||
"KES",
|
||||
"KRW",
|
||||
"KWD",
|
||||
"KYD",
|
||||
"KZT",
|
||||
"LBP",
|
||||
"LKR",
|
||||
"LTL",
|
||||
"LVL",
|
||||
"MAD",
|
||||
"MDL",
|
||||
"MKD",
|
||||
"MMK",
|
||||
"MUR",
|
||||
"MXN",
|
||||
"MYR",
|
||||
"NAD",
|
||||
"NGN",
|
||||
"NIO",
|
||||
"NOK",
|
||||
"NPR",
|
||||
"NZD",
|
||||
"OMR",
|
||||
"PEN",
|
||||
"PGK",
|
||||
"PHP",
|
||||
"PKR",
|
||||
"PLN",
|
||||
"PYG",
|
||||
"QAR",
|
||||
"RON",
|
||||
"RSD",
|
||||
"RUB",
|
||||
"SAR",
|
||||
"SCR",
|
||||
"SEK",
|
||||
"SGD",
|
||||
"SKK",
|
||||
"SLL",
|
||||
"SVC",
|
||||
"THB",
|
||||
"TND",
|
||||
"TRY",
|
||||
"TTD",
|
||||
"TWD",
|
||||
"TZS",
|
||||
"UAH",
|
||||
"UGX",
|
||||
"USD",
|
||||
"UYU",
|
||||
"UZS",
|
||||
"VND",
|
||||
"YER",
|
||||
"ZAR",
|
||||
"ZMK",
|
||||
"ZWD"
|
||||
]
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
["app", "fileStorage", "implementationClassNameMap"],
|
||||
["app", "emailNotifications", "handlerClassNameMap"],
|
||||
["app", "client"],
|
||||
["app", "language"],
|
||||
["app", "auth2FAMethods", "__ANY__", "implementationClassName"],
|
||||
["app", "auth2FAMethods", "__ANY__", "implementationUserClassName"],
|
||||
["authenticationMethods", "__ANY__", "implementationClassName"]
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"languageAclDisabled": true
|
||||
}
|
||||
@@ -60,8 +60,8 @@
|
||||
"currencyList": {
|
||||
"type": "multiEnum",
|
||||
"default": ["USD", "EUR"],
|
||||
"options": ["AED","ANG","ARS","AUD","BAM", "BGN","BHD","BND","BOB","BRL","BWP","CAD","CHF","CLP","CNY","COP","CRC","CZK","DKK","DOP","DZD","EEK","EGP","EUR","FJD","GBP","GNF","HKD","HNL","HRK","HUF","IDR","ILS","INR","IRR","JMD","JOD","JPY","KES","KRW","KWD","KYD","KZT","LBP","LKR","LTL","LVL","MAD","MDL","MKD","MMK","MUR","MXN","MYR","NAD","NGN","NIO","NOK","NPR","NZD","OMR","PEN","PGK","PHP","PKR","PLN","PYG","QAR","RON","RSD","RUB","SAR","SCR","SEK","SGD","SKK","SLL","SVC","THB","TND","TRY","TTD","TWD","TZS","UAH","UGX","USD","UYU","UZS","VND","YER","ZAR","ZMK"],
|
||||
"required": true
|
||||
"required": true,
|
||||
"view": "views/settings/fields/currency-list"
|
||||
},
|
||||
"defaultCurrency": {
|
||||
"type": "enum",
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
"type": "bool",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "displayAsList",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"name": "maxCount",
|
||||
"type": "int",
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"url": "views/fields/foreign-url",
|
||||
"date": "views/fields/foreign-date",
|
||||
"datetime": "views/fields/foreign-datetime",
|
||||
"text": "views/fields/views/fields/foreign-text",
|
||||
"text": "views/fields/foreign-text",
|
||||
"number": "views/fields/foreign-varchar",
|
||||
"bool": "views/fields/foreign-bool",
|
||||
"email": "views/fields/foreign-email",
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
{
|
||||
"name":"readOnly",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"name": "default",
|
||||
"type": "linkMultiple",
|
||||
"view": "views/admin/field-manager/fields/link-multiple/default"
|
||||
}
|
||||
],
|
||||
"actualFields":[
|
||||
|
||||
@@ -33,7 +33,7 @@ class Email extends \Espo\Core\SelectManagers\Base
|
||||
{
|
||||
protected $textFilterUseContainsAttributeList = ['name'];
|
||||
|
||||
protected $fullTextSearchForceOrderOnlyByRelevance = true;
|
||||
protected $fullTextOrderType = self::FT_ORDER_ORIGINAL;
|
||||
|
||||
protected $selectAttributesDependancyMap = [
|
||||
'subject' => ['name'],
|
||||
@@ -50,11 +50,15 @@ class Email extends \Espo\Core\SelectManagers\Base
|
||||
$this->applyFolder($folderId, $result);
|
||||
}
|
||||
|
||||
if (empty($params['textFilter']) && !empty($result['orderBy']) && $result['orderBy'] === 'dateSent') {
|
||||
$textFilter = $params['textFilter'] ?? null;
|
||||
|
||||
if (!$textFilter && !empty($result['orderBy']) && $result['orderBy'] === 'dateSent') {
|
||||
$skipIndex = false;
|
||||
if (isset($params['where'])) {
|
||||
foreach ($params['where'] as $item) {
|
||||
if ($item['type'] === 'textFilter') {
|
||||
$type = $item['type'] ?? null;
|
||||
$value = $item['value'] ?? null;
|
||||
if ($type === 'textFilter') {
|
||||
$skipIndex = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -70,6 +74,17 @@ class Email extends \Espo\Core\SelectManagers\Base
|
||||
if ($folderId === 'important' || $folderId === 'drafts') {
|
||||
$skipIndex = true;
|
||||
}
|
||||
|
||||
$actualDatabaseType = $this->getConfig()->get('actualDatabaseType');
|
||||
$actualDatabaseVersion = $this->getConfig()->get('actualDatabaseVersion');
|
||||
|
||||
if (
|
||||
!$skipIndex &&
|
||||
($actualDatabaseType !== 'mysql' || version_compare($actualDatabaseVersion, '8.0.0') < 0) &&
|
||||
$this->hasLinkJoined('teams', $result)
|
||||
) {
|
||||
$skipIndex = true;
|
||||
}
|
||||
if (!$skipIndex) {
|
||||
$result['useIndex'] = 'dateSent';
|
||||
}
|
||||
@@ -330,12 +345,21 @@ class Email extends \Espo\Core\SelectManagers\Base
|
||||
|
||||
protected function applyAdditionalToTextFilterGroup(string $textFilter, array &$group, array &$result)
|
||||
{
|
||||
if (strlen($textFilter) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH) {
|
||||
if (
|
||||
strlen($textFilter) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH
|
||||
&&
|
||||
strpos($textFilter, '@') !== false
|
||||
&&
|
||||
empty($result['hasFullTextSearch'])
|
||||
) {
|
||||
$emailAddressId = $this->getEmailAddressIdByValue($textFilter);
|
||||
if ($emailAddressId) {
|
||||
$this->leftJoinEmailAddress($result);
|
||||
$group = [];
|
||||
$group['fromEmailAddressId'] = $emailAddressId;
|
||||
$group['emailEmailAddress.emailAddressId'] = $emailAddressId;
|
||||
} else {
|
||||
$group = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,103 +382,159 @@ class Email extends \Espo\Core\SelectManagers\Base
|
||||
|
||||
protected function leftJoinEmailAddress(&$result)
|
||||
{
|
||||
if (empty($result['customJoin'])) {
|
||||
$result['customJoin'] = '';
|
||||
}
|
||||
if (stripos($result['customJoin'], 'emailEmailAddress') === false) {
|
||||
$result['customJoin'] .= "
|
||||
LEFT JOIN email_email_address AS `emailEmailAddress`
|
||||
ON
|
||||
emailEmailAddress.email_id = email.id AND
|
||||
emailEmailAddress.deleted = 0
|
||||
";
|
||||
}
|
||||
if ($this->hasLeftJoin('emailEmailAddress', $result)) return;
|
||||
|
||||
$this->setDistinct(true, $result);
|
||||
|
||||
$this->addLeftJoin([
|
||||
'EmailEmailAddress',
|
||||
'emailEmailAddress',
|
||||
[
|
||||
'emailId:' => 'id',
|
||||
'deleted' => false,
|
||||
]
|
||||
], $result);
|
||||
}
|
||||
|
||||
|
||||
public function whereEmailAddress(string $value, array &$result)
|
||||
protected function getWherePartEmailAddressEquals($value, array &$result)
|
||||
{
|
||||
$orItem = [];
|
||||
if (!$value) {
|
||||
return ['id' => null];
|
||||
}
|
||||
|
||||
$emailAddressId = $this->getEmailAddressIdByValue($value);
|
||||
|
||||
if ($emailAddressId) {
|
||||
$this->leftJoinEmailAddress($result);
|
||||
|
||||
$orItem['fromEmailAddressId'] = $emailAddressId;
|
||||
$orItem['emailEmailAddress.emailAddressId'] = $emailAddressId;
|
||||
$result['whereClause'][] = [
|
||||
'OR' => $orItem
|
||||
];
|
||||
} else {
|
||||
if (empty($result['customWhere'])) {
|
||||
$result['customWhere'] = '';
|
||||
}
|
||||
$result['customWhere'] .= ' AND 0';
|
||||
if (!$emailAddressId) {
|
||||
return ['id' => null];
|
||||
}
|
||||
|
||||
$this->setDistinct(true, $result);
|
||||
$alias = 'emailEmailAddress' . strval(rand(10000, 99999));
|
||||
|
||||
$this->addLeftJoin([
|
||||
'EmailEmailAddress',
|
||||
$alias,
|
||||
[
|
||||
'emailId:' => 'id',
|
||||
'deleted' => false,
|
||||
]
|
||||
], $result);
|
||||
|
||||
return [
|
||||
'OR' => [
|
||||
'fromEmailAddressId' => $emailAddressId,
|
||||
$alias . '.emailAddressId' => $emailAddressId,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getWherePartFromEquals($value, array &$result)
|
||||
{
|
||||
if (!$value) {
|
||||
return ['id' => null];
|
||||
}
|
||||
|
||||
$emailAddressId = $this->getEmailAddressIdByValue($value);
|
||||
|
||||
if (!$emailAddressId) {
|
||||
return ['id' => null];
|
||||
}
|
||||
|
||||
return [
|
||||
'fromEmailAddressId' => $emailAddressId,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getWherePartToEquals($value, array &$result)
|
||||
{
|
||||
if (!$value) {
|
||||
return ['id' => null];
|
||||
}
|
||||
|
||||
$emailAddressId = $this->getEmailAddressIdByValue($value);
|
||||
|
||||
if (!$emailAddressId) {
|
||||
return ['id' => null];
|
||||
}
|
||||
|
||||
$alias = 'emailEmailAddress' . strval(rand(10000, 99999));
|
||||
|
||||
$this->addLeftJoin([
|
||||
'EmailEmailAddress',
|
||||
$alias,
|
||||
[
|
||||
'emailId:' => 'id',
|
||||
'deleted' => false,
|
||||
]
|
||||
], $result);
|
||||
|
||||
return [
|
||||
$alias . '.emailAddressId' => $emailAddressId,
|
||||
$alias . '.addressType' => 'to',
|
||||
];
|
||||
}
|
||||
|
||||
protected function getWherePartIsNotRepliedIsTrue()
|
||||
{
|
||||
return array(
|
||||
return [
|
||||
'isReplied' => false
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
protected function getWherePartIsNotRepliedIsFalse()
|
||||
{
|
||||
return array(
|
||||
return [
|
||||
'isReplied' => true
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
public function getWherePartIsNotReadIsTrue()
|
||||
{
|
||||
return array(
|
||||
return [
|
||||
'usersMiddle.isRead' => false,
|
||||
'OR' => array(
|
||||
'OR' => [
|
||||
'sentById' => null,
|
||||
'sentById!=' => $this->getUser()->id
|
||||
)
|
||||
);
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
protected function getWherePartIsNotReadIsFalse()
|
||||
{
|
||||
return array(
|
||||
return [
|
||||
'usersMiddle.isRead' => true
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
protected function getWherePartIsReadIsTrue()
|
||||
{
|
||||
return array(
|
||||
return [
|
||||
'usersMiddle.isRead' => true
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
protected function getWherePartIsReadIsFalse()
|
||||
{
|
||||
return array(
|
||||
return [
|
||||
'usersMiddle.isRead' => false,
|
||||
'OR' => array(
|
||||
'OR' => [
|
||||
'sentById' => null,
|
||||
'sentById!=' => $this->getUser()->id
|
||||
)
|
||||
);
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
protected function getWherePartIsImportantIsTrue()
|
||||
{
|
||||
return array(
|
||||
return [
|
||||
'usersMiddle.isImportant' => true
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
protected function getWherePartIsImportantIsFalse()
|
||||
{
|
||||
return array(
|
||||
return [
|
||||
'usersMiddle.isImportant' => false
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class EmailFilter extends \Espo\Core\SelectManagers\Base
|
||||
'assignedUserId' => $this->getUser()->id
|
||||
])->find();
|
||||
foreach ($emailAccountList as $emailAccount) {
|
||||
$idList = $emailAccount->id;
|
||||
$idList[] = $emailAccount->id;
|
||||
}
|
||||
|
||||
if (count($idList)) {
|
||||
|
||||
@@ -125,6 +125,16 @@ class User extends \Espo\Core\SelectManagers\Base
|
||||
|
||||
protected function accessOnlyOwn(&$result)
|
||||
{
|
||||
if ($this->getAcl()->get('portalPermission') == 'yes') {
|
||||
$result['whereClause'][] = [
|
||||
'OR' => [
|
||||
'id' => $this->getUser()->id,
|
||||
'type' => 'portal',
|
||||
],
|
||||
];
|
||||
return;
|
||||
}
|
||||
|
||||
$result['whereClause'][] = [
|
||||
'id' => $this->getUser()->id
|
||||
];
|
||||
@@ -141,11 +151,18 @@ class User extends \Espo\Core\SelectManagers\Base
|
||||
{
|
||||
$this->setDistinct(true, $result);
|
||||
$this->addLeftJoin(['teams', 'teamsAccess'], $result);
|
||||
|
||||
$or = [
|
||||
'teamsAccess.id' => $this->getUser()->getLinkMultipleIdList('teams'),
|
||||
'id' => $this->getUser()->id,
|
||||
];
|
||||
|
||||
if ($this->getAcl()->get('portalPermission') == 'yes') {
|
||||
$or['type'] = 'portal';
|
||||
}
|
||||
|
||||
$result['whereClause'][] = [
|
||||
'OR' => [
|
||||
'teamsAccess.id' => $this->getUser()->getLinkMultipleIdList('teams'),
|
||||
'id' => $this->getUser()->id
|
||||
]
|
||||
'OR' => $or,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,6 +380,12 @@ class Email extends Record
|
||||
}
|
||||
|
||||
$this->loadAdditionalFields($entity);
|
||||
|
||||
if (!isset($data->from) && !isset($data->to) && !isset($data->cc)) {
|
||||
$entity->clear('nameHash');
|
||||
$entity->clear('idHash');
|
||||
$entity->clear('typeHash');
|
||||
}
|
||||
}
|
||||
|
||||
public function loadFromField(Entity $entity)
|
||||
@@ -775,31 +781,6 @@ class Email extends Record
|
||||
$this->getEntityManager()->getRepository('Email')->loadNameHash($entity, $fieldList);
|
||||
}
|
||||
|
||||
protected function getSelectParams($params)
|
||||
{
|
||||
$searchByEmailAddress = false;
|
||||
if (!empty($params['where']) && is_array($params['where'])) {
|
||||
foreach ($params['where'] as $i => $p) {
|
||||
if (!empty($p['attribute']) && $p['attribute'] == 'emailAddress') {
|
||||
$searchByEmailAddress = true;
|
||||
$emailAddress = $p['value'];
|
||||
unset($params['where'][$i]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$selectManager = $this->getSelectManager($this->getEntityType());
|
||||
|
||||
$selectParams = $selectManager->getSelectParams($params, true);
|
||||
|
||||
if ($searchByEmailAddress) {
|
||||
$selectManager->whereEmailAddress($emailAddress, $selectParams);
|
||||
}
|
||||
|
||||
return $selectParams;
|
||||
}
|
||||
|
||||
public function copyAttachments($emailId, $parentType, $parentId)
|
||||
{
|
||||
return $this->getCopiedAttachments($emailId, $parentType, $parentId);
|
||||
|
||||
@@ -67,8 +67,15 @@ class EmailAddress extends Record
|
||||
$selectManager = $this->getSelectManagerFactory()->create($entityType);
|
||||
$selectManager->applyAccess($selectParams);
|
||||
|
||||
$select = ['id', 'emailAddress', 'name'];
|
||||
|
||||
if ($this->getMetadata()->get(['entityDefs', $entityType, 'fields', 'name', 'type']) === 'personName') {
|
||||
$select[] = 'firstName';
|
||||
$select[] = 'lastName';
|
||||
}
|
||||
|
||||
$collection = $this->getEntityManager()->getRepository($entityType)
|
||||
->select(['id', 'emailAddress', 'name'])
|
||||
->select($select)
|
||||
->find($selectParams);
|
||||
|
||||
foreach ($collection as $entity) {
|
||||
|
||||
@@ -97,6 +97,14 @@ class GlobalSearch extends \Espo\Core\Services\Base
|
||||
$selectParams['orderBy'] = [['name']];
|
||||
}
|
||||
|
||||
if ($this->getMetadata()->get(['entityDefs', $entityType, 'fields', 'name', 'type']) === 'personName') {
|
||||
$selectParams['select'][] = 'firstName';
|
||||
$selectParams['select'][] = 'lastName';
|
||||
} else {
|
||||
$selectParams['select'][] = ['VALUE:', 'firstName'];
|
||||
$selectParams['select'][] = ['VALUE:', 'lastName'];
|
||||
}
|
||||
|
||||
$selectParams['offset'] = 0;
|
||||
$selectParams['limit'] = $offset + $maxSize + 1;
|
||||
|
||||
@@ -104,6 +112,8 @@ class GlobalSearch extends \Espo\Core\Services\Base
|
||||
$selectParams['useFullTextSearch'] = true;
|
||||
$selectManager->applyTextFilter($query, $selectParams);
|
||||
|
||||
unset($selectParams['additionalSelect']);
|
||||
|
||||
$itemSql = $this->getEntityManager()->getQuery()->createSelectQuery($entityType, $selectParams);
|
||||
|
||||
$unionPartList[] = "(\n" . $itemSql . "\n)";
|
||||
|
||||
@@ -550,7 +550,7 @@ class InboundEmail extends \Espo\Services\Record
|
||||
$className = '\\Espo\\Modules\\Crm\\Business\\Distribution\\CaseObj\\LeastBusy';
|
||||
}
|
||||
|
||||
$distribution = new $className($this->getEntityManager());
|
||||
$distribution = new $className($this->getEntityManager(), $this->getMetadata());
|
||||
|
||||
$user = $distribution->getUser($team, $targetUserPosition);
|
||||
|
||||
|
||||
@@ -97,6 +97,9 @@ class Language extends \Espo\Core\Services\Base
|
||||
$scopeList = array_keys($this->getMetadata()->get(['scopes'], []));
|
||||
|
||||
foreach ($scopeList as $scope) {
|
||||
if (!$this->getMetadata()->get(['scopes', $scope, 'entity'])) continue;
|
||||
if ($this->getMetadata()->get(['entityAcl', $scope, 'languageAclDisabled'])) continue;
|
||||
|
||||
if (!$this->getAcl()->check($scope)) {
|
||||
unset($data[$scope]);
|
||||
unset($data['Global']['scopeNames'][$scope]);
|
||||
@@ -131,12 +134,39 @@ class Language extends \Espo\Core\Services\Base
|
||||
'userHasNoEmailAddress' => $languageObj->translate('userHasNoEmailAddress', 'messages', 'Admin'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$data['User']['fields']['password'] = $languageObj->translate('password', 'fields', 'User');
|
||||
$data['User']['fields']['passwordConfirm'] = $languageObj->translate('passwordConfirm', 'fields', 'User');
|
||||
foreach (($this->getMetadata()->get(['app', 'language', 'aclDependencies']) ?? []) as $target => $item) {
|
||||
$targetArr = explode('.', $target);
|
||||
|
||||
$aclScope = $item['scope'] ?? null;;
|
||||
$aclField = $item['field'] ?? null;
|
||||
if (!$aclScope) continue;
|
||||
if (!$this->getAcl()->check($aclScope)) continue;
|
||||
if ($aclField && in_array($aclField, $this->getAcl()->getScopeForbiddenFieldList($aclScope))) continue;
|
||||
|
||||
$pointer =& $data;
|
||||
foreach ($targetArr as $i => $k) {
|
||||
if ($i === count($targetArr) - 1) {
|
||||
$pointer[$k] = $languageObj->get($targetArr);
|
||||
break;
|
||||
}
|
||||
if (!isset($pointer[$k])) {
|
||||
$pointer[$k] = [];
|
||||
}
|
||||
|
||||
$pointer =& $pointer[$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['User']['fields'] = $data['User']['fields'] ?? [];
|
||||
|
||||
$data['User']['fields']['password'] = $languageObj->translate('password', 'fields', 'User');
|
||||
$data['User']['fields']['passwordConfirm'] = $languageObj->translate('passwordConfirm', 'fields', 'User');
|
||||
$data['User']['fields']['newPassword'] = $languageObj->translate('newPassword', 'fields', 'User');
|
||||
$data['User']['fields']['newPasswordConfirm'] = $languageObj->translate('newPasswordConfirm', 'fields', 'User');
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,9 @@ class Layout extends \Espo\Core\Services\Base
|
||||
if ($name === 'relationships') {
|
||||
$data = json_decode($dataString);
|
||||
if (is_array($data)) {
|
||||
foreach ($data as $i => $link) {
|
||||
foreach ($data as $i => $item) {
|
||||
$link = $item;
|
||||
if (is_object($item)) $link = $item->name ?? null;
|
||||
$foreignEntityType = $this->getMetadata()->get(['entityDefs', $scope, 'links', $link, 'entity']);
|
||||
if ($foreignEntityType) {
|
||||
if (!$this->getAcl()->check($foreignEntityType)) {
|
||||
|
||||
@@ -64,6 +64,7 @@ class Metadata extends \Espo\Core\Services\Base
|
||||
if (!$this->getUser()->isAdmin()) {
|
||||
$scopeList = array_keys($this->getMetadata()->get(['scopes'], []));
|
||||
foreach ($scopeList as $scope) {
|
||||
if (!$this->getMetadata()->get(['scopes', $scope, 'entity'])) continue;
|
||||
if (in_array($scope, ['Reminder'])) continue;
|
||||
if (!$this->getAcl()->check($scope)) {
|
||||
unset($data->entityDefs->$scope);
|
||||
@@ -143,6 +144,39 @@ class Metadata extends \Espo\Core\Services\Base
|
||||
|
||||
unset($data->authenticationMethods);
|
||||
unset($data->formula);
|
||||
|
||||
foreach (($this->getMetadata()->get(['app', 'metadata', 'aclDependencies']) ?? []) as $target => $item) {
|
||||
$targetArr = explode('.', $target);
|
||||
|
||||
if (is_string($item)) {
|
||||
$depArr = explode('.', $item);
|
||||
$pointer = $data;
|
||||
foreach ($depArr as $k) {
|
||||
if (!isset($pointer->$k)) {
|
||||
continue 2;
|
||||
}
|
||||
$pointer = $pointer->$k;
|
||||
}
|
||||
} else if (is_array($item)) {
|
||||
$aclScope = $item['scope'] ?? null;;
|
||||
$aclField = $item['field'] ?? null;
|
||||
if (!$aclScope) continue;
|
||||
if (!$this->getAcl()->check($aclScope)) continue;
|
||||
if ($aclField && in_array($aclField, $this->getAcl()->getScopeForbiddenFieldList($aclScope))) continue;
|
||||
}
|
||||
|
||||
$pointer = $data;
|
||||
foreach ($targetArr as $i => $k) {
|
||||
if ($i === count($targetArr) - 1) {
|
||||
$pointer->$k = $this->getMetadata()->get($targetArr);
|
||||
break;
|
||||
}
|
||||
if (!isset($pointer->$k)) {
|
||||
$pointer->$k = (object) [];
|
||||
}
|
||||
$pointer = $pointer->$k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
|
||||
@@ -307,7 +307,8 @@ class Pdf extends \Espo\Core\Services\Base
|
||||
$this->getAcl(),
|
||||
$this->getInjection('entityManager'),
|
||||
$this->getInjection('metadata'),
|
||||
$this->getInjection('defaultLanguage')
|
||||
$this->getInjection('defaultLanguage'),
|
||||
$this->getInjection('config')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +276,16 @@ class Stream extends \Espo\Core\Services\Base
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = $this->getEntityManager()->getRepository('User')
|
||||
->select(['id', 'type', 'isActive'])
|
||||
->where([
|
||||
'id' => $userId,
|
||||
'isActive' => true,
|
||||
])->findOne();
|
||||
|
||||
if (!$user) return false;
|
||||
if (!$this->getAclManager()->check($user, $entity, 'stream')) return false;
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
if (!$this->checkIsFollowed($entity, $userId)) {
|
||||
@@ -564,6 +574,7 @@ class Stream extends \Espo\Core\Services\Base
|
||||
'orderBy' => 'number',
|
||||
'order' => 'DESC',
|
||||
'limit' => $sqLimit,
|
||||
'_name' => 'selfPost',
|
||||
];
|
||||
|
||||
$selectParamsList[] = [
|
||||
@@ -593,6 +604,7 @@ class Stream extends \Espo\Core\Services\Base
|
||||
'orderBy' => 'number',
|
||||
'order' => 'DESC',
|
||||
'limit' => $sqLimit,
|
||||
'_name' => 'globalPost',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -679,18 +691,22 @@ class Stream extends \Espo\Core\Services\Base
|
||||
}
|
||||
}
|
||||
|
||||
if ($skipOwn) {
|
||||
$whereClause[] = [
|
||||
'createdById!=' => $this->getUser()->id,
|
||||
];
|
||||
}
|
||||
|
||||
$sqlPartList = [];
|
||||
foreach ($selectParamsList as $i => $selectParams) {
|
||||
if (empty($selectParams['whereClause'])) {
|
||||
$selectParams['whereClause'] = [];
|
||||
}
|
||||
$selectParams['whereClause'][] = $whereClause;
|
||||
|
||||
if ($skipOwn) {
|
||||
$itemName = $selectParams['_name'] ?? null;
|
||||
if ($itemName !== 'selfPost' && $itemName !== 'globalPost') {
|
||||
$selectParams['whereClause'][] = [
|
||||
'createdById!=' => $this->getUser()->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$sqlPartList[] = "(\n" . $this->getEntityManager()->getQuery()->createSelectQuery('Note', $selectParams) . "\n)";
|
||||
}
|
||||
|
||||
@@ -1305,6 +1321,39 @@ class Stream extends \Espo\Core\Services\Base
|
||||
$this->getEntityManager()->saveEntity($note, $o);
|
||||
}
|
||||
|
||||
public function noteRelate(Entity $entity, $parentType, $parentId, array $options = [])
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$existing = $this->getEntityManager()->getRepository('Note')->select(['id'])->where([
|
||||
'type' => 'Relate',
|
||||
'parentId' => $parentId,
|
||||
'parentType' => $parentType,
|
||||
'relatedId' => $entity->id,
|
||||
'relatedType' => $entityType,
|
||||
])->findOne();
|
||||
if ($existing) return false;
|
||||
|
||||
$note = $this->getEntityManager()->getEntity('Note');
|
||||
|
||||
$note->set([
|
||||
'type' => 'Relate',
|
||||
'parentId' => $parentId,
|
||||
'parentType' => $parentType,
|
||||
'relatedType' => $entityType,
|
||||
'relatedId' => $entity->id,
|
||||
]);
|
||||
|
||||
$this->processNoteTeamsUsers($note, $entity);
|
||||
|
||||
$o = [];
|
||||
if (!empty($options['createdById'])) {
|
||||
$o['createdById'] = $options['createdById'];
|
||||
}
|
||||
|
||||
$this->getEntityManager()->saveEntity($note, $o);
|
||||
}
|
||||
|
||||
public function noteAssign(Entity $entity, array $options = [])
|
||||
{
|
||||
$note = $this->getEntityManager()->getEntity('Note');
|
||||
|
||||
@@ -45,6 +45,7 @@ class UserSecurity extends \Espo\Core\Services\Base
|
||||
$this->addDependency('metadata');
|
||||
$this->addDependency('totp');
|
||||
$this->addDependency('config');
|
||||
$this->addDependency('container');
|
||||
}
|
||||
|
||||
protected function getUser()
|
||||
@@ -87,7 +88,7 @@ class UserSecurity extends \Espo\Core\Services\Base
|
||||
if (!$password) throw new Forbidden('Passport required.');
|
||||
|
||||
if (!$this->getUser()->isAdmin() || $this->getUser()->id === $id) {
|
||||
$this->checkPassport($id, $password);
|
||||
$this->checkPassword($id, $password);
|
||||
}
|
||||
|
||||
$userData = $this->getEntityManager()->getRepository('UserData')->getByUserId($id);
|
||||
@@ -136,7 +137,7 @@ class UserSecurity extends \Espo\Core\Services\Base
|
||||
if (!$password) throw new Forbidden('Passport required.');
|
||||
|
||||
if (!$this->getUser()->isAdmin() || $this->getUser()->id === $id) {
|
||||
$this->checkPassport($id, $password);
|
||||
$this->checkPassword($id, $password);
|
||||
}
|
||||
|
||||
foreach (get_object_vars($data) as $attribute => $v) {
|
||||
@@ -222,15 +223,21 @@ class UserSecurity extends \Espo\Core\Services\Base
|
||||
];
|
||||
}
|
||||
|
||||
protected function checkPassport(string $id, string $password)
|
||||
protected function checkPassword(string $id, string $password)
|
||||
{
|
||||
$passwordHash = new \Espo\Core\Utils\PasswordHash($this->getConfig());
|
||||
$method = $this->getConfig()->get('authenticationMethod', 'Espo');
|
||||
|
||||
$auth = $this->getInjection('container')->get('authenticationFactory')->create($method);
|
||||
|
||||
$user = $this->getEntityManager()->getRepository('User')->where([
|
||||
'id' => $id,
|
||||
'password' => $passwordHash->hash($password),
|
||||
])->findOne();
|
||||
if (!$user) {
|
||||
throw new Forbidden('Passport is incorrect.');
|
||||
|
||||
if (!$user) throw new Forbidden('User is not found.');
|
||||
|
||||
if (!$auth->login($user->get('userName'), $password)) {
|
||||
throw new Forbidden('Password is incorrect.');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -999,8 +999,16 @@ var Bull = Bull || {};
|
||||
|
||||
this.$el = $(el).eq(0);
|
||||
this.el = this.$el[0];
|
||||
}
|
||||
},
|
||||
|
||||
propagateEvent: function () {
|
||||
this.trigger.apply(this, arguments);
|
||||
|
||||
for (var key in this.nestedViews) {
|
||||
var view = this.nestedViews[key];
|
||||
view.propagateEvent.apply(view, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
}).call(this, Bull, Backbone, _);
|
||||
|
||||
@@ -3,18 +3,20 @@
|
||||
|
||||
{{#if header}}
|
||||
<div class="row button-container">
|
||||
<div class="col-sm-4 col-xs-6">
|
||||
<div class="col-sm-4 col-xs-5">
|
||||
<div class="btn-group range-switch-group">
|
||||
<button class="btn btn-text btn-icon" data-action="prev"><span class="fas fa-chevron-left"></span></button>
|
||||
<button class="btn btn-text btn-icon" data-action="next"><span class="fas fa-chevron-right"></span></button>
|
||||
</div>
|
||||
<button class="btn btn-text strong" data-action="today">{{translate 'Today' scope='Calendar'}}</button>
|
||||
<button class="btn btn-text strong" data-action="today" title="{{todayLabel}}">
|
||||
<span class="hidden-sm hidden-xs">{{todayLabel}}</span><span class="visible-sm visible-xs">{{todayLabelShort}}</span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-text{{#unless isCustomView}} hidden{{/unless}} btn-icon" data-action="editCustomView" title="{{translate 'Edit'}}"><span class="fas fa-pencil-alt fa-sm"></span></button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="date-title col-sm-4 col-xs-6"><h4><span style="cursor: pointer;" data-action="refresh" title="{{translate 'Refresh'}}"></span></h4></div>
|
||||
<div class="date-title col-sm-4 col-xs-7"><h4><span style="cursor: pointer;" data-action="refresh" title="{{translate 'Refresh'}}"></span></h4></div>
|
||||
|
||||
<div class="col-sm-4 col-xs-12">
|
||||
<div class="btn-group pull-right mode-buttons">
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], function (Dep, FullCalendar) {
|
||||
define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], function (Dep, FullCalendar) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
@@ -56,7 +56,7 @@ Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], functi
|
||||
|
||||
titleFormat: {
|
||||
month: 'MMMM YYYY',
|
||||
week: 'MMMM D, YYYY',
|
||||
week: 'MMMM YYYY',
|
||||
day: 'dddd, MMMM D, YYYY'
|
||||
},
|
||||
|
||||
@@ -66,6 +66,8 @@ Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], functi
|
||||
header: this.header,
|
||||
isCustomViewAvailable: this.isCustomViewAvailable,
|
||||
isCustomView: this.isCustomView,
|
||||
todayLabel: this.translate('Today', 'labels', 'Calendar'),
|
||||
todayLabelShort: this.translate('Today', 'labels', 'Calendar').substr(0, 2),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -286,7 +288,7 @@ Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], functi
|
||||
var title;
|
||||
|
||||
if (viewName == 'week') {
|
||||
title = $.fullCalendar.formatRange(view.start, view.end, this.titleFormat[viewName], ' - ');
|
||||
title = $.fullCalendar.formatRange(view.start, view.end, this.titleFormat[viewName], ' – ');
|
||||
} else {
|
||||
title = view.intervalStart.format(this.titleFormat[viewName]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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('crm:views/call/fields/date-end', 'views/fields/datetime', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
validateAfter: function () {
|
||||
var field = this.model.getFieldParam(this.name, 'after');
|
||||
if (field) {
|
||||
var value = this.model.get(this.name);
|
||||
var otherValue = this.model.get(field);
|
||||
if (value && otherValue) {
|
||||
if (moment(value).unix() < moment(otherValue).unix()) {
|
||||
var msg = this.translate('fieldShouldAfter', 'messages').replace('{field}', this.getLabelText())
|
||||
.replace('{otherField}', this.translate(field, 'fields', this.model.name));
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -16,7 +16,11 @@
|
||||
<div class="panel-heading"><h4 class="panel-title"><%= panelLabelString %></h4></div>
|
||||
<% } %>
|
||||
<div class="panel-body panel-body-form">
|
||||
<% _.each(panel.rows, function (row, rowNumber) { %>
|
||||
|
||||
<% var rows = panel.rows || [] %>
|
||||
<% var columns = panel.columns || [] %>
|
||||
|
||||
<% _.each(rows, function (row, rowNumber) { %>
|
||||
<div class="row">
|
||||
<% var columnCount = row.length; %>
|
||||
<% _.each(row, function (cell, cellNumber) { %>
|
||||
@@ -90,6 +94,78 @@
|
||||
<% }); %>
|
||||
</div>
|
||||
<% }); %>
|
||||
|
||||
<%
|
||||
var columnCount = columns.length;
|
||||
if (columnCount) {
|
||||
%>
|
||||
<div class="row">
|
||||
<%
|
||||
}
|
||||
%>
|
||||
<% _.each(columns, function (column, columnNumber) { %>
|
||||
<%
|
||||
var spanClass;
|
||||
if (!columnCount) return;
|
||||
|
||||
if (columnCount === 1 || column.fullWidth) {
|
||||
spanClass = 'col-sm-12';
|
||||
} else if (columnCount === 2) {
|
||||
if (column.span === 2) {
|
||||
spanClass = 'col-sm-12';
|
||||
} else {
|
||||
spanClass = 'col-sm-6';
|
||||
}
|
||||
} else if (columnCount === 3) {
|
||||
if (column.span === 2) {
|
||||
spanClass = 'col-sm-8';
|
||||
} else if (column.span === 3) {
|
||||
spanClass = 'col-sm-12';
|
||||
} else {
|
||||
spanClass = 'col-sm-4';
|
||||
}
|
||||
} else if (columnCount === 4) {
|
||||
if (column.span === 2) {
|
||||
spanClass = 'col-sm-6';
|
||||
} else if (column.span === 3) {
|
||||
spanClass = 'col-sm-9';
|
||||
} else if (column.span === 4) {
|
||||
spanClass = 'col-sm-12';
|
||||
} else {
|
||||
spanClass = 'col-md-3 col-sm-6';
|
||||
}
|
||||
} else {
|
||||
spanClass = 'col-sm-12';
|
||||
}
|
||||
%>
|
||||
<div class="column <%= spanClass %>">
|
||||
<% _.each(column, function (cell, cellNumber) { %>
|
||||
<div class="cell form-group<% if (cell.field) { %>{{#if hiddenFields.<%= cell.field %>}} hidden-cell{{/if}}<% } %>" data-name="<%= cell.field %>">
|
||||
<% if (!cell.noLabel) { %><label class="control-label<% if (cell.field) { %>{{#if hiddenFields.<%= cell.field %>}} hidden{{/if}}<% } %>" data-name="<%= cell.field %>"><span class="label-text"><%
|
||||
if ('customLabel' in cell) {
|
||||
print (cell.customLabel);
|
||||
} else {
|
||||
print ("{{translate \""+cell.field+"\" scope=\""+model.name+"\" category='fields'}}");
|
||||
}
|
||||
%></span></label><% } %>
|
||||
<div class="field<% if (cell.field) { %>{{#if hiddenFields.<%= cell.field %>}} hidden{{/if}}<% } %>" data-name="<%= cell.field %>"><%
|
||||
if ('customCode' in cell) {
|
||||
print (cell.customCode);
|
||||
} else {
|
||||
print ("{{{this."+cell.name+"}}}");
|
||||
}
|
||||
%></div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
<% }); %>
|
||||
<%
|
||||
if (columnCount) {
|
||||
%>
|
||||
</div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="pull-right">
|
||||
<a target="_blank" href="https://www.espocrm.com/documentation/administration/server-configuration/" style="font-weight:bold;">Configuration Instructions</a>
|
||||
<a target="_blank" href="https://www.espocrm.com/documentation/administration/server-configuration/" style="font-weight:600;">Configuration Instructions</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
<input type="checkbox"{{#ifEqual searchParams.type 'isTrue'}} checked{{/ifEqual}} data-name="{{name}}" class="main-element">
|
||||
<select data-name="{{name}}" class="main-element form-control input-sm">
|
||||
<option value="isTrue" {{#ifEqual searchType 'isTrue'}} selected{{/ifEqual}}>{{translate 'Yes'}}</option>
|
||||
<option value="isFalse" {{#ifEqual searchType 'isFalse'}} selected{{/ifEqual}}>{{translate 'No'}}</option>
|
||||
<option value="any" {{#ifEqual searchType 'any'}} selected{{/ifEqual}}>{{translateOption 'any' field='searchRanges'}}</option>
|
||||
</select>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div class="input-group input-group-link-parent">
|
||||
{{#if foreignScopeList.length}}
|
||||
<span class="input-group-btn">
|
||||
<select class="form-control" data-name="{{typeName}}">
|
||||
{{options foreignScopeList foreignScope category='scopeNames'}}
|
||||
@@ -9,5 +10,8 @@
|
||||
<button data-action="selectLink" class="btn btn-default btn-icon" type="button" tabindex="-1" title="{{translate 'Select'}}"><i class="fas fa-angle-up"></i></button>
|
||||
<button data-action="clearLink" class="btn btn-default btn-icon" type="button" tabindex="-1"><i class="fas fa-times"></i></button>
|
||||
</span>
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<input type="hidden" data-name="{{idName}}" value="{{idValue}}">
|
||||
|
||||
+12
-2
@@ -26,13 +26,23 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
Espo.define('acl/user', 'acl', function (Dep) {
|
||||
define('acl/user', 'acl', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
checkModelRead: function (model, data, precise) {
|
||||
if (model.isPortal()) {
|
||||
if (this.get('portalPermission') === 'yes') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return Dep.prototype.checkModelRead.call(this, model, data, precise);
|
||||
},
|
||||
|
||||
checkIsOwner: function (model) {
|
||||
return this.getUser().id === model.id;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
@@ -313,7 +313,7 @@ define(
|
||||
controller.doAction(params.action, params.options);
|
||||
this.trigger('action:done');
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.error(e);
|
||||
switch (e.name) {
|
||||
case 'AccessDenied':
|
||||
this.baseController.error403();
|
||||
|
||||
@@ -157,7 +157,13 @@ define('controller', [], function () {
|
||||
|
||||
storeMainView: function (key, view) {
|
||||
this.set('storedMainView-' + key, view);
|
||||
view.once('remove', function () {
|
||||
|
||||
this.listenTo(view, 'remove', function (o) {
|
||||
o = o || {};
|
||||
if (o.ignoreCleaning) return;
|
||||
|
||||
this.stopListening(view, 'remove');
|
||||
|
||||
this.clearStoredMainView(key);
|
||||
}, this);
|
||||
},
|
||||
@@ -278,6 +284,10 @@ define('controller', [], function () {
|
||||
if (master.currentViewKey) {
|
||||
this.set('storedScrollTop-' + master.currentViewKey, $(window).scrollTop());
|
||||
if (this.hasStoredMainView(master.currentViewKey)) {
|
||||
var mainView = master.getView('main');
|
||||
if (mainView) {
|
||||
mainView.propagateEvent('remove', {ignoreCleaning: true});
|
||||
}
|
||||
master.unchainView('main');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,15 @@ define('controllers/portal-user', 'controllers/record', function (Dep) {
|
||||
options.attributes = options.attributes || {};
|
||||
options.attributes.type = 'portal';
|
||||
Dep.prototype.actionCreate.call(this, options);
|
||||
}
|
||||
},
|
||||
|
||||
checkAccess: function (action) {
|
||||
|
||||
if (this.getAcl().get('portalPermission') === 'yes')
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -306,7 +306,7 @@ define('controllers/record', 'controller', function (Dep) {
|
||||
var collectionName = this.entityType || this.name;
|
||||
if (usePreviouslyFetched) {
|
||||
if (collectionName in this.collectionMap) {
|
||||
var collection = this.collectionMap[collectionName];// = this.collectionMap[collectionName].clone();
|
||||
var collection = this.collectionMap[collectionName];
|
||||
callback.call(context, collection);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,15 @@ define('utils', [], function () {
|
||||
if ($dropdown.length) {
|
||||
var $dropdownToggle = $dropdown.parent().find('[data-toggle="dropdown"]');
|
||||
if ($dropdownToggle.length) {
|
||||
var isDisabled = false;
|
||||
if ($dropdownToggle.attr('disabled')) {
|
||||
isDisabled = true;
|
||||
$dropdownToggle.removeAttr('disabled').removeClass('disabled');
|
||||
}
|
||||
$dropdownToggle.dropdown('toggle');
|
||||
if (isDisabled) {
|
||||
$dropdownToggle.attr('disabled', 'disabled').addClass('disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +289,10 @@ define('view-helper', ['lib!client/lib/purify.min.js'], function () {
|
||||
|
||||
for (var key in list) {
|
||||
var keyVal = list[key];
|
||||
html += "<option value=\"" + keyVal + "\" " + (checkOption(list[key]) ? 'selected' : '') + ">" + translate(list[key]) + "</option>"
|
||||
var label = translate(list[key]);
|
||||
keyVal = self.escapeString(keyVal);
|
||||
label = self.escapeString(label);
|
||||
html += "<option value=\"" + keyVal + "\" " + (checkOption(list[key]) ? 'selected' : '') + ">" + label + "</option>"
|
||||
}
|
||||
return new Handlebars.SafeString(html);
|
||||
});
|
||||
|
||||
@@ -392,6 +392,8 @@ define('views/admin/entity-manager/modals/edit-entity', ['views/modal', 'model']
|
||||
toPlural: function (string) {
|
||||
if (string.slice(-1) == 'y') {
|
||||
return string.substr(0, string.length - 1) + 'ies';
|
||||
} else if (string.slice(-1) == 's') {
|
||||
return string + 'es';
|
||||
} else {
|
||||
return string + 's';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy 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/admin/field-manager/fields/link-multiple/default', 'views/fields/link-multiple', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
data: function () {
|
||||
var defaultAttributes = this.model.get('defaultAttributes') || {};
|
||||
|
||||
var nameHash = defaultAttributes[this.options.field + 'Names'] || {};
|
||||
var idValues = defaultAttributes[this.options.field + 'Ids'] || [];
|
||||
|
||||
var data = Dep.prototype.data.call(this);
|
||||
|
||||
data.nameHash = nameHash;
|
||||
data.idValues = idValues;
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
this.foreignScope = this.getMetadata().get(['entityDefs', this.options.scope, 'links', this.options.field, 'entity']);
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
var data = Dep.prototype.fetch.call(this);
|
||||
|
||||
var defaultAttributes = {};
|
||||
defaultAttributes[this.options.field + 'Ids'] = data[this.idsName];
|
||||
defaultAttributes[this.options.field + 'Names'] = data[this.nameHashName];
|
||||
|
||||
if (data[this.idsName] === null || data[this.idsName].length === 0) {
|
||||
defaultAttributes = null;
|
||||
}
|
||||
|
||||
return {
|
||||
defaultAttributes: defaultAttributes
|
||||
};
|
||||
},
|
||||
|
||||
copyValuesFromModel: function () {
|
||||
var defaultAttributes = this.model.get('defaultAttributes') || {};
|
||||
|
||||
var idValues = defaultAttributes[this.options.field + 'Ids'] || [];
|
||||
var nameHash = defaultAttributes[this.options.field + 'Names'] || {};
|
||||
|
||||
this.ids = idValues;
|
||||
this.nameHash = nameHash;
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
@@ -51,8 +51,8 @@ Espo.define('views/dashlets/options/base', ['views/modal', 'views/record/detail'
|
||||
buttonList: [
|
||||
{
|
||||
name: 'save',
|
||||
label: 'Save',
|
||||
style: 'primary'
|
||||
label: 'Apply',
|
||||
style: 'danger'
|
||||
},
|
||||
{
|
||||
name: 'cancel',
|
||||
|
||||
@@ -345,11 +345,10 @@ define('views/detail', 'views/main', function (Dep) {
|
||||
|
||||
this.getRouter().dispatch(this.scope, 'create', {
|
||||
attributes: attributes,
|
||||
returnUrl: this.getRouter().getCurrentUrl(),
|
||||
});
|
||||
this.getRouter().navigate(url, {trigger: false});
|
||||
}.bind(this));
|
||||
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user