custom modules support
This commit is contained in:
@@ -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/*',
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
||||
!modules
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
+25
-13
@@ -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);
|
||||
let getResourcePath = (type, name) => {
|
||||
if (name.indexOf(':') === -1) {
|
||||
return 'client/' + getResourceInnerPath(type, name);
|
||||
}
|
||||
|
||||
var getResourcePath = (type, name) => {
|
||||
if (name.indexOf(':') !== -1) {
|
||||
let arr = name.split(':');
|
||||
|
||||
name = arr[1];
|
||||
|
||||
var mod = arr[0];
|
||||
let mod = arr[0];
|
||||
let path = arr[1];
|
||||
|
||||
if (mod === 'custom') {
|
||||
return 'client/custom/' + getResourceInnerPath(type, name);
|
||||
return 'client/custom/' + getResourceInnerPath(type, path);
|
||||
}
|
||||
|
||||
return 'client/modules/' + mod + '/' + getResourceInnerPath(type, name);
|
||||
if (isModuleInternal(mod)) {
|
||||
return 'client/modules/' + mod + '/' + getResourceInnerPath(type, path);
|
||||
}
|
||||
|
||||
return 'client/' + getResourceInnerPath(type, name);
|
||||
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);
|
||||
|
||||
+23
-12
@@ -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) {
|
||||
return 'client/src/' + name + '.js';
|
||||
}
|
||||
|
||||
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;
|
||||
return 'client/custom/src/' + namePart + '.js' ;
|
||||
}
|
||||
|
||||
path += '.js';
|
||||
if (this._isModuleInternal(modulePart)) {
|
||||
return'client/modules/' + modulePart + '/src/' + namePart + '.js';
|
||||
}
|
||||
|
||||
return path;
|
||||
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();
|
||||
|
||||
@@ -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}}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user