Compare commits

...

91 Commits

Author SHA1 Message Date
Yuri Kuznetsov 4d755d3795 fix tests 2021-01-25 11:46:56 +02:00
Yuri Kuznetsov 9fe61d2342 v 2021-01-25 11:12:38 +02:00
Taras Machyshyn 1f17d79e2d Upgrade fixes 2021-01-21 17:53:54 +02:00
Taras Machyshyn 3b0c7f1d5b Upgrade bug fixes 2021-01-21 17:39:44 +02:00
Taras Machyshyn d30edee790 Upgrade bug fixes 2021-01-21 16:14:14 +02:00
Yuri Kuznetsov 0815cc3b5e xlsx export fix and refactoring 2021-01-18 13:58:13 +02:00
Yuri Kuznetsov ddff78756d fix label 2021-01-11 12:24:07 +02:00
Taras Machyshyn f8c01808d4 Wrong directory owner fixes 2021-01-05 16:16:49 +02:00
Yuri Kuznetsov e073c82992 fix orm repository 2021-01-05 10:27:57 +02:00
Yuri Kuznetsov aa82f96993 Fix reply email with external account 2021-01-04 12:17:57 +02:00
Yuri Kuznetsov 867a0225f1 v 2021-01-04 11:47:18 +02:00
Yuri Kuznetsov 4ab636ded8 autoload test and refactoring 2020-12-31 09:59:29 +02:00
Yuri Kuznetsov f764375e0e file manager method cleanups 2020-12-31 09:33:12 +02:00
Arkady 98b20a3e24 fix autoload path merging (#1875)
Co-authored-by: Arkadiy Asuratov <a.asuratov@dubas.pro>
2020-12-31 08:56:24 +02:00
Yuri Kuznetsov ca17a5453a fix warning 2020-12-28 11:51:56 +02:00
Yuri Kuznetsov 641382dc66 cs fix and error throwing 2020-12-21 11:50:07 +02:00
Yuri Kuznetsov 8f89fa4597 cs fix 2020-12-21 11:33:51 +02:00
Yuri Kuznetsov 543eb21a8b fix flotr 2020-12-21 11:27:48 +02:00
Yuri Kuznetsov 9fbc62837f fix validation 2020-12-21 11:07:31 +02:00
Yuri Kuznetsov d3d710b2b9 v 2020-12-04 15:04:36 +02:00
Yuri Kuznetsov 4426d81e77 fix category tree 2020-12-04 14:28:04 +02:00
Yuri Kuznetsov 6b3e81e3fd settings currency validation 2020-12-03 11:56:44 +02:00
Yuri Kuznetsov dd708778d6 field clone validations 2020-12-03 11:56:15 +02:00
Yuri Kuznetsov 0bc06f3cae fix autofollow 2020-12-02 11:04:33 +02:00
Yuri Kuznetsov 0ab8853455 fix notification for portal user 2020-12-01 13:14:17 +02:00
Yuri Kuznetsov a612ffd2d2 fix pdf google maps 2020-12-01 11:40:23 +02:00
Yuri Kuznetsov bc19a6bb5f flotr2 fix 2020-11-30 12:48:53 +02:00
Eymen Elkum 8cc6a2ddf0 fix bc (#1853) 2020-11-29 11:25:07 +02:00
Yuri Kuznetsov c1f0c47cf7 bc fix 2020-11-28 19:33:12 +02:00
Yuri Kuznetsov 7e71eb0aeb fix warning 2020-11-27 18:42:52 +02:00
Taras Machyshyn 196f787a1f Fix installer for PHP 8 2020-11-27 17:32:00 +02:00
Taras Machyshyn b7bec1793c Fix installer for PHP 8 2020-11-27 12:26:57 +02:00
Taras Machyshyn b97aa8e3a7 Fix gitignore 2020-11-27 12:26:30 +02:00
Taras Machyshyn 21dcf59f21 Fix gitignore 2020-11-27 12:25:06 +02:00
Taras Machyshyn 326b6c8c53 Merge branch 'hotfix/6.0.7' of https://github.com/espocrm/espocrm into hotfix/6.0.7 2020-11-27 12:16:34 +02:00
Taras Machyshyn 7ebf0e7bf8 Fix installer for PHP 8 2020-11-27 12:16:01 +02:00
Yuri Kuznetsov 014df8b9d3 Merge branch 'hotfix/6.0.7' of github.com:espocrm/espocrm into hotfix/6.0.7 2020-11-27 12:12:28 +02:00
Yuri Kuznetsov 6d85055147 fix loaders 2020-11-27 12:12:16 +02:00
Taras Machyshyn 077f7d8855 Fix installer for PHP 8 2020-11-27 12:03:04 +02:00
Taras Machyshyn 296484360f Fix installer for PHP 8 2020-11-27 11:03:40 +02:00
Yuri Kuznetsov 1071f8a045 v 2020-11-26 12:09:45 +02:00
Yuri Kuznetsov 962d3a049c disable edit access to user email address 2020-11-26 09:44:22 +02:00
Yuri Kuznetsov 161859606a ceanup 2020-11-21 14:20:14 +02:00
Yuri Kuznetsov a71df0cff6 fix layout 2020-11-20 09:52:54 +02:00
Yuri Kuznetsov fb9ff0b8c0 fix deprecated 2020-11-18 13:16:24 +02:00
Yuri Kuznetsov d1a62baa12 fix deprecated 2020-11-18 12:53:17 +02:00
Yuri Kuznetsov cdec7ccfe8 v 2020-11-18 11:34:14 +02:00
Yuri Kuznetsov 25bc3a40f7 fix export currency 2020-11-18 09:41:54 +02:00
Yuri Kuznetsov 02e5c1fc44 v 2020-11-16 13:40:49 +02:00
Yuri Kuznetsov 844e9dd8e3 Merge branch 'hotfix/6.0.5' of github.com:espocrm/espocrm into hotfix/6.0.5 2020-11-16 13:38:16 +02:00
Yuri Kuznetsov 39f2ec83d5 fix duplicate attachments multiple 2020-11-16 13:35:53 +02:00
Taras Machyshyn e4d2af03a5 Fixed PHP binary path 2020-11-16 13:34:24 +02:00
Taras Machyshyn 37616d69bf Merge branch 'hotfix/6.0.5' of https://github.com/espocrm/espocrm into hotfix/6.0.5 2020-11-16 13:28:03 +02:00
Taras Machyshyn 4e36f71dd8 Fixed PHP binary path 2020-11-16 13:27:16 +02:00
Yuri Kuznetsov b5877ac32a fix person name 2020-11-16 13:03:56 +02:00
Yuri Kuznetsov 92de588a6e update npm packages 2020-11-16 11:07:12 +02:00
Yuri Kuznetsov 700f4b5b10 fix export 2020-11-14 10:24:22 +02:00
Yuri Kuznetsov 9c0a7b6778 app service changes 2020-11-13 12:39:27 +02:00
Yuri Kuznetsov 9a54c66f68 preferences fixes 2020-11-13 12:32:13 +02:00
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
86 changed files with 2743 additions and 1417 deletions
+3 -3
View File
@@ -7,7 +7,7 @@
/data/tmp/*
/build
/node_modules
npm-debug.log
/npm-debug.log
/test.php
/main.html
@@ -15,7 +15,7 @@ npm-debug.log
!/tests/unit/testData/cache/.data
/tests/integration/config.php
.phpunit.result.cache
composer.phar
vendor/
/composer.phar
/vendor/
/custom/Espo/Custom/*
/install/config.php
@@ -148,14 +148,12 @@ class GoogleMaps
$GLOBALS['log']->debug("Template helper _googleMapsImage: URL: {$url}.");
$image = \Espo\Core\TemplateHelpers\GoogleMaps::getImage($url);
$image = \Espo\Classes\TemplateHelpers\GoogleMaps::getImage($url);
if (!$image) {
return null;
}
$filePath = tempnam(sys_get_temp_dir(), 'google_maps_image');
file_put_contents($filePath, $image);
+2
View File
@@ -178,6 +178,8 @@ class Auth
}
if ($hasAuthData) {
$authResult = null;
try {
$authResult = $this->authentication->login($username, $password, $request, $authenticationMethod);
}
@@ -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();
}
@@ -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');
@@ -41,6 +41,7 @@ use Espo\Core\{
use Symfony\Component\Process\PhpExecutableFinder;
use Exception;
use Throwable;
class Upgrade implements Command
{
@@ -78,7 +79,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);
@@ -127,7 +128,7 @@ class Upgrade implements Command
try {
$this->runUpgradeProcess($upgradeId, $params);
}
catch (Exception $e) {
catch (Throwable $e) {
$errorMessage = $e->getMessage();
}
@@ -137,7 +138,8 @@ class Upgrade implements Command
$this->fileManager->unlink($packageFile);
}
if (!empty($errorMessage)) {
if (isset($errorMessage)) {
$errorMessage = !empty($errorMessage) ? $errorMessage : "Error: An unexpected error occurred.";
fwrite(\STDOUT, $errorMessage . "\n");
return;
@@ -287,8 +289,11 @@ class Upgrade implements Command
$upgradeManager = $this->getUpgradeManager(true);
$upgradeManager->runInstallStep($stepName, ['id' => $upgradeId]);
}
} catch (Exception $e) {
$GLOBALS['log']->error('Upgrade Error: ' . $e->getMessage());
} catch (Throwable $e) {
try {
$GLOBALS['log']->error('Upgrade Error: ' . $e->getMessage());
}
catch (Throwable $t) {}
throw new Error($e->getMessage());
}
@@ -308,7 +313,10 @@ class Upgrade implements Command
$shellResult = shell_exec($command);
if ($shellResult !== 'true') {
$GLOBALS['log']->error('Upgrade Error: ' . $shellResult);
try {
$GLOBALS['log']->error('Upgrade Error: ' . $shellResult);
}
catch (Throwable $t) {}
throw new Error($shellResult);
}
@@ -73,7 +73,7 @@ class UpgradeStep implements Command
try {
$result = $upgradeManager->runInstallStep($stepName, $params); // throw Exception on error
} catch (\Exception $e) {
die("Error: " . $e->getMessage() . "\n");
die("Error: " . $e->getMessage());
}
if (is_bool($result)) {
@@ -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 = [
['='],
['||', '&&'],
['||'],
['&&'],
['==', '!=', '>', '<', '>=', '<='],
['+', '-'],
['*', '/', '%'],
+17 -4
View File
@@ -155,9 +155,11 @@ class InjectableFactory
$dependencyClass = null;
if ($param->getType()) {
$type = $param->getType();
if ($type && !$type->isBuiltin()) {
try {
$dependencyClass = $param->getClass();
$dependencyClass = new ReflectionClass($type->getName());
}
catch (Throwable $e) {
$badClassName = $param->getType()->getName();
@@ -241,15 +243,26 @@ class InjectableFactory
}
$params = $class->getMethod($methodName)->getParameters();
if (!$params || !count($params)) {
return false;
}
if ($skipInstanceCheck) {
return true;
}
$injection = $this->container->get($name);
$paramClass = $params[0]->getClass();
$paramClass = null;
if ($skipInstanceCheck || $paramClass && $paramClass->isInstance($injection)) {
$type = $params[0]->getType();
if ($type && !$type->isBuiltin()) {
$paramClass = new ReflectionClass($type->getName());
}
if ($paramClass && $paramClass->isInstance($injection)) {
return true;
}
@@ -59,7 +59,7 @@ class BaseLanguage implements Loader
$this->fileManager,
$this->metadata,
$this->dataCache,
$this->config->get('useCache')
$this->config->get('useCache') ?? false
);
}
}
@@ -42,7 +42,7 @@ class DefaultLanguage extends BaseLanguage
$this->fileManager,
$this->metadata,
$this->dataCache,
$this->config->get('useCache')
$this->config->get('useCache') ?? false
);
}
}
+1 -1
View File
@@ -64,7 +64,7 @@ class Language implements Loader
$this->fileManager,
$this->metadata,
$this->dataCache,
$this->config->get('useCache')
$this->config->get('useCache') ?? false
);
}
}
+1 -1
View File
@@ -52,7 +52,7 @@ class Metadata implements Loader
public function load() : MetadataService
{
$useCache = $this->config->get('useCache');
$useCache = $this->config->get('useCache') ?? false;
return new MetadataService($this->fileManager, $this->dataCache, $useCache);
}
@@ -123,4 +123,22 @@ class DefaultNotificator implements Notificator
return $this->userIdEnabledMap[$userId];
}
/**
* For backward compatibility.
* @todo Remove.
*/
protected function getEntityManager()
{
return $this->entityManager;
}
/**
* For backward compatibility.
* @todo Remove.
*/
protected function getUser()
{
return $this->user;
}
}
+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);
}
}
@@ -49,6 +49,9 @@ class CategoryTree extends Database
->select()
->from($pathEntityType)
->select(['ascendorId', "'" . $entity->id . "'"])
->where([
'descendorId' => $parentId,
])
->build();
$subSelect2 = $em->getQueryBuilder()
+18 -5
View File
@@ -2506,6 +2506,14 @@ class SelectManager
$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') {
@@ -2686,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) {
@@ -2698,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'];
}
@@ -462,7 +462,7 @@ abstract class Base
{
if (!isset($this->data['restoreFileList'])) {
$backupPath = $this->getPath('backupPath');
$this->data['restoreFileList'] = $this->getFileList($backupPath);
$this->data['restoreFileList'] = $this->getFileList($backupPath, true);
}
return $this->data['restoreFileList'];
@@ -496,7 +496,7 @@ abstract class Base
*
* @return array
*/
protected function getFileList($dirPath)
protected function getFileList($dirPath, $skipVendorFileList = false)
{
$fileList = array();
@@ -508,10 +508,11 @@ abstract class Base
}
}
//vendor file list
$vendorFileList = $this->getVendorFileList('copy');
if (!empty($vendorFileList)) {
$fileList = array_merge($fileList, $vendorFileList);
if (!$skipVendorFileList) {
$vendorFileList = $this->getVendorFileList('copy');
if (!empty($vendorFileList)) {
$fileList = array_merge($fileList, $vendorFileList);
}
}
return $fileList;
@@ -738,7 +739,11 @@ abstract class Base
return true;
}
catch (Throwable $e) {
$GLOBALS['log']->error('Database rebuild failure, details: '. $e->getMessage() .'.');
try {
$GLOBALS['log']->error('Database rebuild failure, details: '. $e->getMessage() .'.');
}
catch (Throwable $e) {}
}
return false;
@@ -77,8 +77,6 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
protected function initPackage(array $data)
{
$GLOBALS['log']->setLevel('info');
$processId = $data['id'];
if (empty($processId)) {
@@ -150,19 +148,23 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
$GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: Start "copy" step.');
/* remove files defined in a manifest "deleteBeforeCopy" */
$this->deleteFiles('deleteBeforeCopy', true);
/* remove files defined in a manifest */
if (!$this->deleteFiles('delete', true)) {
$this->throwErrorAndRemovePackage('Cannot delete files.');
}
/* copy files from directory "Files" to EspoCRM files */
if (!$this->copyFiles()) {
$this->throwErrorAndRemovePackage('Cannot copy files.');
}
/* remove files defined in a manifest */
$this->deleteFiles('delete', true);
if (!$this->deleteFiles('vendor')) {
$this->throwErrorAndRemovePackage('Cannot delete vendor files.');
}
$this->deleteFiles('vendor');
$this->copyFiles('vendor');
if (!$this->copyFiles('vendor')) {
$this->throwErrorAndRemovePackage('Cannot copy vendor files.');
}
$GLOBALS['log']->info('Installation process ['. $this->getProcessId() .']: End "copy" step.');
}
+28 -33
View File
@@ -66,22 +66,13 @@ class Autoload
$this->loader = $loader;
}
public function get($key = null, $returns = null)
protected function getData() : array
{
if (!isset($this->data)) {
$this->init();
}
if (!isset($key)) {
return $this->data;
}
return Util::getValueByKey($this->data, $key, $returns);
}
public function getAll()
{
return $this->get();
return $this->data;
}
protected function init()
@@ -94,46 +85,48 @@ class Autoload
return;
}
$this->data = $this->unify();
$this->data = $this->loadData();
if ($useCache) {
$result = $this->dataCache->store($this->cacheKey, $this->data);
}
}
protected function unify()
protected function loadData() : array
{
$data = $this->loadData($this->paths['corePath']);
$data = $this->loadDataFromFile($this->paths['corePath']);
foreach ($this->metadata->getModuleList() as $moduleName) {
$modulePath = str_replace('{*}', $moduleName, $this->paths['modulePath']);
$data = array_merge($data, $this->loadData($modulePath));
$data = array_merge_recursive($data, $this->loadDataFromFile($modulePath));
}
$data = array_merge($data, $this->loadData($this->paths['customPath']));
$data = array_merge_recursive($data, $this->loadDataFromFile($this->paths['customPath']));
return $data;
}
protected function loadData($autoloadFile, $returns = [])
protected function loadDataFromFile(string $filePath) : array
{
if (file_exists($autoloadFile)) {
$content = $this->fileManager->getContents($autoloadFile);
$arrayContent = Json::getArrayData($content);
if (!empty($arrayContent)) {
return $this->normalizeData($arrayContent);
}
$GLOBALS['log']->error('Autoload: Empty file or syntax error ['.$autoloadFile.'].');
if (!$this->fileManager->isFile($filePath)) {
return [];
}
return $returns;
$content = $this->fileManager->getContents($filePath);
$arrayContent = Json::getArrayData($content);
if (empty($arrayContent)) {
$GLOBALS['log']->error("Autoload: Empty file or syntax error in '{$filePath}'.");
return [];
}
return $this->normalizeData($arrayContent);
}
protected function normalizeData(array $data)
protected function normalizeData(array $data) : array
{
$normalizedData = [];
@@ -161,12 +154,14 @@ class Autoload
public function register()
{
try {
$autoloadList = $this->getAll();
$data = $this->getData();
}
catch (Exception $e) {} //bad permissions
catch (Exception $e) {} // bad permissions
if (!empty($autoloadList)) {
$this->loader->register($autoloadList);
if (empty($data)) {
return;
}
$this->loader->register($data);
}
}
+10 -10
View File
@@ -38,42 +38,42 @@ class Loader
$this->namespaceLoader = $namespaceLoader;
}
public function register(array $autoloadList)
public function register(array $data)
{
/* load "psr-4", "psr-0", "classmap" */
$this->namespaceLoader->register($autoloadList);
$this->namespaceLoader->register($data);
/* load "autoloadFileList" */
$this->registerAutoloadFileList($autoloadList);
$this->registerAutoloadFileList($data);
/* load "files" */
$this->registerFiles($autoloadList);
$this->registerFiles($data);
}
protected function registerAutoloadFileList(array $autoloadList)
protected function registerAutoloadFileList(array $data)
{
$keyName = 'autoloadFileList';
if (!isset($autoloadList[$keyName])) {
if (!isset($data[$keyName])) {
return;
}
foreach ($autoloadList[$keyName] as $filePath) {
foreach ($data[$keyName] as $filePath) {
if (file_exists($filePath)) {
require_once($filePath);
}
}
}
protected function registerFiles(array $autoloadList)
protected function registerFiles(array $data)
{
$keyName = 'files';
if (!isset($autoloadList[$keyName])) {
if (!isset($data[$keyName])) {
return;
}
foreach ($autoloadList[$keyName] as $id => $filePath) {
foreach ($data[$keyName] as $id => $filePath) {
if (file_exists($filePath)) {
require_once($filePath);
}
@@ -74,9 +74,9 @@ class NamespaceLoader
$this->classLoader = new ClassLoader();
}
public function register(array $autoloadList)
public function register(array $data)
{
$this->addListToClassLoader($this->classLoader, $autoloadList);
$this->addListToClassLoader($this->classLoader, $data);
$this->classLoader->register(true);
}
@@ -147,7 +147,7 @@ class NamespaceLoader
return false;
}
protected function addListToClassLoader($classLoader, array $list, $skipVendorNamespaces = false)
protected function addListToClassLoader(ClassLoader $classLoader, array $list, bool $skipVendorNamespaces = false)
{
foreach ($this->methodNameMap as $type => $methodName) {
if (!isset($list[$type])) {
@@ -96,7 +96,7 @@ class PersonName extends Base
$whereItems[] = "CONCAT:({$firstName}, ' ', {$middleName}, ' ', {$lastName})";
} else
if ($format === 'lastFirstMiddle') {
$whereItems[] = "CONCAT:({$lastName}, ' ', {$firstColumn}, ' ', {$middleName})";
$whereItems[] = "CONCAT:({$lastName}, ' ', {$firstName}, ' ', {$middleName})";
}
$selectExpression = $this->getSelect($fullList);
+23 -30
View File
@@ -262,7 +262,7 @@ class Manager
}
if ($result && function_exists('opcache_invalidate')) {
@opcache_invalidate($fullPath);
opcache_invalidate($fullPath);
}
return $result;
@@ -516,6 +516,12 @@ class Manager
return true;
}
$parentDirPath = dirname($fullPath);
if (!file_exists($parentDirPath)) {
$this->mkdir($parentDirPath, $permission);
}
$defaultPermissions = $this->getPermissionUtils()->getRequiredPermissions($fullPath);
if (!isset($permission)) {
@@ -524,11 +530,11 @@ class Manager
}
try {
$umask = @umask(0);
$result = mkdir($fullPath, $permission, true);
$umask = umask(0);
$result = mkdir($fullPath, $permission);
if ($umask) {
@umask($umask);
umask($umask);
}
if (!empty($defaultPermissions['user'])) {
@@ -615,7 +621,7 @@ class Manager
$this->getPermissionUtils()->setDefaultPermissions($destFile);
if (function_exists('opcache_invalidate')) {
@opcache_invalidate($destFile);
opcache_invalidate($destFile);
}
}
}
@@ -719,7 +725,7 @@ class Manager
if (file_exists($filePath) && is_file($filePath)) {
if (function_exists('opcache_invalidate')) {
@opcache_invalidate($filePath, true);
opcache_invalidate($filePath, true);
}
$result &= unlink($filePath);
@@ -821,7 +827,7 @@ class Manager
/**
* Remove empty parent directories if they are empty.
* @param string $path
* @param string $path
* @return bool
*/
protected function removeEmptyDirs($path)
@@ -838,29 +844,19 @@ class Manager
}
/**
* Check if $dirname is directory.
*
* @param string $dirname
* @param string $basePath
*
* @return boolean
* Check whether a path is a directory.
*/
public function isDir($dirname, $basePath = null)
public function isDir(string $dirPath) : bool
{
if (!empty($basePath)) {
$dirname = $this->concatPaths([$basePath, $dirname]);
}
return is_dir($dirname);
return is_dir($dirPath);
}
public function isFile($filename, $basePath = null)
/**
* Check whether a path is a file.
*/
public function isFile(string $filePath) : bool
{
if (!empty($basePath)) {
$filename = $this->concatPaths([$basePath, $filename]);
}
return is_file($filename);
return is_file($filePath);
}
/**
@@ -891,12 +887,9 @@ class Manager
}
/**
* Check if directory is empty.
*
* @param string $path
* @return boolean
* Check whether a directory is empty.
*/
public function isDirEmpty($path)
public function isDirEmpty(string $path) : bool
{
if (is_dir($path)) {
$fileList = $this->getFileList($path, true);
@@ -390,7 +390,7 @@ class Permission
protected function chmodReal($filename, $mode)
{
try {
$result = @chmod($filename, $mode);
$result = chmod($filename, $mode);
} catch (\Exception $e) {
$result = false;
}
@@ -400,7 +400,7 @@ class Permission
$this->chgrp($filename, $this->getDefaultGroup(true));
try {
$result = @chmod($filename, $mode);
$result = chmod($filename, $mode);
} catch (\Exception $e) {
throw new Error($e->getMessage());
}
@@ -412,7 +412,7 @@ class Permission
protected function chownReal($path, $user)
{
try {
$result = @chown($path, $user);
$result = chown($path, $user);
} catch (\Exception $e) {
throw new Error($e->getMessage());
}
@@ -423,7 +423,7 @@ class Permission
protected function chgrpReal($path, $group)
{
try {
$result = @chgrp($path, $group);
$result = chgrp($path, $group);
} catch (\Exception $e) {
throw new Error($e->getMessage());
}
+3 -4
View File
@@ -53,10 +53,10 @@ class ScheduledJob
protected $checkingCronPeriod = '25 hours';
protected $cronSetup = [
'linux' => '* * * * * cd {DOCUMENT_ROOT}; {PHP-BIN-DIR} -f {CRON-FILE} > /dev/null 2>&1',
'linux' => '* * * * * cd {DOCUMENT_ROOT}; {PHP-BINARY} -f {CRON-FILE} > /dev/null 2>&1',
'windows' => '{PHP-BINARY} -f {FULL-CRON-PATH}',
'mac' => '* * * * * cd {DOCUMENT_ROOT}; {PHP-BIN-DIR} -f {CRON-FILE} > /dev/null 2>&1',
'default' => '* * * * * cd {DOCUMENT_ROOT}; {PHP-BIN-DIR} -f {CRON-FILE} > /dev/null 2>&1',
'mac' => '* * * * * cd {DOCUMENT_ROOT}; {PHP-BINARY} -f {CRON-FILE} > /dev/null 2>&1',
'default' => '* * * * * cd {DOCUMENT_ROOT}; {PHP-BINARY} -f {CRON-FILE} > /dev/null 2>&1',
];
protected $classFinder;
@@ -105,7 +105,6 @@ class ScheduledJob
$desc = $language->translate('cronSetup', 'options', 'ScheduledJob');
$data = array(
'PHP-BIN-DIR' => $this->getSystemUtil()->getPhpBin(),
'PHP-BINARY' => $this->getSystemUtil()->getPhpBinary(),
'CRON-FILE' => $this->cronFile,
'DOCUMENT_ROOT' => $this->getSystemUtil()->getRootDir(),
+5 -14
View File
@@ -29,6 +29,8 @@
namespace Espo\Core\Utils;
use Symfony\Component\Process\PhpExecutableFinder;
class System
{
/**
@@ -100,22 +102,11 @@ class System
}
/**
* Get path to PHP
*
* @return string
* Deprecated. Use getPhpBinary()
*/
public function getPhpBin()
{
if (isset($_SERVER['PHP_PATH']) && !empty($_SERVER['PHP_PATH'])) {
return $_SERVER['PHP_PATH'];
}
$phpBin = @exec('which php');
if (!empty($phpBin)) {
return $phpBin;
}
return defined("PHP_BINDIR") ? PHP_BINDIR . DIRECTORY_SEPARATOR . 'php' : 'php';
return $this->getPhpBinary();
}
/**
@@ -125,7 +116,7 @@ class System
*/
public function getPhpBinary()
{
return defined("PHP_BINARY") ? PHP_BINARY : 'php';
return (new PhpExecutableFinder)->find();
}
/**
@@ -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'];
+10 -3
View File
@@ -98,7 +98,7 @@ class Lead extends PersonService implements
$ignoreAttributeList = ['createdAt', 'modifiedAt', 'modifiedById', 'modifiedByName', 'createdById', 'createdByName'];
$convertFieldsDefs = $this->getMetadata()->get('entityDefs.Lead.convertFields', array());
$convertFieldsDefs = $this->getMetadata()->get('entityDefs.Lead.convertFields', []);
foreach ($entityList as $entityType) {
if (!$this->getAcl()->checkScope($entityType, 'edit')) {
@@ -141,7 +141,10 @@ class Lead extends PersonService implements
$attachment = $lead->get($field);
if ($attachment) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
$attachment = $this->getEntityManager()
->getRepository('Attachment')
->getCopiedAttachment($attachment);
$idAttribute = $field . 'Id';
$nameAttribute = $field . 'Name';
@@ -189,7 +192,11 @@ class Lead extends PersonService implements
continue;
}
$leadAttribute = $leadAttributeList[$i];
$leadAttribute = $leadAttributeList[$i] ?? null;
if (!$leadAttribute) {
throw new Error("Not compatible fields in 'convertFields' map.");
}
if (!$lead->has($leadAttribute)) {
continue;
+1 -7
View File
@@ -37,13 +37,7 @@ interface Builder
{
/**
* Build a query instance.
* @todo Uncomment when 7.4 is a min supported PHP version. Need the support of contravariant method parameters.
* @todo Uncomment when 7.4 is a min supported PHP version.
*/
//public function build() : Query;
/**
* Clone an existing query for a further modification and building.
* @todo Uncomment when 7.4 is a min supported PHP version. Need the support of contravariant method parameters.
*/
//public function clone(Query $query) : self;
}
@@ -297,12 +297,6 @@ class RDBRepository extends Repository
$additionalColumnsConditions = $params['additionalColumnsConditions'] ?? [];
unset($params['additionalColumnsConditions']);
if ($type === Entity::MANY_MANY && count($additionalColumnsConditions)) {
$select = $this->applyRelationAdditionalColumnsConditions(
$entity, $relationName, $additionalColumnsConditions, $select
);
}
$select = null;
if ($entityType) {
@@ -310,6 +304,12 @@ class RDBRepository extends Repository
$select = Select::fromRaw($params);
}
if ($type === Entity::MANY_MANY && count($additionalColumnsConditions)) {
$select = $this->applyRelationAdditionalColumnsConditions(
$entity, $relationName, $additionalColumnsConditions, $select
);
}
return (int) $this->getMapper()->countRelated($entity, $relationName, $select);
}
@@ -20,7 +20,7 @@
},
"tooltips": {
"name": "Give the filter a descriptive name.",
"subject": "Use a wildcard *:\n\ntext* - starts with text,\n*text* - contains text,\n*text - ends with text.",
"subject": "Use a wildcard *: \n\n * `text*` starts with text,\n * `*text*` contains text,\n * `*text` ends with text.",
"bodyContains": "Body of the email contains any of the specified words or phrases.",
"from": "Emails being sent from the specified address. Leave empty if not needed. You can use wildcard *.",
"to": "Emails being sent to the specified address. Leave empty if not needed. You can use wildcard *.",
@@ -270,6 +270,7 @@
"notModified": "You have not modified the record",
"duplicate": "The record you are creating might already exist",
"dropToAttach": "Drop to attach",
"fieldInvalid": "{field} is invalid",
"fieldIsRequired": "{field} is required",
"fieldShouldBeEmail": "{field} should be a valid email",
"fieldShouldBeFloat": "{field} should be a valid float",
@@ -144,7 +144,8 @@
},
"currencyFormat": {
"1": "10 USD",
"2": "$10"
"2": "$10",
"3": "10 $"
},
"personNameFormat": {
"firstLast": "First Last",
@@ -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
}
]
}
]
},
@@ -29,6 +29,9 @@
"isActive": {
"nonAdminReadOnly": true
},
"emailAddress": {
"nonAdminReadOnly": true
},
"teams": {
"nonAdminReadOnly": true
},
@@ -12,7 +12,7 @@
},
"userId": {
"type": "varchar",
"maxLength": 11,
"maxLength": 24,
"index": true
}
}
@@ -2,7 +2,7 @@
"fields": {
"timeZone": {
"type": "enum",
"detault": "",
"default": "",
"view": "views/preferences/fields/time-zone"
},
"dateFormat": {
@@ -124,7 +124,8 @@
"assignmentNotificationsIgnoreEntityTypeList": {
"type": "checklist",
"translation": "Global.scopeNamesPlural",
"view": "views/preferences/fields/assignment-notifications-ignore-entity-type-list"
"view": "views/preferences/fields/assignment-notifications-ignore-entity-type-list",
"default": []
},
"assignmentEmailNotificationsIgnoreEntityTypeList": {
"type": "checklist",
@@ -140,14 +141,17 @@
},
"signature": {
"type": "wysiwyg",
"view": "views/preferences/fields/signature"
"view": "views/preferences/fields/signature",
"default": ""
},
"defaultReminders": {
"type": "jsonArray",
"view": "crm:views/meeting/fields/reminders"
"view": "crm:views/meeting/fields/reminders",
"default": []
},
"theme": {
"type": "enum",
"default": "",
"view": "views/preferences/fields/theme",
"translation": "Global.themes"
},
@@ -173,7 +177,8 @@
},
"doNotFillAssignedUserIfNotRequired": {
"type": "bool",
"tooltip": true
"tooltip": true,
"default": false
},
"followEntityOnStreamPost": {
"type": "bool",
@@ -187,16 +192,20 @@
"type": "multiEnum",
"view": "views/preferences/fields/auto-follow-entity-type-list",
"translation": "Global.scopeNamesPlural",
"default": [],
"tooltip": true
},
"emailUseExternalClient": {
"type": "bool"
"type": "bool",
"default": false
},
"scopeColorsDisabled": {
"type": "bool"
"type": "bool",
"default": false
},
"tabColorsDisabled": {
"type": "bool"
"type": "bool",
"default": false
}
},
"noDeletedAttribute": 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)
+60 -13
View File
@@ -46,6 +46,7 @@ use Espo\Core\{
Utils\Config,
Utils\Util,
Utils\Language,
Utils\FieldUtil,
};
use Espo\Entities\User;
@@ -57,8 +58,24 @@ use Espo\ORM\{
Entity,
};
use StdClass;
use Throwable;
class App
{
protected $config;
protected $entityManager;
protected $metadata;
protected $acl;
protected $aclManager;
protected $dataManager;
protected $selectManagerFactory;
protected $injectableFactory;
protected $serviceFactory;
protected $user;
protected $preferences;
protected $fieldUtil;
public function __construct(
Config $config,
EntityManager $entityManager,
@@ -70,7 +87,8 @@ class App
InjectableFactory $injectableFactory,
ServiceFactory $serviceFactory,
User $user,
Preferences $preferences
Preferences $preferences,
FieldUtil $fieldUtil
) {
$this->config = $config;
$this->entityManager = $entityManager;
@@ -83,12 +101,14 @@ class App
$this->serviceFactory = $serviceFactory;
$this->user = $user;
$this->preferences = $preferences;
$this->fieldUtil = $fieldUtil;
}
public function getUserData()
{
$preferencesData = $this->preferences->getValueMap();
unset($preferencesData->smtpPassword);
$this->filterPreferencesData($preferencesData);
$settingsService = $this->serviceFactory->create('Settings');
@@ -134,11 +154,16 @@ class App
foreach (($this->metadata->get(['app', 'appParams']) ?? []) as $paramKey => $item) {
$className = $item['className'] ?? null;
if (!$className) continue;
if (!$className) {
continue;
}
try {
$itemParams = $this->injectableFactory->create($className)->get();
} catch (\Throwable $e) {
} catch (Throwable $e) {
$GLOBALS['log']->error("appParam {$paramKey}: " . $e->getMessage());
continue;
}
$appParams[$paramKey] = $itemParams;
@@ -452,19 +477,41 @@ class App
$entityTypeList = ['Contact', 'Lead'];
foreach ($entityTypeList as $entityType) {
$entityList = $this->entityManager->getRepository($entityType)->where([
'doNotCall' => true,
'phoneNumber!=' => null,
])->select(['id', 'phoneNumber'])->find();
$entityList = $this->entityManager
->getRepository($entityType)
->where([
'doNotCall' => true,
'phoneNumber!=' => null,
])
->select(['id', 'phoneNumber'])
->find();
foreach ($entityList as $entity) {
$phoneNumber = $entity->get('phoneNumber');
if (!$phoneNumber) continue;
$phoneNumberEntity = $this->entityManager->getRepository('PhoneNumber')->getByNumber($phoneNumber);
if (!$phoneNumberEntity) continue;
$phoneNumberEntity->set('optOut', true);
$this->entityManager->saveEntity($phoneNumberEntity);
if (!$phoneNumber) {
continue;
}
$phoneNumberEntity = $this->entityManager->getRepository('PhoneNumber')->getByNumber($phoneNumber);
if (!$phoneNumberEntity) {
continue;
}
$phoneNumberEntity->set('optOut', true);
$this->entityManager->saveEntity($phoneNumberEntity);
}
}
}
protected function filterPreferencesData(StdClass $data)
{
$passwordFieldList = $this->fieldUtil->getFieldByTypeList('Preferences', 'password');
foreach ($passwordFieldList as $field) {
unset($data->$field);
}
}
}
+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}.");
}
+5 -2
View File
@@ -81,7 +81,7 @@ class Notification extends \Espo\Services\Record implements
$collection = $this->entityManager->createCollection();
$userList = $this->getEntityManager()->getRepository('User')
->select(['id'])
->select(['id', 'type'])
->where([
'isActive' => true,
'id' => $userIdList,
@@ -90,7 +90,10 @@ class Notification extends \Espo\Services\Record implements
foreach ($userList as $user) {
$userId = $user->id;
if (!$this->checkUserNoteAccess($user, $note)) continue;
if (!$this->checkUserNoteAccess($user, $note)) {
continue;
}
if ($note->get('createdById') === $user->id) continue;
if ($related && $related->getEntityType() == 'Email' && $related->get('sentById') == $user->id) continue;
if ($related && $related->get('createdById') == $user->id) continue;
+7 -2
View File
@@ -706,7 +706,7 @@ class Record implements Crud,
}
}
protected function processValidationField(Entity $entity, $field, $data)
protected function processValidationField(Entity $entity, string $field, $data)
{
$fieldType = $this->fieldUtil->getEntityTypeFieldParam($this->entityType, $field, 'type');
@@ -724,10 +724,11 @@ class Record implements Crud,
}
$skipPropertyName = 'validate' . ucfirst($type) . 'SkipFieldList';
if (property_exists($this, $skipPropertyName)) {
$skipList = $this->$skipPropertyName;
if (in_array($type, $skipList)) {
if (in_array($field, $skipList)) {
continue;
}
}
@@ -2550,6 +2551,10 @@ class Record implements Crud,
->getRepository('Attachment')
->getCopiedAttachment($attachment);
$attachment->set('field', $field);
$this->getEntityManager()->saveEntity($attachment);
if ($attachment) {
$idList[] = $attachment->id;
$nameHash->{$attachment->id} = $attachment->get('name');
+6 -9
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 {
@@ -147,7 +149,7 @@ class Export
]
];
$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'];
@@ -162,11 +164,9 @@ class Export
}
}
if (isset($searchParams['select']) && is_string($searchParams['select'])) {
$searchParams['select'] = explode(',', $searchParams['select']);
}
unset($searchParams['select']);
$selectParams = $selectManager->getSelectParams($searchParams, true, true);
$selectParams = $selectManager->getSelectParams($searchParams, true, true, true);
}
else {
throw new BadRequest();
@@ -218,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'];
}
+232 -76
View File
@@ -36,12 +36,23 @@ use Espo\Core\{
Utils\Config,
Utils\Metadata,
Utils\Language,
Utils\DateTime,
Utils\DateTime as DateTimeUtil,
FileStorage\Manager as FileStorageManager,
Utils\File\Manager as FileManager,
ORM\EntityManager,
};
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use DateTime;
use DateTimeZone;
use Exception;
class Xlsx
{
protected $config;
@@ -56,7 +67,7 @@ class Xlsx
Config $config,
Metadata $metadata,
Language $language,
DateTime $dateTime,
DateTimeUtil $dateTime,
EntityManager $entityManager,
FileStorageManager $fileStorageManager,
FileManager $fileManager
@@ -146,18 +157,24 @@ class Xlsx
}
$linkDefs = $this->getMetadata()->get(['entityDefs', $entityType, 'links']);
if (is_array($linkDefs)) {
foreach ($linkDefs as $link => $defs) {
if (empty($defs['type'])) continue;
if (empty($defs['type'])) {
continue;
}
if ($defs['type'] === 'belongsToParent') {
$linkList[] = $link;
} else if ($defs['type'] === 'belongsTo' && !empty($defs['noJoin'])) {
}
else if ($defs['type'] === 'belongsTo' && !empty($defs['noJoin'])) {
if ($this->getMetadata()->get(['entityDefs', $entityType, 'fields', $link])) {
$linkList[] = $link;
}
}
}
}
foreach ($linkList as $item) {
if (in_array($item, $fieldList) && !in_array($item . 'Name', $attributeList)) {
$attributeList[] = $item . 'Name';
@@ -166,6 +183,7 @@ class Xlsx
foreach ($fieldList as $field) {
$type = $this->getMetadata()->get(['entityDefs', $entityType, 'fields', $field, 'type']);
if ($type === 'currencyConverted') {
if (!in_array($field, $attributeList)) {
$attributeList[] = $field;
@@ -180,43 +198,49 @@ class Xlsx
throw new Error();
}
$phpExcel = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$phpExcel = new Spreadsheet();
$sheet = $phpExcel->setActiveSheetIndex(0);
if (isset($params['exportName'])) {
$exportName = $params['exportName'];
} else {
}
else {
$exportName = $this->language->translate($entityType, 'scopeNamesPlural');
}
$sheetName = mb_substr($exportName, 0, 30, 'utf-8');
$badCharList = ['*', ':', '/', '\\', '?', '[', ']'];
foreach ($badCharList as $badChar) {
$sheetName = str_replace($badCharList, ' ', $sheetName);
}
$sheetName = str_replace('\'', '', $sheetName);
$sheet->setTitle($sheetName);
$fieldList = $params['fieldList'];
$titleStyle = array(
'font' => array(
$titleStyle = [
'font' => [
'bold' => true,
'size' => 12
)
);
$dateStyle = array(
'font' => array(
'size' => 12
)
);
'size' => 12,
],
];
$now = new \DateTime();
$now->setTimezone(new \DateTimeZone($this->config->get('timeZone', 'UTC')));
$dateStyle = [
'font' => [
'size' => 12,
],
];
$now = new DateTime();
$now->setTimezone(new DateTimeZone($this->config->get('timeZone', 'UTC')));
$sheet->setCellValue('A1', $exportName);
$sheet->setCellValue('B1', \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel(strtotime($now->format('Y-m-d H:i:s'))));
$sheet->setCellValue('B1', SharedDate::PHPToExcel(strtotime($now->format('Y-m-d H:i:s'))));
$sheet->getStyle('A1')->applyFromArray($titleStyle);
$sheet->getStyle('B1')->applyFromArray($dateStyle);
@@ -229,6 +253,7 @@ class Xlsx
foreach ($azRangeCopied as $i => $char1) {
foreach ($azRangeCopied as $j => $char2) {
$azRange[] = $char1 . $char2;
if ($i * count($azRangeCopied) + $j === count($fieldList)) {
break 2;
}
@@ -251,39 +276,45 @@ class Xlsx
}
$label = $name;
if (strpos($name, '_') !== false) {
list($linkName, $foreignField) = explode('_', $name);
$foreignScope = $this->metadata->get(['entityDefs', $entityType, 'links', $linkName, 'entity']);
if ($foreignScope) {
$label = $this->language->translate($linkName, 'links', $entityType) . '.' . $this->language->translate($foreignField, 'fields', $foreignScope);
}
} else {
}
else {
$label = $this->language->translate($name, 'fields', $entityType);
}
$sheet->setCellValue($col . $rowNumber, $label);
$sheet->getColumnDimension($col)->setAutoSize(true);
if (in_array($defs['type'], ['phone', 'email', 'url', 'link', 'linkParent'])) {
$linkColList[] = $col;
} else if ($name == 'name') {
}
else if ($name == 'name') {
$linkColList[] = $col;
}
$lastIndex = $i;
}
$col = $azRange[$i];
$headerStyle = array(
'font' => array(
$headerStyle = [
'font' => [
'bold' => true,
'size' => 12
)
);
'size' => 12,
]
];
$sheet->getStyle("A$rowNumber:$col$rowNumber")->applyFromArray($headerStyle);
$sheet->setAutoFilter("A$rowNumber:$col$rowNumber");
$typesCache = array();
$typesCache = [];
$rowNumber++;
@@ -297,18 +328,28 @@ class Xlsx
if ($dataFp) {
$line = fgets($dataFp);
if ($line === false) break;
if ($line === false) {
break;
}
$row = unserialize(base64_decode($line));
} else {
if ($lineIndex >= $lineCount) break;
}
else {
if ($lineIndex >= $lineCount) {
break;
}
$row = $dataList[$lineIndex];
}
$i = 0;
foreach ($fieldList as $i => $name) {
$col = $azRange[$i];
$defs = $this->metadata->get(['entityDefs', $entityType, 'fields', $name]);
if (!$defs) {
$defs = array();
$defs['type'] = 'base';
@@ -317,17 +358,22 @@ class Xlsx
$type = $defs['type'];
$foreignField = $name;
$linkName = null;
if (strpos($name, '_') !== false) {
list($linkName, $foreignField) = explode('_', $name);
$foreignScope = $this->metadata->get(['entityDefs', $entityType, 'links', $linkName, 'entity']);
if ($foreignScope) {
$type = $this->metadata->get(['entityDefs', $foreignScope, 'fields', $foreignField, 'type'], $type);
}
}
if ($type === 'foreign') {
$linkName = $this->metadata->get(['entityDefs', $entityType, 'fields', $name, 'link']);
$foreignField = $this->metadata->get(['entityDefs', $entityType, 'fields', $name, 'field']);
$foreignScope = $this->metadata->get(['entityDefs', $entityType, 'links', $linkName, 'entity']);
if ($foreignScope) {
$type = $this->metadata->get(['entityDefs', $foreignScope, 'fields', $foreignField, 'type'], $type);
}
@@ -335,40 +381,50 @@ class Xlsx
$typesCache[$name] = $type;
$link = null;
if ($type == 'link') {
if (array_key_exists($name.'Name', $row)) {
$sheet->setCellValue("$col$rowNumber", $row[$name.'Name']);
}
} else if ($type == 'linkParent') {
}
else if ($type == 'linkParent') {
if (array_key_exists($name.'Name', $row)) {
$sheet->setCellValue("$col$rowNumber", $row[$name.'Name']);
}
} else if ($type == 'int') {
}
else if ($type == 'int') {
$sheet->setCellValue("$col$rowNumber", $row[$name] ?: 0);
} else if ($type == 'float') {
}
else if ($type == 'float') {
$sheet->setCellValue("$col$rowNumber", $row[$name] ?: 0);
} else if ($type == 'currency') {
}
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], '');
$currency = $row[$name . 'Currency'] ?? $this->getConfig()->get('defaultCurrency');
$sheet->getStyle("$col$rowNumber")
->getNumberFormat()
->setFormatCode('[$'.$currencySymbol.'-409]#,##0.00;-[$'.$currencySymbol.'-409]#,##0.00');
->setFormatCode(
$this->getCurrencyFormatCode($currency)
);
}
} else if ($type == 'currencyConverted') {
}
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] : '');
}
} else if ($type == 'personName') {
}
else if ($type == 'personName') {
if (!empty($row['name'])) {
$sheet->setCellValue("$col$rowNumber", $row['name']);
} else {
@@ -384,41 +440,51 @@ class Xlsx
}
$sheet->setCellValue("$col$rowNumber", $personName);
}
} else if ($type == 'date') {
}
else if ($type == 'date') {
if (isset($row[$name])) {
$sheet->setCellValue("$col$rowNumber", \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel(strtotime($row[$name])));
$sheet->setCellValue("$col$rowNumber", SharedDate::PHPToExcel(strtotime($row[$name])));
}
} else if ($type == 'datetime' || $type == 'datetimeOptional') {
}
else if ($type == 'datetime' || $type == 'datetimeOptional') {
$value = null;
if ($type == 'datetimeOptional') {
if (isset($row[$name . 'Date']) && $row[$name . 'Date']) {
$value = $row[$name . 'Date'];
}
}
if (!$value) {
if (isset($row[$name])) {
$value = $row[$name];
}
}
if ($value && strlen($value) > 11) {
try {
$timeZone = $this->config->get('timeZone');
$dt = new \DateTime($value);
$dt->setTimezone(new \DateTimeZone($timeZone));
$dt = new DateTime($value);
$dt->setTimezone(new DateTimeZone($timeZone));
$value = $dt->format($this->dateTime->getInternalDateTimeFormat());
} catch (\Exception $e) {
}
catch (Exception $e) {
$value = '';
}
}
if ($value) {
$sheet->setCellValue("$col$rowNumber", \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel(strtotime($value)));
$sheet->setCellValue("$col$rowNumber", SharedDate::PHPToExcel(strtotime($value)));
}
} else if ($type == 'image') {
}
else if ($type == 'image') {
if (isset($row[$name . 'Id']) && $row[$name . 'Id']) {
$attachment = $this->getEntityManager()->getEntity('Attachment', $row[$name . 'Id']);
if ($attachment) {
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
$objDrawing = new Drawing();
$filePath = $this->fileStorageManager->getLocalFilePath($attachment);
if ($filePath && file_exists($filePath)) {
@@ -431,66 +497,87 @@ class Xlsx
}
}
} else if ($type == 'file') {
}
else if ($type == 'file') {
if (array_key_exists($name.'Name', $row)) {
$sheet->setCellValue("$col$rowNumber", $row[$name.'Name']);
}
} else if ($type == 'enum') {
}
else if ($type == 'enum') {
if (array_key_exists($name, $row)) {
if ($linkName) {
$value = $this->language->translateOption($row[$name], $foreignField, $foreignScope);
} else {
}
else {
$value = $this->language->translateOption($row[$name], $name, $entityType);
}
$sheet->setCellValue("$col$rowNumber", $value);
}
} else if ($type == 'linkMultiple' || $type == 'attachmentMultiple') {
}
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) {
$relatedName = $relatedId;
if (property_exists($row[$name . 'Names'], $relatedId)) {
$relatedName = $row[$name . 'Names']->$relatedId;
}
$nameList[] = $relatedName;
}
$sheet->setCellValue("$col$rowNumber", implode(', ', $nameList));
}
} else if ($type == 'address') {
}
else if ($type == 'address') {
$value = '';
if (!empty($row[$name . 'Street'])) {
$value = $value .= $row[$name.'Street'];
}
if (!empty($row[$name.'City']) || !empty($row[$name.'State']) || !empty($row[$name.'PostalCode'])) {
if ($value) {
$value .= "\n";
}
if (!empty($row[$name.'City'])) {
$value .= $row[$name.'City'];
if (
!empty($row[$name.'State']) || !empty($row[$name.'PostalCode'])
) {
$value .= ', ';
}
}
if (!empty($row[$name.'State'])) {
$value .= $row[$name.'State'];
if (!empty($row[$name.'PostalCode'])) {
$value .= ' ';
}
}
if (!empty($row[$name.'PostalCode'])) {
$value .= $row[$name.'PostalCode'];
}
}
if (!empty($row[$name.'Country'])) {
if ($value) {
$value .= "\n";
}
$value .= $row[$name.'Country'];
}
$sheet->setCellValue("$col$rowNumber", $value);
} else if ($type == 'duration') {
}
else if ($type == 'duration') {
if (!empty($row[$name])) {
$seconds = intval($row[$name]);
@@ -501,35 +588,58 @@ class Xlsx
$minutes = intval(floor($seconds / 60));
$value = '';
if ($days) {
$value .= (string) $days . $this->language->translate('d', 'durationUnits');
if ($minutes || $hours) {
$value .= ' ';
}
}
if ($hours) {
$value .= (string) $hours . $this->language->translate('h', 'durationUnits');
if ($minutes) {
$value .= ' ';
}
}
if ($minutes) {
$value .= (string) $minutes . $this->language->translate('m', 'durationUnits');
}
$sheet->setCellValue("$col$rowNumber", $value);
}
} else if ($type == 'multiEnum' || $type == 'array') {
}
else if ($type == 'multiEnum' || $type == 'array') {
if (!empty($row[$name])) {
$array = json_decode($row[$name]);
if (is_array($array)) {
$value = implode(', ', $array);
$sheet->setCellValue("$col$rowNumber", $value, \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
if (!is_array($array)) {
$array = [];
}
foreach ($array as $i => $item) {
if ($linkName) {
$itemValue = $this->language
->translateOption($item, $foreignField, $foreignScope);
}
else {
$itemValue = $this->language
->translateOption($item, $name, $entityType);
}
$array[$i] = $itemValue;
}
$value = implode(', ', $array);
$sheet->setCellValue("$col$rowNumber", $value, DataType::TYPE_STRING);
}
} else {
}
else {
if (array_key_exists($name, $row)) {
$sheet->setCellValueExplicit("$col$rowNumber", $row[$name], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
$sheet->setCellValueExplicit("$col$rowNumber", $row[$name], DataType::TYPE_STRING);
}
}
@@ -537,8 +647,10 @@ class Xlsx
$foreignLink = null;
$isForeign = false;
if (strpos($name, '_')) {
$isForeign = true;
list($foreignLink, $foreignField) = explode('_', $name);
}
@@ -546,59 +658,80 @@ class Xlsx
if (array_key_exists('id', $row)) {
$link = $this->getConfig()->getSiteUrl() . "/#".$entityType . "/view/" . $row['id'];
}
} else if ($type == 'url') {
}
else if ($type == 'url') {
if (array_key_exists($name, $row) && filter_var($row[$name], FILTER_VALIDATE_URL)) {
$link = $row[$name];
}
} else if ($type == 'link') {
}
else if ($type == 'link') {
if (array_key_exists($name.'Id', $row)) {
$foreignEntity = null;
if (!$isForeign) {
$foreignEntity = $this->getMetadata()->get(['entityDefs', $entityType, 'links', $name, 'entity']);
} else {
$foreignEntity1 = $this->getMetadata()->get(['entityDefs', $entityType, 'links', $foreignLink, 'entity']);
$foreignEntity = $this->getMetadata()->get(['entityDefs', $foreignEntity1, 'links', $foreignField, 'entity']);
$foreignEntity = $this->getMetadata()->get(
['entityDefs', $entityType, 'links', $name, 'entity']
);
}
else {
$foreignEntity1 = $this->getMetadata()->get(
['entityDefs', $entityType, 'links', $foreignLink, 'entity']
);
$foreignEntity = $this->getMetadata()->get(
['entityDefs', $foreignEntity1, 'links', $foreignField, 'entity']
);
}
if ($foreignEntity) {
$link = $this->getConfig()->getSiteUrl() . "/#" . $foreignEntity. "/view/". $row[$name.'Id'];
}
}
} else if ($type == 'file') {
}
else if ($type == 'file') {
if (array_key_exists($name.'Id', $row)) {
$link = $this->getConfig()->getSiteUrl() . "/?entryPoint=download&id=" . $row[$name.'Id'];
}
} else if ($type == 'linkParent') {
}
else if ($type == 'linkParent') {
if (array_key_exists($name.'Id', $row) && array_key_exists($name.'Type', $row)) {
$link = $this->getConfig()->getSiteUrl() . "/#".$row[$name.'Type']."/view/". $row[$name.'Id'];
}
} else if ($type == 'phone') {
}
else if ($type == 'phone') {
if (array_key_exists($name, $row)) {
$link = "tel:".$row[$name];
}
} else if ($type == 'email' && array_key_exists($name, $row)) {
}
else if ($type == 'email' && array_key_exists($name, $row)) {
if (array_key_exists($name, $row)) {
$link = "mailto:".$row[$name];
}
}
if ($link) {
$sheet->getCell("$col$rowNumber")->getHyperlink()->setUrl($link);
$sheet->getCell("$col$rowNumber")->getHyperlink()->setTooltip($link);
}
}
$rowNumber++;
}
$sheet->getStyle("A2:A$rowNumber")
->getNumberFormat()
->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_TEXT);
->setFormatCode(NumberFormat::FORMAT_TEXT);
$startingRowNumber = 4;
foreach ($fieldList as $i => $name) {
$col = $azRange[$i];
if (!array_key_exists($name, $typesCache)) {
break;
}
$type = $typesCache[$name];
switch ($type) {
@@ -606,31 +739,37 @@ class Xlsx
case 'currencyConverted': {
} break;
case 'int': {
$sheet->getStyle($col.$startingRowNumber.':'.$col.$rowNumber)
->getNumberFormat()
->setFormatCode('0');
} break;
case 'float': {
$sheet->getStyle($col.$startingRowNumber.':'.$col.$rowNumber)
->getNumberFormat()
->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1);
->setFormatCode(NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1);
} break;
case 'date': {
$sheet->getStyle($col.$startingRowNumber.':'.$col.$rowNumber)
->getNumberFormat()
->setFormatCode($this->dateTime->getDateFormat());
} break;
case 'datetime': {
$sheet->getStyle($col.$startingRowNumber.':'.$col.$rowNumber)
->getNumberFormat()
->setFormatCode($this->dateTime->getDateTimeFormat());
} break;
case 'datetimeOptional': {
$sheet->getStyle($col.$startingRowNumber.':'.$col.$rowNumber)
->getNumberFormat()
->setFormatCode($this->dateTime->getDateTimeFormat());
} break;
default: {
$sheet->getStyle($col.$startingRowNumber.':'.$col.$rowNumber)
->getNumberFormat()
@@ -642,25 +781,42 @@ class Xlsx
$linkStyle = [
'font' => [
'color' => ['rgb' => '345b7c'],
'underline' => 'single'
'underline' => 'single',
]
];
foreach ($linkColList as $linkColumn) {
$sheet->getStyle($linkColumn.$startingRowNumber.':'.$linkColumn.$rowNumber)->applyFromArray($linkStyle);
}
$objWriter = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($phpExcel, 'Xlsx');
$objWriter = IOFactory::createWriter($phpExcel, 'Xlsx');
if (!$this->fileManager->isDir('data/cache/')) {
$this->fileManager->mkdir('data/cache/');
}
$tempFileName = 'data/cache/' . 'export_' . substr(md5(rand()), 0, 7);
$objWriter->save($tempFileName);
$fp = fopen($tempFileName, 'r');
$xlsx = stream_get_contents($fp);
$this->fileManager->unlink($tempFileName);
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 = [];
@@ -201,6 +201,8 @@ class LeadCapture
$lead = $this->getLeadWithPopulatedData($leadCapture, $data);
}
$campaign = null;
$campaingService = $this->serviceFactory->create('Campaign');
if ($leadCapture->get('campaignId')) {
+242 -228
View File
File diff suppressed because it is too large Load Diff
@@ -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'},
]
]
},
@@ -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}}
+7 -1
View File
@@ -219,7 +219,13 @@ define('views/admin/layouts/index', 'view', function (Dep) {
},
navigate: function (scope, type) {
this.getRouter().navigate('#Admin/layouts/scope=' + scope + '&type=' + type, {trigger: false});
var url = '#Admin/layouts/scope=' + scope + '&type=' + type;
if (this.em) {
url += '&em=true';
}
this.getRouter().navigate(url, {trigger: false});
},
renderDefaultPage: function () {
-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({
+2
View File
@@ -257,6 +257,8 @@ define('views/fields/base', 'view', function (Dep) {
this.events = {};
}
this.validations = Espo.Utils.clone(this.validations);
this.defs = this.options.defs || {};
this.name = this.options.name || this.defs.name;
this.params = this.options.params || this.defs.params || {};
+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,
+2
View File
@@ -44,6 +44,8 @@ define('views/fields/enum', ['views/fields/base', 'lib!Selectize'], function (De
translatedOptions: null,
fetchEmptyValueAsNull: false,
searchTypeList: ['anyOf', 'noneOf', 'isEmpty', 'isNotEmpty'],
data: function () {
-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;
+8
View File
@@ -44,6 +44,8 @@ define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], function
seeMoreDisabled: true,
fetchEmptyValueAsNull: false,
setup: function () {
Dep.prototype.setup.call(this);
@@ -532,6 +534,12 @@ define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], function
data[this.name] = code;
} else {
data[this.name] = this.$element.val();
if (this.fetchEmptyValueAsNull) {
if (!data[this.name]) {
data[this.name] = null;
}
}
}
if (this.model.has('isHtml') && this.hasBodyPlainField) {
+33 -9
View File
@@ -58,9 +58,11 @@ define('views/list-with-categories', 'views/list', function (Dep) {
data: function () {
var data = {};
data.categoriesDisabled = this.categoriesDisabled;
data.isExpanded = this.isExpanded;
data.hasExpandedToggler = this.hasExpandedToggler;
return data;
},
@@ -101,7 +103,10 @@ define('views/list-with-categories', 'views/list', function (Dep) {
this.applyCategoryToCollection();
this.listenTo(this.collection, 'sync', function (c, d, o) {
if (o && o.openCategory) return;
if (o && o.openCategory) {
return;
}
this.controlListVisibility();
}, this);
},
@@ -120,6 +125,7 @@ define('views/list-with-categories', 'views/list', function (Dep) {
this.openCategory(params.categoryId, params.categoryName);
}
}
this.selectCurrentCategory();
}
},
@@ -129,13 +135,14 @@ define('views/list-with-categories', 'views/list', function (Dep) {
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;
},
@@ -161,9 +168,11 @@ define('views/list-with-categories', 'views/list', function (Dep) {
} else {
this.controlListVisibility();
}
if (!this.categoriesDisabled && !this.hasView('categories')) {
this.loadCategories();
}
if (!this.isExpanded && !this.hasView('nestedCategories')) {
this.loadNestedCategories();
}
@@ -171,6 +180,7 @@ define('views/list-with-categories', 'views/list', function (Dep) {
actionExpand: function () {
this.isExpanded = true;
this.setIsExpandedStoredValue(true);
this.applyCategoryToCollection();
@@ -307,12 +317,15 @@ define('views/list-with-categories', 'views/list', function (Dep) {
this.listenToOnce(collection, 'sync', function () {
callback.call(this, collection);
}, this);
collection.fetch();
}, this);
},
applyCategoryToNestedCategoriesCollection: function () {
if (!this.nestedCategoriesCollection) return;
if (!this.nestedCategoriesCollection) {
return;
}
this.nestedCategoriesCollection.where = null;
@@ -335,6 +348,7 @@ define('views/list-with-categories', 'views/list', function (Dep) {
collection.maxDepth = 1;
collection.data.checkIfEmpty = true;
if (!this.getAcl().checkScope(this.scope, 'create')) {
collection.data.onlyNotEmpty = true;
}
@@ -344,6 +358,7 @@ define('views/list-with-categories', 'views/list', function (Dep) {
collection.fetch().then(function () {
this.controlListVisibility();
this.controlNestedCategoriesVisibility();
callback.call(this, collection);
}.bind(this));
}, this);
@@ -353,7 +368,7 @@ define('views/list-with-categories', 'views/list', function (Dep) {
this.getNestedCategoriesCollection(function (collection) {
this.createView('nestedCategories', 'views/record/list-nested-categories', {
collection: collection,
el: this.options.el + ' .nested-categories-container'
el: this.options.el + ' .nested-categories-container',
}, function (view) {
view.render();
});
@@ -378,18 +393,22 @@ define('views/list-with-categories', 'views/list', function (Dep) {
if (this.currentCategoryId) {
view.setSelected(this.currentCategoryId);
}
view.render();
this.listenTo(view, 'select', function (model) {
if (!this.isExpanded) {
var id = null;
var name = null;
if (model && model.id) {
id = model.id;
name = model.get('name');
}
this.openCategory(id, name);
this.navigateToCurrentCategory();
return;
}
this.currentCategoryId = null;
@@ -409,6 +428,7 @@ define('views/list-with-categories', 'views/list', function (Dep) {
this.listenToOnce(this.collection, 'sync', function () {
this.notify(false);
}, this);
this.collection.fetch();
}, this);
@@ -417,9 +437,7 @@ define('views/list-with-categories', 'views/list', function (Dep) {
}, this);
},
applyCategoryToCollection: function () {
this.collection.whereFunction = function () {
var filter;
var isExpanded = this.isExpanded;
@@ -457,14 +475,14 @@ define('views/list-with-categories', 'views/list', function (Dep) {
filter = {
field: this.categoryField,
type: this.categoryFilterType,
value: this.currentCategoryId
value: this.currentCategoryId,
};
}
}
if (filter) {
return [filter];
}
}.bind(this);
},
@@ -479,19 +497,26 @@ define('views/list-with-categories', 'views/list', function (Dep) {
if (this.currentCategoryId) {
var names = {};
names[this.currentCategoryId] = this.currentCategoryName;
var data = {};
var idsAttribute = this.categoryField + 'Ids';
var namesAttribute = this.categoryField + 'Names';
data[idsAttribute] = [this.currentCategoryId],
data[namesAttribute] = names;
return data;
}
} else {
var idAttribute = this.categoryField + 'Id';
var nameAttribute = this.categoryField + 'Name';
var data = {};
data[idAttribute] = this.currentCategoryId;
data[nameAttribute] = this.currentCategoryName;
return data;
}
},
@@ -500,6 +525,5 @@ define('views/list-with-categories', 'views/list', function (Dep) {
this.clearView('categories');
this.getRouter().navigate('#' + this.categoryScope, {trigger: true});
},
});
});
+8 -1
View File
@@ -62,8 +62,12 @@ define('views/modals/compose-email', 'views/modals/edit', function (Dep) {
var attributes = this.options.attributes || {};
require('email-helper', function (EmailHelper) {
this.getRouter().confirmLeaveOut = false;
var emailHelper = new EmailHelper();
var link = emailHelper.composeMailToLink(attributes, this.getConfig().get('outboundEmailBccAddress'));
document.location.href = link;
}.bind(this));
@@ -80,7 +84,9 @@ define('views/modals/compose-email', 'views/modals/edit', function (Dep) {
},
createRecordView: function (model, callback) {
var viewName = this.getMetadata().get('clientDefs.' + model.name + '.recordViews.compose') || 'views/email/record/compose';
var viewName = this.getMetadata().get('clientDefs.' + model.name + '.recordViews.compose') ||
'views/email/record/compose';
var options = {
model: model,
el: this.containerSelector + ' .edit-container',
@@ -93,6 +99,7 @@ define('views/modals/compose-email', 'views/modals/edit', function (Dep) {
appendSignature: this.options.appendSignature,
exit: function () {}
};
this.createView('edit', viewName, options, callback);
},
@@ -30,6 +30,8 @@ define('views/preferences/fields/signature', 'views/fields/wysiwyg', function (D
return Dep.extend({
fetchEmptyValueAsNull: true,
toolbar: [
["style", ["bold", "italic", "underline", "clear"]],
["color", ["color"]],
+1 -1
View File
@@ -998,7 +998,7 @@ define('views/record/list', 'view', function (Dep) {
}, this);
if (
this.getConfig().get('exportDisabled') && !this.getUser().get('isAdmin')
this.getConfig().get('exportDisabled') && !this.getUser().isAdmin()
||
this.getAcl().get('exportPermission') === 'no'
||
@@ -25,14 +25,40 @@
* 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.
************************************************************************/
Espo.define('views/settings/fields/default-currency', 'views/fields/enum', function (Dep) {
define('views/settings/fields/default-currency', 'views/fields/enum', function (Dep) {
return Dep.extend({
setup: function () {
Dep.prototype.setup.call(this);
this.validations.push('existing');
},
setupOptions: function () {
this.params.options = Espo.Utils.clone(this.getConfig().get('currencyList') || []);
}
},
validateExisting: function () {
var currencyList = this.model.get('currencyList');
if (!currencyList) {
return;
}
var value = this.model.get(this.name);
if (~currencyList.indexOf(value)) {
return;
}
var msg = this.translate('fieldInvalid', 'messages').replace('{field}', this.getLabelText());
this.showValidationMessage(msg);
return true;
},
});
});
+25 -2
View File
@@ -126,7 +126,9 @@ define('views/user/record/detail', 'views/record/detail', function (Dep) {
},
setupNonAdminFieldsAccess: function () {
if (this.getUser().isAdmin()) return;
if (this.getUser().isAdmin()) {
return;
}
var nonAdminReadOnlyFieldList = [
'userName',
@@ -138,9 +140,30 @@ define('views/user/record/detail', 'views/record/detail', function (Dep) {
'portalRoles',
'contact',
'accounts',
'type'
'type',
'emailAddress',
];
nonAdminReadOnlyFieldList = nonAdminReadOnlyFieldList.filter(
function (item) {
if (!this.model.hasField(item)) {
return true;
}
var aclDefs = this.getMetadata().get(['entityAcl', 'User', 'fields', item]);
if (!aclDefs) {
return true;
}
if (aclDefs.nonAdminReadOnly) {
return true;
}
return false;
}.bind(this),
);
nonAdminReadOnlyFieldList.forEach(function (field) {
this.setFieldReadOnly(field, true);
}, this);
+16 -4
View File
@@ -82,12 +82,24 @@ class SystemHelper extends \Espo\Core\Utils\System
public function getBaseUrl()
{
$pageUrl = ($_SERVER["HTTPS"] == 'on') ? 'https://' : 'http://';
$pageUrl = 'http://';
if ($_SERVER["SERVER_PORT"] != "80") {
$pageUrl .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
} else {
if (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] == 'https') {
$pageUrl = 'https://';
}
if (isset($_SERVER['HTTPS']) && $_SERVER["HTTPS"] == 'on') {
$pageUrl = 'https://';
}
if ($_SERVER["SERVER_PORT"] == '443') {
$pageUrl = 'https://';
}
if (in_array($_SERVER["SERVER_PORT"], ['80', '443'])) {
$pageUrl .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
} else {
$pageUrl .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
}
$baseUrl = str_ireplace('/install/index.php', '', $pageUrl);
+9 -9
View File
@@ -13,10 +13,10 @@
{foreach from=$phpRequirementList key=name item=value}
<tr class="list-row">
<td class="cell col-md-5">
{if $langs['systemRequirements'][$name] eq ''}
{$name}
{if isset($langs['systemRequirements'][$name])}
{$langs['systemRequirements'][{$name}]}
{else}
{$langs['systemRequirements'][{$name}]}
{$name}
{/if}
</td>
<td class="cell col-md-3">{$value['actual']}</td>
@@ -41,10 +41,10 @@
{foreach from=$mysqlRequirementList key=name item=value}
<tr class="list-row">
<td class="cell col-md-5">
{if $langs['systemRequirements'][$name] eq ''}
{$name}
{if isset($langs['systemRequirements'][$name])}
{$langs['systemRequirements'][{$name}]}
{else}
{$langs['systemRequirements'][{$name}]}
{$name}
{/if}
</td>
<td class="cell col-md-3">{$value['actual']}</td>
@@ -68,10 +68,10 @@
{foreach from=$permissionRequirementList key=name item=value}
<tr class="list-row">
<td class="cell col-md-5">
{if $langs['systemRequirements'][$name] eq ''}
{$name}
{if isset($langs['systemRequirements'][$name])}
{$langs['systemRequirements'][{$name}]}
{else}
{$langs['systemRequirements'][{$name}]}
{$name}
{/if}
</td>
<td class="cell col-md-3">{$langs['systemRequirements'][{$value['type']}]}</td>
@@ -72,30 +72,31 @@ abstract class Smarty_Internal_CompileBase
}
// named attribute
} else {
$kv = each($mixed);
// option flag?
if (in_array($kv['key'], $this->option_flags)) {
if (is_bool($kv['value'])) {
$_indexed_attr[$kv['key']] = $kv['value'];
} elseif (is_string($kv['value']) && in_array(trim($kv['value'], '\'"'), array('true', 'false'))) {
if (trim($kv['value']) == 'true') {
$_indexed_attr[$kv['key']] = true;
foreach ($mixed as $k => $v) {
// option flag?
if (in_array($k, $this->option_flags)) {
if (is_bool($v)) {
$_indexed_attr[$k] = $v;
} elseif (is_string($v) && in_array(trim($v, '\'"'), array('true', 'false'))) {
if (trim($v) == 'true') {
$_indexed_attr[$k] = true;
} else {
$_indexed_attr[$k] = false;
}
} elseif (is_numeric($v) && in_array($v, array(0, 1))) {
if ($v == 1) {
$_indexed_attr[$k] = true;
} else {
$_indexed_attr[$k] = false;
}
} else {
$_indexed_attr[$kv['key']] = false;
}
} elseif (is_numeric($kv['value']) && in_array($kv['value'], array(0, 1))) {
if ($kv['value'] == 1) {
$_indexed_attr[$kv['key']] = true;
} else {
$_indexed_attr[$kv['key']] = false;
$compiler->trigger_template_error("illegal value of option flag \"{$k}\"", $compiler->lex->taglineno);
}
// must be named attribute
} else {
$compiler->trigger_template_error("illegal value of option flag \"{$kv['key']}\"", $compiler->lex->taglineno);
reset($mixed);
$_indexed_attr[key($mixed)] = $mixed[key($mixed)];
}
// must be named attribute
} else {
reset($mixed);
$_indexed_attr[key($mixed)] = $mixed[key($mixed)];
}
}
}
+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;
}
}
}
}
+1484 -764
View File
File diff suppressed because it is too large Load Diff
+32 -32
View File
@@ -1,32 +1,32 @@
{
"name": "espocrm",
"version": "6.0.0",
"description": "",
"main": "index.php",
"repository": {
"type": "git",
"url": "git://github.com/espocrm/espocrm.git"
},
"author": "",
"license": "GPL-3.0",
"devDependencies": {
"archiver": "^3.1.1",
"fstream": ">=1.0.12",
"grunt": "^1.0.4",
"grunt-chmod": "~1.1.0",
"grunt-contrib-clean": "~2.0.0",
"grunt-contrib-concat": "~1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-cssmin": "~3.0.0",
"grunt-contrib-less": "~2.0.0",
"grunt-contrib-uglify": "~4.0.0",
"grunt-mkdir": "~1.0.0",
"grunt-replace": "~1.0.1",
"js-yaml": "^3.13.1",
"lodash": "^4.17.19",
"minimist": ">=1.2.2",
"pofile": "~1.0.11",
"tar": "^4.4.8"
},
"dependencies": {}
}
{
"name": "espocrm",
"version": "6.0.10",
"description": "",
"main": "index.php",
"repository": {
"type": "git",
"url": "git://github.com/espocrm/espocrm.git"
},
"author": "",
"license": "GPL-3.0",
"devDependencies": {
"archiver": "^3.1.1",
"fstream": ">=1.0.12",
"grunt": "^1.3.0",
"grunt-chmod": "~1.1.0",
"grunt-contrib-clean": "~2.0.0",
"grunt-contrib-concat": "~1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-cssmin": "~3.0.0",
"grunt-contrib-less": "^2.0.0",
"grunt-contrib-uglify": "~4.0.0",
"grunt-mkdir": "~1.0.0",
"grunt-replace": "~1.0.1",
"js-yaml": "^3.13.1",
"lodash": "^4.17.20",
"minimist": ">=1.2.2",
"pofile": "~1.0.11",
"tar": "^6.0.5"
},
"dependencies": {}
}
+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;
}
@@ -27,7 +27,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\integration\Espo\Extensions;
namespace tests\integration\Espo\Extension;
class GeneralTest extends \tests\integration\Core\BaseTestCase
{
Binary file not shown.
Binary file not shown.
@@ -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);
}
}
+129
View File
@@ -0,0 +1,129 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 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 tests\unit\Espo\Core\Utils;
use Espo\Core\{
Utils\Autoload,
Utils\Config,
Utils\Metadata,
Utils\Autoload\Loader,
Utils\DataCache,
Utils\File\Manager as FileManager,
};
class AutoloadTest extends \PHPUnit\Framework\TestCase
{
protected function setUp() : void
{
$this->config = $this->createMock(Config::class);
$this->metadata = $this->createMock(Metadata::class);
$this->dataCache = $this->createMock(DataCache::class);
$this->fileManager = $this->createMock(FileManager::class);
$this->loader = $this->createMock(Loader::class);
$this->autoload = new Autoload(
$this->config,
$this->metadata,
$this->dataCache,
$this->fileManager,
$this->loader
);
}
public function testMerge()
{
$this->metadata
->expects($this->once())
->method('getModuleList')
->willReturn(['M1', 'M2']);
$this->config
->expects($this->once())
->method('get')
->with('useCache')
->willReturn(false);
$this->fileManager
->expects($this->any())
->method('isFile')
->will(
$this->returnValueMap(
[
['application/Espo/Resources/autoload.json', false],
['application/Espo/Modules/M1/Resources/autoload.json', true],
['application/Espo/Modules/M2/Resources/autoload.json', true],
['custom/Espo/Custom/Resources/autoload.json', false],
]
)
);
$data1 = [
'autoloadFileList' => ['f1.php'],
'psr-4' => [
't1' => 'r1',
],
];
$data2 = [
'autoloadFileList' => ['f2.php'],
'psr-4' => [
't2' => 'r2',
],
];
$expectedData = [
'autoloadFileList' => ['f1.php', 'f2.php'],
'psr-4' => [
't1' => 'r1',
't2' => 'r2',
],
];
$this->fileManager
->expects($this->any())
->method('getContents')
->will(
$this->returnValueMap(
[
['application/Espo/Modules/M1/Resources/autoload.json', json_encode($data1)],
['application/Espo/Modules/M2/Resources/autoload.json', json_encode($data2)],
]
)
);
$this->loader
->expects($this->once())
->method('register')
->with($expectedData);
$this->autoload->register();
}
}
+4 -4
View File
@@ -86,12 +86,12 @@ class SystemTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($rootDir, $this->object->getRootDir());
}
public function testGetPhpBin()
public function testGetPhpBinary()
{
$phpBin = @exec('which php');
$phpBinary = (new \Symfony\Component\Process\PhpExecutableFinder)->find();
if (isset($phpBin)) {
$this->assertEquals($phpBin, $this->object->getPhpBin());
if (isset($phpBinary)) {
$this->assertEquals($phpBinary, $this->object->getPhpBinary());
}
}