From 805f377459edcc0605fc81426a15beefbc3d5d12 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 29 Apr 2021 15:33:23 +0300 Subject: [PATCH] controllers refactoring --- application/Espo/Controllers/Action.php | 5 +- .../Espo/Controllers/ActionHistoryRecord.php | 34 +- application/Espo/Controllers/Admin.php | 39 +- application/Espo/Controllers/ApiIndex.php | 6 +- application/Espo/Controllers/App.php | 8 +- application/Espo/Controllers/Attachment.php | 22 +- .../Espo/Controllers/AuthLogRecord.php | 28 +- application/Espo/Controllers/AuthToken.php | 14 +- application/Espo/Controllers/CurrencyRate.php | 13 +- .../Espo/Controllers/DashboardTemplate.php | 30 +- application/Espo/Controllers/DataPrivacy.php | 7 +- application/Espo/Controllers/Email.php | 34 +- application/Espo/Controllers/EmailAccount.php | 47 +- application/Espo/Controllers/EmailAddress.php | 21 +- application/Espo/Controllers/EmailFolder.php | 17 +- .../Espo/Controllers/EmailTemplate.php | 36 +- .../Espo/Controllers/EntityManager.php | 6 +- application/Espo/Controllers/Extension.php | 93 ++-- .../Espo/Controllers/ExternalAccount.php | 82 ++-- application/Espo/Controllers/FieldManager.php | 3 +- application/Espo/Controllers/GlobalSearch.php | 17 +- application/Espo/Controllers/InboundEmail.php | 36 +- application/Espo/Controllers/MassAction.php | 9 +- application/Espo/Controllers/Notification.php | 47 +- application/Espo/Core/Controllers/Base.php | 103 ++++- application/Espo/Core/Controllers/Record.php | 407 +++--------------- .../Espo/Core/Controllers/RecordBase.php | 318 ++++++++++++++ .../Espo/Core/Controllers/RecordTree.php | 27 +- .../Crm/Services/KnowledgeBaseArticle.php | 15 +- .../metadata/recordDefs/AuthLogRecord.json | 10 + application/Espo/Resources/routes.json | 2 +- application/Espo/Services/App.php | 2 +- application/Espo/Services/CurrencyRate.php | 3 + .../Espo/Services/DashboardTemplate.php | 9 +- application/Espo/Services/Email.php | 10 +- application/Espo/Services/EmailAccount.php | 4 +- application/Espo/Services/EmailTemplate.php | 2 +- application/Espo/Services/GlobalSearch.php | 7 +- application/Espo/Services/RecordTree.php | 52 ++- 39 files changed, 916 insertions(+), 709 deletions(-) create mode 100644 application/Espo/Core/Controllers/RecordBase.php create mode 100644 application/Espo/Resources/metadata/recordDefs/AuthLogRecord.json diff --git a/application/Espo/Controllers/Action.php b/application/Espo/Controllers/Action.php index ca73f72e3a..0d4e72d27c 100644 --- a/application/Espo/Controllers/Action.php +++ b/application/Espo/Controllers/Action.php @@ -37,9 +37,12 @@ use Espo\Core\{ use StdClass; +/** + * Action framework. + */ class Action { - protected $recordServiceContainer; + private $recordServiceContainer; public function __construct(RecordServiceContainer $recordServiceContainer) { diff --git a/application/Espo/Controllers/ActionHistoryRecord.php b/application/Espo/Controllers/ActionHistoryRecord.php index 07165f4da6..40590b3052 100644 --- a/application/Espo/Controllers/ActionHistoryRecord.php +++ b/application/Espo/Controllers/ActionHistoryRecord.php @@ -29,41 +29,17 @@ namespace Espo\Controllers; -use \Espo\Core\Exceptions\Forbidden; +use Espo\Core\Exceptions\Forbidden; +use Espo\Core\Controllers\RecordBase; -class ActionHistoryRecord extends \Espo\Core\Controllers\Record +class ActionHistoryRecord extends RecordBase { - public function beforeUpdate() + public function beforeUpdate(): void { throw new Forbidden(); } - public function beforeCreate() - { - throw new Forbidden(); - } - - public function beforeListLinked() - { - throw new Forbidden(); - } - - public function beforeMassUpdate() - { - throw new Forbidden(); - } - - public function beforeCreateLink() - { - throw new Forbidden(); - } - - public function beforeRemoveLink() - { - throw new Forbidden(); - } - - public function beforeMassConvertCurrency() + public function beforeCreate(): void { throw new Forbidden(); } diff --git a/application/Espo/Controllers/Admin.php b/application/Espo/Controllers/Admin.php index 32cfd7d5a4..892de55506 100644 --- a/application/Espo/Controllers/Admin.php +++ b/application/Espo/Controllers/Admin.php @@ -30,40 +30,36 @@ namespace Espo\Controllers; use Espo\Core\Exceptions\Forbidden; -use Espo\Core\Exceptions\BadRequest; use Espo\Core\UpgradeManager; + use Espo\Core\Utils\AdminNotificationManager; use Espo\Core\Utils\SystemRequirements; -class Admin extends \Espo\Core\Controllers\Base +use Espo\Core\Controllers\Base; + +class Admin extends Base { - protected function checkControllerAccess() + protected function checkAccess(): bool { - if (!$this->getUser()->isAdmin()) { - throw new Forbidden(); - } + return $this->user->isAdmin(); } - public function postActionRebuild($params, $data, $request) + public function postActionRebuild(): bool { - if (!$request->isPost()) { - throw new BadRequest(); - } - $this->getContainer()->get('dataManager')->rebuild(); return true; } - public function postActionClearCache($params) + public function postActionClearCache(): bool { $this->getContainer()->get('dataManager')->clearCache(); return true; } - public function actionJobs() + public function getActionJobs() { $scheduledJob = $this->getContainer()->get('scheduledJob'); @@ -72,8 +68,8 @@ class Admin extends \Espo\Core\Controllers\Base public function postActionUploadUpgradePackage($params, $data) { - if ($this->getConfig()->get('restrictedMode')) { - if (!$this->getUser()->isSuperAdmin()) { + if ($this->config->get('restrictedMode')) { + if (!$this->user->isSuperAdmin()) { throw new Forbidden(); } } @@ -89,33 +85,34 @@ class Admin extends \Espo\Core\Controllers\Base ]; } - public function postActionRunUpgrade($params, $data) + public function postActionRunUpgrade($params, $data): bool { - if ($this->getConfig()->get('restrictedMode')) { - if (!$this->getUser()->isSuperAdmin()) { + if ($this->config->get('restrictedMode')) { + if (!$this->user->isSuperAdmin()) { throw new Forbidden(); } } $upgradeManager = new UpgradeManager($this->getContainer()); + $upgradeManager->install(get_object_vars($data)); return true; } - public function actionCronMessage($params) + public function actionCronMessage() { return $this->getContainer()->get('scheduledJob')->getSetupMessage(); } - public function actionAdminNotificationList($params) + public function actionAdminNotificationList() { $adminNotificationManager = new AdminNotificationManager($this->getContainer()); return $adminNotificationManager->getNotificationList(); } - public function actionSystemRequirementList($params) + public function actionSystemRequirementList() { $systemRequirementManager = new SystemRequirements($this->getContainer()); diff --git a/application/Espo/Controllers/ApiIndex.php b/application/Espo/Controllers/ApiIndex.php index 72f277c916..ce889e89ff 100644 --- a/application/Espo/Controllers/ApiIndex.php +++ b/application/Espo/Controllers/ApiIndex.php @@ -29,9 +29,9 @@ namespace Espo\Controllers; -class ApiIndex { - - public function getActionIndex() +class ApiIndex +{ + public function getActionIndex(): string { return "EspoCRM REST API"; } diff --git a/application/Espo/Controllers/App.php b/application/Espo/Controllers/App.php index b77dd77185..f471a85f22 100644 --- a/application/Espo/Controllers/App.php +++ b/application/Espo/Controllers/App.php @@ -45,13 +45,15 @@ class App implements use Di\ServiceFactorySetter; use Di\InjectableFactorySetter; - public function actionUser() + public function getActionUser(): StdClass { - return $this->serviceFactory->create('App')->getUserData(); + return (object) $this->serviceFactory->create('App')->getUserData(); } - public function postActionDestroyAuthToken(array $params, StdClass $data, Request $request) + public function postActionDestroyAuthToken(Request $request): bool { + $data = $request->getParsedBody(); + if (empty($data->token)) { throw new BadRequest(); } diff --git a/application/Espo/Controllers/Attachment.php b/application/Espo/Controllers/Attachment.php index ff2c488779..90ebe62170 100644 --- a/application/Espo/Controllers/Attachment.php +++ b/application/Espo/Controllers/Attachment.php @@ -34,23 +34,24 @@ use Espo\Core\{ Exceptions\BadRequest, Api\Request, Api\Response, + Controllers\RecordBase, }; -use Espo\Core\Controllers\Record; +use StdClass; -class Attachment extends Record +class Attachment extends RecordBase { - public function actionList($params, $data, $request) + public function beforeList(): void { - if (!$this->getUser()->isAdmin()) { + if (!$this->user->isAdmin()) { throw new Forbidden(); } - - return parent::actionList($params, $data, $request); } - public function postActionGetAttachmentFromImageUrl($params, $data) + public function postActionGetAttachmentFromImageUrl(Request $request): StdClass { + $data = $request->getParsedBody(); + if (empty($data->url)) { throw new BadRequest(); } @@ -62,11 +63,14 @@ class Attachment extends Record return $this->getRecordService()->getAttachmentFromImageUrl($data)->getValueMap(); } - public function postActionGetCopiedAttachment($params, $data) + public function postActionGetCopiedAttachment(Request $request): StdClass { + $data = $request->getParsedBody(); + if (empty($data->id)) { throw new BadRequest(); } + if (empty($data->field)) { throw new BadRequest('postActionGetCopiedAttachment copy: No field specified'); } @@ -74,7 +78,7 @@ class Attachment extends Record return $this->getRecordService()->getCopiedAttachment($data)->getValueMap(); } - public function getActionFile(Request $request, Response $response) + public function getActionFile(Request $request, Response $response): void { $id = $request->getRouteParam('id'); diff --git a/application/Espo/Controllers/AuthLogRecord.php b/application/Espo/Controllers/AuthLogRecord.php index 1ffbaacdad..63a3a00d53 100644 --- a/application/Espo/Controllers/AuthLogRecord.php +++ b/application/Espo/Controllers/AuthLogRecord.php @@ -31,41 +31,31 @@ namespace Espo\Controllers; use Espo\Core\Exceptions\Forbidden; -class AuthLogRecord extends \Espo\Core\Controllers\Record +use Espo\Core\Controllers\Record; + +class AuthLogRecord extends Record { - protected function checkControllerAccess() + protected function checkAccess(): bool { - if (!$this->getUser()->isAdmin()) { - throw new Forbidden(); - } + return $this->user->isAdmin(); } - public function beforeUpdate() + public function beforeUpdate(): void { throw new Forbidden(); } - public function beforeMassUpdate() + public function beforeCreate(): void { throw new Forbidden(); } - public function beforeCreate() + public function beforeCreateLink(): void { throw new Forbidden(); } - public function beforeCreateLink() - { - throw new Forbidden(); - } - - public function beforeRemoveLink() - { - throw new Forbidden(); - } - - public function beforeMassConvertCurrency() + public function beforeRemoveLink(): void { throw new Forbidden(); } diff --git a/application/Espo/Controllers/AuthToken.php b/application/Espo/Controllers/AuthToken.php index 829965dcb8..e111371286 100644 --- a/application/Espo/Controllers/AuthToken.php +++ b/application/Espo/Controllers/AuthToken.php @@ -31,21 +31,21 @@ namespace Espo\Controllers; use Espo\Core\Exceptions\Forbidden; -class AuthToken extends \Espo\Core\Controllers\Record +use Espo\Core\Controllers\Record; + +class AuthToken extends Record { - protected function checkControllerAccess() + protected function checkAccess(): bool { - if (!$this->getUser()->isAdmin()) { - throw new Forbidden(); - } + return $this->user->isAdmin(); } - public function beforeCreateLink() + public function beforeCreateLink(): void { throw new Forbidden(); } - public function beforeRemoveLink() + public function beforeRemoveLink(): void { throw new Forbidden(); } diff --git a/application/Espo/Controllers/CurrencyRate.php b/application/Espo/Controllers/CurrencyRate.php index 64f2d359c4..d08f450896 100644 --- a/application/Espo/Controllers/CurrencyRate.php +++ b/application/Espo/Controllers/CurrencyRate.php @@ -29,31 +29,28 @@ namespace Espo\Controllers; -use Espo\Core\Exceptions\{ - Forbidden, - BadRequest, -}; - use Espo\Core\{ ServiceFactory, Api\Request, }; +use StdClass; + class CurrencyRate { - protected $serviceFactory; + private $serviceFactory; public function __construct(ServiceFactory $serviceFactory) { $this->serviceFactory = $serviceFactory; } - public function getActionIndex(Request $request) + public function getActionIndex(): StdClass { return $this->serviceFactory->create('CurrencyRate')->get(); } - public function putActionUpdate(Request $request) + public function putActionUpdate(Request $request): StdClass { $data = $request->getParsedBody(); diff --git a/application/Espo/Controllers/DashboardTemplate.php b/application/Espo/Controllers/DashboardTemplate.php index 49dcbdfa98..862e25b466 100644 --- a/application/Espo/Controllers/DashboardTemplate.php +++ b/application/Espo/Controllers/DashboardTemplate.php @@ -29,20 +29,24 @@ namespace Espo\Controllers; -use Espo\Core\Exceptions\Forbidden; use Espo\Core\Exceptions\BadRequest; -class DashboardTemplate extends \Espo\Core\Controllers\Record +use Espo\Core\{ + Api\Request, + Controllers\Record, +}; + +class DashboardTemplate extends Record { - protected function checkControllerAccess() + protected function checkAccess(): bool { - if (!$this->getUser()->isAdmin()) { - throw new Forbidden(); - } + return $this->user->isAdmin(); } - public function postActionDeployToUsers($params, $data) + public function postActionDeployToUsers(Request $request): bool { + $data = $request->getParsedBody(); + if (empty($data->id)) { throw new BadRequest(); } @@ -51,15 +55,19 @@ class DashboardTemplate extends \Espo\Core\Controllers\Record throw new BadRequest(); } - return $this->getServiceFactory()->create('DashboardTemplate')->deployToUsers( + $this->getServiceFactory()->create('DashboardTemplate')->deployToUsers( $data->id, $data->userIdList, !empty($data->append) ); + + return true; } - public function postActionDeployToTeam($params, $data) + public function postActionDeployToTeam(Request $request): bool { + $data = $request->getParsedBody(); + if (empty($data->id)) { throw new BadRequest(); } @@ -68,10 +76,12 @@ class DashboardTemplate extends \Espo\Core\Controllers\Record throw new BadRequest(); } - return $this->getServiceFactory()->create('DashboardTemplate')->deployToTeam( + $this->getServiceFactory()->create('DashboardTemplate')->deployToTeam( $data->id, $data->teamId, !empty($data->append) ); + + return true; } } diff --git a/application/Espo/Controllers/DataPrivacy.php b/application/Espo/Controllers/DataPrivacy.php index c471664a38..52228a77ac 100644 --- a/application/Espo/Controllers/DataPrivacy.php +++ b/application/Espo/Controllers/DataPrivacy.php @@ -60,7 +60,12 @@ class DataPrivacy { $data = $request->getParsedBody(); - if (empty($data->entityType) || empty($data->id) || empty($data->fieldList) || !is_array($data->fieldList)) { + if ( + empty($data->entityType) || + empty($data->id) || + empty($data->fieldList) || + !is_array($data->fieldList) + ) { throw new BadRequest(); } diff --git a/application/Espo/Controllers/Email.php b/application/Espo/Controllers/Email.php index a4e61695d4..dbd6660a70 100644 --- a/application/Espo/Controllers/Email.php +++ b/application/Espo/Controllers/Email.php @@ -33,21 +33,36 @@ use Espo\Core\Exceptions\BadRequest; use Espo\Core\Exceptions\Forbidden; use Espo\Core\Exceptions\NotFound; -class Email extends \Espo\Core\Controllers\Record +use Espo\Core\{ + Controllers\Record, + Api\Request, +}; + +use StdClass; + +class Email extends Record { - public function postActionGetCopiedAttachments($params, $data, $request) + public function postActionGetCopiedAttachments(Request $request): StdClass { + $data = $request->getParsedBody(); + if (empty($data->id)) { throw new BadRequest(); } + $id = $data->id; return $this->getRecordService()->getCopiedAttachments($id); } - public function postActionSendTestEmail($params, $data, $request) + /** + * @todo Move to service. + */ + public function postActionSendTestEmail(Request $request) { - if (!$this->getAcl()->checkScope('Email')) { + $data = $request->getParsedBody(); + + if (!$this->acl->checkScope('Email')) { throw new Forbidden(); } @@ -64,7 +79,9 @@ class Email extends \Espo\Core\Controllers\Record } if (is_null($data->password)) { - $data->password = $this->getContainer()->get('crypt')->decrypt($preferences->get('smtpPassword')); + $data->password = $this->getContainer() + ->get('crypt') + ->decrypt($preferences->get('smtpPassword')); } } else if ($data->type == 'emailAccount') { @@ -73,7 +90,8 @@ class Email extends \Espo\Core\Controllers\Record } if (!empty($data->id)) { - $emailAccount = $this->getEntityManager()->getEntity('EmailAccount', $data->id); + $emailAccount = $this->getEntityManager() + ->getEntity('EmailAccount', $data->id); if (!$emailAccount) { throw new NotFound(); @@ -85,7 +103,9 @@ class Email extends \Espo\Core\Controllers\Record } } if (is_null($data->password)) { - $data->password = $this->getContainer()->get('crypt')->decrypt($emailAccount->get('smtpPassword')); + $data->password = $this->getContainer() + ->get('crypt') + ->decrypt($emailAccount->get('smtpPassword')); } } } diff --git a/application/Espo/Controllers/EmailAccount.php b/application/Espo/Controllers/EmailAccount.php index a73dd69267..0daa92608d 100644 --- a/application/Espo/Controllers/EmailAccount.php +++ b/application/Espo/Controllers/EmailAccount.php @@ -32,11 +32,23 @@ namespace Espo\Controllers; use Espo\Core\Exceptions\Forbidden; use Espo\Core\Exceptions\Error; -class EmailAccount extends \Espo\Core\Controllers\Record +use Espo\Core\{ + Controllers\Record, + Api\Request, +}; + +class EmailAccount extends Record { - public function postActionGetFolders($params, $data) + protected function checkAccess(): bool { - return $this->getRecordService()->getFolders([ + return $this->acl->check('EmailAccountScope'); + } + + public function postActionGetFolders(Request $request): array + { + $data = $request->getParsedBody(); + + $params = [ 'host' => $data->host ?? null, 'port' => $data->port ?? null, 'security' => $data->security ?? null, @@ -45,31 +57,36 @@ class EmailAccount extends \Espo\Core\Controllers\Record 'id' => $data->id ?? null, 'emailAddress' => $data->emailAddress ?? null, 'userId' => $data->userId ?? null, - ]); + ]; + + return $this->getRecordService()->getFolders($params); } - protected function checkControllerAccess() + public function postActionTestConnection(Request $request): bool { - if (!$this->getAcl()->check('EmailAccountScope')) { - throw new Forbidden(); - } - } + $data = $request->getParsedBody(); - public function postActionTestConnection($params, $data, $request) - { if (is_null($data->password)) { - $emailAccount = $this->getEntityManager()->getEntity('EmailAccount', $data->id); + $emailAccount = $this->entityManager->getEntity('EmailAccount', $data->id); + if (!$emailAccount || !$emailAccount->id) { throw new Error(); } - if ($emailAccount->get('assignedUserId') != $this->getUser()->id && !$this->getUser()->isAdmin()) { + if ( + $emailAccount->get('assignedUserId') != $this->user->id && + !$this->user->isAdmin() + ) { throw new Forbidden(); } - $data->password = $this->getContainer()->get('crypt')->decrypt($emailAccount->get('password')); + $data->password = $this->getContainer() + ->get('crypt') + ->decrypt($emailAccount->get('password')); } - return $this->getRecordService()->testConnection(get_object_vars($data)); + $this->getRecordService()->testConnection(get_object_vars($data)); + + return true; } } diff --git a/application/Espo/Controllers/EmailAddress.php b/application/Espo/Controllers/EmailAddress.php index 27191f9617..938726245d 100644 --- a/application/Espo/Controllers/EmailAddress.php +++ b/application/Espo/Controllers/EmailAddress.php @@ -31,27 +31,32 @@ namespace Espo\Controllers; use Espo\Core\Exceptions\Forbidden; -class EmailAddress extends \Espo\Core\Controllers\Record +use Espo\Core\{ + Controllers\RecordBase, + Api\Request, +}; + +class EmailAddress extends RecordBase { - public function actionSearchInAddressBook($params, $data, $request) + public function actionSearchInAddressBook(Request $request): array { - if (!$this->getAcl()->checkScope('Email')) { + if (!$this->acl->checkScope('Email')) { throw new Forbidden(); } - if (!$this->getAcl()->checkScope('Email', 'create')) { + if (!$this->acl->checkScope('Email', 'create')) { throw new Forbidden(); } - $q = $request->get('q'); + $q = $request->getQueryParam('q'); - $maxSize = intval($request->get('maxSize')); + $maxSize = intval($request->getQueryParam('maxSize')); if (empty($maxSize) || $maxSize > 50) { - $maxSize = $this->getConfig()->get('recordsPerPage', 20); + $maxSize = $this->config->get('recordsPerPage', 20); } - $onlyActual = $request->get('onlyActual') === 'true'; + $onlyActual = $request->getQueryParam('onlyActual') === 'true'; return $this->getRecordService()->searchInAddressBook($q, $maxSize, $onlyActual); } diff --git a/application/Espo/Controllers/EmailFolder.php b/application/Espo/Controllers/EmailFolder.php index 139523b98a..2e941b664c 100644 --- a/application/Espo/Controllers/EmailFolder.php +++ b/application/Espo/Controllers/EmailFolder.php @@ -31,10 +31,17 @@ namespace Espo\Controllers; use Espo\Core\Exceptions\BadRequest; -class EmailFolder extends \Espo\Core\Controllers\Record +use Espo\Core\{ + Controllers\RecordBase, + Api\Request, +}; + +class EmailFolder extends RecordBase { - public function postActionMoveUp($params, $data, $request) + public function postActionMoveUp(Request $request): bool { + $data = $request->getParsedBody(); + if (empty($data->id)) { throw new BadRequest(); } @@ -44,8 +51,10 @@ class EmailFolder extends \Espo\Core\Controllers\Record return true; } - public function postActionMoveDown($params, $data, $request) + public function postActionMoveDown(Request $request): bool { + $data = $request->getParsedBody(); + if (empty($data->id)) { throw new BadRequest(); } @@ -55,7 +64,7 @@ class EmailFolder extends \Espo\Core\Controllers\Record return true; } - public function getActionListAll() + public function getActionListAll(): array { return $this->getRecordService()->listAll(); } diff --git a/application/Espo/Controllers/EmailTemplate.php b/application/Espo/Controllers/EmailTemplate.php index 66247682c0..f1c4e9bb94 100644 --- a/application/Espo/Controllers/EmailTemplate.php +++ b/application/Espo/Controllers/EmailTemplate.php @@ -29,25 +29,29 @@ namespace Espo\Controllers; -use Espo\Core\Exceptions\Error; +use Espo\Core\{ + Controllers\Record, + Api\Request, +}; -class EmailTemplate extends \Espo\Core\Controllers\Record +use StdClass; + +class EmailTemplate extends Record { - public function actionParse($params, $data, $request) + public function actionParse(Request $request): StdClass { - $id = $request->get('id'); - $emailAddress = $request->get('emailAddress'); + $id = $request->getQueryParam('id'); - if (empty($id)) { - throw new Error(); - } - - return $this->getRecordService()->parse($id, [ - 'emailAddress' => $request->get('emailAddress'), - 'parentType' => $request->get('parentType'), - 'parentId' => $request->get('parentId'), - 'relatedType' => $request->get('relatedType'), - 'relatedId' => $request->get('relatedId') - ], true); + return (object) $this->getRecordService()->parse( + $id, + [ + 'emailAddress' => $request->getQueryParam('emailAddress'), + 'parentType' => $request->getQueryParam('parentType'), + 'parentId' => $request->getQueryParam('parentId'), + 'relatedType' => $request->getQueryParam('relatedType'), + 'relatedId' => $request->getQueryParam('relatedId'), + ], + true + ); } } diff --git a/application/Espo/Controllers/EntityManager.php b/application/Espo/Controllers/EntityManager.php index b02a993cbc..bc67e40ade 100644 --- a/application/Espo/Controllers/EntityManager.php +++ b/application/Espo/Controllers/EntityManager.php @@ -72,11 +72,9 @@ class EntityManager $this->checkControllerAccess(); } - protected function checkControllerAccess() + protected function checkAccess(): bool { - if (!$this->user->isAdmin()) { - throw new Forbidden(); - } + return $this->user->isAdmin(); } public function postActionCreateEntity(Request $request) diff --git a/application/Espo/Controllers/Extension.php b/application/Espo/Controllers/Extension.php index 9a7dcea1cf..77a91598a3 100644 --- a/application/Espo/Controllers/Extension.php +++ b/application/Espo/Controllers/Extension.php @@ -31,44 +31,45 @@ namespace Espo\Controllers; use Espo\Core\Exceptions\Forbidden; -use Espo\Core\ExtensionManager; +use Espo\Core\{ + ExtensionManager, + Controllers\RecordBase, + Api\Request, +}; -class Extension extends \Espo\Core\Controllers\Record +use StdClass; + +class Extension extends RecordBase { - protected function checkControllerAccess() + protected function checkAccess(): bool { - if (!$this->getUser()->isAdmin()) { - throw new Forbidden(); - } + return $this->user->isAdmin(); } - public function actionUpload($params, $data, $request) + public function postActionUpload(Request $request): StdClass { - if (!$request->isPost()) { - throw new Forbidden(); - } + $body = $request->getBodyContents(); $manager = new ExtensionManager($this->getContainer()); - $id = $manager->upload($data); + $id = $manager->upload($body); + $manifest = $manager->getManifest(); - return array( + return (object) [ 'id' => $id, 'version' => $manifest['version'], 'name' => $manifest['name'], 'description' => $manifest['description'], - ); + ]; } - public function actionInstall($params, $data, $request) + public function postActionInstall(Request $request): bool { - if (!$request->isPost()) { - throw new Forbidden(); - } + $data = $request->getParsedBody(); - if ($this->getConfig()->get('restrictedMode')) { - if (!$this->getUser()->isSuperAdmin()) { + if ($this->config->get('restrictedMode')) { + if (!$this->user->isSuperAdmin()) { throw new Forbidden(); } } @@ -80,14 +81,12 @@ class Extension extends \Espo\Core\Controllers\Record return true; } - public function actionUninstall($params, $data, $request) + public function postActionUninstall(Request $request): bool { - if (!$request->isPost()) { - throw new Forbidden(); - } + $data = $request->getParsedBody(); - if ($this->getConfig()->get('restrictedMode')) { - if (!$this->getUser()->isSuperAdmin()) { + if ($this->config->get('restrictedMode')) { + if (!$this->user->isSuperAdmin()) { throw new Forbidden(); } } @@ -100,14 +99,12 @@ class Extension extends \Espo\Core\Controllers\Record } - public function actionDelete($params, $data, $request) + public function deleteActionDelete(Request $request): bool { - if (!$request->isDelete()) { - throw BadRequest(); - } + $params = $request->getRouteParams(); - if ($this->getConfig()->get('restrictedMode')) { - if (!$this->getUser()->isSuperAdmin()) { + if ($this->config->get('restrictedMode')) { + if (!$this->user->isSuperAdmin()) { throw new Forbidden(); } } @@ -119,42 +116,12 @@ class Extension extends \Espo\Core\Controllers\Record return true; } - public function beforeCreate() + public function beforeCreate(): void { throw new Forbidden(); } - public function beforeUpdate() - { - throw new Forbidden(); - } - - public function beforePatch() - { - throw new Forbidden(); - } - - public function beforeListLinked() - { - throw new Forbidden(); - } - - public function beforeMassUpdate() - { - throw new Forbidden(); - } - - public function beforeMassDelete() - { - throw new Forbidden(); - } - - public function beforeCreateLink() - { - throw new Forbidden(); - } - - public function beforeRemoveLink() + public function beforeUpdate(): void { throw new Forbidden(); } diff --git a/application/Espo/Controllers/ExternalAccount.php b/application/Espo/Controllers/ExternalAccount.php index 6af22659f0..b6f43679ab 100644 --- a/application/Espo/Controllers/ExternalAccount.php +++ b/application/Espo/Controllers/ExternalAccount.php @@ -29,94 +29,96 @@ namespace Espo\Controllers; -use Espo\Core\Exceptions\Error; use Espo\Core\Exceptions\Forbidden; -use Espo\Core\Exceptions\BadRequest; -class ExternalAccount extends \Espo\Core\Controllers\Record + +use Espo\Core\{ + Controllers\RecordBase, + Api\Request, +}; + +use StdClass; + +class ExternalAccount extends RecordBase { public static $defaultAction = 'list'; - protected function checkControllerAccess() + protected function checkAccess(): bool { - if (!$this->getAcl()->checkScope('ExternalAccount')) { - throw new Forbidden(); - } + return $this->acl->checkScope('ExternalAccount'); } - public function actionList($params, $data, $request) + public function getActionList(Request $request): StdClass { - $integrations = $this->getEntityManager()->getRepository('Integration')->find(); + $integrations = $this->entityManager->getRepository('Integration')->find(); $list = []; foreach ($integrations as $entity) { if ( $entity->get('enabled') && - $this->getMetadata()->get('integrations.' . $entity->id .'.allowUserAccounts') + $this->metadata->get('integrations.' . $entity->id .'.allowUserAccounts') ) { - $userAccountAclScope = $this->getMetadata()->get(['integrations', $entity->id, 'userAccountAclScope']); + $userAccountAclScope = $this->metadata + ->get(['integrations', $entity->id, 'userAccountAclScope']); if ($userAccountAclScope) { - if (!$this->getAcl()->checkScope($userAccountAclScope)) { + if (!$this->acl->checkScope($userAccountAclScope)) { continue; } } $list[] = [ - 'id' => $entity->id, + 'id' => $entity->getId(), ]; } } - return [ + return (object) [ 'list' => $list ]; } - public function actionGetOAuth2Info($params, $data, $request) + public function getActionGetOAuth2Info(Request $request): ?StdClass { - $id = $request->get('id'); + $id = $request->getQueryParam('id'); list($integration, $userId) = explode('__', $id); - if ($this->getUser()->id != $userId && !$this->getUser()->isAdmin()) { + if ($this->user->getId() != $userId && !$this->user->isAdmin()) { throw new Forbidden(); } - $entity = $this->getEntityManager()->getEntity('Integration', $integration); + $entity = $this->entityManager->getEntity('Integration', $integration); if ($entity) { - return [ + return (object) [ 'clientId' => $entity->get('clientId'), - 'redirectUri' => $this->getConfig()->get('siteUrl') . '?entryPoint=oauthCallback', + 'redirectUri' => $this->config->get('siteUrl') . '?entryPoint=oauthCallback', 'isConnected' => $this->getRecordService()->ping($integration, $userId) ]; } + + return null; } - public function actionRead($params, $data, $request) + public function getActionRead(Request $request): StdClass { - list($integration, $userId) = explode('__', $params['id']); + $id = $request->getRouteParam('id'); - return $this->getRecordService()->read($params['id'])->getValueMap(); + return $this->getRecordService()->read($id)->getValueMap(); } - public function actionUpdate($params, $data, $request) + public function putActionUpdate(Request $request): StdClass { - return $this->actionPatch($params, $data, $request); - } + $id = $request->getRouteParam('id'); - public function actionPatch($params, $data, $request) - { - if (!$request->isPut() && !$request->isPost() && !$request->isPatch()) { - throw new BadRequest(); - } + $data = $request->getParsedBody(); - list($integration, $userId) = explode('__', $params['id']); + list($integration, $userId) = explode('__', $id); - if ($this->getUser()->id != $userId && !$this->getUser()->isAdmin()) { + if ($this->user->getId() !== $userId && !$this->user->isAdmin()) { throw new Forbidden(); } @@ -124,27 +126,25 @@ class ExternalAccount extends \Espo\Core\Controllers\Record $data->data = null; } - $entity = $this->getEntityManager()->getEntity('ExternalAccount', $params['id']); + $entity = $this->entityManager->getEntity('ExternalAccount', $id); $entity->set($data); - $this->getEntityManager()->saveEntity($entity); + $this->entityManager->saveEntity($entity); - return $entity->toArray(); + return $entity->getValueMap(); } - public function actionAuthorizationCode($params, $data, $request) + public function postActionAuthorizationCode(Request $request) { - if (!$request->isPost()) { - throw new Error('Bad HTTP method type.'); - } + $data = $request->getParsedBody(); $id = $data->id; $code = $data->code; list($integration, $userId) = explode('__', $id); - if ($this->getUser()->id != $userId && !$this->getUser()->isAdmin()) { + if ($this->user->getId() != $userId && !$this->user->isAdmin()) { throw new Forbidden(); } diff --git a/application/Espo/Controllers/FieldManager.php b/application/Espo/Controllers/FieldManager.php index b85c868f82..9136cb0ad6 100644 --- a/application/Espo/Controllers/FieldManager.php +++ b/application/Espo/Controllers/FieldManager.php @@ -37,7 +37,6 @@ use Espo\{ use Espo\Core\{ Exceptions\Error, Exceptions\Forbidden, - Exceptions\NotFound, Exceptions\BadRequest, Api\Request, DataManager, @@ -46,7 +45,9 @@ use Espo\Core\{ class FieldManager { protected $user; + protected $dataManager; + protected $fieldManagerTool; public function __construct(User $user, DataManager $dataManager, FieldManagerTool $fieldManagerTool) diff --git a/application/Espo/Controllers/GlobalSearch.php b/application/Espo/Controllers/GlobalSearch.php index 7d539cc646..4cfb17f9e3 100644 --- a/application/Espo/Controllers/GlobalSearch.php +++ b/application/Espo/Controllers/GlobalSearch.php @@ -29,23 +29,28 @@ namespace Espo\Controllers; -use Espo\Core\ServiceFactory; +use Espo\Core\{ + ServiceFactory, + Api\Request, +}; + +use StdClass; class GlobalSearch { - protected $serviceFactory; + private $serviceFactory; public function __construct(ServiceFactory $serviceFactory) { $this->serviceFactory = $serviceFactory; } - public function actionSearch($params, $data, $request) + public function getActionSearch(Request $request): StdClass { - $query = $request->get('q'); + $query = $request->getQueryParam('q'); - $offset = intval($request->get('offset')); - $maxSize = intval($request->get('maxSize')); + $offset = intval($request->getQueryParam('offset')); + $maxSize = intval($request->getQueryParam('maxSize')); return $this->serviceFactory->create('GlobalSearch')->find($query, $offset, $maxSize); } diff --git a/application/Espo/Controllers/InboundEmail.php b/application/Espo/Controllers/InboundEmail.php index b56d05df8a..718c8d890e 100644 --- a/application/Espo/Controllers/InboundEmail.php +++ b/application/Espo/Controllers/InboundEmail.php @@ -31,7 +31,12 @@ namespace Espo\Controllers; use Espo\Core\Exceptions\Forbidden; -class InboundEmail extends \Espo\Core\Controllers\Record +use Espo\Core\{ + Controllers\Record, + Api\Request, +}; + +class InboundEmail extends Record { protected function checkControllerAccess() { @@ -40,30 +45,45 @@ class InboundEmail extends \Espo\Core\Controllers\Record } } - public function postActionGetFolders($params, $data, $request) + protected function checkAccess(): bool { - return $this->getRecordService()->getFolders([ + return $this->getUser()->isAdmin(); + } + + public function postActionGetFolders(Request $request): array + { + $data = $request->getParsedBody(); + + $params = [ 'host' => $data->host ?? null, 'port' => $data->port ?? null, 'security' => $data->security ?? null, 'username' => $data->username ?? null, 'password' => $data->password ?? null, 'id' => $data->id ?? null, - ]); + ]; + + return $this->getRecordService()->getFolders($params); } - public function postActionTestConnection($params, $data, $request) + public function postActionTestConnection(Request $request): bool { + $data = $request->getParsedBody(); + if (is_null($data->password)) { - $inboundEmail = $this->getEntityManager()->getEntity('InboundEmail', $data->id); + $inboundEmail = $this->entityManager->getEntity('InboundEmail', $data->id); if (!$inboundEmail || !$inboundEmail->id) { throw new Error(); } - $data->password = $this->getContainer()->get('crypt')->decrypt($inboundEmail->get('password')); + $data->password = $this->getContainer() + ->get('crypt') + ->decrypt($inboundEmail->get('password')); } - return $this->getRecordService()->testConnection(get_object_vars($data)); + $this->getRecordService()->testConnection(get_object_vars($data)); + + return true; } } diff --git a/application/Espo/Controllers/MassAction.php b/application/Espo/Controllers/MassAction.php index 8a0cd2a7cf..31e1279c37 100644 --- a/application/Espo/Controllers/MassAction.php +++ b/application/Espo/Controllers/MassAction.php @@ -37,16 +37,19 @@ use Espo\Core\{ use StdClass; +/** + * Mass-Action framework. + */ class MassAction { - protected $recordServiceContainer; + private $recordServiceContainer; public function __construct(RecordServiceContainer $recordServiceContainer) { $this->recordServiceContainer = $recordServiceContainer; } - public function postActionProcess(Request $request) : StdClass + public function postActionProcess(Request $request): StdClass { $body = $request->getParsedBody(); @@ -70,7 +73,7 @@ class MassAction return $result->getValueMap(); } - protected function prepareMassActionParams(StdClass $data) : array + private function prepareMassActionParams(StdClass $data): array { $params = []; diff --git a/application/Espo/Controllers/Notification.php b/application/Espo/Controllers/Notification.php index ece5aff3cc..fb2c99b6fa 100644 --- a/application/Espo/Controllers/Notification.php +++ b/application/Espo/Controllers/Notification.php @@ -31,13 +31,20 @@ namespace Espo\Controllers; use Espo\Core\Exceptions\Error; -class Notification extends \Espo\Core\Controllers\Record +use Espo\Core\{ + Controllers\RecordBase, + Api\Request, +}; + +use StdClass; + +class Notification extends RecordBase { public static $defaultAction = 'list'; - public function getActionList($params, $data, $request, $response) + public function getActionList(Request $request): StdClass { - $userId = $this->getUser()->id; + $userId = $this->user->id; $offset = intval($request->get('offset')); $maxSize = intval($request->get('maxSize')); @@ -61,41 +68,23 @@ class Notification extends \Espo\Core\Controllers\Record ]; } - public function actionNotReadCount() + public function getActionNotReadCount(): int { - $userId = $this->getUser()->id; + $userId = $this->user->getId(); return $this->getService('Notification')->getNotReadCount($userId); } - public function postActionMarkAllRead($params, $data, $request) + public function postActionMarkAllRead(Request $request): bool { - $userId = $this->getUser()->id; + $userId = $this->user->getId(); - return $this->getService('Notification')->markAllRead($userId); + $this->getService('Notification')->markAllRead($userId); + + return true; } - public function beforeExport() - { - throw new Error(); - } - - public function beforeMassUpdate() - { - throw new Error(); - } - - public function beforeCreateLink() - { - throw new Error(); - } - - public function beforeRemoveLink() - { - throw new Error(); - } - - public function beforeMerge() + public function beforeExport(): void { throw new Error(); } diff --git a/application/Espo/Core/Controllers/Base.php b/application/Espo/Core/Controllers/Base.php index 0526a6ac94..6ab081117b 100644 --- a/application/Espo/Core/Controllers/Base.php +++ b/application/Espo/Core/Controllers/Base.php @@ -29,51 +29,125 @@ namespace Espo\Core\Controllers; -use Espo\Core\Container; +use Espo\Core\{ + Exceptions\Forbidden, + Container, + Acl, + AclManager, + Utils\Config, + Utils\Metadata, + ServiceFactory, +}; +use Espo\Entities\User; +use Espo\Entities\Preferences; + +/** + * @deprecated + */ abstract class Base { protected $name; - private $container; - public static $defaultAction = 'index'; - public function __construct(Container $container) - { + private $container; + + protected $user; + + protected $acl; + + protected $aclManager; + + protected $config; + + protected $preferences; + + protected $metadata; + + protected $serviceFactory; + + public function __construct( + Container $container, + User $user, + Acl $acl, + AclManager $aclManager, + Config $config, + Preferences $preferences, + Metadata $metadata, + ServiceFactory $serviceFactory + ) { $this->container = $container; + $this->user = $user; + $this->acl = $acl; + $this->aclManager = $aclManager; + $this->config = $config; + $this->preferences = $preferences; + $this->metadata = $metadata; + $this->serviceFactory = $serviceFactory; if (empty($this->name)) { $name = get_class($this); + + $matches = null; + if (preg_match('@\\\\([\w]+)$@', $name, $matches)) { $name = $matches[1]; } + $this->name = $name; } $this->checkControllerAccess(); + + if (!$this->checkAccess()) { + throw new Forbidden("No access to '{$this->name}'."); + } } - protected function getName() + protected function getName(): string { return $this->name; } + protected function checkAccess(): bool + { + return true; + } + + /** + * @throws Forbidden + * @deprecated + */ protected function checkControllerAccess() { return; } + protected function getService($name): object + { + return $this->serviceFactory->create($name); + } + + /** + * @deprecated Use Aware interfaces to inject dependencies. + */ protected function getContainer() { return $this->container; } + /** + * @deprecated + */ protected function getUser() { return $this->container->get('user'); } + /** + * @deprecated + */ protected function getAcl() { return $this->container->get('acl'); @@ -84,28 +158,35 @@ abstract class Base return $this->container->get('aclManager'); } + /** + * @deprecated + */ protected function getConfig() { return $this->container->get('config'); } + /** + * @deprecated + */ protected function getPreferences() { return $this->container->get('preferences'); } + /** + * @deprecated + */ protected function getMetadata() { return $this->container->get('metadata'); } + /** + * @deprecated + */ protected function getServiceFactory() { return $this->container->get('serviceFactory'); } - - protected function getService($name) - { - return $this->getServiceFactory()->create($name); - } } diff --git a/application/Espo/Core/Controllers/Record.php b/application/Espo/Core/Controllers/Record.php index bb7535d8b0..549af489d6 100644 --- a/application/Espo/Core/Controllers/Record.php +++ b/application/Espo/Core/Controllers/Record.php @@ -30,161 +30,31 @@ namespace Espo\Core\Controllers; use Espo\Core\Exceptions\{ - Error, Forbidden, - NotFound, BadRequest, }; use Espo\Core\{ - Utils\ControllerUtil, Record\Collection as RecordCollection, + Api\Request, }; use StdClass; -class Record extends Base +class Record extends RecordBase { - const MAX_SIZE_LIMIT = 200; - - public static $defaultAction = 'list'; - - protected function getEntityManager() + /** + * Kanban data. + */ + public function getActionListKanban(Request $request): StdClass { - return $this->getContainer()->get('entityManager'); - } - - protected function getRecordService(?string $name = null): object - { - $name = $name ?? $this->name; - - return $this->getContainer()->get('recordServiceContainer')->get($name); - } - - public function actionRead($params, $data, $request) - { - $id = $params['id']; - $entity = $this->getRecordService()->read($id); - - if (!$entity) { - throw new NotFound(); - } - - return $entity->getValueMap(); - } - - public function actionPatch($params, $data, $request) - { - return $this->actionUpdate($params, $data, $request); - } - - public function actionCreate($params, $data, $request) - { - if (!is_object($data)) { - throw new BadRequest(); - } - - if (!$request->isPost()) { - throw new BadRequest(); - } - - if (!$this->getAcl()->check($this->name, 'create')) { - throw new Forbidden("No create access for {$this->name}."); - } - - $service = $this->getRecordService(); - - $entity = $service->create($data); - - if ($entity) { - return $entity->getValueMap(); - } - - throw new Error(); - } - - public function actionUpdate($params, $data, $request) - { - if (!is_object($data)) { - throw new BadRequest(); - } - - if (!$request->isPut() && !$request->isPatch()) { - throw new BadRequest(); - } - - if (!$this->getAcl()->check($this->name, 'edit')) { - throw new Forbidden("No edit access for {$this->name}."); - } - - $id = $params['id']; - - $entity = $this->getRecordService()->update($id, $data); - - if ($entity) { - return $entity->getValueMap(); - } - - throw new Error(); - } - - public function actionList($params, $data, $request) - { - if (!$this->getAcl()->check($this->name, 'read')) { - throw new Forbidden("No read access for {$this->name}."); - } + $data = $request->getParsedBody(); $listParams = []; $this->fetchListParamsFromRequest($listParams, $request, $data); - $maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT); - - if (empty($listParams['maxSize'])) { - $listParams['maxSize'] = $maxSizeLimit; - } - - if (!empty($listParams['maxSize']) && $listParams['maxSize'] > $maxSizeLimit) { - throw new Forbidden("Max size should should not exceed " . $maxSizeLimit . ". Use offset and limit."); - } - - $result = $this->getRecordService()->find($listParams); - - if ($result instanceof RecordCollection) { - return (object) [ - 'total' => $result->getTotal(), - 'list' => $result->getValueMapList(), - ]; - } - - if (is_array($result)) { - return (object) [ - 'total' => $result['total'], - 'list' => isset($result['collection']) ? - $result['collection']->getValueMapList() : - $result['list'], - ]; - } - - return (object) [ - 'total' => $result->total, - 'list' => isset($result->collection) ? - $result->collection->getValueMapList() : - $result->list, - ]; - } - - public function getActionListKanban($params, $data, $request) - { - if (!$this->getAcl()->check($this->name, 'read')) { - throw new Forbidden("No read access for {$this->name}."); - } - - $listParams = []; - - $this->fetchListParamsFromRequest($listParams, $request, $data); - - $maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT); + $maxSizeLimit = $this->config->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT); if (empty($listParams['maxSize'])) { $listParams['maxSize'] = $maxSizeLimit; @@ -205,25 +75,26 @@ class Record extends Base ]; } - protected function fetchListParamsFromRequest(&$params, $request, $data) + /** + * List related records. + */ + public function getActionListLinked(Request $request): StdClass { - ControllerUtil::fetchListParamsFromRequest($params, $request, $data); - } + $id = $request->getRouteParam('id'); + $link = $request->getRouteParam('link'); - public function actionListLinked($params, $data, $request) - { - $id = $params['id']; - $link = $params['link']; + $data = $request->getParsedBody(); $listParams = []; $this->fetchListParamsFromRequest($listParams, $request, $data); - $maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT); + $maxSizeLimit = $this->config->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT); if (empty($listParams['maxSize'])) { $listParams['maxSize'] = $maxSizeLimit; } + if (!empty($listParams['maxSize']) && $listParams['maxSize'] > $maxSizeLimit) { throw new Forbidden( "Max size should should not exceed " . $maxSizeLimit . ". Use offset and limit." @@ -256,86 +127,20 @@ class Record extends Base ]; } - public function actionDelete($params, $data, $request) + /** + * Relate records. + */ + public function postActionCreateLink(Request $request): bool { - if (!$request->isDelete()) { + $id = $request->getRouteParam('id'); + $link = $request->getRouteParam('link'); + + $data = $request->getParsedBody(); + + if (!$id || !$link) { throw new BadRequest(); } - $id = $params['id']; - - $this->getRecordService()->delete($id); - - return true; - } - - public function actionExport($params, $data, $request) - { - if (!is_object($data)) { - throw new BadRequest(); - } - - if (!$request->isPost()) { - throw new BadRequest(); - } - - if ($this->getConfig()->get('exportDisabled') && !$this->getUser()->isAdmin()) { - throw new Forbidden(); - } - - if ($this->getAcl()->get('exportPermission') !== 'yes' && !$this->getUser()->isAdmin()) { - throw new Forbidden(); - } - - if (!$this->getAcl()->check($this->name, 'read')) { - throw new Forbidden(); - } - - $ids = isset($data->ids) ? $data->ids : null; - $where = isset($data->where) ? json_decode(json_encode($data->where), true) : null; - $byWhere = isset($data->byWhere) ? $data->byWhere : false; - $selectData = isset($data->selectData) ? json_decode(json_encode($data->selectData), true) : null; - - $actionParams = []; - - if ($byWhere) { - $actionParams['selectData'] = $selectData; - $actionParams['where'] = $where; - } - else { - $actionParams['ids'] = $ids; - } - - if (isset($data->attributeList)) { - $actionParams['attributeList'] = $data->attributeList; - } - - if (isset($data->fieldList)) { - $actionParams['fieldList'] = $data->fieldList; - } - - if (isset($data->format)) { - $actionParams['format'] = $data->format; - } - - return [ - 'id' => $this->getRecordService()->export($actionParams), - ]; - } - - public function actionCreateLink($params, $data, $request) - { - if (!$request->isPost()) { - throw new BadRequest(); - } - - if (empty($params['id']) || empty($params['link'])) { - throw new BadRequest(); - } - - $id = $params['id']; - $link = $params['link']; - if (!empty($data->massRelate)) { if (!is_array($data->where)) { throw new BadRequest(); @@ -351,43 +156,41 @@ class Record extends Base return $this->getRecordService()->massLink($id, $link, $where, $selectData); } - else { - $foreignIdList = []; - if (isset($data->id)) { - $foreignIdList[] = $data->id; - } + $foreignIdList = []; - if (isset($data->ids) && is_array($data->ids)) { - foreach ($data->ids as $foreignId) { - $foreignIdList[] = $foreignId; - } - } - - $result = false; - - foreach ($foreignIdList as $foreignId) { - $this->getRecordService()->link($id, $link, $foreignId); - - $result = true; - } - - return $result; + if (isset($data->id)) { + $foreignIdList[] = $data->id; } - throw new Error(); + if (isset($data->ids) && is_array($data->ids)) { + foreach ($data->ids as $foreignId) { + $foreignIdList[] = $foreignId; + } + } + + $result = false; + + foreach ($foreignIdList as $foreignId) { + $this->getRecordService()->link($id, $link, $foreignId); + + $result = true; + } + + return $result; } - public function actionRemoveLink($params, $data, $request) + /** + * Unrelate records. + */ + public function deleteActionRemoveLink(Request $request): bool { - if (!$request->isDelete()) { - throw new BadRequest(); - } + $id = $request->getRouteParam('id'); + $link = $request->getRouteParam('link'); - $id = $params['id']; - $link = $params['link']; + $data = $request->getParsedBody(); - if (empty($params['id']) || empty($params['link'])) { + if (!$id || !$link) { throw new BadRequest(); } @@ -414,41 +217,29 @@ class Record extends Base return $result; } - public function actionFollow($params, $data, $request) + /** + * Follow a record. + */ + public function putActionFollow(Request $request): bool { - if (!$request->isPut()) { - throw new BadRequest(); - } - - if (!$this->getAcl()->check($this->name, 'stream')) { - throw new Forbidden("No stream access for {$this->name}."); - } - - $id = $params['id']; + $id = $request->getRouteParam('id'); return $this->getRecordService()->follow($id); } - public function actionUnfollow($params, $data, $request) + /** + * Unfollow a record. + */ + public function deleteActionUnfollow(Request $request): bool { - if (!$request->isDelete()) { - throw new BadRequest(); - } - - if (!$this->getAcl()->check($this->name, 'read')) { - throw new Forbidden("No read access for {$this->name}."); - } - - $id = $params['id']; + $id = $request->getRouteParam('id'); return $this->getRecordService()->unfollow($id); } - public function actionMerge($params, $data, $request) + public function postActionMerge(Request $request): bool { - if (!$request->isPost()) { - throw new BadRequest(); - } + $data = $request->getParsedBody(); if ( empty($data->targetId) || @@ -463,7 +254,7 @@ class Record extends Base $sourceIds = $data->sourceIds; $attributes = $data->attributes; - if (!$this->getAcl()->check($this->name, 'edit')) { + if (!$this->acl->check($this->getEntityType(), 'edit')) { throw new Forbidden("No edit access for {$this->name}."); } @@ -471,70 +262,4 @@ class Record extends Base return true; } - - public function postActionGetDuplicateAttributes($params, $data, $request) - { - if (empty($data->id)) { - throw new BadRequest(); - } - - if (!$this->getAcl()->check($this->name, 'create')) { - throw new Forbidden(); - } - if (!$this->getAcl()->check($this->name, 'read')) { - throw new Forbidden(); - } - - return $this->getRecordService()->getDuplicateAttributes($data->id); - } - - public function postActionRestoreDeleted($params, $data, $request) - { - if (!$this->getUser()->isAdmin()) { - throw new Forbidden(); - } - - $id = $data->id ?? null; - - if (!$id) { - throw new Forbidden(); - } - - $this->getRecordService()->restoreDeleted($id); - - return true; - } - - public function postActionConvertCurrency($params, $data, $request) - { - if (!$this->getAcl()->checkScope($this->name, 'edit')) { - throw new Forbidden(); - } - - $fieldList = $data->fieldList ?? null; - - if (!empty($data->field)) { - if (!is_array($fieldList)) { - $fieldList = []; - } - - $fieldList[] = $data->field; - } - - if (empty($data->id)) { - throw new BadRequest(); - } - - if (empty($data->rates)) { - throw new BadRequest(); - } - - if (empty($data->targetCurrency)) { - throw new BadRequest(); - } - - return $this->getRecordService()->convertCurrency( - $data->id, $data->targetCurrency, $data->rates, $fieldList - ); - } } diff --git a/application/Espo/Core/Controllers/RecordBase.php b/application/Espo/Core/Controllers/RecordBase.php new file mode 100644 index 0000000000..17ec9e19e0 --- /dev/null +++ b/application/Espo/Core/Controllers/RecordBase.php @@ -0,0 +1,318 @@ +name; + } + + protected function getRecordService(?string $entityType = null): CrudService + { + return $this->recordServiceContainer->get($entityType ?? $this->getEntityType()); + } + + /** + * Read a record. + */ + public function getActionRead(Request $request): StdClass + { + if (method_exists($this, 'actionRead')) { + // For backward compatibility. + return (object) $this->actionRead($request->getRouteParams(), $request->getParsedBody(), $request); + } + + $id = $request->getRouteParam('id'); + + $entity = $this->getRecordService()->read($id); + + if (!$entity) { + throw new NotFound(); + } + + return $entity->getValueMap(); + } + + /** + * Create a record. + */ + public function postActionCreate(Request $request): StdClass + { + if (method_exists($this, 'actionCreate')) { + // For backward compatibility. + return (object) $this->actionCreate($request->getRouteParams(), $request->getParsedBody(), $request); + } + + $data = $request->getParsedBody(); + + $entity = $this->getRecordService()->create($data); + + return $entity->getValueMap(); + } + + public function patchActionUpdate(Request $request): StdClass + { + return $this->putActionUpdate($request); + } + + /** + * Update a record. + */ + public function putActionUpdate(Request $request): StdClass + { + if (method_exists($this, 'actionUpdate')) { + // For backward compatibility. + return (object) $this->actionUpdate($request->getRouteParams(), $request->getParsedBody(), $request); + } + + $id = $request->getRouteParam('id'); + + $data = $request->getParsedBody(); + + $entity = $this->getRecordService()->update($id, $data); + + return $entity->getValueMap(); + } + + /** + * List records. + */ + public function getActionList(Request $request): StdClass + { + if (method_exists($this, 'actionList')) { + // For backward compatibility. + return (object) $this->actionList($request->getRouteParams(), $request->getParsedBody(), $request); + } + + $data = $request->getParsedBody(); + + $listParams = []; + + $this->fetchListParamsFromRequest($listParams, $request, $data); + + $maxSizeLimit = $this->config->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT); + + if (empty($listParams['maxSize'])) { + $listParams['maxSize'] = $maxSizeLimit; + } + + if (!empty($listParams['maxSize']) && $listParams['maxSize'] > $maxSizeLimit) { + throw new Forbidden( + "Max size should should not exceed " . $maxSizeLimit . ". Use offset and limit." + ); + } + + $result = $this->getRecordService()->find($listParams); + + if ($result instanceof RecordCollection) { + return (object) [ + 'total' => $result->getTotal(), + 'list' => $result->getValueMapList(), + ]; + } + + if (is_array($result)) { + return (object) [ + 'total' => $result['total'], + 'list' => isset($result['collection']) ? + $result['collection']->getValueMapList() : + $result['list'], + ]; + } + + return (object) [ + 'total' => $result->total, + 'list' => isset($result->collection) ? + $result->collection->getValueMapList() : + $result->list, + ]; + } + + /** + * Delete a record. + */ + public function deleteActionDelete(Request $request): bool + { + if (method_exists($this, 'actionDelete')) { + // For backward compatibility. + return (object) $this->actionDelete($request->getRouteParams(), $request->getParsedBody(), $request); + } + + $id = $request->getRouteParam('id'); + + $this->getRecordService()->delete($id); + + return true; + } + + protected function fetchListParamsFromRequest(&$params, $request, $data) + { + ControllerUtil::fetchListParamsFromRequest($params, $request, $data); + } + + public function postActionExport(Request $request): StdClass + { + $data = $request->getParsedBody(); + + if ($this->config->get('exportDisabled') && !$this->user->isAdmin()) { + throw new Forbidden(); + } + + if ($this->acl->get('exportPermission') !== 'yes' && !$this->user->isAdmin()) { + throw new Forbidden(); + } + + if (!$this->acl->check($this->name, 'read')) { + throw new Forbidden(); + } + + $ids = isset($data->ids) ? + $data->ids : null; + + $where = isset($data->where) ? + json_decode(json_encode($data->where), true) : null; + + $byWhere = isset($data->byWhere) ? + $data->byWhere : false; + + $selectData = isset($data->selectData) ? + json_decode(json_encode($data->selectData), true) : null; + + $actionParams = []; + + if ($byWhere) { + $actionParams['selectData'] = $selectData; + $actionParams['where'] = $where; + } + else { + $actionParams['ids'] = $ids; + } + + if (isset($data->attributeList)) { + $actionParams['attributeList'] = $data->attributeList; + } + + if (isset($data->fieldList)) { + $actionParams['fieldList'] = $data->fieldList; + } + + if (isset($data->format)) { + $actionParams['format'] = $data->format; + } + + return (object) [ + 'id' => $this->getRecordService()->export($actionParams), + ]; + } + + public function postActionGetDuplicateAttributes(Request $request): StdClass + { + $id = $request->getParsedBody()->id ?? null; + + if (!$id) { + throw new BadRequest(); + } + + if (!$this->acl->check($this->name, 'create')) { + throw new Forbidden(); + } + + if (!$this->acl->check($this->name, 'read')) { + throw new Forbidden(); + } + + return $this->getRecordService()->getDuplicateAttributes($id); + } + + public function postActionRestoreDeleted(Request $request): bool + { + if (!$this->user->isAdmin()) { + throw new Forbidden(); + } + + $id = $request->getParsedBody()->id ?? null; + + if (!$id) { + throw new Forbidden(); + } + + $this->getRecordService()->restoreDeleted($id); + + return true; + } + + /** + * @deprecated + */ + protected function getEntityManager() + { + return $this->entityManager; + } +} diff --git a/application/Espo/Core/Controllers/RecordTree.php b/application/Espo/Core/Controllers/RecordTree.php index 4721e5736f..e42ec850ec 100644 --- a/application/Espo/Core/Controllers/RecordTree.php +++ b/application/Espo/Core/Controllers/RecordTree.php @@ -29,20 +29,26 @@ namespace Espo\Core\Controllers; -use Espo\Core\Exceptions\Error; use Espo\Core\Exceptions\Forbidden; -use Espo\Core\Exceptions\NotFound; -use Espo\Core\Exceptions\BadRequest; -use Espo\Core\Utils\Util; -class RecordTree extends Record +use Espo\Core\{ + Api\Request, +}; + +use StdClass; + +class RecordTree extends RecordBase { public static $defaultAction = 'list'; - public function actionListTree($params, $data, $request) + /** + * Get a category tree. + */ + public function getActionListTree(Request $request): StdClass { - if (!$this->getAcl()->check($this->name, 'read')) { - throw new Forbidden(); + if (method_exists($this, 'actionListTree')) { + // For backward compatibility. + return (object) $this->actionListTree($request->getRouteParams(), $request->getParsedBody(), $request); } $where = $request->get('where'); @@ -56,17 +62,16 @@ class RecordTree extends Record 'where' => $where, 'onlyNotEmpty' => $onlyNotEmpty, ], - 0, $maxDepth ); return (object) [ - 'list' => $collection->toArray(), + 'list' => $collection->getValueMapList(), 'path' => $this->getRecordService()->getTreeItemPath($parentId), ]; } - public function getActionLastChildrenIdList($params, $data, $request) + public function getActionLastChildrenIdList($params, $data, $request): array { if (!$this->getAcl()->check($this->name, 'read')) { throw new Forbidden(); diff --git a/application/Espo/Modules/Crm/Services/KnowledgeBaseArticle.php b/application/Espo/Modules/Crm/Services/KnowledgeBaseArticle.php index 76da7fff27..2622513f07 100644 --- a/application/Espo/Modules/Crm/Services/KnowledgeBaseArticle.php +++ b/application/Espo/Modules/Crm/Services/KnowledgeBaseArticle.php @@ -38,6 +38,8 @@ use Espo\Core\{ Select\SearchParams, }; +use StdClass; + class KnowledgeBaseArticle extends \Espo\Services\Record implements Di\FileStorageManagerAware @@ -59,26 +61,32 @@ class KnowledgeBaseArticle extends \Espo\Services\Record implements return $this->fileStorageManager; } - public function getCopiedAttachments(string $id, ?string $parentType = null, ?string $parentId = null) + public function getCopiedAttachments(string $id, ?string $parentType = null, ?string $parentId = null): StdClass { $ids = []; - $names = new \stdClass(); + $names = (object) []; if (empty($id)) { throw new BadRequest(); } + $entity = $this->getEntityManager()->getEntity('KnowledgeBaseArticle', $id); + if (!$entity) { throw new NotFound(); } + if (!$this->getAcl()->checkEntity($entity, 'read')) { throw new Forbidden(); } + $entity->loadLinkMultipleField('attachments'); + $attachmentsIds = $entity->get('attachmentsIds'); foreach ($attachmentsIds as $attachmentId) { $source = $this->getEntityManager()->getEntity('Attachment', $attachmentId); + if ($source) { $attachment = $this->getEntityManager()->getEntity('Attachment'); $attachment->set('role', 'Attachment'); @@ -102,12 +110,13 @@ class KnowledgeBaseArticle extends \Espo\Services\Record implements $this->getFileStorageManager()->putContents($attachment, $contents); $ids[] = $attachment->id; + $names->{$attachment->id} = $attachment->get('name'); } } } - return [ + return (object) [ 'ids' => $ids, 'names' => $names, ]; diff --git a/application/Espo/Resources/metadata/recordDefs/AuthLogRecord.json b/application/Espo/Resources/metadata/recordDefs/AuthLogRecord.json new file mode 100644 index 0000000000..f511520cb0 --- /dev/null +++ b/application/Espo/Resources/metadata/recordDefs/AuthLogRecord.json @@ -0,0 +1,10 @@ +{ + "massActions": { + "update": { + "disabled": true + }, + "recalculateFormula": { + "disabled": true + } + } +} diff --git a/application/Espo/Resources/routes.json b/application/Espo/Resources/routes.json index dcb5d63b2e..c39d5afacc 100644 --- a/application/Espo/Resources/routes.json +++ b/application/Espo/Resources/routes.json @@ -299,7 +299,7 @@ "method": "patch", "params": { "controller": ":controller", - "action": "patch", + "action": "update", "id": ":id" } }, diff --git a/application/Espo/Services/App.php b/application/Espo/Services/App.php index 5d40e3df4b..e44194eba4 100644 --- a/application/Espo/Services/App.php +++ b/application/Espo/Services/App.php @@ -108,7 +108,7 @@ class App $this->fieldUtil = $fieldUtil; } - public function getUserData() + public function getUserData(): array { $preferencesData = $this->preferences->getValueMap(); diff --git a/application/Espo/Services/CurrencyRate.php b/application/Espo/Services/CurrencyRate.php index ce2d32cdfb..65a040a10c 100644 --- a/application/Espo/Services/CurrencyRate.php +++ b/application/Espo/Services/CurrencyRate.php @@ -46,8 +46,11 @@ use StdClass; class CurrencyRate { protected $config; + protected $configWriter; + protected $dataManager; + protected $acl; public function __construct(Config $config, ConfigWriter $configWriter, DataManager $dataManager, Acl $acl) diff --git a/application/Espo/Services/DashboardTemplate.php b/application/Espo/Services/DashboardTemplate.php index b27cd9e5b9..87cd19e22f 100644 --- a/application/Espo/Services/DashboardTemplate.php +++ b/application/Espo/Services/DashboardTemplate.php @@ -85,7 +85,7 @@ class DashboardTemplate extends Record } } - public function deployToUsers(string $id, array $userIdList, bool $append = false) + public function deployToUsers(string $id, array $userIdList, bool $append = false): void { $template = $this->getEntityManager()->fetchEntity('DashboardTemplate', $id); @@ -95,6 +95,7 @@ class DashboardTemplate extends Record foreach ($userIdList as $userId) { $user = $this->getEntityManager()->fetchEntity('User', $userId); + if ($user) { if ($user->isPortal() || $user->isApi()) { throw new Forbidden("Not allowed user type."); @@ -113,11 +114,9 @@ class DashboardTemplate extends Record $this->getEntityManager()->saveEntity($preferences); } - - return true; } - public function deployToTeam(string $id, string $teamId, bool $append = false) + public function deployToTeam(string $id, string $teamId, bool $append = false): void { $template = $this->getEntityManager()->fetchEntity('DashboardTemplate', $id); @@ -151,7 +150,5 @@ class DashboardTemplate extends Record $this->getEntityManager()->saveEntity($preferences); } - - return true; } } diff --git a/application/Espo/Services/Email.php b/application/Espo/Services/Email.php index 64d2b0a29e..1d8a8a4e4d 100644 --- a/application/Espo/Services/Email.php +++ b/application/Espo/Services/Email.php @@ -731,12 +731,7 @@ class Email extends Record implements return $fromAddress; } - public function copyAttachments(string $emailId, ?string $parentType, ?string $parentId) - { - return $this->getCopiedAttachments($emailId, $parentType, $parentId); - } - - public function getCopiedAttachments(string $id, ?string $parentType = null, ?string $parentId = null) + public function getCopiedAttachments(string $id, ?string $parentType = null, ?string $parentId = null): StdClass { $ids = []; $names = (object) []; @@ -756,6 +751,7 @@ class Email extends Record implements } $email->loadLinkMultipleField('attachments'); + $attachmentsIds = $email->get('attachmentsIds'); foreach ($attachmentsIds as $attachmentId) { @@ -790,7 +786,7 @@ class Email extends Record implements } } - return [ + return (object) [ 'ids' => $ids, 'names' => $names ]; diff --git a/application/Espo/Services/EmailAccount.php b/application/Espo/Services/EmailAccount.php index feba964601..1c95fd0985 100644 --- a/application/Espo/Services/EmailAccount.php +++ b/application/Espo/Services/EmailAccount.php @@ -97,7 +97,7 @@ class EmailAccount extends Record implements } } - public function getFolders($params) + public function getFolders(array $params): array { $userId = $params['userId'] ?? null; @@ -134,6 +134,7 @@ class EmailAccount extends Record implements { if (!empty($params['id'])) { $account = $this->getEntityManager()->getEntity('EmailAccount', $params['id']); + if ($account) { $params['imapHandler'] = $account->get('imapHandler'); } @@ -152,6 +153,7 @@ class EmailAccount extends Record implements if ($storage->getFolders()) { return true; } + throw new Error(); } diff --git a/application/Espo/Services/EmailTemplate.php b/application/Espo/Services/EmailTemplate.php index 0d29af1872..99ab8f037e 100644 --- a/application/Espo/Services/EmailTemplate.php +++ b/application/Espo/Services/EmailTemplate.php @@ -206,7 +206,7 @@ class EmailTemplate extends Record implements ]; } - public function parse(string $id, array $params = [], bool $copyAttachments = false) + public function parse(string $id, array $params = [], bool $copyAttachments = false): array { $emailTemplate = $this->getEntity($id); diff --git a/application/Espo/Services/GlobalSearch.php b/application/Espo/Services/GlobalSearch.php index 36eff44b66..309ac48967 100644 --- a/application/Espo/Services/GlobalSearch.php +++ b/application/Espo/Services/GlobalSearch.php @@ -41,6 +41,7 @@ use Espo\Core\{ }; use PDO; +use StdClass; class GlobalSearch implements Di\EntityManagerAware, @@ -65,7 +66,7 @@ class GlobalSearch implements $this->selectBuilderFactory = $selectBuilderFactory; } - public function find(string $filter, int $offset, int $maxSize) + public function find(string $filter, int $offset, int $maxSize): StdClass { $entityTypeList = $this->config->get('globalSearchEntityList') ?? []; @@ -84,7 +85,7 @@ class GlobalSearch implements } if (count($queryList) === 0) { - return [ + return (object) [ 'total' => 0, 'list' => [], ]; @@ -139,7 +140,7 @@ class GlobalSearch implements unset($resultList[count($resultList) - 1]); } - return [ + return (object) [ 'total' => $total, 'list' => $resultList, ]; diff --git a/application/Espo/Services/RecordTree.php b/application/Espo/Services/RecordTree.php index 2a91a9b5b0..f6eaa32db0 100644 --- a/application/Espo/Services/RecordTree.php +++ b/application/Espo/Services/RecordTree.php @@ -29,7 +29,10 @@ namespace Espo\Services; -use Espo\ORM\Entity; +use Espo\ORM\{ + Entity, + Collection, +}; use Espo\Core\{ Exceptions\Error, @@ -59,8 +62,26 @@ class RecordTree extends Record } } - public function getTree(string $parentId = null, array $params = [], int $level = 0, ?int $maxDepth = null) - { + public function getTree( + string $parentId = null, + array $params = [], + ?int $maxDepth = null + ): ?Collection { + + if (!$this->acl->check($this->getEntityType(), 'read')) { + throw new Forbidden(); + } + + return $this->getTreeInternal($parentId, $params, $maxDepth, 0); + } + + protected function getTreeInternal( + string $parentId = null, + array $params = [], + ?int $maxDepth = null, + int $level = 0 + ): ?Collection { + if (!$maxDepth) { $maxDepth = self::MAX_DEPTH; } @@ -104,7 +125,7 @@ class RecordTree extends Record } foreach ($collection as $entity) { - $childList = $this->getTree($entity->id, $params, $level + 1, $maxDepth); + $childList = $this->getTreeInternal($entity->id, $params, $maxDepth, $level + 1); $entity->set('childList', $childList); } @@ -114,7 +135,7 @@ class RecordTree extends Record protected function checkFilterOnlyNotEmpty() { - if (!$this->getAcl()->checkScope($this->subjectEntityType, 'create')) { + if (!$this->acl->checkScope($this->subjectEntityType, 'create')) { return true; } } @@ -150,21 +171,31 @@ class RecordTree extends Record return true; } - public function getTreeItemPath($parentId = null) + public function getTreeItemPath(?string $parentId = null): array { + if (!$this->acl->check($this->getEntityType(), 'read')) { + throw new Forbidden(); + } + $arr = []; + while (1) { if (empty($parentId)) { break; } + $parent = $this->getEntityManager()->getEntity($this->entityType, $parentId); + if ($parent) { $parentId = $parent->get('parentId'); + array_unshift($arr, $parent->id); - } else { + } + else { $parentId = null; } } + return $arr; } @@ -173,15 +204,18 @@ class RecordTree extends Record if (empty($this->seed)) { $this->seed = $this->getEntityManager()->getEntity($this->getEntityType()); } + return $this->seed; } protected function hasOrder() { $seed = $this->getSeed(); + if ($seed->hasAttribute('order')) { return true; } + return false; } @@ -222,6 +256,10 @@ class RecordTree extends Record public function getLastChildrenIdList(?string $parentId = null): array { + if (!$this->acl->check($this->getEntityType(), 'read')) { + throw new Forbidden(); + } + $query = $this->selectBuilderFactory ->create() ->from($this->entityType)