diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml
index 241e8f24fc..b467eac21d 100644
--- a/.idea/jsonSchemas.xml
+++ b/.idea/jsonSchemas.xml
@@ -797,6 +797,25 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 333c511351..cdefcea218 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -334,6 +334,12 @@
],
"url": "./schema/metadata/app/language.json"
},
+ {
+ "fileMatch": [
+ "*/Resources/metadata/app/layouts.json"
+ ],
+ "url": "./schema/metadata/app/layouts.json"
+ },
{
"fileMatch": [
"*/Resources/metadata/app/linkManager.json"
diff --git a/application/Espo/Resources/metadata/app/layouts.json b/application/Espo/Resources/metadata/app/layouts.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/application/Espo/Resources/metadata/app/layouts.json
@@ -0,0 +1 @@
+{}
diff --git a/application/Espo/Tools/Layout/LayoutProvider.php b/application/Espo/Tools/Layout/LayoutProvider.php
index 6f1b6657f0..48087a45cf 100644
--- a/application/Espo/Tools/Layout/LayoutProvider.php
+++ b/application/Espo/Tools/Layout/LayoutProvider.php
@@ -32,6 +32,7 @@ namespace Espo\Tools\Layout;
use Espo\Core\InjectableFactory;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\Json;
+use Espo\Core\Utils\Metadata;
use Espo\Core\Utils\Resource\FileReader;
use Espo\Core\Utils\Resource\FileReader\Params as FileReaderParams;
use RuntimeException;
@@ -46,6 +47,7 @@ class LayoutProvider
public function __construct(
private FileManager $fileManager,
private InjectableFactory $injectableFactory,
+ private Metadata $metadata,
FileReader $fileReader
) {
$this->fileReader = $fileReader;
@@ -62,8 +64,15 @@ class LayoutProvider
$path = 'layouts/' . $scope . '/' . $name . '.json';
- $params = FileReaderParams::create()
- ->withScope($scope);
+ $params = FileReaderParams::create()->withScope($scope);
+
+ $module = $this->getLayoutLocationModule($scope, $name);
+
+ if ($module) {
+ $params = $params
+ ->withScope(null)
+ ->withModuleName($module);
+ }
if ($this->fileReader->exists($path, $params)) {
return $this->fileReader->read($path, $params);
@@ -72,6 +81,11 @@ class LayoutProvider
return $this->getDefault($scope, $name);
}
+ private function getLayoutLocationModule(string $scope, string $name): ?string
+ {
+ return $this->metadata->get("app.layouts.{$scope}.{$name}.module");
+ }
+
private function getDefault(string $scope, string $name): ?string
{
$defaultImplClassName = 'Espo\\Custom\\Classes\\DefaultLayouts\\' . ucfirst($name) . 'Type';
diff --git a/schema/metadata/app/layouts.json b/schema/metadata/app/layouts.json
new file mode 100644
index 0000000000..9439fc151b
--- /dev/null
+++ b/schema/metadata/app/layouts.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://www.espocrm.com/schema/metadata/app/layouts.json",
+ "title": "app/layouts",
+ "description": "Layouts.",
+ "type": "object",
+ "propertyNames": {
+ "anyOf": [
+ {"type": "string"}
+ ]
+ },
+ "additionalProperties": {
+ "type": "object",
+ "description": "A scope name.",
+ "additionalProperties": {
+ "type": "object",
+ "description": "A layout name.",
+ "properties": {
+ "module": {
+ "type": "string",
+ "description": "A module in which the layout is located."
+ }
+ }
+ }
+ }
+}
diff --git a/tests/unit/Espo/Core/Utils/LayoutTest.php b/tests/unit/Espo/Core/Utils/LayoutTest.php
index 87740d50ff..c51d8df2f0 100644
--- a/tests/unit/Espo/Core/Utils/LayoutTest.php
+++ b/tests/unit/Espo/Core/Utils/LayoutTest.php
@@ -29,43 +29,38 @@
namespace tests\unit\Espo\Core\Utils;
+use Espo\Core\Utils\Metadata;
use Espo\Tools\Layout\LayoutProvider;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\InjectableFactory;
-use Espo\Core\{
- Utils\Resource\FileReader,
- Utils\Resource\FileReader\Params as FileReaderParams,
-};
+use Espo\Core\Utils\Resource\FileReader;
+use Espo\Core\Utils\Resource\FileReader\Params as FileReaderParams;
class LayoutTest extends \PHPUnit\Framework\TestCase
{
- /**
- * @var LayoutProvider
- */
+ /** @var LayoutProvider */
private $layout;
-
- /**
- * @var InjectableFactory
- */
+ /** @var InjectableFactory */
private $injectableFactory;
-
- /**
- * @var FileManager
- */
+ /** @var FileManager */
private $fileManager;
-
private $fileReader;
protected function setUp(): void
{
$this->fileManager = $this->createMock(FileManager::class);
-
$this->injectableFactory = $this->createMock(InjectableFactory::class);
-
$this->fileReader = $this->createMock(FileReader::class);
- $this->layout = new \Espo\Tools\Layout\LayoutProvider($this->fileManager, $this->injectableFactory, $this->fileReader);
+ $metadata = $this->createMock(Metadata::class);
+
+ $this->layout = new LayoutProvider(
+ $this->fileManager,
+ $this->injectableFactory,
+ $metadata,
+ $this->fileReader
+ );
}
public function testGet1(): void