Compare commits

..

160 Commits

Author SHA1 Message Date
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
Yuri Kuznetsov e73bff5ddf v 2020-10-22 12:14:40 +03:00
Yuri Kuznetsov 7d5c0d754e fix upgrade script 2020-10-22 11:47:59 +03:00
Taras Machyshyn e08a74b129 Bug fixes 2020-10-22 11:30:00 +03:00
Taras Machyshyn be07aafaf4 Fix 2020-10-21 15:33:42 +03:00
Taras Machyshyn e2bf27e524 Fix 2020-10-21 12:00:47 +03:00
Yuri Kuznetsov b0157adbe2 cs fix 2020-10-21 09:40:34 +03:00
Yuri Kuznetsov 239593afb5 fix mass actions 2020-10-21 09:31:42 +03:00
Yuri Kuznetsov b97d764588 fix error msg 2020-10-19 15:30:36 +03:00
Yuri Kuznetsov bf8e7af984 export fixes 2020-10-19 15:19:24 +03:00
Yuri Kuznetsov bf68c475e5 email queue items filters email 2020-10-19 13:37:06 +03:00
Yuri Kuznetsov 29624239ba search queue items 2020-10-19 13:35:43 +03:00
Yuri Kuznetsov 4cb3c4918b campaign log search by email address 2020-10-19 12:59:36 +03:00
Yuri Kuznetsov 26fa0d65e1 fix campaign log search 2020-10-19 12:57:17 +03:00
Yuri Kuznetsov 958baed3df fix email invalid on bounced 2020-10-19 12:35:48 +03:00
Yuri Kuznetsov 40d6fb7565 fix export 2020-10-15 19:40:37 +03:00
Yuri Kuznetsov a7551b7dbf fix request getContentType 2020-10-15 17:35:58 +03:00
Yuri Kuznetsov 9ed33c4dc4 remove self assign in portal 2020-10-14 16:23:02 +03:00
Yuri Kuznetsov 6b18fbe8f7 select manager fix 2020-10-14 15:48:52 +03:00
Yuri Kuznetsov 53b9880ea8 entry point changes 2020-10-14 15:15:09 +03:00
Yuri Kuznetsov 62538664df Merge branch 'master' of github.com:espocrm/espocrm 2020-10-14 13:17:56 +03:00
Yuri Kuznetsov f678fb16cc ouauth callback changes 2020-10-14 13:17:46 +03:00
Yuri Kuznetsov fe09aa1cd9 cs fix 2020-10-13 11:01:52 +03:00
Yuri Kuznetsov 710decc610 fix htmlizer and email template no id 2020-10-13 10:47:37 +03:00
Yuri Kuznetsov 5d481d3528 email auto reply error log 2020-10-13 10:47:27 +03:00
Yuri Kuznetsov 8bcd3b72c5 hook fix 2020-10-13 09:56:13 +03:00
Yuri Kuznetsov 0a0da810e9 application state changes 2020-10-13 09:56:04 +03:00
Yuri Kuznetsov e630c046fe Merge branch 'hotfix/5.9.5' 2020-10-12 14:01:49 +03:00
Yuri Kuznetsov 2857b5c53d fix import button 2020-10-12 14:00:25 +03:00
Yuri Kuznetsov 0965c61f55 fix reminder submition 2020-10-12 10:14:17 +03:00
Yuri Kuznetsov 9dd318b176 fix label 2020-10-10 16:08:58 +03:00
Yuri Kuznetsov f8beca720c label fixes 2020-10-10 16:06:57 +03:00
Yuri Kuznetsov d8f58dcc1d upgrade cleanup 2020-10-09 14:58:34 +03:00
Yuri Kuznetsov 32aaaa350e v 2020-10-09 14:37:50 +03:00
Yuri Kuznetsov 0f6d1c6817 fix diff: ignore upgrades 2020-10-09 13:56:41 +03:00
Yuri Kuznetsov 8c5a342a5a fix diff 2020-10-09 13:53:07 +03:00
Yuri Kuznetsov 257401898b upgrade cleanup files and fix msg 2020-10-09 12:24:24 +03:00
Yuri Kuznetsov e7ff3f903a fix email modal 2020-10-08 14:05:28 +03:00
Taras Machyshyn 6f82638b5d Upgrade script 2020-10-07 17:46:08 +03:00
Taras Machyshyn 0625ce51c4 Upgrade script 2020-10-07 17:26:11 +03:00
Taras Machyshyn f5670436d0 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-10-07 17:19:05 +03:00
Taras Machyshyn 5d20a56ac8 Upgrade script 2020-10-07 17:18:34 +03:00
Yuri Kuznetsov 020cf9713a Merge branch 'master' of https://github.com/espocrm/espocrm 2020-10-07 16:49:33 +03:00
Yuri Kuznetsov fe1361fc9c portal action list restriction 2020-10-07 16:49:01 +03:00
Taras Machyshyn 36621be496 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-10-07 15:51:31 +03:00
Taras Machyshyn 25690030d1 Installation fixes 2020-10-07 15:50:57 +03:00
Yuri Kuznetsov fcd6be3a0c fix password recovery 2020-10-07 15:39:03 +03:00
Yuri Kuznetsov ad8dfa487c fix convert lead 2020-10-07 15:13:39 +03:00
Yuri Kuznetsov 7f6f54628d cs fix 2020-10-07 15:04:42 +03:00
Taras Machyshyn 8ea0b072c6 Installation fixes 2020-10-07 14:22:21 +03:00
Yuri Kuznetsov 0df6248226 getPhpSafeContents for unix 2020-10-07 13:12:55 +03:00
Yuri Kuznetsov 26a132658e date after before validation autocomplete 2020-10-07 12:42:51 +03:00
Yuri Kuznetsov d02529ee86 Merge branch 'hotfix/5.9.5' 2020-10-07 11:02:01 +03:00
Yuri Kuznetsov 0cec0fef51 fix image orientation 2020-10-07 11:01:32 +03:00
Yuri Kuznetsov e56f6ab75d fix image orientation 2020-10-07 10:55:39 +03:00
Yuri Kuznetsov a4980aa8f2 cs fix 2020-10-07 10:53:21 +03:00
Yuri Kuznetsov fd219b49be calendar modal fix 2020-10-06 13:44:35 +03:00
Yuri Kuznetsov b90579aadc cs fix 2020-10-06 13:44:18 +03:00
Yuri Kuznetsov 0b39d7f6b6 cs fix 2020-10-06 12:13:17 +03:00
Yuri Kuznetsov a44ce06162 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-10-06 12:02:35 +03:00
Yuri Kuznetsov f1eb2ab5d5 cs fix 2020-10-06 12:00:48 +03:00
Yuri Kuznetsov 9a965bfba4 v 2020-10-05 15:27:48 +03:00
Yuri Kuznetsov 7349f37786 fix admin panel 2020-10-05 12:21:11 +03:00
Yuri Kuznetsov 3d03dfaef7 css fix 2020-10-03 19:01:19 +03:00
Yuri Kuznetsov 5bf88eaff1 categories tree improvements 2020-10-03 18:59:56 +03:00
Yuri Kuznetsov f3c4b2ba39 image preview improvements 2020-10-03 17:24:05 +03:00
Yuri Kuznetsov 92f6fb4588 settings tooltips 2020-10-03 12:27:54 +03:00
Yuri Kuznetsov 2d6d7fd618 list tree css fix 2020-10-02 14:57:26 +03:00
Yuri Kuznetsov 3a1d997d5a orm cleanup 2020-10-01 20:18:35 +03:00
Yuri Kuznetsov 5bbb259c95 orm fix order list 2020-10-01 20:16:32 +03:00
Yuri Kuznetsov b8e44767b3 upgrade real estate compatibility check 2020-10-01 16:27:46 +03:00
Yuri Kuznetsov 6b486beb5c fix autoload 2020-10-01 14:25:12 +03:00
Yuri Kuznetsov aab1265e4e data cache get safe contents 2020-10-01 14:08:03 +03:00
Yuri Kuznetsov 676181a4be fix ldap 2020-10-01 13:32:06 +03:00
Yuri Kuznetsov 839e5b7c74 data cache support onlt array and stdclass 2020-10-01 12:51:25 +03:00
Yuri Kuznetsov aff7003ceb cs fix 2020-10-01 12:01:51 +03:00
Yuri Kuznetsov 66aa60f303 file rename in loop for windows 2020-10-01 11:44:01 +03:00
Yuri Kuznetsov b196811cf9 fix notice 2020-10-01 10:24:36 +03:00
Yuri Kuznetsov 3fdddadd53 cs fix 2020-09-30 17:42:07 +03:00
142 changed files with 4098 additions and 1804 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);
}
+8 -2
View File
@@ -138,7 +138,13 @@ class RequestWrapper implements ApiRequest
public function getContentType() : ?string
{
return $this->getHeader('Content-Type');
if (!$this->hasHeader('Content-Type')) {
return null;
}
$contentType = $this->request->getHeader('Content-Type')[0];
return strtolower($contentType);
}
public function getBodyContents() : ?string
@@ -163,7 +169,7 @@ class RequestWrapper implements ApiRequest
{
$contents = $this->getBodyContents();
if (strtolower($this->getContentType()) === 'application/json' && $contents) {
if ($this->getContentType() === 'application/json' && $contents) {
$this->parsedBody = json_decode($contents);
if (is_array($this->parsedBody)) {
@@ -39,6 +39,7 @@ use Espo\Core\{
Api\ResponseWrapper,
Api\RouteProcessor,
Utils\Route,
Utils\Log,
};
use Slim\{
@@ -63,12 +64,15 @@ class Api implements ApplicationRunner
protected $injectableFactory;
protected $applicationUser;
protected $routeUtil;
protected $log;
public function __construct(InjectableFactory $injectableFactory, ApplicationUser $applicationUser, Route $routeUtil)
{
public function __construct(
InjectableFactory $injectableFactory, ApplicationUser $applicationUser, Route $routeUtil, Log $log
) {
$this->injectableFactory = $injectableFactory;
$this->applicationUser = $applicationUser;
$this->routeUtil = $routeUtil;
$this->log = $log;
}
public function run()
@@ -85,7 +89,7 @@ class Api implements ApplicationRunner
$this->addRoute($slim, $item);
}
$slim->addErrorMiddleware(false, true, true);
$slim->addErrorMiddleware(false, true, true, $this->log);
$slim->run();
}
@@ -29,9 +29,8 @@
namespace Espo\Core\ApplicationRunners;
use Espo\Core\Exceptions\Error;
use Espo\Core\{
Exceptions\Error,
InjectableFactory,
EntryPointManager,
ApplicationUser,
@@ -39,7 +38,6 @@ use Espo\Core\{
Portal\Application as PortalApplication,
Utils\Route,
Utils\ClientManager,
Authentication\Authentication,
Api\Auth as ApiAuth,
Api\ErrorOutput as ApiErrorOutput,
@@ -48,18 +46,12 @@ use Espo\Core\{
};
use Slim\{
App as SlimApp,
Factory\AppFactory as SlimAppFactory,
};
use Psr\Http\{
Message\ResponseInterface as Psr7Response,
Message\ServerRequestInterface as Psr7Request,
Server\RequestHandlerInterface as Psr7RequestHandler,
ResponseEmitter,
Factory\ServerRequestCreatorFactory,
Psr7\Response,
};
use StdClass;
use Exception;
/**
@@ -113,30 +105,19 @@ class EntryPoint implements ApplicationRunner
}
}
$slim = SlimAppFactory::create();
$request = (ServerRequestCreatorFactory::create())->createServerRequestFromGlobals();
$slim->setBasePath(Route::detectBasePath());
if ($request->getMethod() !== 'GET') {
throw new Error("Only GET request allowed for entry points.");
}
$slim->add(
function (Psr7Request $request, Psr7RequestHandler $handler) use (
$entryPoint, $data, $authRequired, $authNotStrict, $slim
) : Psr7Response {
$requestWrapped = new RequestWrapper($request, $slim->getBasePath());
$responseWrapped = new ResponseWrapper($handler->handle($request));
$requestWrapped = new RequestWrapper($request, Route::detectBasePath());
$this->processRequest($entryPoint, $requestWrapped, $responseWrapped, $data, $authRequired, $authNotStrict);
$responseWrapped = new ResponseWrapper(new Response());
return $responseWrapped->getResponse();
}
);
$this->processRequest($entryPoint, $requestWrapped, $responseWrapped, $data, $authRequired, $authNotStrict);
$route = Route::detectEntryPointRoute();
$slim->get($route, function (Psr7Request $request, Psr7Response $response) : Psr7Response {
return $response;
});
$slim->run();
(new ResponseEmitter())->emit($responseWrapped->getResponse());
}
protected function processRequest(
+36 -9
View File
@@ -55,11 +55,13 @@ class ApplicationState
}
/**
* Get portal ID (if an applicaition is portal).
* Get a portal ID (if an applicaition is portal).
*/
public function getPortalId() : string
{
if (!$this->isPortal()) throw new Error("Can't get portal ID for non-portal application.");
if (!$this->isPortal()) {
throw new Error("Can't get portal ID for non-portal application.");
}
return $this->getPortal()->id;
}
@@ -69,7 +71,9 @@ class ApplicationState
*/
public function getPortal() : PortalEntity
{
if (!$this->isPortal()) throw new Error("Can't get portal for non-portal application.");
if (!$this->isPortal()) {
throw new Error("Can't get portal for non-portal application.");
}
return $this->container->get('portal');
}
@@ -83,21 +87,38 @@ class ApplicationState
}
/**
* Get current logged user. If no auth is applied, then system user will be returned.
* Get a current logged user. If no auth is applied, then the system user will be returned.
*/
public function getUser() : UserEntity
{
if (!$this->hasUser()) throw new Error("User is not yet available.");
if (!$this->hasUser()) {
throw new Error("User is not yet available.");
}
return $this->container->get('user');
}
/**
* Get an ID of a current logged user. If no auth is applied, then the system user will be returned.
*/
public function getUserId() : string
{
return $this->getUser()->id;
}
/**
* Whether a user is logged.
*/
public function isLogged() : bool
{
if (!$this->container->has('user')) return false;
if ($this->getUser()->isSystem()) return false;
if (!$this->container->has('user')) {
return false;
}
if ($this->getUser()->isSystem()) {
return false;
}
return true;
}
@@ -106,7 +127,10 @@ class ApplicationState
*/
public function isAdmin() : bool
{
if (!$this->isLogged()) return false;
if (!$this->isLogged()) {
return false;
}
return $this->getUser()->isAdmin();
}
@@ -116,7 +140,10 @@ class ApplicationState
*/
public function isApi() : bool
{
if (!$this->isLogged()) return false;
if (!$this->isLogged()) {
return false;
}
return $this->getUser()->isApi();
}
}
@@ -368,7 +368,7 @@ class Authentication
if ($code) {
if (!$impl->verifyCode($loggedUser, $code)) {
Result::fail('Code not verified');
return Result::fail('Code not verified');
}
return $result;
@@ -68,14 +68,14 @@ class LDAP extends Espo
Config $config,
EntityManager $entityManager,
PasswordHash $passwordHash,
Language $language,
Language $defaultLanguage,
ApplicationUser $applicationUser,
bool $isPortal = false
) {
$this->config = $config;
$this->entityManager = $entityManager;
$this->passwordHash = $passwordHash;
$this->language = $language;
$this->language = $defaultLanguage;
$this->applicationUser = $applicationUser;
$this->isPortal = $isPortal;
@@ -103,7 +103,7 @@ class CommandManager
$skipIndex = 1;
if (isset($argv[0]) && $argv[0] === 'command.php') {
if (isset($argv[0]) && preg_match('/command\.php$/', $argv[0])) {
$skipIndex = 2;
}
@@ -48,37 +48,57 @@ class AclCheck implements Command
$id = $options['id'] ?? null;
$action = $options['action'] ?? null;
if (empty($userId)) return null;
if (empty($scope)) return null;
if (empty($id)) return null;
if (empty($userId)) {
return null;
}
if (empty($scope)) {
return null;
}
if (empty($id)) {
return null;
}
$container = $this->container;
$entityManager = $container->get('entityManager');
$user = $entityManager->getEntity('User', $userId);
if (!$user) return null;
if (!$user) {
return null;
}
if ($user->isPortal()) {
$portalIdList = $user->getLinkMultipleIdList('portals');
foreach ($portalIdList as $portalId) {
$application = new PortalApplication($portalId);
$containerPortal = $application->getContainer();
$entityManager = $containerPortal->get('entityManager');
$user = $entityManager->getEntity('User', $userId);
if (!$user) return null;
if (!$user) {
return null;
}
$result = $this->check($user, $scope, $id, $action, $containerPortal);
if ($result) {
return 'true';
}
}
return null;
}
if ($this->check($user, $scope, $id, $action, $container)) {
return 'true';
}
return null;
}
protected function check($user, $scope, $id, $action, $container)
@@ -86,7 +106,10 @@ class AclCheck implements Command
$entityManager = $container->get('entityManager');
$entity = $entityManager->getEntity($scope, $id);
if (!$entity) return false;
if (!$entity) {
return false;
}
$aclManager = $container->get('aclManager');
@@ -78,7 +78,7 @@ class Upgrade implements Command
$versionInfo = $this->getVersionInfo($toVersion);
$nextVersion = $versionInfo->nextVersion ?? null;
$lastVersion = $infoData->lastVersion ?? null;
$lastVersion = $versionInfo->lastVersion ?? null;
$packageFile = $this->getPackageFile($params, $versionInfo);
@@ -51,6 +51,8 @@ class ContainerConfiguration
public function getLoaderClassName(string $name) : ?string
{
$className = null;
try {
$className = $this->metadata->get(['app', 'containerServices', $name, 'loaderClassName']);
@@ -38,6 +38,6 @@ class NotType extends BaseFunction
{
public function process(ArgumentList $args)
{
return !$this->evaluate($args);
return !$this->evaluate($args[0]);
}
}
+2 -1
View File
@@ -40,7 +40,8 @@ class Parser
{
protected $priorityList = [
['='],
['||', '&&'],
['||'],
['&&'],
['==', '!=', '>', '<', '>=', '<='],
['+', '-'],
['*', '/', '%'],
+1 -1
View File
@@ -120,7 +120,7 @@ class Htmlizer
$relationList = $entity->getRelationList();
if (!$skipLinks && $level === 0 && $this->entityManager) {
if (!$skipLinks && $level === 0 && $this->entityManager && $entity->id) {
foreach ($relationList as $relation) {
$collection = null;
+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;
}
}
+1 -1
View File
@@ -274,7 +274,7 @@ class Recovery
'isSystem' => true,
]);
if (!$this->emailAddress->hasSystemSmtp()) {
if (!$this->emailSender->hasSystemSmtp()) {
$sender->withSmtpParams([
'server' => $config->get('internalSmtpServer'),
'port' => $config->get('internalSmtpPort'),
+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()
+43 -10
View File
@@ -2368,7 +2368,9 @@ class SelectManager
protected function textFilter($textFilter, array &$result, $noFullText = false)
{
$fieldDefs = $this->getSeed()->getAttributes();
$fieldList = $this->getTextFilterFieldList();
$group = [];
$textFilterContainsMinLength = $this->getConfig()->get('textFilterContainsMinLength', self::MIN_LENGTH_FOR_CONTENT_SEARCH);
@@ -2401,6 +2403,7 @@ class SelectManager
$textFilterForFullTextSearch = str_replace('%', '*', $textFilterForFullTextSearch);
$skipFullTextSearch = false;
if (!$forceFullTextSearch) {
if (mb_strpos($textFilterForFullTextSearch, '*') === 0) {
$skipFullTextSearch = true;
@@ -2409,9 +2412,12 @@ class SelectManager
}
}
if ($noFullText) $skipFullTextSearch = true;
if ($noFullText) {
$skipFullTextSearch = true;
}
$fullTextSearchData = null;
if (!$skipFullTextSearch) {
$fullTextSearchData = $this->getFullTextSearchDataForTextFilter($textFilterForFullTextSearch, !$useFullTextSearch);
}
@@ -2419,6 +2425,7 @@ class SelectManager
$fullTextGroup = [];
$fullTextSearchFieldList = [];
if ($fullTextSearchData) {
if ($this->fullTextRelevanceThreshold) {
$fullTextGroup[] = [$fullTextSearchData['where'] . '>=' => $this->fullTextRelevanceThreshold];
@@ -2434,12 +2441,15 @@ class SelectManager
$orderTypeMap = [
'combined' => self::FT_ORDER_COMBINTED,
'relavance' => self::FT_ORDER_RELEVANCE,
'relevance' => self::FT_ORDER_RELEVANCE,
'original' => self::FT_ORDER_ORIGINAL,
];
$mOrderType = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'fullTextSearchOrderType']);
if ($mOrderType) $fullTextOrderType = $orderTypeMap[$mOrderType];
if ($mOrderType) {
$fullTextOrderType = $orderTypeMap[$mOrderType];
}
if (!isset($result['orderBy']) || $fullTextOrderType === self::FT_ORDER_RELEVANCE) {
$result['orderBy'] = [[$relevanceExpression, 'desc']];
@@ -2468,9 +2478,14 @@ class SelectManager
foreach ($fieldList as $field) {
if ($useFullTextSearch) {
if (in_array($field, $fullTextSearchFieldList)) continue;
if (in_array($field, $fullTextSearchFieldList)) {
continue;
}
}
if ($forceFullTextSearch) {
continue;
}
if ($forceFullTextSearch) continue;
$seed = $this->getSeed();
@@ -2478,21 +2493,34 @@ class SelectManager
if (strpos($field, '.') !== false) {
list($link, $foreignField) = explode('.', $field);
$foreignEntityType = $seed->getRelationParam($link, 'entity');
$seed = $this->getEntityManager()->getEntity($foreignEntityType);
$this->addLeftJoin($link, $result);
if ($seed->getRelationParam($link, 'type') === $seed::HAS_MANY) {
$this->setDistinct(true, $result);
}
$attributeType = $seed->getAttributeType($foreignField);
} else {
$attributeType = $seed->getAttributeType($field);
if ($attributeType === 'foreign') {
$link = $seed->getAttributeParam($field, 'relation');
if ($link) {
$this->addLeftJoin($link, $result);
}
}
}
if ($attributeType === 'int') {
if (is_numeric($textFilter)) {
$group[$field] = intval($textFilter);
}
continue;
}
@@ -2666,11 +2694,6 @@ class SelectManager
$selectParams1['havingClause'][] = $selectParams2['havingClause'];
}
if (!empty($selectParams2['leftJoins'])) {
foreach ($selectParams2['leftJoins'] as $item) {
$this->addLeftJoin($item, $selectParams1);
}
}
if (!empty($selectParams2['joins'])) {
foreach ($selectParams2['joins'] as $item) {
@@ -2678,6 +2701,16 @@ class SelectManager
}
}
if (!empty($selectParams2['leftJoins'])) {
foreach ($selectParams2['leftJoins'] as $item) {
if ($this->hasJoin($item, $selectParams1)) {
continue;
}
$this->addLeftJoin($item, $selectParams1);
}
}
if (isset($selectParams2['select'])) {
$selectParams1['select'] = $selectParams2['select'];
}
@@ -169,6 +169,7 @@ class AdminNotificationManager
$extension = $this->getEntityManager()->getRepository('Extension')
->select(['version'])
->where([
'name' => $extensionName,
'isInstalled' => true,
])
->order('createdAt', true)
+34 -36
View File
@@ -33,6 +33,7 @@ use Espo\Core\{
Exceptions\Error,
Utils\Autoload\Loader,
Utils\DataCache,
Utils\File\Manager as FileManager,
};
use Exception;
@@ -52,33 +53,26 @@ class Autoload
protected $config;
protected $metadata;
protected $dataCache;
protected $fileManager;
protected $loader;
public function __construct(Config $config, Metadata $metadata, DataCache $dataCache, Loader $loader)
{
public function __construct(
Config $config, Metadata $metadata, DataCache $dataCache, FileManager $fileManager, Loader $loader
) {
$this->config = $config;
$this->metadata = $metadata;
$this->dataCache = $dataCache;
$this->fileManager = $fileManager;
$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()
@@ -91,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 = [];
@@ -158,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])) {
+19 -12
View File
@@ -63,35 +63,33 @@ class DataCache
*
* @throws Error if is not cached.
*
* @return ?array|StdClass|int|float|string
* @return ?array|StdClass
*/
public function get(string $key)
{
$cacheFile = $this->getCacheFile($key);
$result = $this->fileManager->getPhpContents($cacheFile);
$data = $this->fileManager->getPhpSafeContents($cacheFile);
if ($result === false) {
if ($data === false) {
throw new Error("Could not get '{$key}'.");
}
return $result;
if (! $this->checkDataIsValid($data)) {
throw new Error("Bad data fetched from cache by key '{$key}'.");
}
return $data;
}
/**
* Store in cache.
*
* @param ?array|StdClass|int|float|string $data
* @param ?array|StdClass $data
*/
public function store(string $key, $data)
{
if (
! is_array($data) &&
! $data instanceof StdClass &&
! is_int($data) &&
! is_float($data) &&
! is_int($data)
) {
if (! $this->checkDataIsValid($data)) {
throw new InvalidArgumentException("Bad cache data type.");
}
@@ -104,6 +102,15 @@ class DataCache
}
}
protected function checkDataIsValid($data)
{
$isInvalid =
! is_array($data) &&
! $data instanceof StdClass;
return ! $isInvalid;
}
protected function getCacheFile(string $key) : string
{
if (
@@ -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);
+104 -64
View File
@@ -47,6 +47,14 @@ class Manager
protected $tmpDir = 'data/tmp';
const RENAME_RETRY_NUMBER = 10;
const RENAME_RETRY_INTERVAL = 0.1;
const GET_SAFE_CONTENTS_RETRY_NUMBER = 10;
const GET_SAFE_CONTENTS_RETRY_INTERVAL = 0.1;
public function __construct(?Config $config = null)
{
$params = null;
@@ -175,10 +183,10 @@ class Manager
}
/**
* Get PHP array from PHP file.
* Get data from PHP file.
*
* @param string|array $path
* @return array|object|bool
* @return mixed|bool
*/
public function getPhpContents($path)
{
@@ -193,6 +201,39 @@ class Manager
return false;
}
/**
* Get array or StdClass data from PHP file.
* If a file is not yet written, it will wait until it's ready.
*
* @return array|StdClass
*/
public function getPhpSafeContents(string $path)
{
if (!file_exists($path)) {
throw new Error("Can't get contents from non-existing file '{$path}'.");
}
if (!strtolower(substr($path, -4)) == '.php') {
throw new Error("Only PHP file are allowed for getting contents.");
}
$counter = 0;
while ($counter < self::GET_SAFE_CONTENTS_RETRY_NUMBER) {
$data = include($path);
if (is_array($data) || $data instanceof StdClass) {
return $data;
}
usleep(self::GET_SAFE_CONTENTS_RETRY_INTERVAL * 1000000);
$counter ++;
}
throw new Error("Bad data stored in file '{$path}'.");
}
/**
* Write data to a file.
*
@@ -266,6 +307,10 @@ class Manager
$result = rename($tmpPath, $path);
if (!$result && stripos(\PHP_OS, 'WIN') === 0) {
$result = $this->renameInLoop($tmpPath, $path);
}
if ($this->isFile($tmpPath)) {
$this->removeFile($tmpPath);
}
@@ -273,6 +318,29 @@ class Manager
return $result;
}
protected function renameInLoop(string $source, string $destination) : bool
{
$counter = 0;
while ($counter < self::RENAME_RETRY_NUMBER) {
if (!$this->isWritable($destination)) {
break;
}
$result = rename($source, $destination);
if ($result !== false) {
return true;
}
usleep(self::RENAME_RETRY_INTERVAL * 1000000);
$counter ++;
}
return false;
}
/**
* Save PHP content to file.
*
@@ -288,8 +356,8 @@ class Manager
*
* @param string | array $path
* @param string $data
* @param integer $flags
* @param resource $context
* @param integer $flags
* @param resource $context
*
* @return bool
*/
@@ -377,8 +445,8 @@ class Manager
/**
* Unset some element of content data.
*
* @param string | array $path
* @param array | string $unsets
* @param string | array $path
* @param array | string $unsets
* @return bool
*/
public function unsetContents($path, $unsets, $isJSON = true)
@@ -412,8 +480,8 @@ class Manager
/**
* Concat paths
* @param string | array $paths Ex. array('pathPart1', 'pathPart2', 'pathPart3')
* Concat paths.
* @param string | array $paths Ex. array('pathPart1', 'pathPart2', 'pathPart3')
* @return string
*/
protected function concatPaths($paths)
@@ -432,11 +500,11 @@ class Manager
}
/**
* Create a new dir
* Create a new dir.
*
* @param string | array $path
* @param int $permission - ex. 0755
* @param bool $recursive
* @param string | array $path
* @param int $permission - ex. 0755
* @param bool $recursive
*
* @return bool
*/
@@ -487,11 +555,11 @@ class Manager
* Ex. $sourcePath = 'data/uploads/extensions/file.json',
* $destPath = 'data/uploads/backup', result will be data/uploads/backup/data/uploads/backup/file.json.
*
* @param string $sourcePath
* @param string $destPath
* @param boolean $recursively
* @param array $fileList - list of files that should be copied
* @param boolean $copyOnlyFiles - copy only files, instead of full path with directories,
* @param string $sourcePath
* @param string $destPath
* @param boolean $recursively
* @param array $fileList - list of files that should be copied
* @param boolean $copyOnlyFiles - copy only files, instead of full path with directories,
* Ex. $sourcePath = 'data/uploads/extensions/file.json',
* $destPath = 'data/uploads/backup', result will be 'data/uploads/backup/file.json'
* @return boolen
@@ -510,7 +578,6 @@ class Manager
$permissionDeniedList = [];
foreach ($fileList as $file) {
if ($copyOnlyFiles) {
$file = pathinfo($file, PATHINFO_BASENAME);
}
@@ -667,7 +734,6 @@ class Manager
*
* @param string $dirPath - directory path.
* @param bool $removeWithDir - if remove with directory.
*
* @return bool
*/
public function removeInDir($dirPath, $removeWithDir = false)
@@ -699,8 +765,8 @@ class Manager
/**
* Remove items (files or directories).
*
* @param string | array $items
* @param string $dirPath
* @param string | array $items
* @param string $dirPath
* @return boolean
*/
public function remove($items, $dirPath = null, $removeEmptyDirs = false)
@@ -755,7 +821,7 @@ class Manager
/**
* Remove empty parent directories if they are empty.
* @param string $path
* @param string $path
* @return bool
*/
protected function removeEmptyDirs($path)
@@ -772,29 +838,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);
}
/**
@@ -825,12 +881,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);
@@ -893,10 +946,10 @@ class Manager
}
/**
* Get parent dir name/path
* Get parent dir name/path.
*
* @param string $path
* @param boolean $isFullPath
* @param string $path
* @param boolean $isFullPath
* @return string
*/
public function getParentDirName($path, $isFullPath = true)
@@ -907,7 +960,6 @@ class Manager
/**
* Return content of PHP file.
*
* @param string $varName - name of variable which contains the content.
* @param array $content
*
* @return string | false
@@ -961,12 +1013,8 @@ class Manager
/**
* Check if $paths are writable. Permission denied list are defined in getLastPermissionDeniedList().
*
* @param array $paths
*
* @return boolean
*/
public function isWritableList(array $paths)
public function isWritableList(array $paths) : bool
{
$permissionDeniedList = [];
@@ -1002,12 +1050,8 @@ class Manager
/**
* Check if $path is writable.
*
* @param string |array $path
*
* @return boolean
*/
public function isWritable($path)
public function isWritable(string $path) : bool
{
$existFile = $this->getExistsPath($path);
@@ -1016,12 +1060,8 @@ class Manager
/**
* Check if $path is writable.
*
* @param string | array $path
*
* @return boolean
*/
public function isReadable($path)
public function isReadable(string $path) : bool
{
$existFile = $this->getExistsPath($path);
+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();
}
/**
@@ -34,13 +34,15 @@ use Espo\Core\EntryPoints\{
NoAuth,
};
use Espo\Core\Api\Request;
class OauthCallback implements EntryPoint
{
use NoAuth;
public function run()
public function run(Request $request)
{
echo "EspoCRM rocks! If this window is not closed automatically, it's probable that URL you use to access ".
echo "If this window is not closed automatically, it's probable that URL you use to access ".
"EspoCRM doesn't match URL specified at Administration > Settings > Site URL.";
}
}
@@ -34,17 +34,20 @@ use Espo\ORM\Entity;
use Espo\Core\{
Utils\Config,
ORM\EntityManager,
ApplicationState,
};
class AssignmentEmailNotification
{
protected $config;
protected $entityManager;
protected $applicationState;
public function __construct(Config $config, EntityManager $entityManager)
public function __construct(Config $config, EntityManager $entityManager, ApplicationState $applicationState)
{
$this->config = $config;
$this->entityManager = $entityManager;
$this->applicationState = $applicationState;
}
public function afterSave(Entity $entity, array $options = [])
@@ -67,17 +70,25 @@ class AssignmentEmailNotification
if ($entity->has('assignedUsersIds')) {
$userIdList = $entity->getLinkMultipleIdList('assignedUsers');
$fetchedAssignedUserIdList = $entity->getFetched('assignedUsersIds');
if (!is_array($fetchedAssignedUserIdList)) {
$fetchedAssignedUserIdList = [];
}
foreach ($userIdList as $userId) {
if (in_array($userId, $fetchedAssignedUserIdList)) continue;
if (!$this->isNotSelfAssignment($entity, $userId)) continue;
if (in_array($userId, $fetchedAssignedUserIdList)) {
continue;
}
if (!$this->isNotSelfAssignment($entity, $userId)) {
continue;
}
$this->createJob($entity, $userId);
}
} else {
$userId = $entity->get('assignedUserId');
if (!empty($userId) &&
$entity->isAttributeChanged('assignedUserId') && $this->isNotSelfAssignment($entity, $userId)
) {
@@ -96,26 +107,29 @@ class AssignmentEmailNotification
$isNotSelfAssignment = $assignedUserId !== $entity->get('modifiedById');
}
} else {
$isNotSelfAssignment = $assignedUserId !== $this->getUser()->id;
$isNotSelfAssignment = $assignedUserId !== $this->applicationState->getUserId();
}
return $isNotSelfAssignment;
}
protected function createJob(Entity $entity, $userId)
{
$job = $this->entityManager->getEntity('Job');
$job->set([
'serviceName' => 'EmailNotification',
'methodName' => 'notifyAboutAssignmentJob',
'data' => [
'userId' => $userId,
'assignerUserId' => $this->getUser()->id,
'assignerUserId' => $this->applicationState->getUserId(),
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
],
'executeTime' => date('Y-m-d H:i:s'),
'queue' => 'e0',
]);
$this->entityManager->saveEntity($job);
}
}
@@ -36,6 +36,9 @@ use Espo\Core\{
Jobs\Job,
};
use Throwable;
use DateTime;
class SubmitPopupReminders implements Job
{
const REMINDER_PAST_HOURS = 24;
@@ -53,21 +56,27 @@ class SubmitPopupReminders implements Job
public function run()
{
if (!$this->config->get('useWebSocket')) return;
if (!$this->config->get('useWebSocket')) {
return;
}
$dt = new \DateTime();
$dt = new DateTime();
$now = $dt->format('Y-m-d H:i:s');
$pastHours = $this->config->get('reminderPastHours', self::REMINDER_PAST_HOURS);
$nowShifted = $dt->modify('-' . $pastHours . ' hours')->format('Y-m-d H:i:s');
$reminderList = $this->entityManager->getRepository('Reminder')->where([
'type' => 'Popup',
'remindAt<=' => $now,
'startAt>' => $nowShifted,
'isSubmitted' => false,
])->find();
$reminderList = $this->entityManager
->getRepository('Reminder')
->where([
'type' => 'Popup',
'remindAt<=' => $now,
'startAt>' => $nowShifted,
'isSubmitted' => false,
])
->find();
$submitData = [];
@@ -78,6 +87,7 @@ class SubmitPopupReminders implements Job
if (!$userId || !$entityType || !$entityId) {
$this->deleteReminder($reminder);
continue;
}
@@ -85,19 +95,24 @@ class SubmitPopupReminders implements Job
if (!$entity) {
$this->deleteReminder($reminder);
continue;
}
if ($entity->hasLinkMultipleField('users')) {
$entity->loadLinkMultipleField('users', ['status' => 'acceptanceStatus']);
$status = $entity->getLinkMultipleColumn('users', 'status', $userId);
if ($status === 'Declined') {
$this->deleteReminder($reminder);
continue;
}
}
$dateAttribute = 'dateStart';
if ($entityType === 'Task') {
$dateAttribute = 'dateEnd';
}
@@ -125,10 +140,10 @@ class SubmitPopupReminders implements Job
foreach ($submitData as $userId => $list) {
try {
$this->getContainer()->get('webSocketSubmission')->submit('popupNotifications.event', $userId, (object) [
$this->webSocketSubmission->submit('popupNotifications.event', $userId, (object) [
'list' => $list
]);
} catch (\Throwable $e) {
} catch (Throwable $e) {
$GLOBALS['log']->error('Job SubmitPopupReminders: [' . $e->getCode() . '] ' .$e->getMessage());
}
}
@@ -38,7 +38,8 @@
},
"labels": {
"Create Task": "Create Task",
"Complete": "Complete"
"Complete": "Complete",
"overdue": "overdue"
},
"presetFilters": {
"actual": "Actual",
@@ -2,5 +2,6 @@
"massEmail",
"status",
"target",
"sentAt"
"sentAt",
"emailAddress"
]
@@ -78,6 +78,7 @@
}
},
"collection": {
"textFilterFields": ["queueItem.id", "queueItem.emailAddress"],
"orderBy": "createdAt",
"order": "desc"
},
@@ -45,6 +45,7 @@
}
},
"collection": {
"textFilterFields": ["id", "emailAddress"],
"orderBy": "createdAt",
"order": "desc"
}
@@ -4,6 +4,7 @@
"tab": false,
"acl": "recordAllTeamNo",
"aclPortal": "recordAllNo",
"aclPortalActionList": ["read"],
"module": "Crm",
"customizable": true,
"importable": false,
@@ -4,6 +4,7 @@
"tab": true,
"acl": "recordAllTeamNo",
"aclPortal": "recordAllNo",
"aclPortalActionList": ["read"],
"module": "Crm",
"customizable": true,
"importable": true,
@@ -4,6 +4,7 @@
"tab": false,
"acl": "recordAllTeamNo",
"aclPortal": "recordAllNo",
"aclPortalActionList": ["read"],
"module": "Crm",
"customizable": true,
"importable": false,
@@ -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'];
+167 -84
View File
@@ -31,14 +31,17 @@ namespace Espo\Modules\Crm\Services;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\ConflictSilent;
use Espo\ORM\Entity;
use Espo\Modules\Crm\Entities\Lead as LeadEntity;
use Espo\Core\Templates\Services\Person as PersonService;
use Espo\Core\Di;
class Lead extends \Espo\Core\Templates\Services\Person implements
class Lead extends PersonService implements
Di\FieldUtilAware
{
@@ -52,31 +55,36 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
{
if (!empty($data->emailId)) {
$email = $this->getEntityManager()->getEntity('Email', $data->emailId);
if ($email && !$email->get('parentId') && $this->getAcl()->check($email)) {
$email->set(array(
$email->set([
'parentType' => 'Lead',
'parentId' => $entity->id
));
]);
$this->getEntityManager()->saveEntity($email);
}
}
if ($entity->get('campaignId')) {
$campaign = $this->getEntityManager()->getEntity('Campaign', $entity->get('campaignId'));
if ($campaign) {
$log = $this->getEntityManager()->getEntity('CampaignLogRecord');
$log->set(array(
'action' => 'Lead Created',
'actionDate' => date('Y-m-d H:i:s'),
'parentType' => 'Lead',
'parentId' => $entity->id,
'campaignId' => $campaign->id
));
$this->getEntityManager()->saveEntity($log);
}
$campaign = $this->getEntityManager()->getEntity('Campaign', $entity->get('campaignId'));
if ($campaign) {
$log = $this->getEntityManager()->getEntity('CampaignLogRecord');
$log->set([
'action' => 'Lead Created',
'actionDate' => date('Y-m-d H:i:s'),
'parentType' => 'Lead',
'parentId' => $entity->id,
'campaignId' => $campaign->id,
]);
$this->getEntityManager()->saveEntity($log);
}
}
}
public function getConvertAttributes($id)
public function getConvertAttributes(string $id)
{
$lead = $this->getEntity($id);
@@ -84,31 +92,39 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
throw new Forbidden();
}
$data = array();
$data = [];
$entityList = $this->getMetadata()->get('entityDefs.Lead.convertEntityList', []);
$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')) continue;
if (!$this->getAcl()->checkScope($entityType, 'edit')) {
continue;
}
$attributes = array();
$attributes = [];
$target = $this->getEntityManager()->getEntity($entityType);
$fieldMap = array();
$fieldMap = [];
$fieldList = array_keys($this->getMetadata()->get('entityDefs.Lead.fields', []));
$fieldList = array_keys($this->getMetadata()->get('entityDefs.Lead.fields', array()));
foreach ($fieldList as $field) {
if (!$this->getMetadata()->get('entityDefs.'.$entityType.'.fields.' . $field)) continue;
if (!$this->getMetadata()->get('entityDefs.'.$entityType.'.fields.' . $field)) {
continue;
}
if (
$this->getMetadata()->get(['entityDefs', $entityType, 'fields', $field, 'type'])
!==
$this->getMetadata()->get(['entityDefs', 'Lead', 'fields', $field, 'type'])
) continue;
) {
continue;
}
$fieldMap[$field] = $field;
}
@@ -121,55 +137,76 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
foreach ($fieldMap as $field => $leadField) {
$type = $this->getMetadata()->get(['entityDefs', 'Lead', 'fields', $field, 'type']);
if (in_array($type, ['file', 'image'])) {
$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';
if ($attachment) {
$attributes[$idAttribute] = $attachment->id;
$attributes[$nameAttribute] = $attachment->get('name');
}
}
continue;
} else if (in_array($type, ['attachmentMultiple'])) {
}
else if (in_array($type, ['attachmentMultiple'])) {
$attachmentList = $lead->get($field);
if (count($attachmentList)) {
$idList = [];
$nameHash = (object) [];
$typeHash = (object) [];
foreach ($attachmentList as $attachment) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
if ($attachment) {
$idList[] = $attachment->id;
$nameHash->{$attachment->id} = $attachment->get('name');
$typeHash->{$attachment->id} = $attachment->get('type');
}
}
$attributes[$field . 'Ids'] = $idList;
$attributes[$field . 'Names'] = $nameHash;
$attributes[$field . 'Types'] = $typeHash;
}
continue;
}
$leadAttributeList = $this->fieldUtil->getAttributeList('Lead', $leadField);
$attributeList = $this->fieldUtil->getAttributeList($entityType, $field);
foreach ($attributeList as $i => $attribute) {
if (in_array($attribute, $ignoreAttributeList)) continue;
if (in_array($attribute, $ignoreAttributeList)) {
continue;
}
$leadAttribute = $leadAttributeList[$i];
if (!$lead->has($leadAttribute)) continue;
$leadAttribute = $leadAttributeList[$i] ?? null;
if (!$leadAttribute) {
throw new Error("Not compatible fields in 'convertFields' map.");
}
if (!$lead->has($leadAttribute)) {
continue;
}
$attributes[$attribute] = $lead->get($leadAttribute);
}
}
$data[$entityType] = $attributes;
}
return $data;
@@ -197,7 +234,10 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
$account->set(get_object_vars($recordsData->Account));
if ($duplicateCheck) {
$rDuplicateList = $this->getServiceFactory()->create('Account')->findDuplicates($account, $recordsData->Account);
$rDuplicateList = $this->getServiceFactory()
->create('Account')
->findDuplicates($account, $recordsData->Account);
if ($rDuplicateList) {
foreach ($rDuplicateList as $e) {
$item = $e->getValueMap();
@@ -210,6 +250,7 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
if (!$skipSave) {
$entityManager->saveEntity($account);
$lead->set('createdAccountId', $account->id);
}
}
@@ -217,12 +258,16 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
if (!empty($recordsData->Contact)) {
$contact = $entityManager->getEntity('Contact');
$contact->set(get_object_vars($recordsData->Contact));
if (isset($account)) {
$contact->set('accountId', $account->id);
}
if ($duplicateCheck) {
$rDuplicateList = $this->getServiceFactory()->create('Contact')->findDuplicates($contact, $recordsData->Contact);
$rDuplicateList = $this->getServiceFactory()
->create('Contact')
->findDuplicates($contact, $recordsData->Contact);
if ($rDuplicateList) {
foreach ($rDuplicateList as $e) {
$item = $e->getValueMap();
@@ -235,6 +280,7 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
if (!$skipSave) {
$entityManager->saveEntity($contact);
$lead->set('createdContactId', $contact->id);
}
}
@@ -242,15 +288,18 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
if (!empty($recordsData->Opportunity)) {
$opportunity = $entityManager->getEntity('Opportunity');
$opportunity->set(get_object_vars($recordsData->Opportunity));
if (isset($account)) {
$opportunity->set('accountId', $account->id);
}
if (isset($contact)) {
$opportunity->set('contactId', $contact->id);
}
if ($duplicateCheck) {
$rDuplicateList = $this->getServiceFactory()->create('Opportunity')
$rDuplicateList = $this->getServiceFactory()
->create('Opportunity')
->findDuplicates($opportunity, $recordsData->Opportunity);
if ($rDuplicateList) {
@@ -265,9 +314,11 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
if (!$skipSave) {
$entityManager->saveEntity($opportunity);
if (isset($contact)) {
$entityManager->getRepository('Contact')->relate($contact, 'opportunities', $opportunity);
}
$lead->set('createdOpportunityId', $opportunity->id);
}
}
@@ -277,78 +328,110 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
'reason' => 'duplicate',
'duplicates' => $duplicateList,
];
throw new \Espo\Core\Exceptions\ConflictSilent(json_encode($reason));
throw new ConflictSilent(json_encode($reason));
}
$lead->set('status', 'Converted');
$entityManager->saveEntity($lead);
if ($meetings = $lead->get('meetings')) {
foreach ($meetings as $meeting) {
if (!empty($contact)) {
$entityManager->getRepository('Meeting')->relate($meeting, 'contacts', $contact);
}
$leadRepisotory = $entityManager->getRepository('Lead');
if (!empty($opportunity)) {
$meeting->set('parentId', $opportunity->id);
$meeting->set('parentType', 'Opportunity');
$entityManager->saveEntity($meeting);
} else if (!empty($account)) {
$meeting->set('parentId', $account->id);
$meeting->set('parentType', 'Account');
$entityManager->saveEntity($meeting);
}
$meetings = $leadRepisotory
->getRelation($lead, 'meetings')
->select(['id', 'parentId', 'parentType'])
->find();
foreach ($meetings as $meeting) {
if (!empty($contact)) {
$entityManager->getRepository('Meeting')->relate($meeting, 'contacts', $contact);
}
}
if ($calls = $lead->get('calls')) {
foreach ($calls as $call) {
if (!empty($contact)) {
$entityManager->getRepository('Call')->relate($call, 'contacts', $contact);
}
if (!empty($opportunity)) {
$call->set('parentId', $opportunity->id);
$call->set('parentType', 'Opportunity');
$entityManager->saveEntity($call);
} else if (!empty($account)) {
$call->set('parentId', $account->id);
$call->set('parentType', 'Account');
$entityManager->saveEntity($call);
}
if (!empty($opportunity)) {
$meeting->set('parentId', $opportunity->id);
$meeting->set('parentType', 'Opportunity');
$entityManager->saveEntity($meeting);
}
}
if ($emails = $lead->get('emails')) {
foreach ($emails as $email) {
if (!empty($opportunity)) {
$email->set('parentId', $opportunity->id);
$email->set('parentType', 'Opportunity');
$entityManager->saveEntity($email);
} else if (!empty($account)) {
$email->set('parentId', $account->id);
$email->set('parentType', 'Account');
$entityManager->saveEntity($email);
}
else if (!empty($account)) {
$meeting->set('parentId', $account->id);
$meeting->set('parentType', 'Account');
$entityManager->saveEntity($meeting);
}
}
if ($documents = $lead->get('documents')) {
foreach ($documents as $document) {
if (!empty($account)) {
$entityManager->getRepository('Document')->relate($document, 'accounts', $account);
}
if (!empty($opportunity)) {
$entityManager->getRepository('Document')->relate($document, 'opportunities', $opportunity);
}
$calls = $leadRepisotory
->getRelation($lead, 'calls')
->select(['id', 'parentId', 'parentType'])
->find();
foreach ($calls as $call) {
if (!empty($contact)) {
$entityManager->getRepository('Call')->relate($call, 'contacts', $contact);
}
if (!empty($opportunity)) {
$call->set('parentId', $opportunity->id);
$call->set('parentType', 'Opportunity');
$entityManager->saveEntity($call);
}
else if (!empty($account)) {
$call->set('parentId', $account->id);
$call->set('parentType', 'Account');
$entityManager->saveEntity($call);
}
}
$emails = $leadRepisotory
->getRelation($lead, 'emails')
->select(['id', 'parentId', 'parentType'])
->find();
foreach ($emails as $email) {
if (!empty($opportunity)) {
$email->set('parentId', $opportunity->id);
$email->set('parentType', 'Opportunity');
$entityManager->saveEntity($email);
}
else if (!empty($account)) {
$email->set('parentId', $account->id);
$email->set('parentType', 'Account');
$entityManager->saveEntity($email);
}
}
$documents = $leadRepisotory
->getRelation($lead, 'documents')
->select(['id'])
->find();
foreach ($documents as $document) {
if (!empty($account)) {
$entityManager->getRepository('Document')->relate($document, 'accounts', $account);
}
if (!empty($opportunity)) {
$entityManager->getRepository('Document')->relate($document, 'opportunities', $opportunity);
}
}
$streamService = $this->getStreamService();
if ($streamService->checkIsFollowed($lead, $this->getUser()->id)) {
if (!empty($opportunity)) {
$streamService->followEntity($opportunity, $this->getUser()->id);
}
if (!empty($account)) {
$streamService->followEntity($account, $this->getUser()->id);
}
if (!empty($contact)) {
$streamService->followEntity($contact, $this->getUser()->id);
}
+1 -1
View File
@@ -175,7 +175,7 @@ class BaseEntity implements Entity
// @todo Remove this.
if ($this->hasRelation($name) && $this->id && $this->entityManager) {
trigger_error(
"Accessing related records with Entity::get is deprecated. Use \$entityManager->getRelation(...)->find()",
"Accessing related records with Entity::get is deprecated. Use \$repository->getRelation(...)->find()",
E_USER_DEPRECATED
);
+34 -9
View File
@@ -34,6 +34,8 @@ use Countable;
use ArrayAccess;
use SeekableIterator;
use RuntimeException;
use OutOfBoundsException;
use InvalidArgumentException;
/**
* A standard collection of entities. It allocates a memory for all entities.
@@ -80,7 +82,9 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
{
do {
$this->position ++;
$next = false;
if (!$this->valid() && $this->position <= $this->getLastValidKey()) {
$next = true;
}
@@ -90,13 +94,17 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
private function getLastValidKey()
{
$keys = array_keys($this->dataList);
$i = end($keys);
while ($i > 0) {
if (isset($this->dataList[$i])) {
break;
}
$i--;
}
return $i;
}
@@ -115,20 +123,23 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
if (!isset($this->dataList[$offset])) {
return null;
}
return $this->getEntityByOffset($offset);
}
public function offsetSet($offset, $value)
{
if (!($value instanceof Entity)) {
throw new \InvalidArgumentException('Only Entity is allowed to be added to EntityCollection.');
throw new InvalidArgumentException('Only Entity is allowed to be added to EntityCollection.');
}
if (is_null($offset)) {
$this->dataList[] = $value;
} else {
$this->dataList[$offset] = $value;
return;
}
$this->dataList[$offset] = $value;
}
public function offsetUnset($offset)
@@ -144,8 +155,9 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
public function seek($offset)
{
$this->position = $offset;
if (!$this->valid()) {
throw new \OutOfBoundsException("Invalid seek offset ($offset).");
throw new OutOfBoundsException("Invalid seek offset ($offset).");
}
}
@@ -160,13 +172,15 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
if ($value instanceof Entity) {
return $value;
} else if (is_array($value)) {
$this->dataList[$offset] = $this->buildEntityFromArray($value);
} else {
return null;
}
return $this->dataList[$offset];
if (is_array($value)) {
$this->dataList[$offset] = $this->buildEntityFromArray($value);
return $this->dataList[$offset];
}
return null;
}
protected function buildEntityFromArray(array $dataArray)
@@ -179,9 +193,11 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
if ($entity) {
$entity->set($dataArray);
if ($this->isFetched) {
$entity->setAsFetched();
}
return $entity;
}
}
@@ -213,6 +229,7 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
public function merge(EntityCollection $collection)
{
$newData = $this->dataList;
$incomingDataList = $collection->getDataList();
foreach ($incomingDataList as $v) {
@@ -230,12 +247,14 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
if ($this->indexOf($value) !== false) {
return true;
}
return false;
}
public function indexOf($value)
{
$index = 0;
if (is_array($value)) {
foreach ($this->dataList as $v) {
if (is_array($v)) {
@@ -247,6 +266,7 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
return $index;
}
}
$index ++;
}
} else if ($value instanceof Entity) {
@@ -260,9 +280,11 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
return $index;
}
}
$index ++;
}
}
return false;
}
@@ -272,14 +294,17 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
public function toArray(bool $itemsAsObjects = false) : array
{
$arr = [];
foreach ($this as $entity) {
if ($itemsAsObjects) {
$item = $entity->getValueMap();
} else {
$item = $entity->toArray();
}
$arr[] = $item;
}
return $arr;
}
@@ -1296,7 +1296,7 @@ abstract class BaseQueryComposer implements QueryComposer
$modifiedOrder[] = $newItem;
}
$part = $this->getOrderExpressionPart($entity, $modifiedOrder, null, false, $params, true);
$part = $this->getOrderExpressionPart($entity, $modifiedOrder, null, $params, true);
return $part;
}
@@ -1446,21 +1446,32 @@ abstract class BaseQueryComposer implements QueryComposer
}
if (is_string($value)) {
return self::getAllAttributesFromComplexExpression($value);
$value = [[$value]];
}
if (!is_array($value)) {
return [];
}
$list = [];
if (is_array($value)) {
foreach ($value as $item) {
if (!is_array($item) || !isset($item[0])) continue;
$list = array_merge(
$list,
self::getAllAttributesFromComplexExpression($item[0])
);
foreach ($value as $item) {
if (!is_array($item) || !isset($item[0])) {
continue;
}
$expression = $item[0];
if (strpos($expression, 'LIST:') === 0 && substr_count($expression, ':') === 2) {
$expression = explode(':', $expression)[1];
}
$attributeList = self::getAllAttributesFromComplexExpression($expression);
$list = array_merge(
$list,
$attributeList
);
}
return $list;
@@ -1828,7 +1839,6 @@ abstract class BaseQueryComposer implements QueryComposer
Entity $entity,
$orderBy = null,
$order = null,
bool $useColumnAlias = false,
?array &$params = null,
bool $noCustom = false
) : ?string {
@@ -1849,7 +1859,7 @@ abstract class BaseQueryComposer implements QueryComposer
}
$arr[] = $this->getOrderExpressionPart(
$entity, $orderByInternal, $orderInternal, $useColumnAlias, $params, $noCustom
$entity, $orderByInternal, $orderInternal, $params, $noCustom
);
}
}
@@ -1860,13 +1870,8 @@ abstract class BaseQueryComposer implements QueryComposer
if (strpos($orderBy, 'LIST:') === 0) {
list($l, $field, $list) = explode(':', $orderBy);
if ($useColumnAlias) {
$fieldPath = $this->quoteIdentifier(
$this->sanitizeSelectAlias($field)
);
} else {
$fieldPath = $this->getAttributePathForOrderBy($entity, $field, $params);
}
$fieldPath = $this->getAttributePathForOrderBy($entity, $field, $params);
$listQuoted = [];
$list = array_reverse(explode(',', $list));
@@ -1905,13 +1910,7 @@ abstract class BaseQueryComposer implements QueryComposer
return $this->getAttributeOrderSql($entity, $orderBy, $params, $order);
}
if ($useColumnAlias) {
$fieldPath = $this->quoteIdentifier(
$this->sanitizeSelectAlias($orderBy)
);
} else {
$fieldPath = $this->getAttributePathForOrderBy($entity, $orderBy, $params);
}
$fieldPath = $this->getAttributePathForOrderBy($entity, $orderBy, $params);
if (!$fieldPath) {
throw new LogicException("Could not handle 'order' for '".$entity->getEntityType()."'.");
@@ -1922,7 +1921,7 @@ abstract class BaseQueryComposer implements QueryComposer
protected function getOrderPart(Entity $entity, $orderBy = null, $order = null, &$params = null) : ?string
{
return $this->getOrderExpressionPart($entity, $orderBy, $order, false, $params);
return $this->getOrderExpressionPart($entity, $orderBy, $order, $params);
}
protected function getAttributePathForOrderBy(Entity $entity, string $orderBy, array $params) : ?string
+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;
}
@@ -162,6 +162,7 @@
"max": "Max",
"translation": "Translation",
"previewSize": "Preview Size",
"listPreviewSize": "Preview Size in List View",
"noEmptyString": "Empty string value is not allowed",
"defaultType": "Default Type",
"seeMoreDisabled": "Disable Text Cut",
@@ -289,6 +290,7 @@
},
"options": {
"previewSize": {
"": "Default",
"x-small": "X-Small",
"small": "Small",
"medium": "Medium",
@@ -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",
@@ -162,6 +163,19 @@
}
},
"tooltips": {
"displayListViewRecordCount": "A total number of records will be shown on the list view.",
"currencyList": "What currencies will be available in the system.",
"activitiesEntityList": "What records will be available in the Activities panel.",
"historyEntityList": "What records will be available in the History panel.",
"calendarEntityList": "What records will be available in the Calendar.",
"addressStateList": "State suggestions for address fields.",
"addressCityList": "City suggestions for address fields.",
"addressCountryList": "Country suggestions for address fields.",
"exportDisabled": "Users won't be able to export records. Only admin will be allowed.",
"globalSearchEntityList": "What records can be searched with Global Search.",
"siteUrl": "A URL of this EspoCRM instance. You need to change it if you move to another domain.",
"useCache": "Not recommended to disable, unless for development purpose.",
"useWebSocket": "WebSocket enables two-way interactive communication between a server and a browser. Requires setting up the WebSocket daemon on your server. Check the documentation for more info.",
"passwordRecoveryForInternalUsersDisabled": "Only portal users will be able to recover password.",
"passwordRecoveryNoExposure": "It won't be possible to determine whether a specific email address is registered in the system.",
"emailAddressLookupEntityTypeList": "For email address autocomplete.",
@@ -169,7 +169,7 @@
"Cancel": "Отменить",
"Apply": "Применить",
"Unlink": "Убрать ссылку",
"Mass Update": "Обновить все",
"Mass Update": "Массовое обновление",
"Export": "Экспортировать",
"No Data": "Нет данных",
"No Access": "Нет доступа",
@@ -185,7 +185,7 @@
"uninstallConfirmation": "Ви впевнені, що хочете видалити розширення?",
"cronIsNotConfigured": "Заплановані завдання не працюють. Отже, вхідні електронні листи, сповіщення та нагадування не працюють. Будь ласка, дотримуйтесь інструкцій (https://www.espocrm.com/documentation/administration/server-configuration/#user-content-setup-a-crontab), щоб налаштувати cron job.",
"newExtensionVersionIsAvailable": "Нова {extensionName} версія {latestVersion} доступна.",
"upgradeVersion": "EspoCRM буде оновлено до версії **{версія}**. Будьте терплячі, оскільки це може зайняти деякий час.",
"upgradeVersion": "EspoCRM буде оновлено до версії **{version}**. Будьте терплячі, оскільки це може зайняти деякий час.",
"upgradeDone": "EspoCRM оновлено до версії **{version}**.",
"downloadUpgradePackage": "Завантажте апгрейд [звідси]({url})",
"upgradeInfo": "Перегляньте [документацію]({url}) про те, як оновити EspoCRM.\n",
@@ -257,4 +257,4 @@
"noteStatus": "Повідомлення про оновлення статусу",
"passwordChangeLink": "Посилання на зміну паролю"
}
}
}
@@ -65,7 +65,10 @@
"parent": false
},
"EmailFolder": {
"assignedUser": false
"assignedUser": {
"read": "yes",
"edit": "no"
}
},
"Email": {
"inboundEmails": false,
@@ -53,11 +53,21 @@
{
"label":"Folders",
"link":"#EmailFolder",
"configCheck": "!emailFoldersDisabled"
"configCheck": "!emailFoldersDisabled",
"accessDataList": [
{
"inPortalDisabled": true
}
]
},
{
"label":"Filters",
"link":"#EmailFilter"
"link":"#EmailFilter",
"accessDataList": [
{
"inPortalDisabled": true
}
]
}
]
},
@@ -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
@@ -2,7 +2,8 @@
"fields": {
"useCache": {
"type": "bool",
"default": true
"default": true,
"tooltip": true
},
"recordsPerPage": {
"type": "int",
@@ -61,7 +62,8 @@
"type": "multiEnum",
"default": ["USD", "EUR"],
"required": true,
"view": "views/settings/fields/currency-list"
"view": "views/settings/fields/currency-list",
"tooltip": true
},
"defaultCurrency": {
"type": "enum",
@@ -143,7 +145,8 @@
"globalSearchEntityList": {
"type": "multiEnum",
"translation": "Global.scopeNames",
"view": "views/settings/fields/global-search-entity-list"
"view": "views/settings/fields/global-search-entity-list",
"tooltip": true
},
"exportDelimiter": {
"type": "varchar",
@@ -326,7 +329,8 @@
},
"exportDisabled": {
"type": "bool",
"default": false
"default": false,
"tooltip": true
},
"emailNotificationsDelay": {
"type": "int",
@@ -399,7 +403,8 @@
"type": "varchar"
},
"displayListViewRecordCount": {
"type": "bool"
"type": "bool",
"tooltip": true
},
"userThemesDisabled": {
"type": "bool",
@@ -461,7 +466,8 @@
"disabled": true
},
"siteUrl": {
"type": "varchar"
"type": "varchar",
"tooltip": true
},
"applicationName": {
"type": "varchar"
@@ -490,7 +496,7 @@
},
"currencyFormat": {
"type": "enumInt",
"options": [1, 2]
"options": [1, 2, 3]
},
"currencyDecimalPlaces": {
"type": "int",
@@ -503,15 +509,18 @@
},
"calendarEntityList": {
"type": "multiEnum",
"view": "views/settings/fields/calendar-entity-list"
"view": "views/settings/fields/calendar-entity-list",
"tooltip": true
},
"activitiesEntityList": {
"type": "multiEnum",
"view": "views/settings/fields/activities-entity-list"
"view": "views/settings/fields/activities-entity-list",
"tooltip": true
},
"historyEntityList": {
"type": "multiEnum",
"view": "views/settings/fields/history-entity-list"
"view": "views/settings/fields/history-entity-list",
"tooltip": true
},
"busyRangesEntityList": {
"type": "multiEnum",
@@ -578,13 +587,16 @@
"tooltip": true
},
"addressCountryList": {
"type": "multiEnum"
"type": "multiEnum",
"tooltip": true
},
"addressCityList": {
"type": "multiEnum"
"type": "multiEnum",
"tooltip": true
},
"addressStateList": {
"type": "multiEnum"
"type": "multiEnum",
"tooltip": true
},
"jobRunInParallel": {
"type": "bool",
@@ -621,7 +633,8 @@
"tooltip": true
},
"useWebSocket": {
"type": "bool"
"type": "bool",
"tooltip": true
}
}
}
@@ -42,11 +42,13 @@
},
{
"name":"after",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"name":"before",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"type": "bool",
@@ -39,11 +39,13 @@
},
{
"name":"after",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"name":"before",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"type": "bool",
@@ -39,11 +39,13 @@
},
{
"name":"after",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"name":"before",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"type": "bool",
@@ -11,6 +11,14 @@
"default":"small",
"options": ["x-small", "small", "medium", "large"]
},
{
"name": "listPreviewSize",
"type": "enum",
"default": "small",
"options": ["", "small", "medium"],
"translation": "Admin.options.previewSize",
"default": ""
},
{
"name": "maxFileSize",
"type": "float",
@@ -5,6 +5,7 @@
"acl": "recordAllTeamNo",
"aclPortal": true,
"aclPortalLevelList": ["all", "no"],
"aclPortalActionList": ["read"],
"customizable": true,
"importable": true,
"disabled": false
@@ -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}.");
}
+31 -6
View File
@@ -133,6 +133,7 @@ class EmailTemplate extends Record implements
if (!empty($params['relatedId']) && !empty($params['relatedType'])) {
$related = $this->getEntityManager()->getEntity($params['relatedType'], $params['relatedId']);
if ($related) {
$entityHash[$related->getEntityType()] = $related;
}
@@ -155,6 +156,7 @@ class EmailTemplate extends Record implements
if ($handlebarsInSubject) {
$subject = $htmlizer->render($parent, $subject);
}
if ($handlebarsInBody) {
$body = $htmlizer->render($parent, $body);
}
@@ -244,7 +246,9 @@ class EmailTemplate extends Record implements
}
foreach ($attributeList as $attribute) {
if (in_array($attribute, $forbiddenAttributeList)) continue;
if (in_array($attribute, $forbiddenAttributeList)) {
continue;
}
$value = $entity->get($attribute);
@@ -252,13 +256,18 @@ class EmailTemplate extends Record implements
continue;
}
if (!$entity->getAttributeType($attribute)) continue;
if (!$entity->getAttributeType($attribute)) {
continue;
}
$value = $this->formatAttributeValue($entity, $attribute);
if (is_null($value)) continue;
if (is_null($value)) {
continue;
}
$variableName = $attribute;
if (!is_null($prefixLink)) {
$variableName = $prefixLink . '.' . $attribute;
}
@@ -266,7 +275,7 @@ class EmailTemplate extends Record implements
$text = str_replace('{' . $type . '.' . $variableName . '}', $value, $text);
}
if (!$skipLinks) {
if (!$skipLinks && $entity->id) {
$relationDefs = $entity->getRelations();
foreach ($entity->getRelationList() as $relation) {
@@ -301,10 +310,12 @@ class EmailTemplate extends Record implements
}
$replaceData = [];
$replaceData['today'] = $this->getDateTime()->getTodayString();
$replaceData['now'] = $this->getDateTime()->getNowString();
$timeZone = $this->getConfig()->get('timeZone');
$now = new DateTime('now', new DateTimezone($timeZone));
$replaceData['currentYear'] = $now->format('Y');
@@ -328,11 +339,13 @@ class EmailTemplate extends Record implements
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
} else if ($fieldType === 'array' || $fieldType === 'multiEnum' || $fieldType === 'checklist') {
$valueList = [];
if (is_array($value)) {
foreach ($value as $v) {
$valueList[] = $this->getLanguage()->translateOption($v, $attribute, $entity->getEntityType());
}
}
$value = implode(', ', $valueList);
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
} else {
@@ -348,13 +361,16 @@ class EmailTemplate extends Record implements
if (!is_string($value)) {
$value = '';
}
$value = nl2br($value);
} else if ($attributeType == 'float') {
if (is_float($value)) {
$decimalPlaces = 2;
if ($fieldType === 'currency') {
$decimalPlaces = $this->getConfig()->get('currencyDecimalPlaces');
}
$value = $this->getNumber()->format($value, $decimalPlaces);
}
} else if ($attributeType == 'int') {
@@ -370,7 +386,9 @@ class EmailTemplate extends Record implements
$value = '';
}
if (!is_string($value)) return null;
if (!is_string($value)) {
return null;
}
return $value;
}
@@ -389,6 +407,7 @@ class EmailTemplate extends Record implements
if ($parentId && $parentType) {
$e = $this->getEntityManager()->getEntity($parentType, $parentId);
if ($e && $this->getAcl()->check($e)) {
$dataList[] = [
'type' => 'parent',
@@ -426,6 +445,7 @@ class EmailTemplate extends Record implements
foreach ($fm->getEntityTypeFieldList($entityType) as $field) {
$fieldType = $fm->getEntityTypeFieldParam($entityType, $field, 'type');
$fieldAttributeList = $fm->getAttributeList($entityType, $field);
if (
$fm->getEntityTypeFieldParam($entityType, $field, 'disabled') ||
$fm->getEntityTypeFieldParam($entityType, $field, 'directAccessDisabled') ||
@@ -441,9 +461,14 @@ class EmailTemplate extends Record implements
$attributeList = $fm->getEntityTypeAttributeList($entityType);
$values = (object) [];
foreach ($attributeList as $a) {
if (!$e->has($a)) continue;
if (!$e->has($a)) {
continue;
}
$value = $emailTemplateService->formatAttributeValue($e, $a);
if ($value != '') {
$values->$a = $value;
}
+188 -71
View File
@@ -43,10 +43,16 @@ use Espo\Core\{
Mail\Parsers\MailMimeParser,
};
use Espo\Services\{
Email as EmailService,
Record as RecordService,
};
use Espo\Entities\{
Team,
InboundEmail as InboundEmailEntity,
User,
Email as EmailEntity,
};
use Espo\Core\Di;
@@ -57,7 +63,7 @@ use Throwable;
use DateTime;
use DateTimeZone;
class InboundEmail extends \Espo\Services\Record implements
class InboundEmail extends RecordService implements
Di\CryptAware,
Di\EmailSenderAware,
@@ -83,9 +89,11 @@ class InboundEmail extends \Espo\Services\Record implements
protected function handleInput($data)
{
parent::handleInput($data);
if (property_exists($data, 'password')) {
$data->password = $this->getCrypt()->encrypt($data->password);
}
if (property_exists($data, 'smtpPassword')) {
$data->smtpPassword = $this->getCrypt()->encrypt($data->smtpPassword);
}
@@ -106,6 +114,7 @@ class InboundEmail extends \Espo\Services\Record implements
{
if (!empty($params['id'])) {
$account = $this->getEntityManager()->getEntity('InboundEmail', $params['id']);
if ($account) {
$params['password'] = $this->getCrypt()->decrypt($account->get('password'));
$params['imapHandler'] = $account->get('imapHandler');
@@ -117,9 +126,11 @@ class InboundEmail extends \Espo\Services\Record implements
$storage = $this->createStorage($params);
$folders = new RecursiveIteratorIterator($storage->getFolders(), RecursiveIteratorIterator::SELF_FIRST);
foreach ($folders as $name => $folder) {
$foldersArr[] = mb_convert_encoding($folder->getGlobalName(), 'UTF-8', 'UTF7-IMAP');
}
return $foldersArr;
}
@@ -127,6 +138,7 @@ class InboundEmail extends \Espo\Services\Record implements
{
if (!empty($params['id'])) {
$account = $this->getEntityManager()->getEntity('InboundEmail', $params['id']);
if ($account) {
$params['imapHandler'] = $account->get('imapHandler');
}
@@ -137,6 +149,7 @@ class InboundEmail extends \Espo\Services\Record implements
if ($storage->getFolders()) {
return true;
}
throw new Error();
}
@@ -153,25 +166,30 @@ class InboundEmail extends \Espo\Services\Record implements
$maxSize = $this->getConfig()->get('emailMessageMaxSize');
$teamId = $emailAccount->get('teamId');
$userId = null;
if ($emailAccount->get('assignToUserId')) {
$userId = $emailAccount->get('assignToUserId');
}
$userIdList = [];
$teamIdList = $emailAccount->getLinkMultipleIdList('teams');
if (!empty($teamIdList)) {
if ($emailAccount->get('addAllTeamUsers')) {
$userList = $this->getEntityManager()->getRepository('User')->find([
'select' => ['id'],
'whereClause' => [
$userList = $this->getEntityManager()
->getRepository('User')
->select(['id'])
->distinct()
->join('teams')
->where([
'isActive' => true,
'teamsMiddle.teamId' => $teamIdList
],
'distinct' => true,
'joins' => ['teams'],
]);
'teamsMiddle.teamId' => $teamIdList,
])
->find();
foreach ($userList as $user) {
$userIdList[] = $user->id;
}
@@ -182,18 +200,21 @@ class InboundEmail extends \Espo\Services\Record implements
$teamIdList[] = $teamId;
}
$filterCollection = $this->getEntityManager()->getRepository('EmailFilter')->where([
'action' => 'Skip',
'OR' => [
[
'parentType' => $emailAccount->getEntityType(),
'parentId' => $emailAccount->id
$filterCollection = $this->getEntityManager()
->getRepository('EmailFilter')
->where([
'action' => 'Skip',
'OR' => [
[
'parentType' => $emailAccount->getEntityType(),
'parentId' => $emailAccount->id,
],
[
'parentId' => null
],
],
[
'parentId' => null
]
]
])->find();
])
->find();
$fetchData = $emailAccount->get('fetchData');
@@ -239,6 +260,7 @@ class InboundEmail extends \Espo\Services\Record implements
$GLOBALS['log']->error(
'InboundEmail '.$emailAccount->id.' (Select Folder) [' . $e->getCode() . '] ' .$e->getMessage()
);
continue;
}
@@ -266,6 +288,7 @@ class InboundEmail extends \Espo\Services\Record implements
$idList = $storage->getIdsFromUID($lastUID);
} else {
$fetchSince = $emailAccount->get('fetchSince');
if ($lastDate) {
$fetchSince = $lastDate;
}
@@ -296,8 +319,10 @@ class InboundEmail extends \Espo\Services\Record implements
if ($forceByDate && $previousLastUID) {
$uid = $storage->getUniqueId($id);
if ($uid <= $previousLastUID) {
$k++;
continue;
}
}
@@ -379,6 +404,7 @@ class InboundEmail extends \Espo\Services\Record implements
} else {
if ($emailAccount->get('reply')) {
$user = $this->getEntityManager()->getEntity('User', $userId);
$this->autoReply($emailAccount, $email, $user);
}
}
@@ -394,15 +420,18 @@ class InboundEmail extends \Espo\Services\Record implements
if ($email && $email->get('dateSent')) {
$dt = null;
try {
$dt = new DateTime($email->get('dateSent'));
} catch (Exception $e) {}
if ($dt) {
$nowDt = new DateTime();
if ($dt->getTimestamp() >= $nowDt->getTimestamp()) {
$dt = $nowDt;
}
$dateSent = $dt->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s');
$lastDate = $dateSent;
}
@@ -416,6 +445,7 @@ class InboundEmail extends \Espo\Services\Record implements
if ($forceByDate) {
$nowDt = new DateTime();
$lastDate = $nowDt->format('Y-m-d H:i:s');
}
@@ -425,8 +455,10 @@ class InboundEmail extends \Espo\Services\Record implements
if ($forceByDate) {
if ($previousLastUID) {
$idList = $storage->getIdsFromUID($previousLastUID);
if (count($idList)) {
$uid1 = $storage->getUniqueId($idList[0]);
if ($uid1 && $uid1 > $previousLastUID) {
unset($fetchData->byDate->$folder);
}
@@ -453,6 +485,7 @@ class InboundEmail extends \Espo\Services\Record implements
$filterCollection, $fetchOnlyHeader, $folderData = null
) {
$email = null;
try {
$email = $importer->importMessage(
$message, $userId, $teamIdList, $userIdList, $filterCollection, $fetchOnlyHeader, $folderData
@@ -475,8 +508,10 @@ class InboundEmail extends \Espo\Services\Record implements
{
if ($email->get('parentType') && $email->get('parentId')) {
$parent = $this->getEntityManager()->getEntity($email->get('parentType'), $email->get('parentId'));
if ($parent) {
$this->getServiceFactory()->create('Stream')->noteEmailReceived($parent, $email);
return;
}
}
@@ -490,6 +525,7 @@ class InboundEmail extends \Espo\Services\Record implements
$userIdList = $case->getLinkMultipleIdList('assignedUsers');
} else {
$assignedUserId = $case->get('assignedUserId');
if ($assignedUserId) {
$userIdList[] = $assignedUserId;
}
@@ -515,48 +551,58 @@ class InboundEmail extends \Espo\Services\Record implements
{
if ($email->get('parentType') == 'Case' && $email->get('parentId')) {
$case = $this->getEntityManager()->getEntity('Case', $email->get('parentId'));
if ($case) {
$this->processCaseToEmailFields($case, $email);
if (!$email->isFetched()) {
$this->getServiceFactory()->create('Stream')->noteEmailReceived($case, $email);
}
}
return;
}
if (preg_match('/\[#([0-9]+)[^0-9]*\]/', $email->get('name'), $m)) {
$caseNumber = $m[1];
$case = $this->getEntityManager()->getRepository('Case')->where([
'number' => $caseNumber
])->findOne();
$case = $this->getEntityManager()
->getRepository('Case')
->where([
'number' => $caseNumber,
])
->findOne();
if ($case) {
$email->set('parentType', 'Case');
$email->set('parentId', $case->id);
$this->processCaseToEmailFields($case, $email);
if (!$email->isFetched()) {
$this->getServiceFactory()->create('Stream')->noteEmailReceived($case, $email);
}
}
} else {
$params = [
'caseDistribution' => $inboundEmail->get('caseDistribution'),
'teamId' => $inboundEmail->get('teamId'),
'userId' => $inboundEmail->get('assignToUserId'),
'targetUserPosition' => $inboundEmail->get('targetUserPosition'),
'inboundEmailId' => $inboundEmail->id
];
$case = $this->emailToCase($email, $params);
return;
}
$user = $this->getEntityManager()->getEntity('User', $case->get('assignedUserId'));
$params = [
'caseDistribution' => $inboundEmail->get('caseDistribution'),
'teamId' => $inboundEmail->get('teamId'),
'userId' => $inboundEmail->get('assignToUserId'),
'targetUserPosition' => $inboundEmail->get('targetUserPosition'),
'inboundEmailId' => $inboundEmail->id,
];
$this->getServiceFactory()->create('Stream')->noteEmailReceived($case, $email, true);
$case = $this->emailToCase($email, $params);
if ($inboundEmail->get('reply')) {
$this->autoReply($inboundEmail, $email, $case, $user);
}
$user = $this->getEntityManager()->getEntity('User', $case->get('assignedUserId'));
$this->getServiceFactory()->create('Stream')->noteEmailReceived($case, $email, true);
if ($inboundEmail->get('reply')) {
$this->autoReply($inboundEmail, $email, $case, $user);
}
}
@@ -581,6 +627,7 @@ class InboundEmail extends \Espo\Services\Record implements
protected function assignLeastBusy(Entity $case, Team $team, $targetUserPosition)
{
$className = 'Espo\\Custom\\Business\\Distribution\\CaseObj\\LeastBusy';
if (!class_exists($className)) {
$className = 'Espo\\Modules\\Crm\\Business\\Distribution\\CaseObj\\LeastBusy';
}
@@ -595,10 +642,12 @@ class InboundEmail extends \Espo\Services\Record implements
}
}
protected function emailToCase(\Espo\Entities\Email $email, array $params = [])
protected function emailToCase(EmailEntity $email, array $params = [])
{
$case = $this->getEntityManager()->getEntity('Case');
$case->populateDefaults();
$case->set('name', $email->get('name'));
$bodyPlain = $email->getBodyPlain();
@@ -616,8 +665,13 @@ class InboundEmail extends \Espo\Services\Record implements
foreach ($attachmentIdList as $attachmentId) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->get($attachmentId);
if (!$attachment) continue;
if (!$attachment) {
continue;
}
$copiedAttachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
$copiedAttachmentIdList[] = $copiedAttachment->id;
}
@@ -626,6 +680,7 @@ class InboundEmail extends \Espo\Services\Record implements
}
$userId = null;
if (!empty($params['userId'])) {
$userId = $params['userId'];
}
@@ -635,19 +690,23 @@ class InboundEmail extends \Espo\Services\Record implements
}
$teamId = false;
if (!empty($params['teamId'])) {
$teamId = $params['teamId'];
}
if ($teamId) {
$case->set('teamsIds', [$teamId]);
}
$caseDistribution = '';
if (!empty($params['caseDistribution'])) {
$caseDistribution = $params['caseDistribution'];
}
$targetUserPosition = null;
if (!empty($params['targetUserPosition'])) {
$targetUserPosition = $params['targetUserPosition'];
}
@@ -660,21 +719,26 @@ class InboundEmail extends \Espo\Services\Record implements
$case->set('status', 'Assigned');
}
break;
case 'Round-Robin':
if ($teamId) {
$team = $this->getEntityManager()->getEntity('Team', $teamId);
if ($team) {
$this->assignRoundRobin($case, $team, $targetUserPosition);
}
}
break;
case 'Least-Busy':
if ($teamId) {
$team = $this->getEntityManager()->getEntity('Team', $teamId);
if ($team) {
$this->assignLeastBusy($case, $team, $targetUserPosition);
}
}
break;
}
@@ -686,18 +750,26 @@ class InboundEmail extends \Espo\Services\Record implements
$case->set('accountId', $email->get('accountId'));
}
$contact = $this->getEntityManager()->getRepository('Contact')->join('emailAddresses', 'emailAddressesMultiple')->where([
'emailAddressesMultiple.id' => $email->get('fromEmailAddressId')
])->findOne();
$contact = $this->getEntityManager()
->getRepository('Contact')
->join('emailAddresses', 'emailAddressesMultiple')
->where([
'emailAddressesMultiple.id' => $email->get('fromEmailAddressId')
])
->findOne();
if ($contact) {
$case->set('contactId', $contact->id);
} else {
if (!$case->get('accountId')) {
$lead = $this->getEntityManager()->getRepository('Lead')
->join('emailAddresses', 'emailAddressesMultiple')->where([
'emailAddressesMultiple.id' => $email->get('fromEmailAddressId')
])->findOne();
$lead = $this->getEntityManager()
->getRepository('Lead')
->join('emailAddresses', 'emailAddressesMultiple')
->where([
'emailAddressesMultiple.id' => $email->get('fromEmailAddressId')
])
->findOne();
if ($lead) {
$case->set('leadId', $lead->id);
}
@@ -726,16 +798,22 @@ class InboundEmail extends \Espo\Services\Record implements
}
$d = new DateTime();
$d->modify('-3 hours');
$threshold = $d->format('Y-m-d H:i:s');
$emailAddress = $this->getEntityManager()->getRepository('EmailAddress')->getByAddress($email->get('from'));
$sent = $this->getEntityManager()->getRepository('Email')->where([
'toEmailAddresses.id' => $emailAddress->id,
'dateSent>' => $threshold,
'status' => 'Sent'
])->join('toEmailAddresses')->findOne();
$sent = $this->getEntityManager()
->getRepository('Email')
->where([
'toEmailAddresses.id' => $emailAddress->id,
'dateSent>' => $threshold,
'status' => 'Sent',
])
->join('toEmailAddresses')
->findOne();
if ($sent) {
return false;
@@ -743,17 +821,23 @@ class InboundEmail extends \Espo\Services\Record implements
try {
$replyEmailTemplateId = $inboundEmail->get('replyEmailTemplateId');
if ($replyEmailTemplateId) {
$entityHash = [];
if ($case) {
$entityHash['Case'] = $case;
if ($case->get('contactId')) {
$contact = $this->getEntityManager()->getEntity('Contact', $case->get('contactId'));
}
}
if (empty($contact)) {
$contact = $this->getEntityManager()->getEntity('Contact');
$fromName = \Espo\Services\Email::parseFromName($email->get('fromString'));
$fromName = EmailService::parseFromName($email->get('fromString'));
if (!empty($fromName)) {
$contact->set('name', $fromName);
}
@@ -771,11 +855,13 @@ class InboundEmail extends \Espo\Services\Record implements
$replyData = $emailTemplateService->parse($replyEmailTemplateId, ['entityHash' => $entityHash], true);
$subject = $replyData['subject'];
if ($case) {
$subject = '[#' . $case->get('number'). '] ' . $subject;
}
$reply = $this->getEntityManager()->getEntity('Email');
$reply->set('to', $email->get('from'));
$reply->set('subject', $subject);
$reply->set('body', $replyData['body']);
@@ -829,8 +915,9 @@ class InboundEmail extends \Espo\Services\Record implements
return true;
}
} catch (Exception $e) {}
} catch (Exception $e) {
$GLOBALS['log']->error("Inbound Email: Auto-reply error: " . $e->getMessage());
}
}
protected function getSmtpParamsFromInboundEmail(InboundEmailEntity $emailAccount)
@@ -844,9 +931,11 @@ class InboundEmail extends \Espo\Services\Record implements
$smtpParams['security'] = $emailAccount->get('smtpSecurity');
$smtpParams['username'] = $emailAccount->get('smtpUsername');
$smtpParams['password'] = $emailAccount->get('smtpPassword');
if (array_key_exists('password', $smtpParams)) {
$smtpParams['password'] = $this->getCrypt()->decrypt($smtpParams['password']);
}
return $smtpParams;
}
@@ -858,6 +947,7 @@ class InboundEmail extends \Espo\Services\Record implements
$content = $message->getRawContent();
$isHard = false;
if (preg_match('/permanent[ ]*[error|failure]/', $content)) {
$isHard = true;
}
@@ -876,15 +966,21 @@ class InboundEmail extends \Espo\Services\Record implements
}
}
if (!$queueItemId) return false;
if (!$queueItemId) {
return false;
}
$queueItem = $this->getEntityManager()->getEntity('EmailQueueItem', $queueItemId);
if (!$queueItem) return false;
if (!$queueItem) {
return false;
}
$massEmailId = $queueItem->get('massEmailId');
$massEmail = $this->getEntityManager()->getEntity('MassEmail', $massEmailId);
$campaignId = null;
if ($massEmail) {
$campaignId = $massEmail->get('campaignId');
}
@@ -897,13 +993,19 @@ class InboundEmail extends \Espo\Services\Record implements
if ($isHard && $emailAddress) {
$emailAddressEntity = $this->getEntityManager()->getRepository('EmailAddress')->getByAddress($emailAddress);
$emailAddressEntity->set('invalid', true);
$this->getEntityManager()->saveEntity($emailAddressEntity);
if ($emailAddressEntity) {
$emailAddressEntity->set('invalid', true);
$this->getEntityManager()->saveEntity($emailAddressEntity);
}
}
if ($campaignId && $target && $target->id) {
$this->getCampaignService()
->logBounced($campaignId, $queueItemId, $target, $emailAddress, $isHard, null, $queueItem->get('isTest'));
->logBounced(
$campaignId, $queueItemId, $target, $emailAddress, $isHard, null, $queueItem->get('isTest')
);
}
return true;
@@ -918,14 +1020,17 @@ class InboundEmail extends \Espo\Services\Record implements
return $this->campaignService;
}
public function findAccountForSending(string $emailAddress) : ?\Espo\Entities\InboundEmail
public function findAccountForSending(string $emailAddress) : ?InboundEmailEntity
{
$inboundEmail = $this->getEntityManager()->getRepository('InboundEmail')->where([
'status' => 'Active',
'useSmtp' => true,
'smtpHost!=' => null,
'emailAddress' => $emailAddress,
])->findOne();
$inboundEmail = $this->getEntityManager()
->getRepository('InboundEmail')
->where([
'status' => 'Active',
'useSmtp' => true,
'smtpHost!=' => null,
'emailAddress' => $emailAddress,
])
->findOne();
return $inboundEmail;
}
@@ -933,6 +1038,7 @@ class InboundEmail extends \Espo\Services\Record implements
public function findSharedAccountForUser(User $user, $emailAddress)
{
$groupEmailAccountPermission = $this->getAclManager()->get($user, 'groupEmailAccountPermission');
$teamIdList = $user->getLinkMultipleIdList('teams');
$inboundEmail = null;
@@ -941,17 +1047,20 @@ class InboundEmail extends \Espo\Services\Record implements
if ($groupEmailAccountPermission && $groupEmailAccountPermission !== 'no') {
if ($groupEmailAccountPermission === 'team') {
if (!count($teamIdList)) return;
if (!count($teamIdList)) {
return;
}
$selectParams = [
'whereClause' => [
'status' => 'Active',
'useSmtp' => true,
'smtpIsShared' => true,
'teamsMiddle.teamId' => $teamIdList,
'emailAddress' => $emailAddress
'emailAddress' => $emailAddress,
],
'joins' => ['teams'],
'distinct' => true
'distinct' => true,
];
} else if ($groupEmailAccountPermission === 'all') {
$selectParams = [
@@ -959,7 +1068,7 @@ class InboundEmail extends \Espo\Services\Record implements
'status' => 'Active',
'useSmtp' => true,
'smtpIsShared' => true,
'emailAddress' => $emailAddress
'emailAddress' => $emailAddress,
]
];
}
@@ -967,6 +1076,7 @@ class InboundEmail extends \Espo\Services\Record implements
$inboundEmail = $this->getEntityManager()->getRepository('InboundEmail')->findOne($selectParams);
}
return $inboundEmail;
}
@@ -1034,9 +1144,11 @@ class InboundEmail extends \Espo\Services\Record implements
$storage = $this->getStorage($emailAccount);
$folder = $emailAccount->get('sentFolder');
if (empty($folder)) {
throw new Error("No sent folder for Email Account: " . $emailAccount->id . ".");
}
$storage->appendMessage($message->toString(), $folder);
}
@@ -1044,6 +1156,7 @@ class InboundEmail extends \Espo\Services\Record implements
{
$smtpParams = [];
$smtpParams['server'] = $emailAccount->get('smtpHost');
if ($smtpParams['server']) {
$smtpParams['port'] = $emailAccount->get('smtpPort');
$smtpParams['auth'] = $emailAccount->get('smtpAuth');
@@ -1058,9 +1171,11 @@ class InboundEmail extends \Espo\Services\Record implements
if ($emailAccount->get('fromName')) {
$smtpParams['fromName'] = $emailAccount->get('fromName');
}
if ($emailAccount->get('emailAddress')) {
$smtpParams['fromAddress'] = $emailAccount->get('emailAddress');
}
if (array_key_exists('password', $smtpParams) && is_string($smtpParams['password'])) {
$smtpParams['password'] = $this->getCrypt()->decrypt($smtpParams['password']);
}
@@ -1077,7 +1192,9 @@ class InboundEmail extends \Espo\Services\Record implements
{
$handlerClassName = $emailAccount->get('smtpHandler');
if (!$handlerClassName) return;
if (!$handlerClassName) {
return;
}
try {
$handler = $this->injectableFactory->create($handlerClassName);
+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;
+184 -44
View File
@@ -59,6 +59,7 @@ use Espo\Core\{
use Espo\Tools\Export\Export as ExportTool;
use StdClass;
use Exception;
use Espo\Core\Di;
@@ -705,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');
@@ -723,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;
}
}
@@ -1245,13 +1247,19 @@ class Record implements Crud,
public function delete(string $id)
{
if (empty($id)) throw new BadRequest("ID is empty.");
if (empty($id)) {
throw new BadRequest("ID is empty.");
}
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFound("Record {$id} not found.");
if (!$entity) {
throw new NotFound("Record {$id} not found.");
}
if (!$this->getAcl()->check($entity, 'delete')) throw new ForbiddenSilent("No delete access.");
if (!$this->getAcl()->check($entity, 'delete')) {
throw new ForbiddenSilent("No delete access.");
}
$this->beforeDeleteEntity($entity);
@@ -1292,6 +1300,7 @@ class Record implements Crud,
}
$maxSize = 0;
if ($disableCount) {
if (!empty($params['maxSize'])) {
$maxSize = $params['maxSize'];
@@ -1376,10 +1385,16 @@ class Record implements Crud,
];
foreach ($statusList as $status) {
if (in_array($status, $statusIgnoreList)) continue;
if (!$status) continue;
if (in_array($status, $statusIgnoreList)) {
continue;
}
if (!$status) {
continue;
}
$selectParamsSub = $selectParams;
$selectParamsSub['whereClause'][] = [
$statusField => $status
];
@@ -1454,12 +1469,19 @@ class Record implements Crud,
public function restoreDeleted(string $id)
{
if (!$this->getUser()->isAdmin()) throw new Forbidden();
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
$entity = $this->getEntityEvenDeleted($id);
if (!$entity) throw new NotFound();
if (!$entity->get('deleted')) throw new Forbidden();
if (!$entity) {
throw new NotFound();
}
if (!$entity->get('deleted')) {
throw new Forbidden();
}
$this->getRepository()->restoreDeleted($entity->id);
@@ -1475,6 +1497,7 @@ class Record implements Crud,
return $this->getConfig()->get('maxSelectTextAttributeLengthForList', self::MAX_SELECT_TEXT_ATTRIBUTE_LENGTH);
}
}
return null;
}
@@ -1487,12 +1510,15 @@ class Record implements Crud,
public function findLinked(string $id, string $link, array $params) : RecordCollection
{
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
if (empty($link)) {
throw new Error();
}
@@ -1515,6 +1541,7 @@ class Record implements Crud,
}
$methodName = 'findLinkedEntities' . ucfirst($link);
if (method_exists($this, $methodName)) {
return $this->$methodName($id, $params);
}
@@ -1638,35 +1665,45 @@ class Record implements Crud,
}
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if ($this->noEditAccessRequiredForLink) {
if (!$this->getAcl()->check($entity, 'read')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
} else {
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'edit')) {
throw new Forbidden();
}
}
$methodName = 'link' . ucfirst($link);
if ($link !== 'entity' && $link !== 'entityMass' && method_exists($this, $methodName)) {
return $this->$methodName($id, $foreignId);
}
$foreignEntityType = $entity->getRelationParam($link, 'entity');
if (!$foreignEntityType) {
throw new Error("Entity '{$this->entityType}' has not relation '{$link}'.");
}
$foreignEntity = $this->getEntityManager()->getEntity($foreignEntityType, $foreignId);
if (!$foreignEntity) {
throw new NotFound();
}
$accessActionRequired = 'edit';
if (in_array($link, $this->noEditAccessRequiredLinkList)) {
$accessActionRequired = 'read';
}
if (!$this->getAcl()->check($foreignEntity, $accessActionRequired)) {
throw new Forbidden();
}
@@ -1712,30 +1749,39 @@ class Record implements Crud,
}
if ($this->noEditAccessRequiredForLink) {
if (!$this->getAcl()->check($entity, 'read')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
} else {
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'edit')) {
throw new Forbidden();
}
}
$methodName = 'unlink' . ucfirst($link);
if ($link !== 'entity' && method_exists($this, $methodName)) {
return $this->$methodName($id, $foreignId);
}
$foreignEntityType = $entity->getRelationParam($link, 'entity');
if (!$foreignEntityType) {
throw new Error("Entity '{$this->entityType}' has not relation '{$link}'.");
}
$foreignEntity = $this->getEntityManager()->getEntity($foreignEntityType, $foreignId);
if (!$foreignEntity) {
throw new NotFound();
}
$accessActionRequired = 'edit';
if (in_array($link, $this->noEditAccessRequiredLinkList)) {
$accessActionRequired = 'read';
}
if (!$this->getAcl()->check($foreignEntity, $accessActionRequired)) {
throw new Forbidden();
}
@@ -1756,6 +1802,7 @@ class Record implements Crud,
if (!$this->getMetadata()->get(['scopes', $this->entityType, 'stream'])) throw new NotFound();
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFound();
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'stream')) throw new Forbidden();
@@ -1776,6 +1823,7 @@ class Record implements Crud,
if (!$this->getMetadata()->get(['scopes', $this->entityType, 'stream'])) throw new NotFound();
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFound();
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'stream')) throw new Forbidden();
@@ -1810,14 +1858,17 @@ class Record implements Crud,
}
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if (!$this->getAcl()->check($entity, 'edit')) {
throw new Forbidden();
}
$methodName = 'massLink' . ucfirst($link);
if (method_exists($this, $methodName)) {
return $this->$methodName($id, $where, $selectData);
}
@@ -1874,7 +1925,9 @@ class Record implements Crud,
public function massUpdate(array $params, StdClass $data)
{
if ($this->getAcl()->get('massUpdatePermission') !== 'yes') throw new Forbidden();
if ($this->getAcl()->get('massUpdatePermission') !== 'yes') {
throw new Forbidden();
}
$resultIdList = [];
$repository = $this->getRepository();
@@ -1891,11 +1944,13 @@ class Record implements Crud,
foreach ($collection as $entity) {
if ($this->getAcl()->check($entity, 'edit') && $this->checkEntityForMassUpdate($entity, $data)) {
$entity->set($data);
try {
$this->processValidation($entity, $data);
} catch (\Exception $e) {
} catch (Exception $e) {
continue;
}
if ($this->checkAssignment($entity)) {
$repository->save($entity, ['massUpdate' => true, 'skipStreamNotesAcl' => true]);
$resultIdList[] = $entity->id;
@@ -1944,8 +1999,11 @@ class Record implements Crud,
foreach ($collection as $entity) {
if ($this->getAcl()->check($entity, 'delete') && $this->checkEntityForMassRemove($entity)) {
$repository->remove($entity);
$resultIdList[] = $entity->id;
$count++;
$this->processActionHistoryRecord('delete', $entity);
}
}
@@ -1960,7 +2018,9 @@ class Record implements Crud,
public function massRecalculateFormula(array $params)
{
if (!$this->getUser()->isAdmin()) throw new Forbidden();
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
$count = 0;
@@ -1981,7 +2041,10 @@ class Record implements Crud,
public function follow(string $id, ?string $userId = null)
{
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFoundSilent();
if (!$entity) {
throw new NotFoundSilent();
}
if (!$this->getAcl()->check($entity, 'stream')) {
throw new Forbidden();
@@ -1997,7 +2060,10 @@ class Record implements Crud,
public function unfollow(string $id, ?string $userId = null)
{
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFoundSilent();
if (!$entity) {
throw new NotFoundSilent();
}
if (empty($userId)) {
$userId = $this->getUser()->id;
@@ -2021,7 +2087,10 @@ class Record implements Crud,
$collection = $this->getRepository()->sth()->find($selectParams);
foreach ($collection as $entity) {
if (!$this->getAcl()->check($entity, 'stream') || !$this->getAcl()->check($entity, 'read')) continue;
if (!$this->getAcl()->check($entity, 'stream') || !$this->getAcl()->check($entity, 'read')) {
continue;
}
if ($streamService->followEntity($entity, $userId)) {
$resultIdList[] = $entity->id;
}
@@ -2031,7 +2100,9 @@ class Record implements Crud,
'count' => count($resultIdList),
];
if (isset($params['ids'])) $result['ids'] = $resultIdList;
if (isset($params['ids'])) {
$result['ids'] = $resultIdList;
}
return $result;
}
@@ -2042,7 +2113,9 @@ class Record implements Crud,
$streamService = $this->getStreamService();
if (empty($userId)) $userId = $this->getUser()->id;
if (empty($userId)) {
$userId = $this->getUser()->id;
}
$selectParams = $this->convertMassActionSelectParams($params);
@@ -2066,24 +2139,36 @@ class Record implements Crud,
protected function convertMassActionSelectParams($params)
{
if (array_key_exists('ids', $params)) {
if (!is_array($params['ids'])) throw new BadRequest();
$selectParams = $this->getSelectParams([]);
$selectParams['whereClause'][] = [
'id' => $params['ids']
];
} else if (array_key_exists('where', $params)) {
$p = ['where' => $params['where']];
if (!empty($params['selectData']) && is_array($params['selectData'])) {
foreach ($params['selectData'] as $k => $v) {
$p[$k] = $v;
}
if (!is_array($params['ids'])) {
throw new BadRequest();
}
$selectParams = $this->getSelectParams($p);
} else {
throw new BadRequest();
$selectParams = $this->getSelectParams([]);
$selectParams['whereClause'][] = [
'id' => $params['ids'],
];
return $selectParams;
}
return $selectParams;
if (array_key_exists('where', $params)) {
$searchParams = [
'where' => $params['where'],
];
if (!empty($params['selectData']) && is_array($params['selectData'])) {
foreach ($params['selectData'] as $k => $v) {
$searchParams[$k] = $v;
}
}
unset($searchParams['select']);
return $this->getSelectParams($searchParams);
}
throw new BadRequest();
}
protected function getDuplicateWhereClause(Entity $entity, $data)
@@ -2099,7 +2184,12 @@ class Record implements Crud,
if ($entity->id) {
$where['id!='] = $entity->id;
}
$duplicate = $this->getRepository()->select(['id'])->where($where)->findOne();
$duplicate = $this->getRepository()
->select(['id'])
->where($where)
->findOne();
if ($duplicate) {
return true;
}
@@ -2133,7 +2223,9 @@ class Record implements Crud,
$limit = self::FIND_DUPLICATES_LIMIT;
$duplicateList = $this->getRepository()->limit(0, $limit)->find($selectParams);
$duplicateList = $this->getRepository()
->limit(0, $limit)
->find($selectParams);
if (count($duplicateList)) {
return $duplicateList;
@@ -2168,14 +2260,17 @@ class Record implements Crud,
foreach ($this->internalAttributeList as $attribute) {
$entity->clear($attribute);
}
foreach ($this->forbiddenAttributeList as $attribute) {
$entity->clear($attribute);
}
if (!$this->getUser()->isAdmin()) {
foreach ($this->onlyAdminAttributeList as $attribute) {
$entity->clear($attribute);
}
}
foreach ($this->getAcl()->getScopeForbiddenAttributeList($entity->getEntityType(), 'read') as $attribute) {
$entity->clear($attribute);
}
@@ -2216,14 +2311,16 @@ class Record implements Crud,
$this->beforeMerge($entity, $sourceList, $attributes);
$fieldDefs = $this->getMetadata()->get('entityDefs.' . $entity->getEntityType() . '.fields', array());
$fieldDefs = $this->getMetadata()->get('entityDefs.' . $entity->getEntityType() . '.fields', []);
$hasPhoneNumber = false;
if (!empty($fieldDefs['phoneNumber']) && $fieldDefs['phoneNumber']['type'] == 'phone') {
$hasPhoneNumber = true;
}
$hasEmailAddress = false;
if (!empty($fieldDefs['emailAddress']) && $fieldDefs['emailAddress']['type'] == 'email') {
$hasEmailAddress = true;
}
@@ -2239,6 +2336,7 @@ class Record implements Crud,
if ($hasEmailAddress) {
$emailAddressToRelateList = [];
$emailAddressList = $repository->findRelated($entity, 'emailAddresses');
foreach ($emailAddressList as $emailAddress) {
$emailAddressToRelateList[] = $emailAddress;
}
@@ -2266,12 +2364,14 @@ class Record implements Crud,
if ($hasPhoneNumber) {
$phoneNumberList = $repository->findRelated($source, 'phoneNumbers');
foreach ($phoneNumberList as $phoneNumber) {
$phoneNumberToRelateList[] = $phoneNumber;
}
}
if ($hasEmailAddress) {
$emailAddressList = $repository->findRelated($source, 'emailAddresses');
foreach ($emailAddressList as $emailAddress) {
$emailAddressToRelateList[] = $emailAddress;
}
@@ -2280,10 +2380,12 @@ class Record implements Crud,
$mergeLinkList = [];
$linksDefs = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'links']);
foreach ($linksDefs as $link => $d) {
if (!empty($d['notMergeable'])) {
continue;
}
if (!empty($d['type']) && in_array($d['type'], ['hasMany', 'hasChildren'])) {
$mergeLinkList[] = $link;
}
@@ -2292,6 +2394,7 @@ class Record implements Crud,
foreach ($sourceList as $source) {
foreach ($mergeLinkList as $link) {
$linkedList = $repository->findRelated($source, $link);
foreach ($linkedList as $linked) {
$repository->relate($entity, $link, $linked);
}
@@ -2306,10 +2409,13 @@ class Record implements Crud,
if ($hasEmailAddress) {
$emailAddressData = [];
foreach ($emailAddressToRelateList as $i => $emailAddress) {
$o = (object) [];
$o->emailAddress = $emailAddress->get('name');
$o->primary = false;
if (empty($attributes->emailAddress)) {
if ($i === 0) {
$o->primary = true;
@@ -2317,8 +2423,10 @@ class Record implements Crud,
} else {
$o->primary = $o->emailAddress === $attributes->emailAddress;
}
$o->optOut = $emailAddress->get('optOut');
$o->invalid = $emailAddress->get('invalid');
$emailAddressData[] = $o;
}
$attributes->emailAddressData = $emailAddressData;
@@ -2326,10 +2434,12 @@ class Record implements Crud,
if ($hasPhoneNumber) {
$phoneNumberData = [];
foreach ($phoneNumberToRelateList as $i => $phoneNumber) {
$o = (object) [];
$o->phoneNumber = $phoneNumber->get('name');
$o->primary = false;
if (empty($attributes->phoneNumber)) {
if ($i === 0) {
$o->primary = true;
@@ -2337,7 +2447,9 @@ class Record implements Crud,
} else {
$o->primary = $o->phoneNumber === $attributes->phoneNumber;
}
$o->type = $phoneNumber->get('type');
$phoneNumberData[] = $o;
}
$attributes->phoneNumberData = $phoneNumberData;
@@ -2364,9 +2476,11 @@ class Record implements Crud,
protected function findLinkedFollowers($id, $params)
{
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
@@ -2396,32 +2510,51 @@ class Record implements Crud,
foreach ($fields as $field => $item) {
if (!empty($item['duplicateIgnore']) || in_array($field, $this->duplicateIgnoreFieldList)) {
$attributeToIgnoreList = $fieldManager->getAttributeList($this->entityType, $field);
foreach ($attributeToIgnoreList as $attribute) {
unset($attributes->$attribute);
}
continue;
}
if (empty($item['type'])) {
continue;
}
if (empty($item['type'])) continue;
$type = $item['type'];
if (in_array($type, ['file', 'image'])) {
$attachment = $entity->get($field);
if ($attachment) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
$attachment = $this->getEntityManager()
->getRepository('Attachment')
->getCopiedAttachment($attachment);
$idAttribute = $field . 'Id';
if ($attachment) {
$attributes->$idAttribute = $attachment->id;
}
}
} else if (in_array($type, ['attachmentMultiple'])) {
$attachmentList = $entity->get($field);
if (count($attachmentList)) {
$idList = [];
$nameHash = (object) [];
$typeHash = (object) [];
foreach ($attachmentList as $attachment) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
$attachment = $this->getEntityManager()
->getRepository('Attachment')
->getCopiedAttachment($attachment);
$attachment->set('field', $field);
$this->getEntityManager()->saveEntity($attachment);
if ($attachment) {
$idList[] = $attachment->id;
$nameHash->{$attachment->id} = $attachment->get('name');
@@ -2435,8 +2568,12 @@ class Record implements Crud,
} else if ($type === 'linkMultiple') {
$foreignLink = $entity->getRelationParam($field, 'foreign');
$foreignEntityType = $entity->getRelationParam($field, 'entity');
if ($foreignEntityType && $foreignLink) {
$foreignRelationType = $this->getMetadata()->get(['entityDefs', $foreignEntityType, 'links', $foreignLink, 'type']);
$foreignRelationType = $this->getMetadata()->get(
['entityDefs', $foreignEntityType, 'links', $foreignLink, 'type']
);
if ($foreignRelationType !== 'hasMany') {
unset($attributes->{$field . 'Ids'});
unset($attributes->{$field . 'Names'});
@@ -2461,7 +2598,9 @@ class Record implements Crud,
$duplicatingEntityId = $data->_duplicatingEntityId;
if (!$duplicatingEntityId) return;
$duplicatingEntity = $this->getEntityManager()->getEntity($entity->getEntityType(), $duplicatingEntityId);
if (!$duplicatingEntity) return;
if (!$this->getAcl()->check($duplicatingEntity, 'read')) return;
@@ -2474,6 +2613,7 @@ class Record implements Crud,
foreach ($this->duplicatingLinkList as $link) {
$linkedList = $repository->findRelated($duplicatingEntity, $link);
foreach ($linkedList as $linked) {
$repository->relate($entity, $link, $linked);
}
+20 -12
View File
@@ -131,6 +131,8 @@ class Export
$collection = null;
$exportAllFields = !array_key_exists('fieldList', $params);
if (array_key_exists('collection', $params)) {
$collection = $params['collection'];
} else {
@@ -138,26 +140,33 @@ class Export
if (array_key_exists('ids', $params)) {
$ids = $params['ids'];
$where = [
[
'type' => 'in',
'field' => 'id',
'value' => $ids
'value' => $ids,
]
];
$selectParams = $selectManager->getSelectParams(['where' => $where], true, true);
$selectParams = $selectManager->getSelectParams(['where' => $where], true, true, true);
}
else if (array_key_exists('where', $params)) {
$where = $params['where'];
$p = [];
$p['where'] = $where;
$searchParams = [];
$searchParams['where'] = $where;
if (!empty($params['selectData']) && is_array($params['selectData'])) {
foreach ($params['selectData'] as $k => $v) {
$p[$k] = $v;
$searchParams[$k] = $v;
}
}
$selectParams = $this->getSelectParams($p);
unset($searchParams['select']);
$selectParams = $selectManager->getSelectParams($searchParams, true, true, true);
}
else {
throw new BadRequest();
@@ -172,14 +181,15 @@ class Export
$select = Select::fromRaw($selectParams);
$collection = $this->entityManager->getRepository($this->entityType)
$collection = $this->entityManager
->getRepository($this->entityType)
->clone($select)
->sth()
->find();
}
$attributeListToSkip = [
'deleted'
'deleted',
];
foreach ($this->skipAttributeList as $attribute) {
@@ -208,15 +218,12 @@ class Export
}
}
if (!array_key_exists('fieldList', $params)) {
$exportAllFields = true;
if ($exportAllFields) {
$fieldDefs = $this->metadata->get(['entityDefs', $this->entityType, 'fields'], []);
$fieldList = array_keys($fieldDefs);
array_unshift($fieldList, 'id');
} else {
$exportAllFields = false;
$fieldList = $params['fieldList'];
}
@@ -225,6 +232,7 @@ class Export
unset($fieldList[$i]);
}
}
$fieldList = array_values($fieldList);
if (method_exists($exportObj, 'filterFieldList')) {
@@ -56,7 +56,11 @@ class Csv
public function loadAdditionalFields(Entity $entity, $fieldList)
{
foreach ($fieldList as $field) {
if ($this->metadata->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']) === 'linkMultiple') {
$fieldType = $this->metadata->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
if (
$fieldType === 'linkMultiple' || $fieldType === 'attachmentMultiple'
) {
if (!$entity->has($field . 'Ids')) {
$entity->loadLinkMultipleField($field);
}
+25 -7
View File
@@ -113,7 +113,9 @@ class Xlsx
}
}
foreach ($fieldList as $field) {
if ($this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']) === 'linkMultiple') {
$fieldType = $this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
if ($fieldType === 'linkMultiple' || $fieldType === 'attachmentMultiple') {
if (!$entity->has($field . 'Ids')) {
$entity->loadLinkMultipleField($field);
}
@@ -348,21 +350,24 @@ class Xlsx
} else if ($type == 'currency') {
if (array_key_exists($name.'Currency', $row) && array_key_exists($name, $row)) {
$sheet->setCellValue("$col$rowNumber", $row[$name] ? $row[$name] : '');
$currency = $row[$name . 'Currency'];
$currencySymbol = $this->getMetadata()->get(['app', 'currency', 'symbolMap', $currency], '');
$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') {
if (array_key_exists($name, $row)) {
$currency = $this->getConfig()->get('defaultCurrency');
$currencySymbol = $this->getMetadata()->get(['app', 'currency', 'symbolMap', $currency], '');
$sheet->getStyle("$col$rowNumber")
->getNumberFormat()
->setFormatCode('[$'.$currencySymbol.'-409]#,##0.00;-[$'.$currencySymbol.'-409]#,##0.00');
->setFormatCode(
$this->getCurrencyFormatCode($currency)
);
$sheet->setCellValue("$col$rowNumber", $row[$name] ? $row[$name] : '');
}
@@ -442,7 +447,7 @@ class Xlsx
}
$sheet->setCellValue("$col$rowNumber", $value);
}
} else if ($type == 'linkMultiple') {
} else if ($type == 'linkMultiple' || $type == 'attachmentMultiple') {
if (array_key_exists($name . 'Ids', $row) && array_key_exists($name . 'Names', $row)) {
$nameList = [];
foreach ($row[$name . 'Ids'] as $relatedId) {
@@ -661,4 +666,17 @@ class Xlsx
return $xlsx;
}
protected function getCurrencyFormatCode(string $currency) : string
{
$currencySymbol = $this->getMetadata()->get(['app', 'currency', 'symbolMap', $currency], '');
$currencyFormat = $this->getConfig()->get('currencyFormat') ?? 2;
if ($currencyFormat == 3) {
return '#,##0.00_-"' . $currencySymbol . '"';
}
return '[$'.$currencySymbol.'-409]#,##0.00;-[$'.$currencySymbol.'-409]#,##0.00';
}
}
@@ -37,11 +37,12 @@ use Espo\Core\{
Utils\Language,
};
class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\FileManagerAware
class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\FileManagerAware, Di\DataCacheAware
{
use Di\DefaultLanguageSetter;
use Di\MetadataSetter;
use Di\FileManagerSetter;
use Di\DataCacheSetter;
protected $ignoreList = [
'Global.sets',
@@ -71,7 +72,7 @@ class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\File
public function getScopeData($language, $scope)
{
$languageObj = new Language($language, $this->fileManager, $this->metadata);
$languageObj = new Language($language, $this->fileManager, $this->metadata, $this->dataCache);
$data = $languageObj->get($scope);
@@ -179,8 +180,8 @@ class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\File
public function saveLabels($language, $scope, $labels)
{
$languageObj = new Language($language, $this->fileManager, $this->metadata);
$languageOriginalObj = new Language($language, $this->fileManager, $this->metadata, false, true);
$languageObj = new Language($language, $this->fileManager, $this->metadata, $this->dataCache);
$languageOriginalObj = new Language($language, $this->fileManager, $this->metadata, $this->dataCache, false, true);
$returnDataHash = [];
@@ -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
@@ -9,4 +9,4 @@
</div>
{{/if}}
<div class="edit-container">{{{edit}}}</div>
<div class="edit-container record">{{{edit}}}</div>
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep) {
define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep) {
return Dep.extend({
@@ -42,25 +42,32 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
return {
scopeList: this.scopeList,
scope: this.scope,
isNew: !(this.id)
isNew: !(this.id),
};
},
events: {
'change .scope-switcher input[name="scope"]': function () {
this.notify('Loading...');
var scope = $('.scope-switcher input[name="scope"]:checked').val();
this.scope = scope;
this.getModelFactory().create(this.scope, function (model) {
model.populateDefaults();
var attributes = this.getView('edit').fetch();
attributes = _.extend(attributes, this.getView('edit').model.toJSON());
model.set(attributes);
this.model = model;
this.createRecordView(model, function (view) {
view.render();
view.notify(false);
});
this.handleAccess(model);
}.bind(this));
},
@@ -75,11 +82,13 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
if (this.options.allDay) {
var allDayScopeList = this.getMetadata().get('clientDefs.Calendar.allDayScopeList') || [];
if (~allDayScopeList.indexOf(this.scope)) {
this.model.set('dateStart', null);
this.model.set('dateEnd', null);
this.model.set('dateStartDate', null);
this.model.set('dateEndDate', this.options.dateEndDate);
if (this.options.dateEndDate !== this.options.dateStartDate) {
this.model.set('dateStartDate', this.options.dateStartDate)
}
@@ -102,7 +111,9 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
this.dateIsChanged = true;
}
}, this);
this.listenTo(this.model, 'change:dateEnd', function (m, value, o) {
this.listenTo(
this.model, 'change:dateEnd', function (m, value, o) {
if (o.ui || o.updatedByDuration) {
this.dateIsChanged = true;
}
@@ -115,6 +126,7 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
if (this.id && !this.getAcl().checkModel(model, 'edit') || !this.id && !this.getAcl().checkModel(model, 'create')) {
this.hideButton('save');
this.hideButton('fullForm');
this.$el.find('button[data-name="save"]').addClass('hidden');
this.$el.find('button[data-name="fullForm"]').addClass('hidden');
} else {
@@ -131,8 +143,10 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
afterRender: function () {
Dep.prototype.afterRender.call(this);
if (this.hasView('edit')) {
var model = this.getView('edit').model;
if (model) {
this.handleAccess(model);
}
@@ -145,6 +159,7 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
if (!this.options.id && !this.options.scope) {
var scopeList = [];
this.scopeList.forEach(function (scope) {
if (this.getAcl().check(scope, 'create')) {
if (~this.enabledScopeList.indexOf(scope)) {
@@ -152,6 +167,7 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
}
}
}, this);
this.scopeList = scopeList;
var calendarDefaultEntity = scopeList[0];
@@ -187,6 +203,7 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
this.confirm(this.translate('removeRecordConfirmation', 'messages'), function () {
var $buttons = this.dialog.$el.find('.modal-footer button');
$buttons.addClass('disabled');
model.destroy({
success: function () {
this.trigger('after:destroy', model);
@@ -194,10 +211,9 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
}.bind(this),
error: function () {
$buttons.removeClass('disabled');
}
},
});
}, this);
}
},
});
});
@@ -34,19 +34,30 @@ define('crm:views/case/record/detail', 'views/record/detail', function (Dep) {
setupActionItems: function () {
Dep.prototype.setupActionItems.call(this);
if (
this.getAcl().checkModel(this.model, 'edit') &&
!~['Closed', 'Rejected', 'Duplicate'].indexOf(this.model.get('status')) &&
this.getAcl().checkField(this.entityType, 'status', 'edit')
) {
this.dropdownItemList.push({
'label': 'Close',
'name': 'close',
});
this.dropdownItemList.push({
'label': 'Reject',
'name': 'reject',
});
var statusList = this.getMetadata().get(
['entityDefs', 'Case', 'fields', 'status', 'options']
) || [];
if (~statusList.indexOf('Closed')) {
this.dropdownItemList.push({
'label': 'Close',
'name': 'close',
});
}
if (~statusList.indexOf('Rejected')) {
this.dropdownItemList.push({
'label': 'Reject',
'name': 'reject',
});
}
}
},
@@ -68,14 +68,14 @@ define('crm:views/record/panels/tasks', 'views/record/panels/relationship', func
name: 'name',
link: true,
},
{
name: 'isOverdue'
}
],
[
{
name: 'isOverdue'
},
{name: 'assignedUser'},
{name: 'status'},
{name: 'dateEnd'},
{name: 'status'},
]
]
},
@@ -32,7 +32,8 @@ Espo.define('crm:views/task/fields/is-overdue', 'views/fields/base', function (D
readOnly: true,
_template: '{{#if isOverdue}}<span class="label label-danger">{{translate "overdue"}}</span>{{/if}}',
_template: '{{#if isOverdue}}<span class="label label-danger">' +
'{{translate "overdue" scope="Task"}}</span>{{/if}}',
data: function () {
var isOverdue = false;
@@ -0,0 +1,5 @@
{{#if isNotEmpty}}
{{value}} {{currencySymbol}}
{{else}}
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
@@ -0,0 +1,3 @@
{{#if isNotEmpty}}
<span title="{{currencySymbol}}{{value}}">{{value}} {{currencySymbol}}</span>
{{/if}}
+1 -1
View File
@@ -4,7 +4,7 @@
<div class="button-container">
<button class="btn btn-default pull-right hidden" data-action="reset">{{translate 'Reset'}}</button>
<div class="btn-group">
<button class="btn btn-default dropdown-toggle select-field" data-toggle="dropdown" tabindex="-1">{{translate 'Select Field'}} <span class="caret"></span></button>
<button class="btn btn-default dropdown-toggle select-field" data-toggle="dropdown" tabindex="-1">{{translate 'Add Field'}} <span class="caret"></span></button>
<ul class="dropdown-menu pull-left filter-list">
{{#each ../fieldList}}
<li data-name="{{./this}}"><a href="javascript:" data-name="{{./this}}" data-action="add-field">{{translate this scope=../../entityType category='fields'}}</a></li>
+26 -7
View File
@@ -1,9 +1,28 @@
<div class="cell">
<a href="javascript:"
class="action{{#unless showFold}} hidden{{/unless}} small"
data-action="fold"
data-id="{{model.id}}"><span class="fas fa-chevron-down"></span></a>
<div class="cell">
<a href="javascript:" class="action{{#unless showFold}} hidden{{/unless}} small" data-action="fold" data-id="{{model.id}}"><span class="fas fa-chevron-down"></span></a>
<a href="javascript:" class="action{{#unless showUnfold}} hidden{{/unless}} small" data-action="unfold" data-id="{{model.id}}"><span class="fas fa-chevron-right"></span></a>
<span data-name="white-space" data-id="{{model.id}}" class="empty-icon{{#unless isEnd}} hidden{{/unless}}">&nbsp;</span>
<a href="javascript:"
class="action{{#unless showUnfold}} hidden{{/unless}} small"
data-action="unfold"
data-id="{{model.id}}"><span class="fas fa-chevron-right"></span></a>
<a href="#{{model.name}}/view/{{model.id}}" class="link{{#if isSelected}} text-bold{{/if}}" data-id="{{model.id}}">{{name}}</a>
</div>
<div class="children{{#unless isUnfolded}} hidden{{/unless}}">{{{children}}}</div>
<span data-name="white-space" data-id="{{model.id}}" class="empty-icon{{#unless isEnd}} hidden{{/unless}}">&nbsp;</span>
<a href="#{{model.name}}/view/{{model.id}}"
class="link{{#if isSelected}} text-bold{{/if}}" data-id="{{model.id}}"
>{{name}}</a>
{{#unless readOnly}}
<a href="javascript:"
class="action small remove-link hidden"
data-action="remove" data-id="{{model.id}}"
title="{{translate 'Remove'}}"
>
<span class="fas fa-times"></span>
</a>
{{/unless}}
</div>
<div class="children{{#unless isUnfolded}} hidden{{/unless}}">{{{children}}}</div>
+1 -1
View File
@@ -14,7 +14,7 @@
{{/unless}}
{{/if}}
{{/unless}}
<div class="list list-expanded">
<div class="list list-expanded list-tree">
{{#if showRootMenu}}
<div class="btn-group pull-right">
<a href="javascript:" class="small dropdown-toggle btn-link" data-toggle="dropdown">
+17 -1
View File
@@ -142,7 +142,10 @@ define('ui', [], function () {
if (this.removeOnClose) {
this.$el.on('hidden.bs.modal', function (e) {
if (this.$el.get(0) == e.target) {
if (this.skipRemove) return;
if (this.skipRemove) {
return;
}
this.remove();
}
}.bind(this));
@@ -324,11 +327,22 @@ define('ui', [], function () {
$('body > .popover').addClass('hidden');
};
Dialog.prototype.hide = function () {
this.$el.find('.modal-content').addClass('hidden');
};
Dialog.prototype.hideWithBackdrop = function () {
var $modalBackdrop = $('.modal-backdrop');
$modalBackdrop.last().addClass('hidden');
$($modalBackdrop.get($modalBackdrop.length - 2)).removeClass('hidden');
var $modalConainer = $('.modal-container');
$($modalConainer.get($modalConainer.length - 2)).removeClass('overlaid');
this.skipRemove = true;
setTimeout(function () {
this.skipRemove = false;
}.bind(this), 50);
@@ -337,6 +351,7 @@ define('ui', [], function () {
this.$el.find('.modal-content').addClass('hidden');
};
Dialog.prototype.close = function () {
var $modalBackdrop = $('.modal-backdrop');
$modalBackdrop.last().removeClass('hidden');
@@ -347,6 +362,7 @@ define('ui', [], function () {
this.$el.modal('hide');
$(this).trigger('dialog:close');
};
Dialog.prototype.remove = function () {
this.onRemove();
this.$el.remove();
@@ -88,6 +88,7 @@ define('views/admin/field-manager/edit', ['view', 'model'], function (Dep, Model
this.model.set('tooltipText', this.getLanguage().translate(this.field, 'tooltips', this.scope));
}
} else {
this.model.scope = this.scope;
this.model.set('type', this.type);
}
@@ -0,0 +1,57 @@
/************************************************************************
* 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.
************************************************************************/
define('views/admin/field-manager/fields/date/after-before', 'views/fields/varchar', function (Dep) {
return Dep.extend({
setupOptions: function () {
Dep.prototype.setupOptions.call(this);
if (!this.model.scope) {
return;
}
var list = this.getFieldManager().getEntityTypeFieldList(
this.model.scope,
{
typeList: ['date', 'datetime', 'datetimeOptional'],
}
);
if (this.model.get('name')) {
list = list.filter(function (item) {
return item !== this.model.get('name');
}, this);
}
this.params.options = list;
},
});
});
+1
View File
@@ -84,6 +84,7 @@ define('views/admin/index', 'view', function (Dep) {
panelItem.items.forEach(function (item) {
item.label = this.translate(item.label, 'labels', 'Admin');
panelItem.itemList.push(item);
item.keywords = [];
}, this);
}
+15 -5
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/admin/integrations/index', 'view', function (Dep) {
define('views/admin/integrations/index', 'view', function (Dep) {
return Dep.extend({
@@ -51,12 +51,13 @@ Espo.define('views/admin/integrations/index', 'view', function (Dep) {
},
setup: function () {
this.integrationList = Object.keys(this.getMetadata().get('integrations') || {});;
this.integrationList = Object.keys(this.getMetadata().get('integrations') || {});
this.integration = this.options.integration || null;
this.on('after:render', function () {
this.renderHeader();
if (!this.integration) {
this.renderDefaultPage();
} else {
@@ -70,34 +71,45 @@ Espo.define('views/admin/integrations/index', 'view', function (Dep) {
this.getRouter().navigate('#Admin/integrations/name=' + integration, {trigger: false});
var viewName = this.getMetadata().get('integrations.' + integration + '.view') || 'views/admin/integrations/' + Espo.Utils.camelCaseToHyphen(this.getMetadata().get('integrations.' + integration + '.authMethod'));
var viewName = this.getMetadata().get('integrations.' + integration + '.view') ||
'views/admin/integrations/' +
Espo.Utils.camelCaseToHyphen(this.getMetadata().get('integrations.' + integration + '.authMethod'));
this.notify('Loading...');
this.createView('content', viewName, {
el: '#integration-content',
integration: integration,
}, function (view) {
this.renderHeader();
view.render();
this.notify(false);
$(window).scrollTop(0);
}.bind(this));
},
renderDefaultPage: function () {
$('#integration-header').html('').hide();
if (this.integrationList.length) {
var msg = this.translate('selectIntegration', 'messages', 'Integration');
} else {
var msg = '<p class="lead">' + this.translate('noIntegrations', 'messages', 'Integration') + '</p>';
}
$('#integration-content').html(msg);
},
renderHeader: function () {
if (!this.integration) {
$('#integration-header').html('');
return;
}
$('#integration-header').show().html(this.translate(this.integration, 'titles', 'Integration'));
},
@@ -106,5 +118,3 @@ Espo.define('views/admin/integrations/index', 'view', function (Dep) {
},
});
});
+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 () {
+17 -8
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/export/modals/export', ['views/modal', 'model'], function (Dep, Model) {
define('views/export/modals/export', ['views/modal', 'model'], function (Dep, Model) {
return Dep.extend({
@@ -44,11 +44,11 @@ Espo.define('views/export/modals/export', ['views/modal', 'model'], function (De
{
name: 'export',
label: 'Export',
style: 'danger'
style: 'danger',
},
{
name: 'cancel',
label: 'Cancel'
label: 'Cancel',
}
];
@@ -73,44 +73,53 @@ Espo.define('views/export/modals/export', ['views/modal', 'model'], function (De
this.createView('record', 'views/export/record/record', {
scope: this.scope,
model: this.model,
el: this.getSelector() + ' .record'
el: this.getSelector() + ' .record',
});
},
actionExport: function () {
var data = this.getView('record').fetch();
this.model.set(data);
if (this.getView('record').validate()) return;
if (this.getView('record').validate()) {
return;
}
var returnData = {
exportAllFields: data.exportAllFields,
format: data.format
format: data.format,
};
if (!data.exportAllFields) {
var attributeList = [];
data.fieldList.forEach(function (item) {
if (item === 'id') {
attributeList.push('id');
return;
}
var type = this.getMetadata().get(['entityDefs', this.scope, 'fields', item, 'type']);
if (type) {;
if (type) {
this.getFieldManager().getAttributeList(type, item).forEach(function (attribute) {
attributeList.push(attribute);
}, this);
}
if (~item.indexOf('_')) {
attributeList.push(item);
}
}, this);
returnData.attributeList = attributeList;
returnData.fieldList = data.fieldList;
}
this.trigger('proceed', returnData);
this.close();
}
},
});
});
+22 -10
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
define('views/export/record/record', 'views/record/base', function (Dep) {
return Dep.extend({
@@ -48,10 +48,10 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
fieldList = fieldList.filter(function (item) {
var defs = this.getMetadata().get(['entityDefs', this.scope, 'fields', item]) || {};
if (defs.disabled) return;
if (defs.exportDisabled) return;
if (defs.type === 'map') return;
if (defs.type === 'attachmentMultiple') return;
return true;
}, this);
@@ -61,32 +61,43 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
fieldList.unshift('id');
var translatedOptions = {};
fieldList.forEach(function (item) {
translatedOptions[item] = this.getLanguage().translate(item, 'fields', this.scope);
}, this);
this.createField('exportAllFields', 'views/fields/bool', {
});
this.createField('exportAllFields', 'views/fields/bool', {});
var setFieldList = this.model.get('fieldList') || [];
setFieldList.forEach(function (item) {
if (~fieldList.indexOf(item)) return;
if (!~item.indexOf('_')) return;
if (~fieldList.indexOf(item)) {
return;
}
if (!~item.indexOf('_')) {
return;
}
var arr = item.split('_');
fieldList.push(item);
var foreignScope = this.getMetadata().get(['entityDefs', this.scope, 'links', arr[0], 'entity']);
if (!foreignScope) return;
translatedOptions[item] = this.getLanguage().translate(arr[0], 'links', this.scope) + '.' + this.getLanguage().translate(arr[1], 'fields', foreignScope);
if (!foreignScope) {
return;
}
translatedOptions[item] = this.getLanguage().translate(arr[0], 'links', this.scope) + '.' +
this.getLanguage().translate(arr[1], 'fields', foreignScope);
}, this);
this.createField('fieldList', 'views/fields/multi-enum', {
required: true,
translatedOptions: translatedOptions,
options: fieldList
options: fieldList,
});
var formatList =
@@ -98,6 +109,7 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
});
this.controlAllFields();
this.listenTo(this.model, 'change:exportAllFields', function () {
this.controlAllFields();
}, this);
@@ -109,7 +121,7 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
} else {
this.hideField('fieldList');
}
}
},
});
});
-10
View File
@@ -356,11 +356,6 @@ define('views/fields/address', 'views/fields/base', function (Dep) {
this.$country.attr('autocomplete', 'espo-country');
}
this.controlStreetTextareaHeight();
this.$street.on('input', function (e) {
this.controlStreetTextareaHeight();
}.bind(this));
var cityList = this.getConfig().get('addressCityList') || [];
if (cityList.length) {
this.$city.autocomplete({
@@ -394,11 +389,6 @@ define('views/fields/address', 'views/fields/base', function (Dep) {
this.$city.attr('autocomplete', 'espo-city');
}
this.controlStreetTextareaHeight();
this.$street.on('input', function (e) {
this.controlStreetTextareaHeight();
}.bind(this));
var stateList = this.getConfig().get('addressStateList') || [];
if (stateList.length) {
this.$state.autocomplete({

Some files were not shown because too many files have changed in this diff Show More