Compare commits

..

63 Commits

Author SHA1 Message Date
Yuri Kuznetsov 83332178a0 fix acl check command 2020-11-12 15:05:03 +02:00
Yuri Kuznetsov fb33c2acf1 fix task panel layout 2020-11-10 12:48:40 +02:00
Yuri Kuznetsov 3c5914b5d9 fix text filter by foreign field 2020-11-10 09:22:20 +02:00
Taras Machyshyn 8c48f72a3c Command fix 2020-11-09 15:16:30 +02:00
Taras Machyshyn a8ce780718 Upgrade fix 2020-11-04 10:23:07 +02:00
Yuri Kuznetsov 31eff86578 fix link search blur 2020-11-03 16:44:20 +02:00
Yuri Kuznetsov 892f9f01f6 export xlsx currency format 2020-11-03 10:43:33 +02:00
Yuri Kuznetsov 860a58847d currency format 10 $ 2020-11-03 10:03:12 +02:00
Yuri Kuznetsov 1ed90d2306 v 2020-11-02 12:55:01 +02:00
Yuri Kuznetsov aff0b350f3 fix portal runner 2020-11-02 12:54:13 +02:00
Yuri Kuznetsov 651b0be5ed add log to slim 2020-10-31 13:38:11 +02:00
Yuri Kuznetsov cead33c09d template manager image 2020-10-30 09:26:37 +02:00
Yuri Kuznetsov 80602c084f fix pdf page number 2020-10-29 16:34:49 +02:00
Yuri Kuznetsov b0420b8b93 fix email folder 2020-10-28 13:19:49 +02:00
Yuri Kuznetsov b47018cbab hide email menu items in portal 2020-10-28 11:58:44 +02:00
Yuri Kuznetsov ea69ab6eaf v 2020-10-28 11:12:51 +02:00
Eymen Elkum 92887401b2 address view remove duplicated codes (#1823) 2020-10-28 11:10:52 +02:00
Yuri Kuznetsov df0bda6324 Merge branch 'hotfix/6.0.3' of github.com:espocrm/espocrm into hotfix/6.0.3 2020-10-28 11:05:37 +02:00
Yuri Kuznetsov 9d67808496 fix tester 2020-10-28 11:02:24 +02:00
Yuri Kuznetsov cf33a98f20 fix activities and select manager 2020-10-27 17:04:49 +02:00
Yuri Kuznetsov afbda344ec fix export specific fields 2020-10-27 14:25:47 +02:00
Yuri Kuznetsov 1079597584 fix 2fa 2020-10-27 14:13:00 +02:00
Yuri Kuznetsov af9b411a4c diff all patch versions 2020-10-27 12:07:39 +02:00
Yuri Kuznetsov cd49d951d8 v 2020-10-27 11:59:55 +02:00
Yuri Kuznetsov 7d63c114aa fix export all fields 2020-10-27 11:06:52 +02:00
Yuri Kuznetsov af6f05ba07 formula fix logical operator priority 2020-10-27 10:31:42 +02:00
Yuri Kuznetsov 46f8eb0cdb formula fix logical not 2020-10-27 10:23:03 +02:00
Pomazan Bogdan c543bb5a5b Update Admin.json (#1814) 2020-10-26 15:36:46 +02:00
Yuri Kuznetsov d1997089e4 v 2020-10-26 13:06:11 +02:00
Yuri Kuznetsov 57dd0177f9 fix export 2020-10-25 18:56:57 +02:00
Yuri Kuznetsov ba76e5ee3d fix language manager 2020-10-25 18:54:34 +02:00
Yuri Kuznetsov 76c93b842c case close reject actions only if available in status list 2020-10-23 10:25:26 +03:00
Yuri Kuznetsov e73bff5ddf v 2020-10-22 12:14:40 +03:00
Yuri Kuznetsov 7d5c0d754e fix upgrade script 2020-10-22 11:47:59 +03:00
Taras Machyshyn e08a74b129 Bug fixes 2020-10-22 11:30:00 +03:00
Taras Machyshyn be07aafaf4 Fix 2020-10-21 15:33:42 +03:00
Taras Machyshyn e2bf27e524 Fix 2020-10-21 12:00:47 +03:00
Yuri Kuznetsov b0157adbe2 cs fix 2020-10-21 09:40:34 +03:00
Yuri Kuznetsov 239593afb5 fix mass actions 2020-10-21 09:31:42 +03:00
Yuri Kuznetsov b97d764588 fix error msg 2020-10-19 15:30:36 +03:00
Yuri Kuznetsov bf8e7af984 export fixes 2020-10-19 15:19:24 +03:00
Yuri Kuznetsov bf68c475e5 email queue items filters email 2020-10-19 13:37:06 +03:00
Yuri Kuznetsov 29624239ba search queue items 2020-10-19 13:35:43 +03:00
Yuri Kuznetsov 4cb3c4918b campaign log search by email address 2020-10-19 12:59:36 +03:00
Yuri Kuznetsov 26fa0d65e1 fix campaign log search 2020-10-19 12:57:17 +03:00
Yuri Kuznetsov 958baed3df fix email invalid on bounced 2020-10-19 12:35:48 +03:00
Yuri Kuznetsov 40d6fb7565 fix export 2020-10-15 19:40:37 +03:00
Yuri Kuznetsov a7551b7dbf fix request getContentType 2020-10-15 17:35:58 +03:00
Yuri Kuznetsov 9ed33c4dc4 remove self assign in portal 2020-10-14 16:23:02 +03:00
Yuri Kuznetsov 6b18fbe8f7 select manager fix 2020-10-14 15:48:52 +03:00
Yuri Kuznetsov 53b9880ea8 entry point changes 2020-10-14 15:15:09 +03:00
Yuri Kuznetsov 62538664df Merge branch 'master' of github.com:espocrm/espocrm 2020-10-14 13:17:56 +03:00
Yuri Kuznetsov f678fb16cc ouauth callback changes 2020-10-14 13:17:46 +03:00
Yuri Kuznetsov fe09aa1cd9 cs fix 2020-10-13 11:01:52 +03:00
Yuri Kuznetsov 710decc610 fix htmlizer and email template no id 2020-10-13 10:47:37 +03:00
Yuri Kuznetsov 5d481d3528 email auto reply error log 2020-10-13 10:47:27 +03:00
Yuri Kuznetsov 8bcd3b72c5 hook fix 2020-10-13 09:56:13 +03:00
Yuri Kuznetsov 0a0da810e9 application state changes 2020-10-13 09:56:04 +03:00
Yuri Kuznetsov e630c046fe Merge branch 'hotfix/5.9.5' 2020-10-12 14:01:49 +03:00
Yuri Kuznetsov 2857b5c53d fix import button 2020-10-12 14:00:25 +03:00
Yuri Kuznetsov 0965c61f55 fix reminder submition 2020-10-12 10:14:17 +03:00
Yuri Kuznetsov 9dd318b176 fix label 2020-10-10 16:08:58 +03:00
Yuri Kuznetsov f8beca720c label fixes 2020-10-10 16:06:57 +03:00
63 changed files with 676 additions and 256 deletions
+8 -2
View File
@@ -138,7 +138,13 @@ class RequestWrapper implements ApiRequest
public function getContentType() : ?string
{
return $this->getHeader('Content-Type');
if (!$this->hasHeader('Content-Type')) {
return null;
}
$contentType = $this->request->getHeader('Content-Type')[0];
return strtolower($contentType);
}
public function getBodyContents() : ?string
@@ -163,7 +169,7 @@ class RequestWrapper implements ApiRequest
{
$contents = $this->getBodyContents();
if (strtolower($this->getContentType()) === 'application/json' && $contents) {
if ($this->getContentType() === 'application/json' && $contents) {
$this->parsedBody = json_decode($contents);
if (is_array($this->parsedBody)) {
@@ -39,6 +39,7 @@ use Espo\Core\{
Api\ResponseWrapper,
Api\RouteProcessor,
Utils\Route,
Utils\Log,
};
use Slim\{
@@ -63,12 +64,15 @@ class Api implements ApplicationRunner
protected $injectableFactory;
protected $applicationUser;
protected $routeUtil;
protected $log;
public function __construct(InjectableFactory $injectableFactory, ApplicationUser $applicationUser, Route $routeUtil)
{
public function __construct(
InjectableFactory $injectableFactory, ApplicationUser $applicationUser, Route $routeUtil, Log $log
) {
$this->injectableFactory = $injectableFactory;
$this->applicationUser = $applicationUser;
$this->routeUtil = $routeUtil;
$this->log = $log;
}
public function run()
@@ -85,7 +89,7 @@ class Api implements ApplicationRunner
$this->addRoute($slim, $item);
}
$slim->addErrorMiddleware(false, true, true);
$slim->addErrorMiddleware(false, true, true, $this->log);
$slim->run();
}
@@ -29,9 +29,8 @@
namespace Espo\Core\ApplicationRunners;
use Espo\Core\Exceptions\Error;
use Espo\Core\{
Exceptions\Error,
InjectableFactory,
EntryPointManager,
ApplicationUser,
@@ -39,7 +38,6 @@ use Espo\Core\{
Portal\Application as PortalApplication,
Utils\Route,
Utils\ClientManager,
Authentication\Authentication,
Api\Auth as ApiAuth,
Api\ErrorOutput as ApiErrorOutput,
@@ -48,18 +46,12 @@ use Espo\Core\{
};
use Slim\{
App as SlimApp,
Factory\AppFactory as SlimAppFactory,
};
use Psr\Http\{
Message\ResponseInterface as Psr7Response,
Message\ServerRequestInterface as Psr7Request,
Server\RequestHandlerInterface as Psr7RequestHandler,
ResponseEmitter,
Factory\ServerRequestCreatorFactory,
Psr7\Response,
};
use StdClass;
use Exception;
/**
@@ -113,30 +105,19 @@ class EntryPoint implements ApplicationRunner
}
}
$slim = SlimAppFactory::create();
$request = (ServerRequestCreatorFactory::create())->createServerRequestFromGlobals();
$slim->setBasePath(Route::detectBasePath());
if ($request->getMethod() !== 'GET') {
throw new Error("Only GET request allowed for entry points.");
}
$slim->add(
function (Psr7Request $request, Psr7RequestHandler $handler) use (
$entryPoint, $data, $authRequired, $authNotStrict, $slim
) : Psr7Response {
$requestWrapped = new RequestWrapper($request, $slim->getBasePath());
$responseWrapped = new ResponseWrapper($handler->handle($request));
$requestWrapped = new RequestWrapper($request, Route::detectBasePath());
$this->processRequest($entryPoint, $requestWrapped, $responseWrapped, $data, $authRequired, $authNotStrict);
$responseWrapped = new ResponseWrapper(new Response());
return $responseWrapped->getResponse();
}
);
$this->processRequest($entryPoint, $requestWrapped, $responseWrapped, $data, $authRequired, $authNotStrict);
$route = Route::detectEntryPointRoute();
$slim->get($route, function (Psr7Request $request, Psr7Response $response) : Psr7Response {
return $response;
});
$slim->run();
(new ResponseEmitter())->emit($responseWrapped->getResponse());
}
protected function processRequest(
+36 -9
View File
@@ -55,11 +55,13 @@ class ApplicationState
}
/**
* Get portal ID (if an applicaition is portal).
* Get a portal ID (if an applicaition is portal).
*/
public function getPortalId() : string
{
if (!$this->isPortal()) throw new Error("Can't get portal ID for non-portal application.");
if (!$this->isPortal()) {
throw new Error("Can't get portal ID for non-portal application.");
}
return $this->getPortal()->id;
}
@@ -69,7 +71,9 @@ class ApplicationState
*/
public function getPortal() : PortalEntity
{
if (!$this->isPortal()) throw new Error("Can't get portal for non-portal application.");
if (!$this->isPortal()) {
throw new Error("Can't get portal for non-portal application.");
}
return $this->container->get('portal');
}
@@ -83,21 +87,38 @@ class ApplicationState
}
/**
* Get current logged user. If no auth is applied, then system user will be returned.
* Get a current logged user. If no auth is applied, then the system user will be returned.
*/
public function getUser() : UserEntity
{
if (!$this->hasUser()) throw new Error("User is not yet available.");
if (!$this->hasUser()) {
throw new Error("User is not yet available.");
}
return $this->container->get('user');
}
/**
* Get an ID of a current logged user. If no auth is applied, then the system user will be returned.
*/
public function getUserId() : string
{
return $this->getUser()->id;
}
/**
* Whether a user is logged.
*/
public function isLogged() : bool
{
if (!$this->container->has('user')) return false;
if ($this->getUser()->isSystem()) return false;
if (!$this->container->has('user')) {
return false;
}
if ($this->getUser()->isSystem()) {
return false;
}
return true;
}
@@ -106,7 +127,10 @@ class ApplicationState
*/
public function isAdmin() : bool
{
if (!$this->isLogged()) return false;
if (!$this->isLogged()) {
return false;
}
return $this->getUser()->isAdmin();
}
@@ -116,7 +140,10 @@ class ApplicationState
*/
public function isApi() : bool
{
if (!$this->isLogged()) return false;
if (!$this->isLogged()) {
return false;
}
return $this->getUser()->isApi();
}
}
@@ -368,7 +368,7 @@ class Authentication
if ($code) {
if (!$impl->verifyCode($loggedUser, $code)) {
Result::fail('Code not verified');
return Result::fail('Code not verified');
}
return $result;
@@ -103,7 +103,7 @@ class CommandManager
$skipIndex = 1;
if (isset($argv[0]) && $argv[0] === 'command.php') {
if (isset($argv[0]) && preg_match('/command\.php$/', $argv[0])) {
$skipIndex = 2;
}
@@ -48,37 +48,57 @@ class AclCheck implements Command
$id = $options['id'] ?? null;
$action = $options['action'] ?? null;
if (empty($userId)) return null;
if (empty($scope)) return null;
if (empty($id)) return null;
if (empty($userId)) {
return null;
}
if (empty($scope)) {
return null;
}
if (empty($id)) {
return null;
}
$container = $this->container;
$entityManager = $container->get('entityManager');
$user = $entityManager->getEntity('User', $userId);
if (!$user) return null;
if (!$user) {
return null;
}
if ($user->isPortal()) {
$portalIdList = $user->getLinkMultipleIdList('portals');
foreach ($portalIdList as $portalId) {
$application = new PortalApplication($portalId);
$containerPortal = $application->getContainer();
$entityManager = $containerPortal->get('entityManager');
$user = $entityManager->getEntity('User', $userId);
if (!$user) return null;
if (!$user) {
return null;
}
$result = $this->check($user, $scope, $id, $action, $containerPortal);
if ($result) {
return 'true';
}
}
return null;
}
if ($this->check($user, $scope, $id, $action, $container)) {
return 'true';
}
return null;
}
protected function check($user, $scope, $id, $action, $container)
@@ -86,7 +106,10 @@ class AclCheck implements Command
$entityManager = $container->get('entityManager');
$entity = $entityManager->getEntity($scope, $id);
if (!$entity) return false;
if (!$entity) {
return false;
}
$aclManager = $container->get('aclManager');
@@ -78,7 +78,7 @@ class Upgrade implements Command
$versionInfo = $this->getVersionInfo($toVersion);
$nextVersion = $versionInfo->nextVersion ?? null;
$lastVersion = $infoData->lastVersion ?? null;
$lastVersion = $versionInfo->lastVersion ?? null;
$packageFile = $this->getPackageFile($params, $versionInfo);
@@ -38,6 +38,6 @@ class NotType extends BaseFunction
{
public function process(ArgumentList $args)
{
return !$this->evaluate($args);
return !$this->evaluate($args[0]);
}
}
+2 -1
View File
@@ -40,7 +40,8 @@ class Parser
{
protected $priorityList = [
['='],
['||', '&&'],
['||'],
['&&'],
['==', '!=', '>', '<', '>=', '<='],
['+', '-'],
['*', '/', '%'],
+1 -1
View File
@@ -120,7 +120,7 @@ class Htmlizer
$relationList = $entity->getRelationList();
if (!$skipLinks && $level === 0 && $this->entityManager) {
if (!$skipLinks && $level === 0 && $this->entityManager && $entity->id) {
foreach ($relationList as $relation) {
$collection = null;
+8 -8
View File
@@ -120,11 +120,11 @@ class Tcpdf extends \TCPDF
$html = $this->headerHtml;
if ($this->useGroupNumbers) {
$html = str_replace('{pageNumber}', '{:png:}', $html);
$html = str_replace('{pageAbsoluteNumber}', '{:pnp:}', $html);
$html = str_replace('{pageNumber}', '{{:png:}}', $html);
$html = str_replace('{pageAbsoluteNumber}', '{{:pnp:}}', $html);
} else {
$html = str_replace('{pageNumber}', '{:pnp:}', $html);
$html = str_replace('{pageAbsoluteNumber}', '{:pnp:}', $html);
$html = str_replace('{pageNumber}', '{{:pnp:}', $html);
$html = str_replace('{pageAbsoluteNumber}', '{{:pnp:}}', $html);
}
if ($this->isUnicodeFont()) {
@@ -148,11 +148,11 @@ class Tcpdf extends \TCPDF
$html = $this->footerHtml;
if ($this->useGroupNumbers) {
$html = str_replace('{pageNumber}', '{:png:}', $html);
$html = str_replace('{pageAbsoluteNumber}', '{:pnp:}', $html);
$html = str_replace('{pageNumber}', '{{:png:}}', $html);
$html = str_replace('{pageAbsoluteNumber}', '{{:pnp:}}', $html);
} else {
$html = str_replace('{pageNumber}', '{:pnp:}', $html);
$html = str_replace('{pageAbsoluteNumber}', '{:pnp:}', $html);
$html = str_replace('{pageNumber}', '{{:pnp:}}', $html);
$html = str_replace('{pageAbsoluteNumber}', '{{:pnp:}}', $html);
}
if ($this->isUnicodeFont()) {
@@ -34,12 +34,13 @@ use Espo\Core\{
InjectableFactory,
ApplicationUser,
Portal\Utils\Route,
Utils\Log,
};
class Api extends ApiBase
{
public function __construct(InjectableFactory $injectableFactory, ApplicationUser $applicationUser, Route $routeUtil)
public function __construct(InjectableFactory $injectableFactory, ApplicationUser $applicationUser, Route $routeUtil, Log $log)
{
parent::__construct($injectableFactory, $applicationUser, $routeUtil);
parent::__construct($injectableFactory, $applicationUser, $routeUtil, $log);
}
}
+43 -10
View File
@@ -2368,7 +2368,9 @@ class SelectManager
protected function textFilter($textFilter, array &$result, $noFullText = false)
{
$fieldDefs = $this->getSeed()->getAttributes();
$fieldList = $this->getTextFilterFieldList();
$group = [];
$textFilterContainsMinLength = $this->getConfig()->get('textFilterContainsMinLength', self::MIN_LENGTH_FOR_CONTENT_SEARCH);
@@ -2401,6 +2403,7 @@ class SelectManager
$textFilterForFullTextSearch = str_replace('%', '*', $textFilterForFullTextSearch);
$skipFullTextSearch = false;
if (!$forceFullTextSearch) {
if (mb_strpos($textFilterForFullTextSearch, '*') === 0) {
$skipFullTextSearch = true;
@@ -2409,9 +2412,12 @@ class SelectManager
}
}
if ($noFullText) $skipFullTextSearch = true;
if ($noFullText) {
$skipFullTextSearch = true;
}
$fullTextSearchData = null;
if (!$skipFullTextSearch) {
$fullTextSearchData = $this->getFullTextSearchDataForTextFilter($textFilterForFullTextSearch, !$useFullTextSearch);
}
@@ -2419,6 +2425,7 @@ class SelectManager
$fullTextGroup = [];
$fullTextSearchFieldList = [];
if ($fullTextSearchData) {
if ($this->fullTextRelevanceThreshold) {
$fullTextGroup[] = [$fullTextSearchData['where'] . '>=' => $this->fullTextRelevanceThreshold];
@@ -2434,12 +2441,15 @@ class SelectManager
$orderTypeMap = [
'combined' => self::FT_ORDER_COMBINTED,
'relavance' => self::FT_ORDER_RELEVANCE,
'relevance' => self::FT_ORDER_RELEVANCE,
'original' => self::FT_ORDER_ORIGINAL,
];
$mOrderType = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'fullTextSearchOrderType']);
if ($mOrderType) $fullTextOrderType = $orderTypeMap[$mOrderType];
if ($mOrderType) {
$fullTextOrderType = $orderTypeMap[$mOrderType];
}
if (!isset($result['orderBy']) || $fullTextOrderType === self::FT_ORDER_RELEVANCE) {
$result['orderBy'] = [[$relevanceExpression, 'desc']];
@@ -2468,9 +2478,14 @@ class SelectManager
foreach ($fieldList as $field) {
if ($useFullTextSearch) {
if (in_array($field, $fullTextSearchFieldList)) continue;
if (in_array($field, $fullTextSearchFieldList)) {
continue;
}
}
if ($forceFullTextSearch) {
continue;
}
if ($forceFullTextSearch) continue;
$seed = $this->getSeed();
@@ -2478,21 +2493,34 @@ class SelectManager
if (strpos($field, '.') !== false) {
list($link, $foreignField) = explode('.', $field);
$foreignEntityType = $seed->getRelationParam($link, 'entity');
$seed = $this->getEntityManager()->getEntity($foreignEntityType);
$this->addLeftJoin($link, $result);
if ($seed->getRelationParam($link, 'type') === $seed::HAS_MANY) {
$this->setDistinct(true, $result);
}
$attributeType = $seed->getAttributeType($foreignField);
} else {
$attributeType = $seed->getAttributeType($field);
if ($attributeType === 'foreign') {
$link = $seed->getAttributeParam($field, 'relation');
if ($link) {
$this->addLeftJoin($link, $result);
}
}
}
if ($attributeType === 'int') {
if (is_numeric($textFilter)) {
$group[$field] = intval($textFilter);
}
continue;
}
@@ -2666,11 +2694,6 @@ class SelectManager
$selectParams1['havingClause'][] = $selectParams2['havingClause'];
}
if (!empty($selectParams2['leftJoins'])) {
foreach ($selectParams2['leftJoins'] as $item) {
$this->addLeftJoin($item, $selectParams1);
}
}
if (!empty($selectParams2['joins'])) {
foreach ($selectParams2['joins'] as $item) {
@@ -2678,6 +2701,16 @@ class SelectManager
}
}
if (!empty($selectParams2['leftJoins'])) {
foreach ($selectParams2['leftJoins'] as $item) {
if ($this->hasJoin($item, $selectParams1)) {
continue;
}
$this->addLeftJoin($item, $selectParams1);
}
}
if (isset($selectParams2['select'])) {
$selectParams1['select'] = $selectParams2['select'];
}
@@ -169,6 +169,7 @@ class AdminNotificationManager
$extension = $this->getEntityManager()->getRepository('Extension')
->select(['version'])
->where([
'name' => $extensionName,
'isInstalled' => true,
])
->order('createdAt', true)
@@ -34,13 +34,15 @@ use Espo\Core\EntryPoints\{
NoAuth,
};
use Espo\Core\Api\Request;
class OauthCallback implements EntryPoint
{
use NoAuth;
public function run()
public function run(Request $request)
{
echo "EspoCRM rocks! If this window is not closed automatically, it's probable that URL you use to access ".
echo "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.";
}
}
@@ -34,17 +34,20 @@ use Espo\ORM\Entity;
use Espo\Core\{
Utils\Config,
ORM\EntityManager,
ApplicationState,
};
class AssignmentEmailNotification
{
protected $config;
protected $entityManager;
protected $applicationState;
public function __construct(Config $config, EntityManager $entityManager)
public function __construct(Config $config, EntityManager $entityManager, ApplicationState $applicationState)
{
$this->config = $config;
$this->entityManager = $entityManager;
$this->applicationState = $applicationState;
}
public function afterSave(Entity $entity, array $options = [])
@@ -67,17 +70,25 @@ class AssignmentEmailNotification
if ($entity->has('assignedUsersIds')) {
$userIdList = $entity->getLinkMultipleIdList('assignedUsers');
$fetchedAssignedUserIdList = $entity->getFetched('assignedUsersIds');
if (!is_array($fetchedAssignedUserIdList)) {
$fetchedAssignedUserIdList = [];
}
foreach ($userIdList as $userId) {
if (in_array($userId, $fetchedAssignedUserIdList)) continue;
if (!$this->isNotSelfAssignment($entity, $userId)) continue;
if (in_array($userId, $fetchedAssignedUserIdList)) {
continue;
}
if (!$this->isNotSelfAssignment($entity, $userId)) {
continue;
}
$this->createJob($entity, $userId);
}
} else {
$userId = $entity->get('assignedUserId');
if (!empty($userId) &&
$entity->isAttributeChanged('assignedUserId') && $this->isNotSelfAssignment($entity, $userId)
) {
@@ -96,26 +107,29 @@ class AssignmentEmailNotification
$isNotSelfAssignment = $assignedUserId !== $entity->get('modifiedById');
}
} else {
$isNotSelfAssignment = $assignedUserId !== $this->getUser()->id;
$isNotSelfAssignment = $assignedUserId !== $this->applicationState->getUserId();
}
return $isNotSelfAssignment;
}
protected function createJob(Entity $entity, $userId)
{
$job = $this->entityManager->getEntity('Job');
$job->set([
'serviceName' => 'EmailNotification',
'methodName' => 'notifyAboutAssignmentJob',
'data' => [
'userId' => $userId,
'assignerUserId' => $this->getUser()->id,
'assignerUserId' => $this->applicationState->getUserId(),
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
],
'executeTime' => date('Y-m-d H:i:s'),
'queue' => 'e0',
]);
$this->entityManager->saveEntity($job);
}
}
@@ -36,6 +36,9 @@ use Espo\Core\{
Jobs\Job,
};
use Throwable;
use DateTime;
class SubmitPopupReminders implements Job
{
const REMINDER_PAST_HOURS = 24;
@@ -53,21 +56,27 @@ class SubmitPopupReminders implements Job
public function run()
{
if (!$this->config->get('useWebSocket')) return;
if (!$this->config->get('useWebSocket')) {
return;
}
$dt = new \DateTime();
$dt = new DateTime();
$now = $dt->format('Y-m-d H:i:s');
$pastHours = $this->config->get('reminderPastHours', self::REMINDER_PAST_HOURS);
$nowShifted = $dt->modify('-' . $pastHours . ' hours')->format('Y-m-d H:i:s');
$reminderList = $this->entityManager->getRepository('Reminder')->where([
'type' => 'Popup',
'remindAt<=' => $now,
'startAt>' => $nowShifted,
'isSubmitted' => false,
])->find();
$reminderList = $this->entityManager
->getRepository('Reminder')
->where([
'type' => 'Popup',
'remindAt<=' => $now,
'startAt>' => $nowShifted,
'isSubmitted' => false,
])
->find();
$submitData = [];
@@ -78,6 +87,7 @@ class SubmitPopupReminders implements Job
if (!$userId || !$entityType || !$entityId) {
$this->deleteReminder($reminder);
continue;
}
@@ -85,19 +95,24 @@ class SubmitPopupReminders implements Job
if (!$entity) {
$this->deleteReminder($reminder);
continue;
}
if ($entity->hasLinkMultipleField('users')) {
$entity->loadLinkMultipleField('users', ['status' => 'acceptanceStatus']);
$status = $entity->getLinkMultipleColumn('users', 'status', $userId);
if ($status === 'Declined') {
$this->deleteReminder($reminder);
continue;
}
}
$dateAttribute = 'dateStart';
if ($entityType === 'Task') {
$dateAttribute = 'dateEnd';
}
@@ -125,10 +140,10 @@ class SubmitPopupReminders implements Job
foreach ($submitData as $userId => $list) {
try {
$this->getContainer()->get('webSocketSubmission')->submit('popupNotifications.event', $userId, (object) [
$this->webSocketSubmission->submit('popupNotifications.event', $userId, (object) [
'list' => $list
]);
} catch (\Throwable $e) {
} catch (Throwable $e) {
$GLOBALS['log']->error('Job SubmitPopupReminders: [' . $e->getCode() . '] ' .$e->getMessage());
}
}
@@ -38,7 +38,8 @@
},
"labels": {
"Create Task": "Create Task",
"Complete": "Complete"
"Complete": "Complete",
"overdue": "overdue"
},
"presetFilters": {
"actual": "Actual",
@@ -2,5 +2,6 @@
"massEmail",
"status",
"target",
"sentAt"
"sentAt",
"emailAddress"
]
@@ -78,6 +78,7 @@
}
},
"collection": {
"textFilterFields": ["queueItem.id", "queueItem.emailAddress"],
"orderBy": "createdAt",
"order": "desc"
},
@@ -45,6 +45,7 @@
}
},
"collection": {
"textFilterFields": ["id", "emailAddress"],
"orderBy": "createdAt",
"order": "desc"
}
@@ -873,7 +873,7 @@ class Activities implements
$service->handleListParams($params);
$selectParams = $selectManager->getSelectParams($params, false, true);
$selectParams = $selectManager->getSelectParams($params, false, true, true);
$offset = $selectParams['offset'];
$limit = $selectParams['limit'];
+1 -1
View File
@@ -175,7 +175,7 @@ class BaseEntity implements Entity
// @todo Remove this.
if ($this->hasRelation($name) && $this->id && $this->entityManager) {
trigger_error(
"Accessing related records with Entity::get is deprecated. Use \$entityManager->getRelation(...)->find()",
"Accessing related records with Entity::get is deprecated. Use \$repository->getRelation(...)->find()",
E_USER_DEPRECATED
);
@@ -144,7 +144,8 @@
},
"currencyFormat": {
"1": "10 USD",
"2": "$10"
"2": "$10",
"3": "10 $"
},
"personNameFormat": {
"firstLast": "First Last",
@@ -169,7 +169,7 @@
"Cancel": "Отменить",
"Apply": "Применить",
"Unlink": "Убрать ссылку",
"Mass Update": "Обновить все",
"Mass Update": "Массовое обновление",
"Export": "Экспортировать",
"No Data": "Нет данных",
"No Access": "Нет доступа",
@@ -185,7 +185,7 @@
"uninstallConfirmation": "Ви впевнені, що хочете видалити розширення?",
"cronIsNotConfigured": "Заплановані завдання не працюють. Отже, вхідні електронні листи, сповіщення та нагадування не працюють. Будь ласка, дотримуйтесь інструкцій (https://www.espocrm.com/documentation/administration/server-configuration/#user-content-setup-a-crontab), щоб налаштувати cron job.",
"newExtensionVersionIsAvailable": "Нова {extensionName} версія {latestVersion} доступна.",
"upgradeVersion": "EspoCRM буде оновлено до версії **{версія}**. Будьте терплячі, оскільки це може зайняти деякий час.",
"upgradeVersion": "EspoCRM буде оновлено до версії **{version}**. Будьте терплячі, оскільки це може зайняти деякий час.",
"upgradeDone": "EspoCRM оновлено до версії **{version}**.",
"downloadUpgradePackage": "Завантажте апгрейд [звідси]({url})",
"upgradeInfo": "Перегляньте [документацію]({url}) про те, як оновити EspoCRM.\n",
@@ -257,4 +257,4 @@
"noteStatus": "Повідомлення про оновлення статусу",
"passwordChangeLink": "Посилання на зміну паролю"
}
}
}
@@ -65,7 +65,10 @@
"parent": false
},
"EmailFolder": {
"assignedUser": false
"assignedUser": {
"read": "yes",
"edit": "no"
}
},
"Email": {
"inboundEmails": false,
@@ -53,11 +53,21 @@
{
"label":"Folders",
"link":"#EmailFolder",
"configCheck": "!emailFoldersDisabled"
"configCheck": "!emailFoldersDisabled",
"accessDataList": [
{
"inPortalDisabled": true
}
]
},
{
"label":"Filters",
"link":"#EmailFilter"
"link":"#EmailFilter",
"accessDataList": [
{
"inPortalDisabled": true
}
]
}
]
},
@@ -496,7 +496,7 @@
},
"currencyFormat": {
"type": "enumInt",
"options": [1, 2]
"options": [1, 2, 3]
},
"currencyDecimalPlaces": {
"type": "int",
@@ -162,8 +162,6 @@ class Email extends \Espo\Core\Select\SelectManager
$result['whereClause'][] = [
'usersMiddle.userId' => $this->getUser()->id
];
$this->addUsersColumns($result);
}
protected function boolFilterOnlyMy(&$result)
+16
View File
@@ -44,6 +44,10 @@ class Attachment extends Record
protected $inlineAttachmentFieldTypeList = ['wysiwyg'];
protected $adminOnlyHavingInlineAttachmentsEntityTypeList = [
'TemplateManager',
];
protected $imageTypeList = [
'image/png',
'image/jpeg',
@@ -160,12 +164,24 @@ class Attachment extends Record
protected function checkAttachmentField($relatedEntityType, $field, $role = 'Attachment')
{
if (
$this->getUser()->isAdmin()
&&
$role === 'Inline Attachment'
&&
in_array($relatedEntityType, $this->adminOnlyHavingInlineAttachmentsEntityTypeList)
) {
return;
}
$fieldType = $this->getMetadata()->get(['entityDefs', $relatedEntityType, 'fields', $field, 'type']);
if (!$fieldType) {
throw new Error("Field '{$field}' does not exist.");
}
$attachmentFieldTypeListParam = lcfirst(str_replace(' ', '', $role)) . 'FieldTypeList';
if (!in_array($fieldType, $this->$attachmentFieldTypeListParam)) {
throw new Error("Field type '{$fieldType}' is not allowed for {$role}.");
}
+31 -6
View File
@@ -133,6 +133,7 @@ class EmailTemplate extends Record implements
if (!empty($params['relatedId']) && !empty($params['relatedType'])) {
$related = $this->getEntityManager()->getEntity($params['relatedType'], $params['relatedId']);
if ($related) {
$entityHash[$related->getEntityType()] = $related;
}
@@ -155,6 +156,7 @@ class EmailTemplate extends Record implements
if ($handlebarsInSubject) {
$subject = $htmlizer->render($parent, $subject);
}
if ($handlebarsInBody) {
$body = $htmlizer->render($parent, $body);
}
@@ -244,7 +246,9 @@ class EmailTemplate extends Record implements
}
foreach ($attributeList as $attribute) {
if (in_array($attribute, $forbiddenAttributeList)) continue;
if (in_array($attribute, $forbiddenAttributeList)) {
continue;
}
$value = $entity->get($attribute);
@@ -252,13 +256,18 @@ class EmailTemplate extends Record implements
continue;
}
if (!$entity->getAttributeType($attribute)) continue;
if (!$entity->getAttributeType($attribute)) {
continue;
}
$value = $this->formatAttributeValue($entity, $attribute);
if (is_null($value)) continue;
if (is_null($value)) {
continue;
}
$variableName = $attribute;
if (!is_null($prefixLink)) {
$variableName = $prefixLink . '.' . $attribute;
}
@@ -266,7 +275,7 @@ class EmailTemplate extends Record implements
$text = str_replace('{' . $type . '.' . $variableName . '}', $value, $text);
}
if (!$skipLinks) {
if (!$skipLinks && $entity->id) {
$relationDefs = $entity->getRelations();
foreach ($entity->getRelationList() as $relation) {
@@ -301,10 +310,12 @@ class EmailTemplate extends Record implements
}
$replaceData = [];
$replaceData['today'] = $this->getDateTime()->getTodayString();
$replaceData['now'] = $this->getDateTime()->getNowString();
$timeZone = $this->getConfig()->get('timeZone');
$now = new DateTime('now', new DateTimezone($timeZone));
$replaceData['currentYear'] = $now->format('Y');
@@ -328,11 +339,13 @@ class EmailTemplate extends Record implements
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
} else if ($fieldType === 'array' || $fieldType === 'multiEnum' || $fieldType === 'checklist') {
$valueList = [];
if (is_array($value)) {
foreach ($value as $v) {
$valueList[] = $this->getLanguage()->translateOption($v, $attribute, $entity->getEntityType());
}
}
$value = implode(', ', $valueList);
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
} else {
@@ -348,13 +361,16 @@ class EmailTemplate extends Record implements
if (!is_string($value)) {
$value = '';
}
$value = nl2br($value);
} else if ($attributeType == 'float') {
if (is_float($value)) {
$decimalPlaces = 2;
if ($fieldType === 'currency') {
$decimalPlaces = $this->getConfig()->get('currencyDecimalPlaces');
}
$value = $this->getNumber()->format($value, $decimalPlaces);
}
} else if ($attributeType == 'int') {
@@ -370,7 +386,9 @@ class EmailTemplate extends Record implements
$value = '';
}
if (!is_string($value)) return null;
if (!is_string($value)) {
return null;
}
return $value;
}
@@ -389,6 +407,7 @@ class EmailTemplate extends Record implements
if ($parentId && $parentType) {
$e = $this->getEntityManager()->getEntity($parentType, $parentId);
if ($e && $this->getAcl()->check($e)) {
$dataList[] = [
'type' => 'parent',
@@ -426,6 +445,7 @@ class EmailTemplate extends Record implements
foreach ($fm->getEntityTypeFieldList($entityType) as $field) {
$fieldType = $fm->getEntityTypeFieldParam($entityType, $field, 'type');
$fieldAttributeList = $fm->getAttributeList($entityType, $field);
if (
$fm->getEntityTypeFieldParam($entityType, $field, 'disabled') ||
$fm->getEntityTypeFieldParam($entityType, $field, 'directAccessDisabled') ||
@@ -441,9 +461,14 @@ class EmailTemplate extends Record implements
$attributeList = $fm->getEntityTypeAttributeList($entityType);
$values = (object) [];
foreach ($attributeList as $a) {
if (!$e->has($a)) continue;
if (!$e->has($a)) {
continue;
}
$value = $emailTemplateService->formatAttributeValue($e, $a);
if ($value != '') {
$values->$a = $value;
}
+11 -3
View File
@@ -798,7 +798,9 @@ class InboundEmail extends RecordService implements
}
$d = new DateTime();
$d->modify('-3 hours');
$threshold = $d->format('Y-m-d H:i:s');
$emailAddress = $this->getEntityManager()->getRepository('EmailAddress')->getByAddress($email->get('from'));
@@ -819,6 +821,7 @@ class InboundEmail extends RecordService implements
try {
$replyEmailTemplateId = $inboundEmail->get('replyEmailTemplateId');
if ($replyEmailTemplateId) {
$entityHash = [];
@@ -912,7 +915,9 @@ class InboundEmail extends RecordService implements
return true;
}
} catch (Exception $e) {}
} catch (Exception $e) {
$GLOBALS['log']->error("Inbound Email: Auto-reply error: " . $e->getMessage());
}
}
protected function getSmtpParamsFromInboundEmail(InboundEmailEntity $emailAccount)
@@ -988,9 +993,12 @@ class InboundEmail extends RecordService implements
if ($isHard && $emailAddress) {
$emailAddressEntity = $this->getEntityManager()->getRepository('EmailAddress')->getByAddress($emailAddress);
$emailAddressEntity->set('invalid', true);
$this->getEntityManager()->saveEntity($emailAddressEntity);
if ($emailAddressEntity) {
$emailAddressEntity->set('invalid', true);
$this->getEntityManager()->saveEntity($emailAddressEntity);
}
}
if ($campaignId && $target && $target->id) {
+177 -42
View File
@@ -59,6 +59,7 @@ use Espo\Core\{
use Espo\Tools\Export\Export as ExportTool;
use StdClass;
use Exception;
use Espo\Core\Di;
@@ -1245,13 +1246,19 @@ class Record implements Crud,
public function delete(string $id)
{
if (empty($id)) throw new BadRequest("ID is empty.");
if (empty($id)) {
throw new BadRequest("ID is empty.");
}
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFound("Record {$id} not found.");
if (!$entity) {
throw new NotFound("Record {$id} not found.");
}
if (!$this->getAcl()->check($entity, 'delete')) throw new ForbiddenSilent("No delete access.");
if (!$this->getAcl()->check($entity, 'delete')) {
throw new ForbiddenSilent("No delete access.");
}
$this->beforeDeleteEntity($entity);
@@ -1292,6 +1299,7 @@ class Record implements Crud,
}
$maxSize = 0;
if ($disableCount) {
if (!empty($params['maxSize'])) {
$maxSize = $params['maxSize'];
@@ -1376,10 +1384,16 @@ class Record implements Crud,
];
foreach ($statusList as $status) {
if (in_array($status, $statusIgnoreList)) continue;
if (!$status) continue;
if (in_array($status, $statusIgnoreList)) {
continue;
}
if (!$status) {
continue;
}
$selectParamsSub = $selectParams;
$selectParamsSub['whereClause'][] = [
$statusField => $status
];
@@ -1454,12 +1468,19 @@ class Record implements Crud,
public function restoreDeleted(string $id)
{
if (!$this->getUser()->isAdmin()) throw new Forbidden();
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
$entity = $this->getEntityEvenDeleted($id);
if (!$entity) throw new NotFound();
if (!$entity->get('deleted')) throw new Forbidden();
if (!$entity) {
throw new NotFound();
}
if (!$entity->get('deleted')) {
throw new Forbidden();
}
$this->getRepository()->restoreDeleted($entity->id);
@@ -1475,6 +1496,7 @@ class Record implements Crud,
return $this->getConfig()->get('maxSelectTextAttributeLengthForList', self::MAX_SELECT_TEXT_ATTRIBUTE_LENGTH);
}
}
return null;
}
@@ -1487,12 +1509,15 @@ class Record implements Crud,
public function findLinked(string $id, string $link, array $params) : RecordCollection
{
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
if (empty($link)) {
throw new Error();
}
@@ -1515,6 +1540,7 @@ class Record implements Crud,
}
$methodName = 'findLinkedEntities' . ucfirst($link);
if (method_exists($this, $methodName)) {
return $this->$methodName($id, $params);
}
@@ -1638,35 +1664,45 @@ class Record implements Crud,
}
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if ($this->noEditAccessRequiredForLink) {
if (!$this->getAcl()->check($entity, 'read')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
} else {
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'edit')) {
throw new Forbidden();
}
}
$methodName = 'link' . ucfirst($link);
if ($link !== 'entity' && $link !== 'entityMass' && method_exists($this, $methodName)) {
return $this->$methodName($id, $foreignId);
}
$foreignEntityType = $entity->getRelationParam($link, 'entity');
if (!$foreignEntityType) {
throw new Error("Entity '{$this->entityType}' has not relation '{$link}'.");
}
$foreignEntity = $this->getEntityManager()->getEntity($foreignEntityType, $foreignId);
if (!$foreignEntity) {
throw new NotFound();
}
$accessActionRequired = 'edit';
if (in_array($link, $this->noEditAccessRequiredLinkList)) {
$accessActionRequired = 'read';
}
if (!$this->getAcl()->check($foreignEntity, $accessActionRequired)) {
throw new Forbidden();
}
@@ -1712,30 +1748,39 @@ class Record implements Crud,
}
if ($this->noEditAccessRequiredForLink) {
if (!$this->getAcl()->check($entity, 'read')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
} else {
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'edit')) {
throw new Forbidden();
}
}
$methodName = 'unlink' . ucfirst($link);
if ($link !== 'entity' && method_exists($this, $methodName)) {
return $this->$methodName($id, $foreignId);
}
$foreignEntityType = $entity->getRelationParam($link, 'entity');
if (!$foreignEntityType) {
throw new Error("Entity '{$this->entityType}' has not relation '{$link}'.");
}
$foreignEntity = $this->getEntityManager()->getEntity($foreignEntityType, $foreignId);
if (!$foreignEntity) {
throw new NotFound();
}
$accessActionRequired = 'edit';
if (in_array($link, $this->noEditAccessRequiredLinkList)) {
$accessActionRequired = 'read';
}
if (!$this->getAcl()->check($foreignEntity, $accessActionRequired)) {
throw new Forbidden();
}
@@ -1756,6 +1801,7 @@ class Record implements Crud,
if (!$this->getMetadata()->get(['scopes', $this->entityType, 'stream'])) throw new NotFound();
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFound();
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'stream')) throw new Forbidden();
@@ -1776,6 +1822,7 @@ class Record implements Crud,
if (!$this->getMetadata()->get(['scopes', $this->entityType, 'stream'])) throw new NotFound();
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFound();
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'stream')) throw new Forbidden();
@@ -1810,14 +1857,17 @@ class Record implements Crud,
}
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if (!$this->getAcl()->check($entity, 'edit')) {
throw new Forbidden();
}
$methodName = 'massLink' . ucfirst($link);
if (method_exists($this, $methodName)) {
return $this->$methodName($id, $where, $selectData);
}
@@ -1874,7 +1924,9 @@ class Record implements Crud,
public function massUpdate(array $params, StdClass $data)
{
if ($this->getAcl()->get('massUpdatePermission') !== 'yes') throw new Forbidden();
if ($this->getAcl()->get('massUpdatePermission') !== 'yes') {
throw new Forbidden();
}
$resultIdList = [];
$repository = $this->getRepository();
@@ -1891,11 +1943,13 @@ class Record implements Crud,
foreach ($collection as $entity) {
if ($this->getAcl()->check($entity, 'edit') && $this->checkEntityForMassUpdate($entity, $data)) {
$entity->set($data);
try {
$this->processValidation($entity, $data);
} catch (\Exception $e) {
} catch (Exception $e) {
continue;
}
if ($this->checkAssignment($entity)) {
$repository->save($entity, ['massUpdate' => true, 'skipStreamNotesAcl' => true]);
$resultIdList[] = $entity->id;
@@ -1944,8 +1998,11 @@ class Record implements Crud,
foreach ($collection as $entity) {
if ($this->getAcl()->check($entity, 'delete') && $this->checkEntityForMassRemove($entity)) {
$repository->remove($entity);
$resultIdList[] = $entity->id;
$count++;
$this->processActionHistoryRecord('delete', $entity);
}
}
@@ -1960,7 +2017,9 @@ class Record implements Crud,
public function massRecalculateFormula(array $params)
{
if (!$this->getUser()->isAdmin()) throw new Forbidden();
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
$count = 0;
@@ -1981,7 +2040,10 @@ class Record implements Crud,
public function follow(string $id, ?string $userId = null)
{
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFoundSilent();
if (!$entity) {
throw new NotFoundSilent();
}
if (!$this->getAcl()->check($entity, 'stream')) {
throw new Forbidden();
@@ -1997,7 +2059,10 @@ class Record implements Crud,
public function unfollow(string $id, ?string $userId = null)
{
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFoundSilent();
if (!$entity) {
throw new NotFoundSilent();
}
if (empty($userId)) {
$userId = $this->getUser()->id;
@@ -2021,7 +2086,10 @@ class Record implements Crud,
$collection = $this->getRepository()->sth()->find($selectParams);
foreach ($collection as $entity) {
if (!$this->getAcl()->check($entity, 'stream') || !$this->getAcl()->check($entity, 'read')) continue;
if (!$this->getAcl()->check($entity, 'stream') || !$this->getAcl()->check($entity, 'read')) {
continue;
}
if ($streamService->followEntity($entity, $userId)) {
$resultIdList[] = $entity->id;
}
@@ -2031,7 +2099,9 @@ class Record implements Crud,
'count' => count($resultIdList),
];
if (isset($params['ids'])) $result['ids'] = $resultIdList;
if (isset($params['ids'])) {
$result['ids'] = $resultIdList;
}
return $result;
}
@@ -2042,7 +2112,9 @@ class Record implements Crud,
$streamService = $this->getStreamService();
if (empty($userId)) $userId = $this->getUser()->id;
if (empty($userId)) {
$userId = $this->getUser()->id;
}
$selectParams = $this->convertMassActionSelectParams($params);
@@ -2066,24 +2138,36 @@ class Record implements Crud,
protected function convertMassActionSelectParams($params)
{
if (array_key_exists('ids', $params)) {
if (!is_array($params['ids'])) throw new BadRequest();
$selectParams = $this->getSelectParams([]);
$selectParams['whereClause'][] = [
'id' => $params['ids']
];
} else if (array_key_exists('where', $params)) {
$p = ['where' => $params['where']];
if (!empty($params['selectData']) && is_array($params['selectData'])) {
foreach ($params['selectData'] as $k => $v) {
$p[$k] = $v;
}
if (!is_array($params['ids'])) {
throw new BadRequest();
}
$selectParams = $this->getSelectParams($p);
} else {
throw new BadRequest();
$selectParams = $this->getSelectParams([]);
$selectParams['whereClause'][] = [
'id' => $params['ids'],
];
return $selectParams;
}
return $selectParams;
if (array_key_exists('where', $params)) {
$searchParams = [
'where' => $params['where'],
];
if (!empty($params['selectData']) && is_array($params['selectData'])) {
foreach ($params['selectData'] as $k => $v) {
$searchParams[$k] = $v;
}
}
unset($searchParams['select']);
return $this->getSelectParams($searchParams);
}
throw new BadRequest();
}
protected function getDuplicateWhereClause(Entity $entity, $data)
@@ -2099,7 +2183,12 @@ class Record implements Crud,
if ($entity->id) {
$where['id!='] = $entity->id;
}
$duplicate = $this->getRepository()->select(['id'])->where($where)->findOne();
$duplicate = $this->getRepository()
->select(['id'])
->where($where)
->findOne();
if ($duplicate) {
return true;
}
@@ -2133,7 +2222,9 @@ class Record implements Crud,
$limit = self::FIND_DUPLICATES_LIMIT;
$duplicateList = $this->getRepository()->limit(0, $limit)->find($selectParams);
$duplicateList = $this->getRepository()
->limit(0, $limit)
->find($selectParams);
if (count($duplicateList)) {
return $duplicateList;
@@ -2168,14 +2259,17 @@ class Record implements Crud,
foreach ($this->internalAttributeList as $attribute) {
$entity->clear($attribute);
}
foreach ($this->forbiddenAttributeList as $attribute) {
$entity->clear($attribute);
}
if (!$this->getUser()->isAdmin()) {
foreach ($this->onlyAdminAttributeList as $attribute) {
$entity->clear($attribute);
}
}
foreach ($this->getAcl()->getScopeForbiddenAttributeList($entity->getEntityType(), 'read') as $attribute) {
$entity->clear($attribute);
}
@@ -2216,14 +2310,16 @@ class Record implements Crud,
$this->beforeMerge($entity, $sourceList, $attributes);
$fieldDefs = $this->getMetadata()->get('entityDefs.' . $entity->getEntityType() . '.fields', array());
$fieldDefs = $this->getMetadata()->get('entityDefs.' . $entity->getEntityType() . '.fields', []);
$hasPhoneNumber = false;
if (!empty($fieldDefs['phoneNumber']) && $fieldDefs['phoneNumber']['type'] == 'phone') {
$hasPhoneNumber = true;
}
$hasEmailAddress = false;
if (!empty($fieldDefs['emailAddress']) && $fieldDefs['emailAddress']['type'] == 'email') {
$hasEmailAddress = true;
}
@@ -2239,6 +2335,7 @@ class Record implements Crud,
if ($hasEmailAddress) {
$emailAddressToRelateList = [];
$emailAddressList = $repository->findRelated($entity, 'emailAddresses');
foreach ($emailAddressList as $emailAddress) {
$emailAddressToRelateList[] = $emailAddress;
}
@@ -2266,12 +2363,14 @@ class Record implements Crud,
if ($hasPhoneNumber) {
$phoneNumberList = $repository->findRelated($source, 'phoneNumbers');
foreach ($phoneNumberList as $phoneNumber) {
$phoneNumberToRelateList[] = $phoneNumber;
}
}
if ($hasEmailAddress) {
$emailAddressList = $repository->findRelated($source, 'emailAddresses');
foreach ($emailAddressList as $emailAddress) {
$emailAddressToRelateList[] = $emailAddress;
}
@@ -2280,10 +2379,12 @@ class Record implements Crud,
$mergeLinkList = [];
$linksDefs = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'links']);
foreach ($linksDefs as $link => $d) {
if (!empty($d['notMergeable'])) {
continue;
}
if (!empty($d['type']) && in_array($d['type'], ['hasMany', 'hasChildren'])) {
$mergeLinkList[] = $link;
}
@@ -2292,6 +2393,7 @@ class Record implements Crud,
foreach ($sourceList as $source) {
foreach ($mergeLinkList as $link) {
$linkedList = $repository->findRelated($source, $link);
foreach ($linkedList as $linked) {
$repository->relate($entity, $link, $linked);
}
@@ -2306,10 +2408,13 @@ class Record implements Crud,
if ($hasEmailAddress) {
$emailAddressData = [];
foreach ($emailAddressToRelateList as $i => $emailAddress) {
$o = (object) [];
$o->emailAddress = $emailAddress->get('name');
$o->primary = false;
if (empty($attributes->emailAddress)) {
if ($i === 0) {
$o->primary = true;
@@ -2317,8 +2422,10 @@ class Record implements Crud,
} else {
$o->primary = $o->emailAddress === $attributes->emailAddress;
}
$o->optOut = $emailAddress->get('optOut');
$o->invalid = $emailAddress->get('invalid');
$emailAddressData[] = $o;
}
$attributes->emailAddressData = $emailAddressData;
@@ -2326,10 +2433,12 @@ class Record implements Crud,
if ($hasPhoneNumber) {
$phoneNumberData = [];
foreach ($phoneNumberToRelateList as $i => $phoneNumber) {
$o = (object) [];
$o->phoneNumber = $phoneNumber->get('name');
$o->primary = false;
if (empty($attributes->phoneNumber)) {
if ($i === 0) {
$o->primary = true;
@@ -2337,7 +2446,9 @@ class Record implements Crud,
} else {
$o->primary = $o->phoneNumber === $attributes->phoneNumber;
}
$o->type = $phoneNumber->get('type');
$phoneNumberData[] = $o;
}
$attributes->phoneNumberData = $phoneNumberData;
@@ -2364,9 +2475,11 @@ class Record implements Crud,
protected function findLinkedFollowers($id, $params)
{
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
@@ -2396,32 +2509,47 @@ class Record implements Crud,
foreach ($fields as $field => $item) {
if (!empty($item['duplicateIgnore']) || in_array($field, $this->duplicateIgnoreFieldList)) {
$attributeToIgnoreList = $fieldManager->getAttributeList($this->entityType, $field);
foreach ($attributeToIgnoreList as $attribute) {
unset($attributes->$attribute);
}
continue;
}
if (empty($item['type'])) {
continue;
}
if (empty($item['type'])) continue;
$type = $item['type'];
if (in_array($type, ['file', 'image'])) {
$attachment = $entity->get($field);
if ($attachment) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
$attachment = $this->getEntityManager()
->getRepository('Attachment')
->getCopiedAttachment($attachment);
$idAttribute = $field . 'Id';
if ($attachment) {
$attributes->$idAttribute = $attachment->id;
}
}
} else if (in_array($type, ['attachmentMultiple'])) {
$attachmentList = $entity->get($field);
if (count($attachmentList)) {
$idList = [];
$nameHash = (object) [];
$typeHash = (object) [];
foreach ($attachmentList as $attachment) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
$attachment = $this->getEntityManager()
->getRepository('Attachment')
->getCopiedAttachment($attachment);
if ($attachment) {
$idList[] = $attachment->id;
$nameHash->{$attachment->id} = $attachment->get('name');
@@ -2435,8 +2563,12 @@ class Record implements Crud,
} else if ($type === 'linkMultiple') {
$foreignLink = $entity->getRelationParam($field, 'foreign');
$foreignEntityType = $entity->getRelationParam($field, 'entity');
if ($foreignEntityType && $foreignLink) {
$foreignRelationType = $this->getMetadata()->get(['entityDefs', $foreignEntityType, 'links', $foreignLink, 'type']);
$foreignRelationType = $this->getMetadata()->get(
['entityDefs', $foreignEntityType, 'links', $foreignLink, 'type']
);
if ($foreignRelationType !== 'hasMany') {
unset($attributes->{$field . 'Ids'});
unset($attributes->{$field . 'Names'});
@@ -2461,7 +2593,9 @@ class Record implements Crud,
$duplicatingEntityId = $data->_duplicatingEntityId;
if (!$duplicatingEntityId) return;
$duplicatingEntity = $this->getEntityManager()->getEntity($entity->getEntityType(), $duplicatingEntityId);
if (!$duplicatingEntity) return;
if (!$this->getAcl()->check($duplicatingEntity, 'read')) return;
@@ -2474,6 +2608,7 @@ class Record implements Crud,
foreach ($this->duplicatingLinkList as $link) {
$linkedList = $repository->findRelated($duplicatingEntity, $link);
foreach ($linkedList as $linked) {
$repository->relate($entity, $link, $linked);
}
+20 -12
View File
@@ -131,6 +131,8 @@ class Export
$collection = null;
$exportAllFields = !array_key_exists('fieldList', $params);
if (array_key_exists('collection', $params)) {
$collection = $params['collection'];
} else {
@@ -138,26 +140,33 @@ class Export
if (array_key_exists('ids', $params)) {
$ids = $params['ids'];
$where = [
[
'type' => 'in',
'field' => 'id',
'value' => $ids
'value' => $ids,
]
];
$selectParams = $selectManager->getSelectParams(['where' => $where], true, true);
$selectParams = $selectManager->getSelectParams(['where' => $where], true, true, true);
}
else if (array_key_exists('where', $params)) {
$where = $params['where'];
$p = [];
$p['where'] = $where;
$searchParams = [];
$searchParams['where'] = $where;
if (!empty($params['selectData']) && is_array($params['selectData'])) {
foreach ($params['selectData'] as $k => $v) {
$p[$k] = $v;
$searchParams[$k] = $v;
}
}
$selectParams = $this->getSelectParams($p);
unset($searchParams['select']);
$selectParams = $selectManager->getSelectParams($searchParams, true, true, true);
}
else {
throw new BadRequest();
@@ -172,14 +181,15 @@ class Export
$select = Select::fromRaw($selectParams);
$collection = $this->entityManager->getRepository($this->entityType)
$collection = $this->entityManager
->getRepository($this->entityType)
->clone($select)
->sth()
->find();
}
$attributeListToSkip = [
'deleted'
'deleted',
];
foreach ($this->skipAttributeList as $attribute) {
@@ -208,15 +218,12 @@ class Export
}
}
if (!array_key_exists('fieldList', $params)) {
$exportAllFields = true;
if ($exportAllFields) {
$fieldDefs = $this->metadata->get(['entityDefs', $this->entityType, 'fields'], []);
$fieldList = array_keys($fieldDefs);
array_unshift($fieldList, 'id');
} else {
$exportAllFields = false;
$fieldList = $params['fieldList'];
}
@@ -225,6 +232,7 @@ class Export
unset($fieldList[$i]);
}
}
$fieldList = array_values($fieldList);
if (method_exists($exportObj, 'filterFieldList')) {
@@ -56,7 +56,11 @@ class Csv
public function loadAdditionalFields(Entity $entity, $fieldList)
{
foreach ($fieldList as $field) {
if ($this->metadata->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']) === 'linkMultiple') {
$fieldType = $this->metadata->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
if (
$fieldType === 'linkMultiple' || $fieldType === 'attachmentMultiple'
) {
if (!$entity->has($field . 'Ids')) {
$entity->loadLinkMultipleField($field);
}
+24 -6
View File
@@ -113,7 +113,9 @@ class Xlsx
}
}
foreach ($fieldList as $field) {
if ($this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']) === 'linkMultiple') {
$fieldType = $this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
if ($fieldType === 'linkMultiple' || $fieldType === 'attachmentMultiple') {
if (!$entity->has($field . 'Ids')) {
$entity->loadLinkMultipleField($field);
}
@@ -348,21 +350,24 @@ class Xlsx
} else if ($type == 'currency') {
if (array_key_exists($name.'Currency', $row) && array_key_exists($name, $row)) {
$sheet->setCellValue("$col$rowNumber", $row[$name] ? $row[$name] : '');
$currency = $row[$name . 'Currency'];
$currencySymbol = $this->getMetadata()->get(['app', 'currency', 'symbolMap', $currency], '');
$sheet->getStyle("$col$rowNumber")
->getNumberFormat()
->setFormatCode('[$'.$currencySymbol.'-409]#,##0.00;-[$'.$currencySymbol.'-409]#,##0.00');
->setFormatCode(
$this->getCurrencyFormatCode($currency)
);
}
} else if ($type == 'currencyConverted') {
if (array_key_exists($name, $row)) {
$currency = $this->getConfig()->get('defaultCurrency');
$currencySymbol = $this->getMetadata()->get(['app', 'currency', 'symbolMap', $currency], '');
$sheet->getStyle("$col$rowNumber")
->getNumberFormat()
->setFormatCode('[$'.$currencySymbol.'-409]#,##0.00;-[$'.$currencySymbol.'-409]#,##0.00');
->setFormatCode(
$this->getCurrencyFormatCode($currency)
);
$sheet->setCellValue("$col$rowNumber", $row[$name] ? $row[$name] : '');
}
@@ -442,7 +447,7 @@ class Xlsx
}
$sheet->setCellValue("$col$rowNumber", $value);
}
} else if ($type == 'linkMultiple') {
} else if ($type == 'linkMultiple' || $type == 'attachmentMultiple') {
if (array_key_exists($name . 'Ids', $row) && array_key_exists($name . 'Names', $row)) {
$nameList = [];
foreach ($row[$name . 'Ids'] as $relatedId) {
@@ -661,4 +666,17 @@ class Xlsx
return $xlsx;
}
protected function getCurrencyFormatCode(string $currency) : string
{
$currencySymbol = $this->getMetadata()->get(['app', 'currency', 'symbolMap', $currency], '');
$currencyFormat = $this->getConfig()->get('currencyFormat') ?? 2;
if ($currencyFormat == 3) {
return '#,##0.00_-"' . $currencySymbol . '"';
}
return '[$'.$currencySymbol.'-409]#,##0.00;-[$'.$currencySymbol.'-409]#,##0.00';
}
}
@@ -37,11 +37,12 @@ use Espo\Core\{
Utils\Language,
};
class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\FileManagerAware
class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\FileManagerAware, Di\DataCacheAware
{
use Di\DefaultLanguageSetter;
use Di\MetadataSetter;
use Di\FileManagerSetter;
use Di\DataCacheSetter;
protected $ignoreList = [
'Global.sets',
@@ -71,7 +72,7 @@ class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\File
public function getScopeData($language, $scope)
{
$languageObj = new Language($language, $this->fileManager, $this->metadata);
$languageObj = new Language($language, $this->fileManager, $this->metadata, $this->dataCache);
$data = $languageObj->get($scope);
@@ -179,8 +180,8 @@ class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\File
public function saveLabels($language, $scope, $labels)
{
$languageObj = new Language($language, $this->fileManager, $this->metadata);
$languageOriginalObj = new Language($language, $this->fileManager, $this->metadata, false, true);
$languageObj = new Language($language, $this->fileManager, $this->metadata, $this->dataCache);
$languageOriginalObj = new Language($language, $this->fileManager, $this->metadata, $this->dataCache, false, true);
$returnDataHash = [];
@@ -34,19 +34,30 @@ define('crm:views/case/record/detail', 'views/record/detail', function (Dep) {
setupActionItems: function () {
Dep.prototype.setupActionItems.call(this);
if (
this.getAcl().checkModel(this.model, 'edit') &&
!~['Closed', 'Rejected', 'Duplicate'].indexOf(this.model.get('status')) &&
this.getAcl().checkField(this.entityType, 'status', 'edit')
) {
this.dropdownItemList.push({
'label': 'Close',
'name': 'close',
});
this.dropdownItemList.push({
'label': 'Reject',
'name': 'reject',
});
var statusList = this.getMetadata().get(
['entityDefs', 'Case', 'fields', 'status', 'options']
) || [];
if (~statusList.indexOf('Closed')) {
this.dropdownItemList.push({
'label': 'Close',
'name': 'close',
});
}
if (~statusList.indexOf('Rejected')) {
this.dropdownItemList.push({
'label': 'Reject',
'name': 'reject',
});
}
}
},
@@ -68,14 +68,14 @@ define('crm:views/record/panels/tasks', 'views/record/panels/relationship', func
name: 'name',
link: true,
},
{
name: 'isOverdue'
}
],
[
{
name: 'isOverdue'
},
{name: 'assignedUser'},
{name: 'status'},
{name: 'dateEnd'},
{name: 'status'},
]
]
},
@@ -32,7 +32,8 @@ Espo.define('crm:views/task/fields/is-overdue', 'views/fields/base', function (D
readOnly: true,
_template: '{{#if isOverdue}}<span class="label label-danger">{{translate "overdue"}}</span>{{/if}}',
_template: '{{#if isOverdue}}<span class="label label-danger">' +
'{{translate "overdue" scope="Task"}}</span>{{/if}}',
data: function () {
var isOverdue = false;
@@ -0,0 +1,5 @@
{{#if isNotEmpty}}
{{value}} {{currencySymbol}}
{{else}}
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
@@ -0,0 +1,3 @@
{{#if isNotEmpty}}
<span title="{{currencySymbol}}{{value}}">{{value}} {{currencySymbol}}</span>
{{/if}}
+1 -1
View File
@@ -4,7 +4,7 @@
<div class="button-container">
<button class="btn btn-default pull-right hidden" data-action="reset">{{translate 'Reset'}}</button>
<div class="btn-group">
<button class="btn btn-default dropdown-toggle select-field" data-toggle="dropdown" tabindex="-1">{{translate 'Select Field'}} <span class="caret"></span></button>
<button class="btn btn-default dropdown-toggle select-field" data-toggle="dropdown" tabindex="-1">{{translate 'Add Field'}} <span class="caret"></span></button>
<ul class="dropdown-menu pull-left filter-list">
{{#each ../fieldList}}
<li data-name="{{./this}}"><a href="javascript:" data-name="{{./this}}" data-action="add-field">{{translate this scope=../../entityType category='fields'}}</a></li>
+15 -5
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/admin/integrations/index', 'view', function (Dep) {
define('views/admin/integrations/index', 'view', function (Dep) {
return Dep.extend({
@@ -51,12 +51,13 @@ Espo.define('views/admin/integrations/index', 'view', function (Dep) {
},
setup: function () {
this.integrationList = Object.keys(this.getMetadata().get('integrations') || {});;
this.integrationList = Object.keys(this.getMetadata().get('integrations') || {});
this.integration = this.options.integration || null;
this.on('after:render', function () {
this.renderHeader();
if (!this.integration) {
this.renderDefaultPage();
} else {
@@ -70,34 +71,45 @@ Espo.define('views/admin/integrations/index', 'view', function (Dep) {
this.getRouter().navigate('#Admin/integrations/name=' + integration, {trigger: false});
var viewName = this.getMetadata().get('integrations.' + integration + '.view') || 'views/admin/integrations/' + Espo.Utils.camelCaseToHyphen(this.getMetadata().get('integrations.' + integration + '.authMethod'));
var viewName = this.getMetadata().get('integrations.' + integration + '.view') ||
'views/admin/integrations/' +
Espo.Utils.camelCaseToHyphen(this.getMetadata().get('integrations.' + integration + '.authMethod'));
this.notify('Loading...');
this.createView('content', viewName, {
el: '#integration-content',
integration: integration,
}, function (view) {
this.renderHeader();
view.render();
this.notify(false);
$(window).scrollTop(0);
}.bind(this));
},
renderDefaultPage: function () {
$('#integration-header').html('').hide();
if (this.integrationList.length) {
var msg = this.translate('selectIntegration', 'messages', 'Integration');
} else {
var msg = '<p class="lead">' + this.translate('noIntegrations', 'messages', 'Integration') + '</p>';
}
$('#integration-content').html(msg);
},
renderHeader: function () {
if (!this.integration) {
$('#integration-header').html('');
return;
}
$('#integration-header').show().html(this.translate(this.integration, 'titles', 'Integration'));
},
@@ -106,5 +118,3 @@ Espo.define('views/admin/integrations/index', 'view', function (Dep) {
},
});
});
+17 -8
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/export/modals/export', ['views/modal', 'model'], function (Dep, Model) {
define('views/export/modals/export', ['views/modal', 'model'], function (Dep, Model) {
return Dep.extend({
@@ -44,11 +44,11 @@ Espo.define('views/export/modals/export', ['views/modal', 'model'], function (De
{
name: 'export',
label: 'Export',
style: 'danger'
style: 'danger',
},
{
name: 'cancel',
label: 'Cancel'
label: 'Cancel',
}
];
@@ -73,44 +73,53 @@ Espo.define('views/export/modals/export', ['views/modal', 'model'], function (De
this.createView('record', 'views/export/record/record', {
scope: this.scope,
model: this.model,
el: this.getSelector() + ' .record'
el: this.getSelector() + ' .record',
});
},
actionExport: function () {
var data = this.getView('record').fetch();
this.model.set(data);
if (this.getView('record').validate()) return;
if (this.getView('record').validate()) {
return;
}
var returnData = {
exportAllFields: data.exportAllFields,
format: data.format
format: data.format,
};
if (!data.exportAllFields) {
var attributeList = [];
data.fieldList.forEach(function (item) {
if (item === 'id') {
attributeList.push('id');
return;
}
var type = this.getMetadata().get(['entityDefs', this.scope, 'fields', item, 'type']);
if (type) {;
if (type) {
this.getFieldManager().getAttributeList(type, item).forEach(function (attribute) {
attributeList.push(attribute);
}, this);
}
if (~item.indexOf('_')) {
attributeList.push(item);
}
}, this);
returnData.attributeList = attributeList;
returnData.fieldList = data.fieldList;
}
this.trigger('proceed', returnData);
this.close();
}
},
});
});
+22 -10
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
define('views/export/record/record', 'views/record/base', function (Dep) {
return Dep.extend({
@@ -48,10 +48,10 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
fieldList = fieldList.filter(function (item) {
var defs = this.getMetadata().get(['entityDefs', this.scope, 'fields', item]) || {};
if (defs.disabled) return;
if (defs.exportDisabled) return;
if (defs.type === 'map') return;
if (defs.type === 'attachmentMultiple') return;
return true;
}, this);
@@ -61,32 +61,43 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
fieldList.unshift('id');
var translatedOptions = {};
fieldList.forEach(function (item) {
translatedOptions[item] = this.getLanguage().translate(item, 'fields', this.scope);
}, this);
this.createField('exportAllFields', 'views/fields/bool', {
});
this.createField('exportAllFields', 'views/fields/bool', {});
var setFieldList = this.model.get('fieldList') || [];
setFieldList.forEach(function (item) {
if (~fieldList.indexOf(item)) return;
if (!~item.indexOf('_')) return;
if (~fieldList.indexOf(item)) {
return;
}
if (!~item.indexOf('_')) {
return;
}
var arr = item.split('_');
fieldList.push(item);
var foreignScope = this.getMetadata().get(['entityDefs', this.scope, 'links', arr[0], 'entity']);
if (!foreignScope) return;
translatedOptions[item] = this.getLanguage().translate(arr[0], 'links', this.scope) + '.' + this.getLanguage().translate(arr[1], 'fields', foreignScope);
if (!foreignScope) {
return;
}
translatedOptions[item] = this.getLanguage().translate(arr[0], 'links', this.scope) + '.' +
this.getLanguage().translate(arr[1], 'fields', foreignScope);
}, this);
this.createField('fieldList', 'views/fields/multi-enum', {
required: true,
translatedOptions: translatedOptions,
options: fieldList
options: fieldList,
});
var formatList =
@@ -98,6 +109,7 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
});
this.controlAllFields();
this.listenTo(this.model, 'change:exportAllFields', function () {
this.controlAllFields();
}, this);
@@ -109,7 +121,7 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
} else {
this.hideField('fieldList');
}
}
},
});
});
-10
View File
@@ -356,11 +356,6 @@ define('views/fields/address', 'views/fields/base', function (Dep) {
this.$country.attr('autocomplete', 'espo-country');
}
this.controlStreetTextareaHeight();
this.$street.on('input', function (e) {
this.controlStreetTextareaHeight();
}.bind(this));
var cityList = this.getConfig().get('addressCityList') || [];
if (cityList.length) {
this.$city.autocomplete({
@@ -394,11 +389,6 @@ define('views/fields/address', 'views/fields/base', function (Dep) {
this.$city.attr('autocomplete', 'espo-city');
}
this.controlStreetTextareaHeight();
this.$street.on('input', function (e) {
this.controlStreetTextareaHeight();
}.bind(this));
var stateList = this.getConfig().get('addressStateList') || [];
if (stateList.length) {
this.$state.autocomplete({
+5 -1
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/fields/currency', 'views/fields/float', function (Dep) {
define('views/fields/currency', 'views/fields/float', function (Dep) {
return Dep.extend({
@@ -40,12 +40,16 @@ Espo.define('views/fields/currency', 'views/fields/float', function (Dep) {
detailTemplate2: 'fields/currency/detail-2',
detailTemplate3: 'fields/currency/detail-3',
listTemplate: 'fields/currency/list',
listTemplate1: 'fields/currency/list-1',
listTemplate2: 'fields/currency/list-2',
listTemplate3: 'fields/currency/list-3',
detailTemplateNoCurrency: 'fields/currency/detail-no-currency',
maxDecimalPlaces: 3,
-4
View File
@@ -253,10 +253,6 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
e.currentTarget.value = this.model.get(this.nameName);
}
}.bind(this));
} else if (this.mode == 'search') {
this.$elementName.on('blur', function (e) {
e.currentTarget.value = '';
}.bind(this));
}
if (!this.autocompleteDisabled) {
-4
View File
@@ -260,10 +260,6 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
e.currentTarget.value = this.model.get(this.nameName);
}
}.bind(this));
} else if (this.mode == 'search') {
this.$elementName.on('blur', function (e) {
e.currentTarget.value = '';
}.bind(this));
}
var $elementName = this.$elementName;
+4 -4
View File
@@ -388,13 +388,13 @@ define('views/import/step2', 'view', function (Dep) {
},
disableButtons: function () {
this.$el.find('button[data-action="next"]').addClass('disabled');
this.$el.find('button[data-action="back"]').addClass('disabled');
this.$el.find('button[data-action="next"]').addClass('disabled').attr('disabled', 'disabled');
this.$el.find('button[data-action="back"]').addClass('disabled').attr('disabled', 'disabled');
},
enableButtons: function () {
this.$el.find('button[data-action="next"]').removeClass('disabled');
this.$el.find('button[data-action="back"]').removeClass('disabled');
this.$el.find('button[data-action="next"]').removeClass('disabled').removeAttr('disabled');
this.$el.find('button[data-action="back"]').removeClass('disabled').removeAttr('disabled');
},
fetch: function (skipValidation) {
+2
View File
@@ -246,6 +246,8 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
this.getAcl().check(this.entityType, 'edit')
&&
!~this.getAcl().getScopeForbiddenFieldList(this.entityType).indexOf('assignedUser')
&&
!this.getUser().isPortal()
) {
if (this.model.has('assignedUserId')) {
this.dropdownItemList.push({
+7 -3
View File
@@ -31,6 +31,11 @@ if (substr(php_sapi_name(), 0, 3) != 'cli') exit;
include "bootstrap.php";
use Espo\Core\{
Application,
ApplicationRunners\Rebuild,
};
$arg = isset($_SERVER['argv'][1]) ? trim($_SERVER['argv'][1]) : '';
if (empty($arg)) {
@@ -46,7 +51,7 @@ if (!isset($pathInfo['extension']) || $pathInfo['extension'] !== 'zip' || !is_fi
die("Unsupported package.\n");
}
$app = new \Espo\Core\Application();
$app = new Application();
$app->setupSystemUser();
$config = $app->getContainer()->get('config');
@@ -67,8 +72,7 @@ try {
}
try {
$app = new \Espo\Core\Application();
$app->runRebuild();
(new Application())->run(Rebuild::class);
} catch (\Exception $e) {}
echo "Extension installation is complete.\n";
+6 -1
View File
@@ -93,7 +93,12 @@ class Diff
if (!~tag.indexOf('beta') && !~tag.indexOf('alpha')) {
versionFromList.push(tag);
break;
var patchVersionNumberI = tag.split('.')[2];
if (patchVersionNumberI === '0') {
break;
}
}
}
}
+13 -2
View File
@@ -29,5 +29,16 @@
include "bootstrap.php";
$app = new \Espo\Core\Application();
$app->runEntryPoint('OauthCallback');
use Espo\Core\{
Application,
ApplicationRunners\EntryPoint,
};
$app = new Application();
$app->run(
EntryPoint::class,
(object) [
'entryPoint' => 'oauthCallback',
]
);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "6.0.0-beta4",
"version": "6.0.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "6.0.0-beta4",
"version": "6.0.4",
"description": "",
"main": "index.php",
"repository": {
+1 -1
View File
@@ -146,7 +146,7 @@ class Tester
{
$configData = $this->getTestConfigData();
if ($configData[$optionName] === $data) {
if (array_key_exists($optionName, $configData) && $configData[$optionName] === $data) {
return true;
}
@@ -434,4 +434,22 @@ class EvaluatorTest extends \PHPUnit\Framework\TestCase
$this->assertEquals('test', $result);
}
public function testNegate1()
{
$expression = "!string\contains('hello', 'test')";
$result = $this->evaluator->process($expression);
$this->assertTrue($result);
}
public function testLogicalProority()
{
$expression = "0 && 0 || 1";
$result = $this->evaluator->process($expression);
$this->assertTrue($result);
}
}
+7 -3
View File
@@ -31,6 +31,11 @@ if (substr(php_sapi_name(), 0, 3) != 'cli') exit;
include "bootstrap.php";
use Espo\Core\{
Application,
ApplicationRunners\Rebuild,
};
$arg = isset($_SERVER['argv'][1]) ? trim($_SERVER['argv'][1]) : '';
if ($arg == 'version' || $arg == '-v') {
@@ -51,7 +56,7 @@ if (!isset($pathInfo['extension']) || $pathInfo['extension'] !== 'zip' || !is_fi
die("Unsupported package.\n");
}
$app = new \Espo\Core\Application();
$app = new Application();
$app->setupSystemUser();
$config = $app->getContainer()->get('config');
@@ -73,8 +78,7 @@ try {
}
try {
$app = new \Espo\Core\Application();
$app->runRebuild();
(new Application())->run(Rebuild::class);
} catch (\Exception $e) {}
echo "Upgrade is complete. Current version is " . $config->get('version') . ". \n";
+2 -2
View File
@@ -86,9 +86,9 @@ class BeforeUpgrade
if ($extension) {
$version = $extension->get('version');
if (version_compare($version, '1.5.0', '<')) {
if (version_compare($version, '1.4.0', '<')) {
$message =
"EspoCRM 6.0.0 is not compatible with Real Estate extension of a version lower than 1.5.0. " .
"EspoCRM 6.0.0 is not compatible with Real Estate extension of a version lower than 1.4.0. " .
"Please upgrade the extension or uninstall it. Then run the upgrade command again.";
throw new Error($message);