Compare commits

...

37 Commits

Author SHA1 Message Date
Yuri Kuznetsov 5e7f61b46d Merge branch 'hotfix/5.7.8' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.7.8 2019-12-06 12:38:42 +02:00
Taras Machyshyn c5e41faa0f Upgrade fixes 2019-12-06 12:38:28 +02:00
Yuri Kuznetsov 465377c67b email update fix 2019-12-06 12:09:24 +02:00
Yuri Kuznetsov f41cc85cba Merge branch 'hotfix/5.7.8' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.7.8 2019-12-05 16:26:33 +02:00
Taras Machyshyn fdf08624cb Added cli installation 2019-12-05 16:26:11 +02:00
Yuri Kuznetsov 5c89f4f389 fix 2019-12-05 15:34:24 +02:00
Yuri Kuznetsov acce8f1b1f fix 2019-12-05 15:30:07 +02:00
Yuri Kuznetsov f1382e802e fix 2019-12-05 15:17:12 +02:00
Yuri Kuznetsov f64e59b0de Merge branch 'hotfix/5.7.8' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.7.8 2019-12-05 15:08:03 +02:00
Yuri Kuznetsov 5b41abe76f auth refactoring 2019-12-05 15:07:48 +02:00
Taras Machyshyn f286d2277e Fixed typo 2019-12-05 10:45:47 +02:00
Yuri Kuznetsov b38c983e73 wysiwyg copy image issue 2019-12-04 15:51:06 +02:00
Yuri Kuznetsov a70d133471 fix sumRelated 2019-12-04 15:06:53 +02:00
Yuri Kuznetsov bc3d488bfe message fix 2019-12-04 13:25:47 +02:00
Yuri Kuznetsov e9578d8d44 fix smtp message 2019-12-04 13:21:33 +02:00
Yuri Kuznetsov 4c38d96730 fix users teams positions 2019-12-04 13:01:45 +02:00
Yuri Kuznetsov b962d1572a fix users positions 2019-12-04 12:58:56 +02:00
Yuri Kuznetsov 3f1f686bd1 fix layout acl check 2019-12-04 10:30:32 +02:00
Yuri Kuznetsov 611bbb8fb2 fallback dashboard overflow hidden 2019-11-28 12:06:01 +02:00
Yuri Kuznetsov b89b39cd44 fix enum 2019-11-27 15:04:18 +02:00
Yuri Kuznetsov 9a6e2d6578 enum: options w/ quotes not working 2019-11-27 13:10:41 +02:00
Yuri Kuznetsov 9f63c00f5c portal user list view access 2019-11-26 13:29:56 +02:00
Yuri Kuznetsov f11b9c0bbc user access to portal users 2019-11-26 13:13:10 +02:00
Yuri Kuznetsov 6ea109f712 app error 2019-11-26 12:57:44 +02:00
Yuri Kuznetsov 1c3dc61264 fix stream input disapear 2019-11-21 11:23:53 +02:00
Yuri Kuznetsov c415ce677d stream noteRelate 2019-11-21 11:09:57 +02:00
Yuri Kuznetsov 927a580dce stream follow check acl 2019-11-21 10:37:45 +02:00
Yuri Kuznetsov 8e3b44c2e1 email import: support multiple references 2019-11-18 13:55:06 +02:00
Yuri Kuznetsov 4048b7207b lang fix 2019-11-18 13:09:02 +02:00
Yuri Kuznetsov a87231552b case distribution fixes 2019-11-18 13:00:38 +02:00
Yuri Kuznetsov a234f503a1 list with categories no data if text filter 2019-11-13 12:07:52 +02:00
Yuri Kuznetsov 55be5b12b2 formula record relate 2019-11-13 10:43:51 +02:00
Yuri Kuznetsov 1f400649f5 Merge branch 'hotfix/5.7.8' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.7.8 2019-11-12 15:23:24 +02:00
Yuri Kuznetsov e6f65440f2 add more currencies 2019-11-12 14:59:05 +02:00
Taras Machyshyn def8455d78 Improved Integration tests 2019-11-12 14:58:25 +02:00
Yuri Kuznetsov 1ad2144432 currency list in app metadata 2019-11-12 14:55:30 +02:00
Yuri Kuznetsov c2828e3273 v 2019-11-12 14:46:36 +02:00
52 changed files with 973 additions and 263 deletions
+10
View File
@@ -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()) {
@@ -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,29 @@ 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);
}
if (!empty($selectParams['distinct'])) {
$sqSelectParams = $selectParams;
$sqSelectParams['whereClause'][] = [
$foreignLink . '.id' => $entity->id
$foreignLinkAlias . '.id' => $entity->id
];
$sqSelectParams['select'] = ['id'];
@@ -133,11 +134,11 @@ class SumRelatedType extends \Espo\Core\Formula\Functions\Base
];
} else {
$selectParams['whereClause'][] = [
$foreignLink . '.id' => $entity->id
$foreignLinkAlias . '.id' => $entity->id
];
}
$selectParams['groupBy'] = [$foreignLink . '.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,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;
}
}
+40 -30
View File
@@ -229,43 +229,53 @@ class Importer
}
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;
}
}
}
@@ -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().'.');
}
+1 -14
View File
@@ -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;
}
}
+1 -1
View File
@@ -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()
+2 -37
View File
@@ -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);
}
}
+14 -2
View File
@@ -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)
@@ -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",
@@ -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": {
@@ -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"
]
}
@@ -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",
+21 -4
View File
@@ -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,
];
}
}
+6
View File
@@ -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)
+1 -1
View File
@@ -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);
+3 -1
View File
@@ -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)) {
+43
View File
@@ -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)) {
@@ -1311,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');
+14 -7
View File
@@ -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;
}
}
+12 -2
View File
@@ -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
View File
@@ -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();
+9 -1
View File
@@ -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;
},
});
});
+4 -1
View File
@@ -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);
});
@@ -32,9 +32,18 @@ Espo.define('views/email/fields/compose-from-address', 'views/fields/base', func
editTemplate: 'email/fields/compose-from-address/edit',
data: function () {
var noSmtpMessage = this.translate('noSmtpSetup', 'messages', 'Email');
var linkHtml = '<a href="#EmailAccount">'+this.translate('EmailAccount', 'scopeNamesPlural')+'</a>';
if (!this.getAcl().check('EmailAccount')) {
linkHtml = '<a href="#Preferences">'+this.translate('Preferences')+'</a>';
}
noSmtpMessage = noSmtpMessage.replace('{link}', linkHtml);
return _.extend({
list: this.list,
noSmtpMessage: this.translate('noSmtpSetup', 'messages', 'Email').replace('{link}', '<a href="#Preferences">'+this.translate('Preferences')+'</a>')
noSmtpMessage: noSmtpMessage,
}, Dep.prototype.data.call(this));
},
@@ -58,9 +58,8 @@ define('views/fields/link-multiple-with-role', 'views/fields/link-multiple', fun
this.roleFieldScope = this.model.name;
}
if (this.roleType == 'enum') {
if (this.roleType == 'enum' && !this.forceRoles) {
this.roleList = this.getMetadata().get('entityDefs.' + this.roleFieldScope + '.fields.' + this.roleField + '.options');
if (!this.roleList) {
this.roleList = [];
this.skipRoles = true;
+6
View File
@@ -490,6 +490,12 @@ define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], function
if (code == '<p><br></p>') {
code = '';
}
var imageTagString = '<img src="' + window.location.origin + window.location.pathname + '?entryPoint=attachment';
code = code.replace(
new RegExp(imageTagString.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"), 'g'),
'<img src="?entryPoint=attachment'
);
data[this.name] = code;
} else {
data[this.name] = this.$element.val();
+25 -18
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/list-with-categories', 'views/list', function (Dep) {
define('views/list-with-categories', 'views/list', function (Dep) {
return Dep.extend({
@@ -124,6 +124,21 @@ Espo.define('views/list-with-categories', 'views/list', function (Dep) {
}
},
hasTextFilter: function () {
if (this.collection.where) {
for (var i = 0; i < this.collection.where.length; i++) {
if (this.collection.where[i].type === 'textFilter') {
return true;
break;
}
}
}
if (this.collection.data && this.collection.data.textFilter) {
return true;
}
return false;
},
hasIsExpandedStoredValue: function () {
return this.getStorage().has('state', 'categories-expanded-' + this.scope);
},
@@ -263,7 +278,12 @@ Espo.define('views/list-with-categories', 'views/list', function (Dep) {
this.$listContainer.removeClass('hidden');
return;
}
if (!this.collection.models.length && this.nestedCategoriesCollection && this.nestedCategoriesCollection.models.length) {
if (
!this.collection.models.length &&
this.nestedCategoriesCollection &&
this.nestedCategoriesCollection.models.length &&
!this.hasTextFilter()
) {
this.$listContainer.addClass('hidden');
} else {
this.$listContainer.removeClass('hidden');
@@ -396,27 +416,14 @@ Espo.define('views/list-with-categories', 'views/list', function (Dep) {
}, this);
},
applyCategoryToCollection: function () {
this.collection.whereFunction = function () {
var filter;
var isExpanded = this.isExpanded;
var hasTextFilter = false;
if (this.collection.where) {
for (var i = 0; i < this.collection.where.length; i++) {
if (this.collection.where[i].type === 'textFilter') {
hasTextFilter = true;
break;
}
}
}
if (this.collection.data && this.collection.data.textFilter) {
hasTextFilter = true;
}
if (!isExpanded && !hasTextFilter) {
if (!isExpanded && !this.hasTextFilter()) {
if (this.isCategoryMultiple()) {
if (this.currentCategoryId) {
filter = {
@@ -491,7 +498,7 @@ Espo.define('views/list-with-categories', 'views/list', function (Dep) {
actionManageCategories: function () {
this.clearView('categories');
this.getRouter().navigate('#' + this.categoryScope, {trigger: true});
}
},
});
});
@@ -0,0 +1,38 @@
/************************************************************************
* 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/settings/fields/currency-list', 'views/fields/multi-enum', function (Dep) {
return Dep.extend({
setupOptions: function () {
this.params.options = this.getMetadata().get(['app', 'currency', 'list']) || [];
},
});
});
+7 -2
View File
@@ -42,7 +42,7 @@ define('views/stream/panel', ['views/record/panels/relationship', 'lib!Textcompl
events: _.extend({
'focus textarea[data-name="post"]': function (e) {
this.enablePostingMode();
this.enablePostingMode(true);
},
'click button.post': function () {
this.post();
@@ -79,14 +79,19 @@ define('views/stream/panel', ['views/record/panels/relationship', 'lib!Textcompl
return data;
},
enablePostingMode: function () {
enablePostingMode: function (byFocus) {
this.$el.find('.buttons-panel').removeClass('hide');
if (!this.postingMode) {
if (this.$textarea.val() && this.$textarea.val().length) {
this.getView('postField').controlTextareaHeight();
}
var isClicked = false;
$('body').on('click.stream-panel', function (e) {
if (byFocus && !isClicked) {
isClicked = true;
return;
}
var $target = $(e.target);
if ($target.parent().hasClass('remove-attachment')) return;
if ($.contains(this.$postContainer.get(0), e.target)) return;
+6 -10
View File
@@ -26,10 +26,12 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/user/fields/teams', 'views/fields/link-multiple-with-role', function (Dep) {
define('views/user/fields/teams', 'views/fields/link-multiple-with-role', function (Dep) {
return Dep.extend({
forceRoles: true,
setup: function () {
Dep.prototype.setup.call(this);
@@ -43,7 +45,6 @@ Espo.define('views/user/fields/teams', 'views/fields/link-multiple-with-role', f
}
}, this);
this.listenTo(this.model, 'change:teamsIds', function () {
var toLoad = false;
this.ids.forEach(function (id) {
@@ -85,7 +86,6 @@ Espo.define('views/user/fields/teams', 'views/fields/link-multiple-with-role', f
teams.fetch();
}, this);
},
getDetailLinkHtml: function (id, name) {
@@ -94,6 +94,7 @@ Espo.define('views/user/fields/teams', 'views/fields/link-multiple-with-role', f
var role = (this.columns[id] || {})[this.columnName] || '';
var roleHtml = '';
if (role != '') {
role = this.getHelper().escapeString(role);
roleHtml = '<span class="text-muted small"> &#187; ' + role + '</span>';
}
var lineHtml = '<div>' + '<a href="#' + this.foreignScope + '/view/' + id + '">' + name + '</a> ' + roleHtml + '</div>';
@@ -101,21 +102,18 @@ Espo.define('views/user/fields/teams', 'views/fields/link-multiple-with-role', f
},
getJQSelect: function (id, roleValue) {
var roleList = Espo.Utils.clone((this.roleListMap[id] || []));
if (!roleList.length) {
return;
};
if (roleList.length || roleValue) {
$role = $('<select class="role form-control input-sm pull-right" data-id="'+id+'">');
roleList.unshift('');
roleList.forEach(function (role) {
var selectedHtml = (role == roleValue) ? 'selected': '';
role = this.getHelper().escapeString(role);
var label = role;
if (role == '') {
label = '--' + this.translate('None', 'labels') + '--';
@@ -127,8 +125,6 @@ Espo.define('views/user/fields/teams', 'views/fields/link-multiple-with-role', f
} else {
return $('<div class="small pull-right text-muted">').html(roleValue);
}
}
},
});
});
+4
View File
@@ -1787,6 +1787,10 @@ table.less-padding td.cell[data-name="buttons"] > .btn-group {
}
margin-left: 0;
margin-right: 0;
.panel.dashlet > .panel-body {
overflow-y: hidden;
}
}
.panel.dashlet > .panel-body {
+102
View File
@@ -0,0 +1,102 @@
<?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.
************************************************************************/
if (substr(php_sapi_name(), 0, 3) != 'cli') die('The file can be run only via CLI.');
$options = getopt("a:d:");
if (empty($options['a'])) {
fwrite(\STDOUT, "Error: the option [-a] is required.\n");
exit;
}
$allPostData = [];
if (!empty($options['d'])) {
parse_str($options['d'], $allPostData);
if (empty($allPostData) || !is_array($allPostData)) {
fwrite(\STDOUT, "Error: Incorrect input data.\n");
exit;
}
}
$action = $options['a'];
$allPostData['action'] = $action;
chdir(dirname(__FILE__));
set_include_path(dirname(__FILE__));
require_once('../bootstrap.php');
$_SERVER['SERVER_SOFTWARE'] = 'Cli';
require_once('core/PostData.php');
$postData = new PostData();
$postData->set($allPostData);
require_once('core/InstallerConfig.php');
$installerConfig = new InstallerConfig();
if ($installerConfig->get('isInstalled')) {
fwrite(\STDOUT, "Error: EspoCRM is already installed.\n");
exit;
}
if (!$installerConfig->get('cliSessionId')) {
session_start();
$installerConfig->set('cliSessionId', session_id());
$installerConfig->save();
}
$sessonId = session_id($installerConfig->get('cliSessionId'));
ob_start();
try {
require('install/index.php');
} catch (\Throwable $e) {
fwrite(\STDOUT, "Error: ". $e->getMessage() .".\n");
exit;
}
$result = ob_get_contents();
ob_end_clean();
if (preg_match('/"success":false/i', $result)) {
$resultData = json_decode($result, true);
if (empty($resultData)) {
fwrite(\STDOUT, "Error: Unexpected error occurred.\n");
exit;
}
fwrite(\STDOUT, "Error: ". (!empty($resultData['errors']) ? print_r($resultData['errors'], true) : $resultData['errorMsg']) .".\n");
exit;
}
fwrite(\STDOUT, "Done.\n");
+16 -12
View File
@@ -248,29 +248,33 @@ class Installer
* @param string $language
* @return bool
*/
public function saveData($database, $language)
public function saveData(array $saveData)
{
$initData = include('install/core/afterInstall/config.php');
$siteUrl = $this->getSystemHelper()->getBaseUrl();
$databaseDefaults = $this->app->getContainer()->get('config')->get('database');
$data = [
'database' => array_merge($databaseDefaults, $database),
'language' => $language,
'siteUrl' => $siteUrl,
'database' => array_merge($databaseDefaults, $saveData['database']),
'language' => $saveData['language'],
'siteUrl' => !empty($saveData['siteUrl']) ? $saveData['siteUrl'] : $this->getSystemHelper()->getBaseUrl(),
'passwordSalt' => $this->getPasswordHash()->generateSalt(),
'cryptKey' => $this->getContainer()->get('crypt')->generateKey(),
'hashSecretKey' => \Espo\Core\Utils\Util::generateSecretKey(),
];
$owner = $this->getFileManager()->getPermissionUtils()->getDefaultOwner(true);
$group = $this->getFileManager()->getPermissionUtils()->getDefaultGroup(true);
if (!empty($owner)) {
$data['defaultPermissions']['user'] = $owner;
if (empty($saveData['defaultPermissions']['user'])) {
$saveData['defaultPermissions']['user'] = $this->getFileManager()->getPermissionUtils()->getDefaultOwner(true);
}
if (!empty($group)) {
$data['defaultPermissions']['group'] = $group;
if (empty($saveData['defaultPermissions']['group'])) {
$saveData['defaultPermissions']['group'] = $this->getFileManager()->getPermissionUtils()->getDefaultGroup(true);
}
if (!empty($saveData['defaultPermissions']['user'])) {
$data['defaultPermissions']['user'] = $saveData['defaultPermissions']['user'];
}
if (!empty($saveData['defaultPermissions']['group'])) {
$data['defaultPermissions']['group'] = $saveData['defaultPermissions']['group'];
}
$data = array_merge($data, $initData);
+45
View File
@@ -0,0 +1,45 @@
<?php
class PostData
{
protected $data = [];
public function __construct()
{
$this->init();
}
protected function init()
{
if (isset($_POST) && is_array($_POST)) {
$this->data = $_POST;
}
}
public function set($name, $value = null)
{
if (!is_array($name)) {
$name = [
$name => $value
];
}
foreach ($name as $key => $value) {
$this->data[$key] = $value;
}
}
public function get($name, $default = null)
{
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return $default;
}
public function getAll()
{
return $this->data;
}
}
+17 -5
View File
@@ -25,13 +25,13 @@
*
* 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.
************************************************************************/
************************************************************************/
ob_start();
$result = array('success' => true, 'errorMsg' => '');
// save settings
$data = array(
$database = array(
'driver' => 'pdo_mysql',
'dbname' => $_SESSION['install']['db-name'],
'user' => $_SESSION['install']['db-user-name'],
@@ -41,10 +41,22 @@ $host = $_SESSION['install']['host-name'];
if (strpos($host,':') === false) {
$host .= ":";
}
list($data['host'], $data['port']) = explode(':', $host);
list($database['host'], $database['port']) = explode(':', $host);
$lang = (!empty($_SESSION['install']['user-lang']))? $_SESSION['install']['user-lang'] : 'en_US';
if (!$installer->saveData($data, $lang)) {
$saveData = [
'database' => $database,
'language' => !empty($_SESSION['install']['user-lang']) ? $_SESSION['install']['user-lang'] : 'en_US',
'siteUrl' => !empty($_SESSION['install']['site-url']) ? $_SESSION['install']['site-url'] : null,
];
if (!empty($_SESSION['install']['default-permissions-user']) && !empty($_SESSION['install']['default-permissions-group'])) {
$saveData['defaultPermissions'] = [
'user' => $_SESSION['install']['default-permissions-user'],
'group' => $_SESSION['install']['default-permissions-group'],
];
}
if (!$installer->saveData($saveData)) {
$result['success'] = false;
$result['errorMsg'] = $langs['messages']['Can not save settings'];
}
+10 -8
View File
@@ -50,16 +50,18 @@ foreach ($phpRequiredList as $name => $details) {
}
}
if ($result['success'] && !empty($_REQUEST['dbName']) && !empty($_REQUEST['hostName']) && !empty($_REQUEST['dbUserName'])) {
$allPostData = $postData->getAll();
if ($result['success'] && !empty($allPostData['dbName']) && !empty($allPostData['hostName']) && !empty($allPostData['dbUserName'])) {
$connect = false;
$dbName = trim($_REQUEST['dbName']);
if (strpos($_REQUEST['hostName'],':') === false) {
$_REQUEST['hostName'] .= ":";
$dbName = trim($allPostData['dbName']);
if (strpos($allPostData['hostName'],':') === false) {
$allPostData['hostName'] .= ":";
}
list($hostName, $port) = explode(':', trim($_REQUEST['hostName']));
$dbUserName = trim($_REQUEST['dbUserName']);
$dbUserPass = trim($_REQUEST['dbUserPass']);
list($hostName, $port) = explode(':', trim($allPostData['hostName']));
$dbUserName = trim($allPostData['dbUserName']);
$dbUserPass = trim($allPostData['dbUserPass']);
$databaseParams = [
'host' => $hostName,
@@ -84,7 +86,7 @@ if ($result['success'] && !empty($_REQUEST['dbName']) && !empty($_REQUEST['hostN
$databaseRequiredList = $installer->getSystemRequirementList('database', true, ['database' => $databaseParams]);
foreach ($databaseRequiredList as $name => $details) {
if (!$details['acceptable']) {
if (!$details['acceptable']) {
switch ($details['type']) {
case 'version':
+19 -7
View File
@@ -27,11 +27,23 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
session_start();
require_once('../bootstrap.php');
if (session_status() !== \PHP_SESSION_ACTIVE) {
session_start();
}
if (file_exists('../bootstrap.php')) {
require_once('../bootstrap.php');
}
if (!isset($postData)) {
require_once('core/PostData.php');
$postData = new PostData();
}
$allPostData = $postData->getAll();
//action
$action = (!empty($_POST['action']))? $_POST['action'] : 'main';
$action = (!empty($allPostData['action']))? $allPostData['action'] : 'main';
require_once('core/Utils.php');
if (!Utils::checkActionExists($action)) {
die('This page does not exist.');
@@ -40,8 +52,8 @@ if (!Utils::checkActionExists($action)) {
// temp save all settings
$ignoredFields = array('installProcess', 'dbName', 'hostName', 'dbUserName', 'dbUserPass', 'dbDriver');
if (!empty($_POST)) {
foreach ($_POST as $key => $val) {
if (!empty($allPostData)) {
foreach ($allPostData as $key => $val) {
if (!in_array($key, $ignoredFields)) {
$_SESSION['install'][$key] = trim($val);
}
@@ -64,7 +76,7 @@ $systemHelper = new SystemHelper();
$systemConfig = include('application/Espo/Core/defaults/systemConfig.php');
if (isset($systemConfig['requiredPhpVersion']) && version_compare(PHP_VERSION, $systemConfig['requiredPhpVersion'], '<')) {
die(str_replace('{minVersion}', $systemConfig['requiredPhpVersion'], $sanitizedLangs['messages']['phpVersion']) . '.');
die(str_replace("{minVersion}", $systemConfig['requiredPhpVersion'], $sanitizedLangs['messages']['phpVersion']) . ".\n");
}
if (!$systemHelper->initWritable()) {
@@ -74,7 +86,7 @@ if (!$systemHelper->initWritable()) {
$message = str_replace('{*}', $dir, $message);
$message = str_replace('{C}', $systemHelper->getPermissionCommands(array($dir, ''), '775'), $message);
$message = str_replace('{CSU}', $systemHelper->getPermissionCommands(array($dir, ''), '775', true), $message);
die($message);
die($message . "\n");
}
require_once ('install/vendor/smarty/libs/Smarty.class.php');
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "5.7.7",
"version": "5.7.8",
"description": "",
"main": "index.php",
"repository": {
+4 -3
View File
@@ -116,8 +116,6 @@ abstract class BaseTestCase extends \PHPUnit\Framework\TestCase
protected function setUp() : void
{
$this->beforeSetUp();
$params = array(
'className' => get_class($this),
'dataFile' => $this->dataFile,
@@ -126,6 +124,9 @@ abstract class BaseTestCase extends \PHPUnit\Framework\TestCase
);
$this->espoTester = new Tester($params);
$this->beforeSetUp();
$this->espoTester->initialize();
$this->auth($this->userName, $this->password, null, $this->authenticationMethod);
@@ -190,7 +191,7 @@ abstract class BaseTestCase extends \PHPUnit\Framework\TestCase
$this->espoTester->setData($data);
}
protected function enableFullReset()
protected function fullReset()
{
$this->espoTester->setParam('fullReset', true);
}
+8 -5
View File
@@ -251,15 +251,18 @@ class Tester
$fullReset = false;
$modifiedTime = filemtime($latestEspoDir . '/application');
if (!isset($configData['lastModifiedTime']) || $configData['lastModifiedTime'] != $modifiedTime) {
if ($this->getParam('fullReset') || !isset($configData['lastModifiedTime']) || $configData['lastModifiedTime'] != $modifiedTime) {
$fullReset = true;
$this->saveTestConfigData('lastModifiedTime', $modifiedTime);
}
if ($fullReset) {
Utils::dropTables($configData['database']);
$fileManager->removeInDir($this->installPath);
$fileManager->copy($latestEspoDir, $this->installPath, true);
//$fileManager->removeInDir($this->installPath);
//$fileManager->copy($latestEspoDir, $this->installPath, true);
shell_exec('rm -rf "' . $this->installPath . '"');
shell_exec('cp -r "' . $latestEspoDir . '" "' . $this->installPath . '"');
return true;
}
@@ -301,8 +304,8 @@ class Tester
{
$applyChanges = false;
if (!empty($this->params['pathToFiles'])) {
$this->getDataLoader()->loadFiles($this->params['pathToFiles']);
if (!empty($this->params['pathToFiles']) && file_exists($this->params['pathToFiles'])) {
$result = $this->getDataLoader()->loadFiles($this->params['pathToFiles']);
$this->getApplication(true, true)->runRebuild();
}
@@ -377,4 +377,44 @@ class FormulaTest extends \tests\integration\Core\BaseTestCase
$result = $fm->run($script, $lead);
$this->assertFalse($result);
}
public function testRecordRelate()
{
$fm = $this->getContainer()->get('formulaManager');
$em = $this->getContainer()->get('entityManager');
$a = $em->createEntity('Account', [
'name' => '1',
]);
$o = $em->createEntity('Opportunity', [
'name' => '1',
]);
$script = "record\\relate('Account', '".$a->id."', 'opportunities', '".$o->id."')";
$result = $fm->run($script, $contact);
$this->assertTrue($result);
$this->assertTrue($em->getRepository('Account')->isRelated($a, 'opportunities', $o));
}
public function testRecordUnrelate()
{
$fm = $this->getContainer()->get('formulaManager');
$em = $this->getContainer()->get('entityManager');
$a = $em->createEntity('Account', [
'name' => '1',
]);
$o = $em->createEntity('Opportunity', [
'name' => '1',
]);
$em->getRepository('Account')->relate($a, 'opportunities', $o);
$script = "record\\unrelate('Account', '".$a->id."', 'opportunities', '".$o->id."')";
$result = $fm->run($script, $contact);
$this->assertTrue($result);
$this->assertFalse($em->getRepository('Account')->isRelated($a, 'opportunities', $o));
}
}
@@ -38,6 +38,11 @@ class GeneralTest extends \tests\integration\Core\BaseTestCase
protected $packagePath = 'Extension/General.zip';
protected function beforeSetUp()
{
$this->fullReset();
}
public function testUpload()
{
$fileData = file_get_contents($this->normalizePath($this->packagePath));
@@ -50,8 +55,6 @@ class GeneralTest extends \tests\integration\Core\BaseTestCase
$this->assertFileExists('data/upload/extensions/' . $extensionId . 'z');
$this->assertFileExists('data/upload/extensions/' . $extensionId); //directory
$this->enableFullReset();
return $extensionId;
}
@@ -75,8 +78,6 @@ class GeneralTest extends \tests\integration\Core\BaseTestCase
$this->assertFileNotExists('extension.php');
$this->assertFileNotExists('upgrade.php');
$this->enableFullReset();
return $extensionId;
}
@@ -100,8 +101,6 @@ class GeneralTest extends \tests\integration\Core\BaseTestCase
$this->assertFileExists('extension.php');
$this->assertFileExists('upgrade.php');
$this->enableFullReset();
return $extensionId;
}
@@ -124,7 +123,5 @@ class GeneralTest extends \tests\integration\Core\BaseTestCase
$this->assertFileExists('vendor/zendframework'); //directory
$this->assertFileExists('extension.php');
$this->assertFileExists('upgrade.php');
$this->enableFullReset();
}
}