diff --git a/api/v1/portal/index.php b/api/v1/portal/index.php index 57687bb851..6ab5c3479f 100644 --- a/api/v1/portal/index.php +++ b/api/v1/portal/index.php @@ -29,10 +29,7 @@ require_once('../../../bootstrap.php'); -//print_r($_SERVER); -//die; - -$portalId = '567d4a0503c88c061'; +$portalId = explode('/', $_SERVER['REQUEST_URI'])[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1]; $app = new \Espo\Core\ApplicationPortal($portalId); $app->run(); diff --git a/application/Espo/Controllers/Email.php b/application/Espo/Controllers/Email.php index e109565c22..61cf329dd7 100644 --- a/application/Espo/Controllers/Email.php +++ b/application/Espo/Controllers/Email.php @@ -35,9 +35,12 @@ use \Espo\Core\Exceptions\Error; class Email extends \Espo\Core\Controllers\Record { - public function actionGetCopiedAttachments($params, $data, $request) + public function postActionGetCopiedAttachments($params, $data, $request) { - $id = $request->get('id'); + if (empty($data['id'])) { + throw new BadRequest(); + } + $id = $data['id']; return $this->getRecordService()->getCopiedAttachments($id); } diff --git a/application/Espo/Controllers/ExternalAccount.php b/application/Espo/Controllers/ExternalAccount.php index 28663947cc..10119f61ef 100644 --- a/application/Espo/Controllers/ExternalAccount.php +++ b/application/Espo/Controllers/ExternalAccount.php @@ -37,6 +37,13 @@ class ExternalAccount extends \Espo\Core\Controllers\Record { public static $defaultAction = 'list'; + protected function checkControllerAccess() + { + if (!$this->getAcl()->checkScope('ExternalAccount')) { + throw new Forbidden(); + } + } + public function actionList($params, $data, $request) { $integrations = $this->getEntityManager()->getRepository('Integration')->find(); diff --git a/application/Espo/Core/Container.php b/application/Espo/Core/Container.php index 31895283dd..c2ca214690 100644 --- a/application/Espo/Core/Container.php +++ b/application/Espo/Core/Container.php @@ -215,7 +215,8 @@ class Container { return new \Espo\Core\Utils\Layout( $this->get('fileManager'), - $this->get('metadata') + $this->get('metadata'), + $this->get('user') ); } diff --git a/application/Espo/Core/ContainerPortal.php b/application/Espo/Core/ContainerPortal.php index bc142805a2..f81102aaee 100644 --- a/application/Espo/Core/ContainerPortal.php +++ b/application/Espo/Core/ContainerPortal.php @@ -63,6 +63,16 @@ class ContainerPortal extends Container $this->get('portal') ); } + + protected function loadLayout() + { + return new \Espo\Core\Utils\LayoutPortal( + $this->get('fileManager'), + $this->get('metadata'), + $this->get('user') + ); + } + public function setPortal(\Espo\Entities\Portal $portal) { $this->set('portal', $portal); diff --git a/application/Espo/Core/ORM/Repositories/RDB.php b/application/Espo/Core/ORM/Repositories/RDB.php index c08c68f573..d3c32c0f08 100644 --- a/application/Espo/Core/ORM/Repositories/RDB.php +++ b/application/Espo/Core/ORM/Repositories/RDB.php @@ -47,6 +47,11 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable private $restoreData = null; + protected function addDependency($name) + { + $this->dependencies[] = $name; + } + public function inject($name, $object) { $this->injections[$name] = $object; diff --git a/application/Espo/Core/Utils/Layout.php b/application/Espo/Core/Utils/Layout.php index 7b8e838d42..8d06504a2b 100644 --- a/application/Espo/Core/Utils/Layout.php +++ b/application/Espo/Core/Utils/Layout.php @@ -28,18 +28,16 @@ ************************************************************************/ namespace Espo\Core\Utils; + class Layout { private $fileManager; private $metadata; - private $changedData = array(); + private $user; - /** - * @var string - uses for loading default values - */ - private $name = 'layout'; + protected $changedData = array(); protected $params = array( 'defaultsPath' => 'application/Espo/Core/defaults', @@ -49,17 +47,18 @@ class Layout /** * @var array - path to layout files */ - private $paths = array( + protected $paths = array( 'corePath' => 'application/Espo/Resources/layouts', 'modulePath' => 'application/Espo/Modules/{*}/Resources/layouts', 'customPath' => 'custom/Espo/Custom/Resources/layouts', ); - public function __construct(\Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Metadata $metadata) + public function __construct(\Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Metadata $metadata, \Espo\Entities\User $user) { $this->fileManager = $fileManager; $this->metadata = $metadata; + $this->user = $user; } protected function getFileManager() @@ -72,6 +71,11 @@ class Layout return $this->metadata; } + protected function getUser() + { + return $this->user; + } + protected function sanitizeInput($name) { return preg_replace("([\.]{2,})", '', $name); @@ -94,16 +98,14 @@ class Layout return Json::encode($this->changedData[$scope][$name]); } - $fileFullPath = Util::concatPath($this->getLayoutPath($scope, true), $name.'.json'); + $fileFullPath = Util::concatPath($this->getLayoutPath($scope, true), $name . '.json'); if (!file_exists($fileFullPath)) { - $fileFullPath = Util::concatPath($this->getLayoutPath($scope), $name.'.json'); + $fileFullPath = Util::concatPath($this->getLayoutPath($scope), $name . '.json'); } if (!file_exists($fileFullPath)) { - //load defaults $defaultPath = $this->params['defaultsPath']; - $fileFullPath = Util::concatPath( Util::concatPath($defaultPath, $this->name), $name.'.json' ); - //END: load defaults + $fileFullPath = Util::concatPath(Util::concatPath($defaultPath, 'layouts'), $name . '.json' ); if (!file_exists($fileFullPath)) { return false; @@ -162,11 +164,9 @@ class Layout if (!empty($this->changedData)) { foreach ($this->changedData as $scope => $rowData) { foreach ($rowData as $layoutName => $layoutData) { - if (empty($scope) || empty($layoutName)) { continue; } - $layoutPath = $this->getLayoutPath($scope, true); $data = Json::encode($layoutData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); @@ -221,17 +221,17 @@ class Layout /** * Get Layout path, ex. application/Modules/Crm/Layouts/Account * - * @param string $entityName + * @param string $entityType * @param bool $isCustom - if need to check custom folder * * @return string */ - protected function getLayoutPath($entityName, $isCustom = false) + protected function getLayoutPath($entityType, $isCustom = false) { $path = $this->paths['customPath']; if (!$isCustom) { - $moduleName = $this->getMetadata()->getScopeModuleName($entityName); + $moduleName = $this->getMetadata()->getScopeModuleName($entityType); $path = $this->paths['corePath']; if ($moduleName !== false) { @@ -239,7 +239,7 @@ class Layout } } - $path = Util::concatPath($path, $entityName); + $path = Util::concatPath($path, $entityType); return $path; } diff --git a/application/Espo/Core/Utils/LayoutPortal.php b/application/Espo/Core/Utils/LayoutPortal.php new file mode 100644 index 0000000000..73d22e9fe0 --- /dev/null +++ b/application/Espo/Core/Utils/LayoutPortal.php @@ -0,0 +1,127 @@ +sanitizeInput($scope); + $name = $this->sanitizeInput($name); + + if (isset($this->changedData[$scope][$name])) { + return Json::encode($this->changedData[$scope][$name]); + } + + $fileFullPath = Util::concatPath($this->getLayoutPath($scope, true), 'portal/' . $name . '.json'); + + if (!file_exists($fileFullPath)) { + $fileFullPath = Util::concatPath($this->getLayoutPath($scope), 'portal/' . $name . '.json'); + } + if (!file_exists($fileFullPath)) { + $fileFullPath = Util::concatPath($this->getLayoutPath($scope, true), $name . '.json'); + } + if (!file_exists($fileFullPath)) { + $fileFullPath = Util::concatPath($this->getLayoutPath($scope), $name . '.json'); + } + + + if (!file_exists($fileFullPath)) { + $defaultPath = $this->params['defaultsPath']; + $fileFullPath = Util::concatPath(Util::concatPath($defaultPath, 'layouts'), $name . '.json' ); + + if (!file_exists($fileFullPath)) { + return false; + } + } + + return $this->getFileManager()->getContents($fileFullPath); + } + + + public function set($data, $scope, $name) + { + $scope = $this->sanitizeInput($scope); + $name = $this->sanitizeInput($name); + + if (empty($scope) || empty($name)) { + return false; + } + + $this->changedData[$scope][$name] = $data; + } + + public function resetToDefault($scope, $name) + { + $scope = $this->sanitizeInput($scope); + $name = $this->sanitizeInput($name); + + $filePath = 'custom/Espo/Custom/Resources/layouts/' . $scope . '/' . $name . '.json'; + if ($this->getFileManager()->isFile($filePath)) { + $this->getFileManager()->removeFile($filePath); + } + if (!empty($this->changedData[$scope]) && !empty($this->changedData[$scope][$name])) { + unset($this->changedData[$scope][$name]); + } + return $this->get($scope, $name); + } + + /** + * Save changes + * + * @return bool + */ + public function save() + { + $result = true; + + if (!empty($this->changedData)) { + foreach ($this->changedData as $scope => $rowData) { + foreach ($rowData as $layoutName => $layoutData) { + if (empty($scope) || empty($layoutName)) { + continue; + } + $layoutPath = $this->getLayoutPath($scope, true); + $data = Json::encode($layoutData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + + $result &= $this->getFileManager()->putContents(array($layoutPath, $layoutName.'.json'), $data); + } + } + } + + if ($result == true) { + $this->clearChanges(); + } + + return (bool) $result; + } + +} diff --git a/application/Espo/Core/defaults/layout/detail.json b/application/Espo/Core/defaults/layouts/detail.json similarity index 100% rename from application/Espo/Core/defaults/layout/detail.json rename to application/Espo/Core/defaults/layouts/detail.json diff --git a/application/Espo/Core/defaults/layout/detailSmall.json b/application/Espo/Core/defaults/layouts/detailSmall.json similarity index 100% rename from application/Espo/Core/defaults/layout/detailSmall.json rename to application/Espo/Core/defaults/layouts/detailSmall.json diff --git a/application/Espo/Core/defaults/layout/filters.json b/application/Espo/Core/defaults/layouts/filters.json similarity index 100% rename from application/Espo/Core/defaults/layout/filters.json rename to application/Espo/Core/defaults/layouts/filters.json diff --git a/application/Espo/Core/defaults/layout/list.json b/application/Espo/Core/defaults/layouts/list.json similarity index 100% rename from application/Espo/Core/defaults/layout/list.json rename to application/Espo/Core/defaults/layouts/list.json diff --git a/application/Espo/Core/defaults/layout/listDashlet.json b/application/Espo/Core/defaults/layouts/listDashlet.json similarity index 100% rename from application/Espo/Core/defaults/layout/listDashlet.json rename to application/Espo/Core/defaults/layouts/listDashlet.json diff --git a/application/Espo/Core/defaults/layout/listSmall.json b/application/Espo/Core/defaults/layouts/listSmall.json similarity index 100% rename from application/Espo/Core/defaults/layout/listSmall.json rename to application/Espo/Core/defaults/layouts/listSmall.json diff --git a/application/Espo/Core/defaults/layout/massUpdate.json b/application/Espo/Core/defaults/layouts/massUpdate.json similarity index 100% rename from application/Espo/Core/defaults/layout/massUpdate.json rename to application/Espo/Core/defaults/layouts/massUpdate.json diff --git a/application/Espo/Core/defaults/layout/relationships.json b/application/Espo/Core/defaults/layouts/relationships.json similarity index 100% rename from application/Espo/Core/defaults/layout/relationships.json rename to application/Espo/Core/defaults/layouts/relationships.json diff --git a/application/Espo/EntryPoints/Portal.php b/application/Espo/EntryPoints/Portal.php index 9f7b240e6c..2b72b92d47 100644 --- a/application/Espo/EntryPoints/Portal.php +++ b/application/Espo/EntryPoints/Portal.php @@ -39,13 +39,16 @@ class Portal extends \Espo\Core\EntryPoints\Base public function run() { - if (empty($_GET['id'])) { - throw new BadRequest(); + if (!empty($_GET['id'])) { + $id = $_GET['id']; + } else { + $id = $this->getConfig()->get('defaultPortalId'); + if (!$id) { + throw new NotFound(); + } } - $id = $_GET['id']; $application = new \Espo\Core\ApplicationPortal($id); $application->runClient(); } } - diff --git a/application/Espo/Repositories/Attachment.php b/application/Espo/Repositories/Attachment.php index f047a96f84..9867468034 100644 --- a/application/Espo/Repositories/Attachment.php +++ b/application/Espo/Repositories/Attachment.php @@ -25,7 +25,7 @@ * * In accordance with Section 7(b) of the GNU General Public License version 3, * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. - ************************************************************************/ + ************************************************************************/ namespace Espo\Repositories; diff --git a/application/Espo/Repositories/Portal.php b/application/Espo/Repositories/Portal.php new file mode 100644 index 0000000000..cccd61b3ea --- /dev/null +++ b/application/Espo/Repositories/Portal.php @@ -0,0 +1,62 @@ +addDependency('config'); + } + + protected function getConfig() + { + return $this->getInjection('config'); + } + + protected function afterSave(Entity $entity, array $options) + { + parent::afterSave($entity, $options); + + if ($entity->has('isDefault')) { + if ($entity->get('isDefault')) { + $this->getConfig()->set('defaultPortalId', $entity->id); + } else { + $this->getConfig()->set('defaultPortalId', null); + } + $this->getConfig()->save(); + } + } +} + diff --git a/application/Espo/Resources/i18n/en_US/User.json b/application/Espo/Resources/i18n/en_US/User.json index 47fda00b03..9d88f2d8d0 100644 --- a/application/Espo/Resources/i18n/en_US/User.json +++ b/application/Espo/Resources/i18n/en_US/User.json @@ -74,6 +74,7 @@ "onlyMyTeam": "Only My Team" }, "presetFilters": { - "active": "Active" + "active": "Active", + "activePortal": "Portal" } } diff --git a/application/Espo/Resources/layouts/Portal/detail.json b/application/Espo/Resources/layouts/Portal/detail.json index 3ec4df0f4d..563f31afdf 100644 --- a/application/Espo/Resources/layouts/Portal/detail.json +++ b/application/Espo/Resources/layouts/Portal/detail.json @@ -3,7 +3,8 @@ "label": "General", "rows": [ [{"name": "name"}, {"name": "isActive"}], - [{"name": "url"}, {"name": "isDefault"}] + [{"name": "url"}, {"name": "isDefault"}], + [{"name": "portalRoles"}, false] ] }, { diff --git a/application/Espo/Resources/layouts/Preferences/portal/detail.json b/application/Espo/Resources/layouts/Preferences/portal/detail.json new file mode 100644 index 0000000000..7aa8371cdb --- /dev/null +++ b/application/Espo/Resources/layouts/Preferences/portal/detail.json @@ -0,0 +1,31 @@ +[ + { + "label": "Locale", + "rows": [ + [{"name": "dateFormat"}, {"name": "timeFormat"}], + [{"name": "timeZone"}, {"name": "weekStart"}], + [{"name": "defaultCurrency"}, false], + [{"name": "thousandSeparator"}, {"name": "decimalMark"}], + [{"name": "language"}, false] + ] + }, + { + "label": "Misc", + "rows": [ + [{"name": "exportDelimiter"}, {"name":"autoFollowEntityTypeList"}] + ] + }, + { + "label": "User Interface", + "rows": [ + [ + {"name":"theme"}, + false + ], + [ + {"name":"useCustomTabList"}, + {"name":"tabList"} + ] + ] + } +] diff --git a/application/Espo/Resources/metadata/app/aclPortal.json b/application/Espo/Resources/metadata/app/aclPortal.json index 2e1384a6af..a3c267fdeb 100644 --- a/application/Espo/Resources/metadata/app/aclPortal.json +++ b/application/Espo/Resources/metadata/app/aclPortal.json @@ -20,6 +20,7 @@ "delete": "own" }, "EmailAccount": false, + "ExternalAccount": false, "Role": false, "PortalRole": false, "EmailFilter": false, diff --git a/application/Espo/Resources/metadata/clientDefs/Portal.json b/application/Espo/Resources/metadata/clientDefs/Portal.json index eb8ec9625e..e4d518c93a 100644 --- a/application/Espo/Resources/metadata/clientDefs/Portal.json +++ b/application/Espo/Resources/metadata/clientDefs/Portal.json @@ -2,9 +2,10 @@ "controller": "controllers/record", "relationshipPanels": { "users": { - "create": true, + "create": false, "rowActionsView": "views/record/row-actions/relationship-unlink-only", - "layout": "listForTeam" + "layout": "listSmall", + "selectPrimaryFilterName": "activePortal" } }, "recordViews": { diff --git a/application/Espo/Resources/metadata/clientDefs/Team.json b/application/Espo/Resources/metadata/clientDefs/Team.json index de25fa5d15..90bcb069db 100644 --- a/application/Espo/Resources/metadata/clientDefs/Team.json +++ b/application/Espo/Resources/metadata/clientDefs/Team.json @@ -1,15 +1,16 @@ { - "relationshipPanels":{ + "relationshipPanels": { "users":{ - "create":false, - "rowActionsView": "Record.RowActions.RelationshipUnlinkOnly", - "layout":"listForTeam" + "create": false, + "rowActionsView": "views/record/row-actions/relationship-unlink-only", + "layout": "listForTeam", + "selectPrimaryFilterName": "active" } }, - "recordViews":{ - "detail":"Team.Record.Detail", - "edit":"Team.Record.Edit", - "list":"Team.Record.List" + "recordViews": { + "detail": "views/team/record/detail", + "edit": "views/team/record/edit", + "list": "views/team/record/list" }, "boolFilterList": ["onlyMy"] } diff --git a/application/Espo/Resources/metadata/clientDefs/User.json b/application/Espo/Resources/metadata/clientDefs/User.json index 928eef3057..db8056d6c1 100644 --- a/application/Espo/Resources/metadata/clientDefs/User.json +++ b/application/Espo/Resources/metadata/clientDefs/User.json @@ -9,6 +9,6 @@ "editQuick":"User.Record.Edit", "list":"User.Record.List" }, - "filterList": ["active"], + "filterList": ["active","activePortal"], "boolFilterList": ["onlyMyTeam"] } diff --git a/application/Espo/Resources/metadata/entityDefs/Portal.json b/application/Espo/Resources/metadata/entityDefs/Portal.json index b21301f53f..678a5ff597 100644 --- a/application/Espo/Resources/metadata/entityDefs/Portal.json +++ b/application/Espo/Resources/metadata/entityDefs/Portal.json @@ -28,12 +28,12 @@ "tabList": { "type": "array", "translation": "Global.scopeNamesPlural", - "view": "views/settings/fields/tab-list" + "view": "views/portal/fields/tab-list" }, "quickCreateList": { "type": "array", "translation": "Global.scopeNames", - "view": "views/settings/fields/quick-create-list" + "view": "views/portal/fields/quick-create-list" }, "companyLogo": { "type": "image" diff --git a/application/Espo/Resources/metadata/scopes/ExternalAccount.json b/application/Espo/Resources/metadata/scopes/ExternalAccount.json index 432d5091d0..1bb0cdec39 100644 --- a/application/Espo/Resources/metadata/scopes/ExternalAccount.json +++ b/application/Espo/Resources/metadata/scopes/ExternalAccount.json @@ -2,6 +2,7 @@ "entity":true, "layouts":false, "tab":false, - "acl":false, + "acl": "boolean", + "aclPortal": false, "customizable":false } diff --git a/application/Espo/SelectManagers/User.php b/application/Espo/SelectManagers/User.php index e72ee66093..647d559b25 100644 --- a/application/Espo/SelectManagers/User.php +++ b/application/Espo/SelectManagers/User.php @@ -53,7 +53,7 @@ class User extends \Espo\Core\SelectManagers\Base ); } - protected function filterActivePortalUsers(&$result) + protected function filterActivePortal(&$result) { $result['whereClause'][] = array( 'isActive' => true, diff --git a/application/Espo/Services/Email.php b/application/Espo/Services/Email.php index 50b349819b..b7dac4d9ff 100644 --- a/application/Espo/Services/Email.php +++ b/application/Espo/Services/Email.php @@ -34,6 +34,9 @@ use \Espo\Entities; use \Espo\Core\Exceptions\Error; use \Espo\Core\Exceptions\Forbidden; +use \Espo\Core\Exceptions\NotFound; +use \Espo\Core\Exceptions\BadRequest; + class Email extends Record { @@ -548,36 +551,41 @@ class Email extends Record $ids = array(); $names = new \stdClass(); - if (!empty($id)) { - $email = $this->getEntityManager()->getEntity('Email', $id); - if ($email && $this->getAcl()->check($email, 'read')) { - $email->loadLinkMultipleField('attachments'); - $attachmentsIds = $email->get('attachmentsIds'); + if (empty($id)) { + throw new BadRequest(); + } + $email = $this->getEntityManager()->getEntity('Email', $id); + if (!$email) { + throw new NotFound(); + } + if (!$this->getAcl()->checkEntity($email, 'read')) { + throw new Forbidden(); + } + $email->loadLinkMultipleField('attachments'); + $attachmentsIds = $email->get('attachmentsIds'); - foreach ($attachmentsIds as $attachmentId) { - $source = $this->getEntityManager()->getEntity('Attachment', $attachmentId); - if ($source) { - $attachment = $this->getEntityManager()->getEntity('Attachment'); - $attachment->set('role', 'Attachment'); - $attachment->set('type', $source->get('type')); - $attachment->set('size', $source->get('size')); - $attachment->set('global', $source->get('global')); - $attachment->set('name', $source->get('name')); + foreach ($attachmentsIds as $attachmentId) { + $source = $this->getEntityManager()->getEntity('Attachment', $attachmentId); + if ($source) { + $attachment = $this->getEntityManager()->getEntity('Attachment'); + $attachment->set('role', 'Attachment'); + $attachment->set('type', $source->get('type')); + $attachment->set('size', $source->get('size')); + $attachment->set('global', $source->get('global')); + $attachment->set('name', $source->get('name')); - if (!empty($parentType) && !empty($parentId)) { - $attachment->set('parentType', $parentType); - $attachment->set('parentId', $parentId); - } + if (!empty($parentType) && !empty($parentId)) { + $attachment->set('parentType', $parentType); + $attachment->set('parentId', $parentId); + } - $this->getEntityManager()->saveEntity($attachment); + $this->getEntityManager()->saveEntity($attachment); - $contents = $this->getFileManager()->getContents('data/upload/' . $source->id); - if (!empty($contents)) { - $this->getFileManager()->putContents('data/upload/' . $attachment->id, $contents); - $ids[] = $attachment->id; - $names->{$attachment->id} = $attachment->get('name'); - } - } + $contents = $this->getFileManager()->getContents('data/upload/' . $source->id); + if (!empty($contents)) { + $this->getFileManager()->putContents('data/upload/' . $attachment->id, $contents); + $ids[] = $attachment->id; + $names->{$attachment->id} = $attachment->get('name'); } } } diff --git a/application/Espo/Services/Portal.php b/application/Espo/Services/Portal.php new file mode 100644 index 0000000000..4acccfbe9e --- /dev/null +++ b/application/Espo/Services/Portal.php @@ -0,0 +1,49 @@ +getConfig()->get('siteUrl'); + $url = $siteUrl . '?entryPoint=portal'; + if ($entity->id === $this->getConfig()->get('defaultPortalId')) { + $entity->set('isDefault', true); + } else { + $url .= '&id=' . $entity->id; + } + $entity->set('url', $url); + } +} + diff --git a/frontend/client/res/templates/record/bottom.tpl b/frontend/client/res/templates/record/bottom.tpl index 3e3111b0ed..c3e4108e6d 100644 --- a/frontend/client/res/templates/record/bottom.tpl +++ b/frontend/client/res/templates/record/bottom.tpl @@ -4,7 +4,7 @@