From 29398de6781f758f14884d4425987417569e882f Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Fri, 2 Jul 2021 12:11:33 +0300 Subject: [PATCH] custom modules support --- Gruntfile.js | 4 ++ application/Espo/Core/DataManager.php | 20 +++++++- application/Espo/Core/Utils/ClientManager.php | 32 ++++++++---- application/Espo/Core/Utils/Module.php | 14 +++++- client/custom/.gitignore | 5 +- client/custom/modules/.gitignore | 2 + client/src/app.js | 50 ++++++++++++------- client/src/loader.js | 47 ++++++++++------- html/main.html | 2 + 9 files changed, 124 insertions(+), 52 deletions(-) create mode 100644 client/custom/modules/.gitignore diff --git a/Gruntfile.js b/Gruntfile.js index 44ee22fa3f..c63374e23e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -106,6 +106,10 @@ module.exports = grunt => { '!build/tmp/custom/Espo/Modules/.htaccess', 'build/tmp/install/config.php', 'build/tmp/vendor/*/*/.git', + 'build/tmp/custom/Espo/Custom/*', + 'build/tmp/client/custom/*', + '!build/tmp/client/custom/modules', + 'build/tmp/client/custom/modules/*', ] } }, diff --git a/application/Espo/Core/DataManager.php b/application/Espo/Core/DataManager.php index bea626e97e..5a893d97f5 100644 --- a/application/Espo/Core/DataManager.php +++ b/application/Espo/Core/DataManager.php @@ -41,6 +41,7 @@ use Espo\Core\{ HookManager, Utils\Database\Schema\SchemaProxy, Utils\Log, + Utils\Module, }; use Throwable; @@ -68,6 +69,8 @@ class DataManager private $log; + private $module; + private $cachePath = 'data/cache'; public function __construct( @@ -79,7 +82,8 @@ class DataManager OrmMetadataData $ormMetadataData, HookManager $hookManager, SchemaProxy $schemaProxy, - Log $log + Log $log, + Module $module ) { $this->entityManager = $entityManager; $this->config = $config; @@ -90,6 +94,7 @@ class DataManager $this->hookManager = $hookManager; $this->schemaProxy = $schemaProxy; $this->log = $log; + $this->module = $module; } /** @@ -99,6 +104,7 @@ class DataManager { $this->clearCache(); $this->disableHooks(); + $this->checkModules(); $this->populateConfigParameters(); $this->rebuildMetadata(); $this->rebuildDatabase($entityList); @@ -329,4 +335,16 @@ class DataManager { $this->hookManager->enable(); } + + private function checkModules(): void + { + $moduleNameList = $this->module->getList(); + + if (count(array_unique($moduleNameList)) !== count($moduleNameList)) { + throw new Error( + "There is a same module in both `custom` and `internal` directories. " . + "Should be only in one location." + ); + } + } } diff --git a/application/Espo/Core/Utils/ClientManager.php b/application/Espo/Core/Utils/ClientManager.php index 65fecce178..9795bc4047 100644 --- a/application/Espo/Core/Utils/ClientManager.php +++ b/application/Espo/Core/Utils/ClientManager.php @@ -32,6 +32,8 @@ namespace Espo\Core\Utils; use Espo\Core\{ Utils\File\Manager as FileManager, Utils\Client\DevModeJsFileListProvider, + Utils\Module, + Utils\Json, }; /** @@ -39,14 +41,6 @@ use Espo\Core\{ */ class ClientManager { - private $themeManager; - - private $config; - - private $metadata; - - private $fileManager; - protected $mainHtmlFilePath = 'html/main.html'; protected $runScript = "app.start();"; @@ -55,20 +49,32 @@ class ClientManager private $libsConfigPath = 'client/cfg/libs.json'; + private $themeManager; + + private $config; + + private $metadata; + + private $fileManager; + private $devModeJsFileListProvider; + private $module; + public function __construct( Config $config, ThemeManager $themeManager, Metadata $metadata, FileManager $fileManager, - DevModeJsFileListProvider $devModeJsFileListProvider + DevModeJsFileListProvider $devModeJsFileListProvider, + Module $module ) { $this->config = $config; $this->themeManager = $themeManager; $this->metadata = $metadata; $this->fileManager = $fileManager; $this->devModeJsFileListProvider = $devModeJsFileListProvider; + $this->module = $module; } public function setBasePath(string $basePath): void @@ -166,6 +172,13 @@ class ClientManager $faviconPath = $this->metadata->get(['app', 'client', 'favicon']) ?? 'client/img/favicon.ico'; + $internalModuleList = array_map( + function (string $moduleName): string { + return Util::fromCamelCase($moduleName, '-'); + }, + $this->module->getInternalList() + ); + $data = [ 'applicationId' => 'espocrm-application-id', 'apiUrl' => 'api/v1', @@ -184,6 +197,7 @@ class ClientManager 'faviconPath' => $faviconPath, 'ajaxTimeout' => $this->config->get('ajaxTimeout') ?? 60000, 'libsConfigPath' => $this->libsConfigPath, + 'internalModuleList' => Json::encode($internalModuleList), ]; $html = $this->fileManager->getContents($htmlFilePath); diff --git a/application/Espo/Core/Utils/Module.php b/application/Espo/Core/Utils/Module.php index 5f07e651cc..75d11619a4 100644 --- a/application/Espo/Core/Utils/Module.php +++ b/application/Espo/Core/Utils/Module.php @@ -145,7 +145,12 @@ class Module return array_keys($modulesToSort); } - private function getInternalList(): array + /** + * Get the list of internal modules. + * + * @return string[] + */ + public function getInternalList(): array { if ($this->internalList === null) { $this->internalList = $this->fileManager->getDirList($this->internalPath); @@ -166,7 +171,12 @@ class Module return $basePath . '/' . $moduleName; } - private function getList(): array + /** + * Get the list of modules. Not ordered. + * + * @return string[] + */ + public function getList(): array { if ($this->list === null) { $this->list = array_merge( diff --git a/client/custom/.gitignore b/client/custom/.gitignore index 86d0cb2726..51f0ad5f0a 100644 --- a/client/custom/.gitignore +++ b/client/custom/.gitignore @@ -1,4 +1,3 @@ -# Ignore everything in this directory * -# Except this file -!.gitignore \ No newline at end of file +!.gitignore +!modules diff --git a/client/custom/modules/.gitignore b/client/custom/modules/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/client/custom/modules/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/client/src/app.js b/client/src/app.js index a54ef9102c..041ec89d2a 100644 --- a/client/src/app.js +++ b/client/src/app.js @@ -102,6 +102,7 @@ define( this.apiUrl = options.apiUrl || this.apiUrl; this.basePath = options.basePath || ''; this.ajaxTimeout = options.ajaxTimeout || 0; + this.internalModuleList = options.internalModuleList || []; this.initCache(options) .then(() => this.init(options, callback)); @@ -532,7 +533,17 @@ define( require(Utils.composeViewClassName(viewName), callback); }; - var getResourceInnerPath = function (type, name) { + let internalModuleMap = {}; + + let isModuleInternal = (module) => { + if (!(module in internalModuleMap)) { + internalModuleMap[module] = this.internalModuleList.indexOf(module) !== -1; + } + + return internalModuleMap[module]; + }; + + let getResourceInnerPath = (type, name) => { let path = null; switch (type) { @@ -557,25 +568,26 @@ define( } return path; + }; - }.bind(this); - - var getResourcePath = (type, name) => { - if (name.indexOf(':') !== -1) { - let arr = name.split(':'); - - name = arr[1]; - - var mod = arr[0]; - - if (mod === 'custom') { - return 'client/custom/' + getResourceInnerPath(type, name); - } - - return 'client/modules/' + mod + '/' + getResourceInnerPath(type, name); + let getResourcePath = (type, name) => { + if (name.indexOf(':') === -1) { + return 'client/' + getResourceInnerPath(type, name); } - return 'client/' + getResourceInnerPath(type, name); + let arr = name.split(':'); + let mod = arr[0]; + let path = arr[1]; + + if (mod === 'custom') { + return 'client/custom/' + getResourceInnerPath(type, path); + } + + if (isModuleInternal(mod)) { + return 'client/modules/' + mod + '/' + getResourceInnerPath(type, path); + } + + return 'client/custom/modules/' + mod + '/' + getResourceInnerPath(type, path); }; this.viewFactory = new Bull.Factory({ @@ -585,12 +597,12 @@ define( viewLoader: this.viewLoader, resources: { loaders: { - 'template': (name, callback) => { + template: (name, callback) => { var path = getResourcePath('template', name); this.loader.load('res!' + path, callback); }, - 'layoutTemplate': (name, callback) => { + layoutTemplate: (name, callback) => { var path = getResourcePath('layoutTemplate', name); this.loader.load('res!' + path, callback); diff --git a/client/src/loader.js b/client/src/loader.js index f2bea5471a..0cf1ebe91f 100644 --- a/client/src/loader.js +++ b/client/src/loader.js @@ -42,6 +42,8 @@ var Espo = Espo || {classMap: {}}; this._loadingSubject = null; this._responseCache = null; this._basePath = ''; + this._internalModuleList = []; + this._internalModuleMap = {}; this.isDeveloperMode = false; }; @@ -76,6 +78,11 @@ var Espo = Espo || {classMap: {}}; this._responseCache = responseCache; }, + setInternalModuleList: function (internalModuleList) { + this._internalModuleList = internalModuleList; + this._internalModuleMap = {}; + }, + _getClass: function (name) { if (name in this._classMap) { return this._classMap[name]; @@ -89,27 +96,23 @@ var Espo = Espo || {classMap: {}}; }, _nameToPath: function (name) { - let path; - - if (name.indexOf(':') !== -1) { - let arr = name.split(':'); - let namePart = arr[1]; - let modulePart = arr[0]; - - if (modulePart === 'custom') { - path = 'client/custom/src/' + namePart; - } - else { - path = 'client/modules/' + modulePart + '/src/' + namePart; - } - } - else { - path = 'client/src/' + name; + if (name.indexOf(':') === -1) { + return 'client/src/' + name + '.js'; } - path += '.js'; + let arr = name.split(':'); + let namePart = arr[1]; + let modulePart = arr[0]; - return path; + if (modulePart === 'custom') { + return 'client/custom/src/' + namePart + '.js' ; + } + + if (this._isModuleInternal(modulePart)) { + return'client/modules/' + modulePart + '/src/' + namePart + '.js'; + } + + return 'client/custom/modules/' + modulePart + '/src/' + namePart + '.js'; }, _execute: function (script) { @@ -545,6 +548,14 @@ var Espo = Espo || {classMap: {}}; addLibsConfig: function (data) { this._libsConfig = _.extend(this._libsConfig, data); }, + + _isModuleInternal: function (moduleName) { + if (!(moduleName in this._internalModuleMap)) { + this._internalModuleMap[moduleName] = this._internalModuleList.indexOf(moduleName) !== -1; + } + + return this._internalModuleMap[moduleName]; + }, }); Espo.loader = new Espo.Loader(); diff --git a/html/main.html b/html/main.html index 29aa681a9c..fd9e865993 100644 --- a/html/main.html +++ b/html/main.html @@ -19,6 +19,7 @@ window.addEventListener('DOMContentLoaded', () => { Espo.loader.setCacheTimestamp({{loaderCacheTimestamp}}); Espo.loader.setBasePath('{{basePath}}'); + Espo.loader.setInternalModuleList({{internalModuleList}}); Espo.loader .loadLibsConfig('{{libsConfigPath}}') @@ -31,6 +32,7 @@ basePath: '{{basePath}}', apiUrl: '{{apiUrl}}', ajaxTimeout: {{ajaxTimeout}}, + internalModuleList: {{internalModuleList}}, }, app => { {{runScript}} });