Commit 073d372a authored by tino.goratsch@hotmail.com's avatar [email protected]

- added the possibility for custom module namespaces and module customizations

parent be5f0f90
......@@ -75,7 +75,7 @@ class Application
define('ROOT_DIR_ABSOLUTE', HOST_NAME . ROOT_DIR);
define('ACP3_DIR', ACP3_ROOT_DIR . 'ACP3/');
define('CLASSES_DIR', ACP3_DIR . 'Core/');
define('MODULES_DIR', ACP3_DIR . 'Modules/ACP3/');
define('MODULES_DIR', ACP3_DIR . 'Modules/');
define('LIBRARIES_DIR', ACP3_ROOT_DIR . 'libraries/');
define('VENDOR_DIR', ACP3_ROOT_DIR . 'vendor/');
define('UPLOADS_DIR', ACP3_ROOT_DIR . 'uploads/');
......@@ -162,10 +162,15 @@ class Application
/** @var Modules $modules */
$modules = $containerBuilder->get('core.modules');
$activeModules = $modules->getActiveModules();
$moduleNamespaces = $modules->getModuleNamespaces();
foreach ($activeModules as $module) {
$path = MODULES_DIR . $module['dir'] . '/config/services.yml';
if (is_file($path)) {
$loader->load($path);
foreach ($moduleNamespaces as $namespace) {
$path = MODULES_DIR . $namespace . '/' . $module['dir'] . '/config/services.yml';
if (is_file($path)) {
$loader->load($path);
}
}
}
......
......@@ -33,6 +33,10 @@ class ThemeResolver
* @var string
*/
protected $designAssetsPath = DESIGN_PATH_INTERNAL;
/**
* @var array
*/
protected $moduleNamespaces = [];
/**
* @param \ACP3\Core\XML $xml
......@@ -58,6 +62,22 @@ class ThemeResolver
}
}
/**
* @return array
*/
protected function _getModuleNamespaces()
{
if ($this->moduleNamespaces === []) {
$this->moduleNamespaces = array_merge(
['ACP3'],
array_diff(scandir(MODULES_DIR), ['.', '..', 'ACP3', 'Custom']),
['Custom']
);
}
return $this->moduleNamespaces;
}
/**
* @param $modulePath
* @param $designPath
......@@ -78,14 +98,14 @@ class ThemeResolver
$dir .= '/';
}
$systemAssetPath = $this->modulesAssetsPath . $modulePath . $dir . $file;
$systemAssetPath = $this->modulesAssetsPath . $modulePath . $dir . $file;
// Return early, if the path has been already cached
if (isset($this->cachedPaths[$systemAssetPath])) {
return $this->cachedPaths[$systemAssetPath];
}
// Return early, if the path has been already cached
if (isset($this->cachedPaths[$systemAssetPath])) {
return $this->cachedPaths[$systemAssetPath];
}
return $this->_resolveAssetPath($modulePath, $designPath, $dir, $file);
return $this->_resolveAssetPath($modulePath, $designPath, $dir, $file);
}
/**
......@@ -99,23 +119,32 @@ class ThemeResolver
private function _resolveAssetPath($modulePath, $designPath, $dir, $file)
{
$assetPath = '';
$systemAssetPath = $this->modulesAssetsPath . $modulePath . $dir . $file;
$designAssetPath = $this->designAssetsPath . $designPath . $dir . $file;
// A theme has overridden a static asset of a module
if (is_file($designAssetPath) === true) {
$assetPath = $designAssetPath;
} else {
$designInfo = $this->xml->parseXmlFile($this->designAssetsPath . '/info.xml', '/design');
// Recursively iterate over the nested themes
if (!empty($designInfo['parent'])) {
$this->designAssetsPath = ACP3_ROOT_DIR . 'designs/' . $designInfo['parent'];
$assetPath = $this->getStaticAssetPath($modulePath, $designPath, $dir, $file);
$this->designAssetsPath = DESIGN_PATH_INTERNAL;
} elseif (is_file($systemAssetPath) === true) {
$assetPath = $systemAssetPath;
}
// No overrides have been found -> iterate over all possible module namespaces
foreach (array_reverse($this->_getModuleNamespaces()) as $namespace) {
$moduleAssetPath = $this->modulesAssetsPath . $namespace . '/' . $modulePath . $dir . $file;
if (is_file($moduleAssetPath) === true) {
$assetPath = $moduleAssetPath;
break;
}
}
}
$systemAssetPath = $this->modulesAssetsPath . $modulePath . $dir . $file;
$this->cachedPaths[$systemAssetPath] = $assetPath;
$this->newAssetPathsAdded = true;
......
......@@ -37,6 +37,10 @@ class Lang
* @var array
*/
protected $buffer = [];
/**
* @var array
*/
protected $moduleNamespaces = [];
/**
* @param \ACP3\Core\Auth $auth
......@@ -47,7 +51,8 @@ class Lang
Auth $auth,
Cache $langCache,
Config $config
) {
)
{
$this->auth = $auth;
$this->cache = $langCache;
$this->config = $config;
......@@ -57,11 +62,12 @@ class Lang
* Überprüft, ob das angegebene Sprachpaket existiert
*
* @param string $lang
*
* @return boolean
*/
public static function languagePackExists($lang)
{
return !preg_match('=/=', $lang) && is_file(MODULES_DIR . 'System/Languages/' . $lang . '.xml') === true;
return !preg_match('=/=', $lang) && is_file(MODULES_DIR . 'ACP3/System/Languages/' . $lang . '.xml') === true;
}
/**
......@@ -327,33 +333,34 @@ class Lang
*/
final public static function parseAcceptLanguage()
{
$langs = [];
$languages = [];
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$matches = [];
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
if (!empty($matches[1])) {
$langs = array_combine($matches[1], $matches[4]);
$languages = array_combine($matches[1], $matches[4]);
// Für Einträge ohne q-Faktor, Wert auf 1 setzen
foreach ($langs as $lang => $val) {
foreach ($languages as $lang => $val) {
if ($val === '') {
$langs[$lang] = 1;
$languages[$lang] = 1;
}
}
// Liste nach Sprachpräferenz sortieren
arsort($langs, SORT_NUMERIC);
arsort($languages, SORT_NUMERIC);
}
}
// Über die Sprachen iterieren und das passende Sprachpaket auswählen
foreach ($langs as $lang => $val) {
foreach ($languages as $lang => $val) {
if (self::languagePackExists($lang) === true) {
return $lang;
}
}
return 'en_US';
}
......@@ -425,6 +432,22 @@ class Lang
return $this->cache->fetch($this->getLanguage());
}
/**
* @return array
*/
protected function _getModuleNamespaces()
{
if ($this->moduleNamespaces === []) {
$this->moduleNamespaces = array_merge(
['ACP3'],
array_diff(scandir(MODULES_DIR), ['.', '..', 'ACP3', 'Custom']),
['Custom']
);
}
return $this->moduleNamespaces;
}
/**
* Cacht die Sprachfiles, um diese schneller verarbeiten zu können
*/
......@@ -432,19 +455,23 @@ class Lang
{
$data = [];
$modules = array_diff(scandir(MODULES_DIR), ['.', '..']);
foreach ($this->_getModuleNamespaces() as $namespace) {
$namespaceModules = array_diff(scandir(MODULES_DIR . $namespace . '/'), ['.', '..']);
foreach ($modules as $module) {
$path = MODULES_DIR . $module . '/Languages/' . $this->getLanguage() . '.xml';
if (is_file($path) === true) {
$xml = simplexml_load_file($path);
if (isset($data['info']['direction']) === false) {
$data['info']['direction'] = (string)$xml->info->direction;
}
if (!empty($namespaceModules)) {
foreach ($namespaceModules as $module) {
$path = MODULES_DIR . $namespace . '/' . $module . '/Languages/' . $this->getLanguage() . '.xml';
if (is_file($path) === true) {
$xml = simplexml_load_file($path);
if (isset($data['info']['direction']) === false) {
$data['info']['direction'] = (string)$xml->info->direction;
}
// Über die einzelnen Sprachstrings iterieren
foreach ($xml->keys->item as $item) {
$data['keys'][strtolower($module)][(string)$item['key']] = trim((string)$item);
// Über die einzelnen Sprachstrings iterieren
foreach ($xml->keys->item as $item) {
$data['keys'][strtolower($module)][(string)$item['key']] = trim((string)$item);
}
}
}
}
}
......@@ -518,23 +545,19 @@ class Lang
*/
protected function _setLanguagesCache()
{
$modules = array_diff(scandir(MODULES_DIR), ['.', '..']);
$languages = [];
foreach ($modules as $module) {
$path = MODULES_DIR . $module . '/Languages/';
if (is_dir($path) === true) {
$moduleLanguages = array_diff(scandir($path), ['.', '..']);
foreach ($moduleLanguages as $language) {
if (is_file($path . $language) === true) {
$xml = simplexml_load_file($path . $language);
$languageIso = substr($language, 0, -4);
if (!empty($xml) && isset($languages[$languageIso]) === false) {
$languages[$languageIso] = [
'iso' => $languageIso,
'name' => (string)$xml->info->name
];
foreach ($this->_getModuleNamespaces() as $namespace) {
$namespaceModules = array_diff(scandir(MODULES_DIR . $namespace . '/'), ['.', '..']);
if (!empty($namespaceModules)) {
foreach ($namespaceModules as $module) {
$path = MODULES_DIR . $namespace . '/' . $module . '/Languages/';
if (is_dir($path) === true) {
$languagePacks = $this->_registerLanguagePacks($path);
if (!empty($languagePacks)) {
$languages += $languagePacks;
}
}
}
......@@ -543,4 +566,30 @@ class Lang
return $this->cache->save('languages', $languages);
}
/**
* @param $path
*
* @return array
*/
protected function _registerLanguagePacks($path)
{
$languagePacks = [];
$moduleLanguages = array_diff(scandir($path), ['.', '..']);
foreach ($moduleLanguages as $language) {
if (is_file($path . $language) === true) {
$xml = simplexml_load_file($path . $language);
$languageIso = substr($language, 0, -4);
if (!empty($xml) && isset($languagePacks[$languageIso]) === false) {
$languagePacks[$languageIso] = [
'iso' => $languageIso,
'name' => (string)$xml->info->name
];
}
}
}
return $languagePacks;
}
}
......@@ -38,6 +38,10 @@ class Modules
* @var array
*/
private $allModules = [];
/**
* @var array
*/
private $moduleNamespaces = [];
/**
* @param \Symfony\Component\DependencyInjection\Container $container
......@@ -131,7 +135,8 @@ class Modules
*/
public function getModuleId($module)
{
return $this->getModuleInfo($module)['id'];
$info = $this->getModuleInfo($module);
return !empty($info) ? $info['id'] : 0;
}
/**
......@@ -142,53 +147,95 @@ class Modules
return 'infos_' . $this->lang->getLanguage();
}
/**
* @return array
*/
public function getModuleNamespaces()
{
if ($this->moduleNamespaces === []) {
$this->moduleNamespaces = array_merge(
['ACP3'],
array_diff(scandir(MODULES_DIR), ['.', '..', 'ACP3', 'Custom']),
['Custom']
);
}
return $this->moduleNamespaces;
}
/**
* Setzt den Cache für alle vorliegenden Modulinformationen
*/
public function setModulesCache()
{
$infos = [];
$dirs = array_diff(scandir(MODULES_DIR), ['.', '..']);
foreach ($dirs as $dir) {
$moduleInfo = $this->_setModuleInfo($dir);
if (!empty($moduleInfo)) {
$infos[strtolower($dir)] = $moduleInfo;
}
// 1. fetch all core modules
// 2. Fetch all 3rd party modules
// 3. Fetch all local module customizations
foreach ($this->getModuleNamespaces() as $namespace) {
$infos += $this->_fetchModulesInNamespaces($namespace);
}
$this->modulesCache->save($this->_getCacheKey(), $infos);
}
/**
* @param string $namespace
*
* @return array
*/
protected function _fetchModulesInNamespaces($namespace)
{
$infos = [];
$modules = array_diff(scandir(MODULES_DIR . $namespace . '/'), ['.', '..']);
if (!empty($modules)) {
foreach ($modules as $module) {
$moduleInfo = $this->_fetchModuleInfo($module);
if (!empty($moduleInfo)) {
$infos[strtolower($module)] = $moduleInfo;
}
}
}
return $infos;
}
/**
* @param $moduleDirectory
*
* @return array
*/
protected function _setModuleInfo($moduleDirectory)
protected function _fetchModuleInfo($moduleDirectory)
{
$path = MODULES_DIR . '/' . $moduleDirectory . '/config/module.xml';
if (is_file($path) === true) {
$moduleInfo = $this->xml->parseXmlFile($path, 'info');
if (!empty($moduleInfo)) {
$moduleName = strtolower($moduleDirectory);
$moduleInfoDb = $this->systemModel->getInfoByModuleName($moduleName);
return [
'id' => !empty($moduleInfoDb) ? $moduleInfoDb['id'] : 0,
'dir' => $moduleDirectory,
'installed' => (!empty($moduleInfoDb)),
'active' => (!empty($moduleInfoDb) && $moduleInfoDb['active'] == 1),
'schema_version' => !empty($moduleInfoDb) ? (int)$moduleInfoDb['version'] : 0,
'description' => isset($moduleInfo['description']['lang']) && $moduleInfo['description']['lang'] === 'true' ? $this->lang->t($moduleName, 'mod_description') : $moduleInfo['description']['lang'],
'author' => $moduleInfo['author'],
'version' => $moduleInfo['version'],
'name' => isset($moduleInfo['name']['lang']) && $moduleInfo['name']['lang'] == 'true' ? $this->lang->t($moduleName, $moduleName) : $moduleInfo['name'],
'categories' => isset($moduleInfo['categories']),
'protected' => isset($moduleInfo['protected']),
'dependencies' => array_values($this->xml->parseXmlFile($path, 'info/dependencies')),
];
$namespaces = array_reverse($this->getModuleNamespaces()); // Reverse the order of the array -> search module customizations first, then 3rd party modules, then core modules
foreach ($namespaces as $namespace) {
$path = MODULES_DIR . $namespace . '/' . $moduleDirectory . '/config/module.xml';
if (is_file($path) === true) {
$moduleInfo = $this->xml->parseXmlFile($path, 'info');
if (!empty($moduleInfo)) {
$moduleName = strtolower($moduleDirectory);
$moduleInfoDb = $this->systemModel->getInfoByModuleName($moduleName);
return [
'id' => !empty($moduleInfoDb) ? $moduleInfoDb['id'] : 0,
'dir' => $moduleDirectory,
'installed' => (!empty($moduleInfoDb)),
'active' => (!empty($moduleInfoDb) && $moduleInfoDb['active'] == 1),
'schema_version' => !empty($moduleInfoDb) ? (int)$moduleInfoDb['version'] : 0,
'description' => isset($moduleInfo['description']['lang']) && $moduleInfo['description']['lang'] === 'true' ? $this->lang->t($moduleName, 'mod_description') : $moduleInfo['description']['lang'],
'author' => $moduleInfo['author'],
'version' => $moduleInfo['version'],
'name' => isset($moduleInfo['name']['lang']) && $moduleInfo['name']['lang'] == 'true' ? $this->lang->t($moduleName, $moduleName) : $moduleInfo['name'],
'categories' => isset($moduleInfo['categories']),
'protected' => isset($moduleInfo['protected']),
'dependencies' => array_values($this->xml->parseXmlFile($path, 'info/dependencies')),
];
}
}
}
......@@ -254,13 +301,16 @@ class Modules
public function getAllModules()
{
if (empty($this->allModules)) {
$dir = array_diff(scandir(MODULES_DIR), ['.', '..']);
foreach ($dir as $module) {
$info = $this->getModuleInfo($module);
if (!empty($info)) {
$this->allModules[$info['name']] = $info;
foreach ($this->getModuleNamespaces() as $namespace) {
$modules = array_diff(scandir(MODULES_DIR . $namespace . '/'), ['.', '..']);
foreach ($modules as $module) {
$info = $this->getModuleInfo($module);
if (!empty($info)) {
$this->allModules[$info['name']] = $info;
}
}
}
ksort($this->allModules);
}
......
......@@ -235,22 +235,12 @@ abstract class AbstractInstaller extends ContainerAware implements InstallerInte
*/
public function addResources($mode = 1)
{
$moduleName = static::MODULE_NAME;
$dir = ucfirst($moduleName);
$path = MODULES_DIR . $dir . '/Controller/';
$controllers = array_diff(scandir($path), ['.', '..']);
foreach ($controllers as $controller) {
if (is_file($path . $controller) === true) {
$this->_insertAclResources($dir, substr($controller, 0, -4), 'frontend');
} elseif (is_dir($path . $controller) === true) {
$subModuleControllers = array_diff(scandir($path . $controller), ['.', '..']);
foreach ($subModuleControllers as $subController) {
if (is_file($path . $controller . '/' . $subController) === true) {
$this->_insertAclResources($dir, substr($subController, 0, -4), $controller);
}
}
$serviceIds = $this->container->getServiceIds();
foreach ($serviceIds as $serviceId) {
if (strpos($serviceId, static::MODULE_NAME . '.controller.') !== false) {
list($module, $unused, $area, $controller) = explode('.', $serviceId);
$this->_insertAclResources($module, $controller, $area);
}
}
......
......@@ -139,7 +139,7 @@ class Index extends Core\Modules\Controller\Admin
/**
* @param array $formData
*/
private function _createPost(array $formData)
protected function _createPost(array $formData)
{
try {
$this->articlesValidator->validate($formData);
......@@ -269,7 +269,7 @@ class Index extends Core\Modules\Controller\Admin
/**
* @param array $formData
*/
private function _editPost(array $formData)
protected function _editPost(array $formData)
{
try {
$this->articlesValidator->validate(
......
......@@ -22,7 +22,7 @@ class Index extends Core\Modules\Controller\Frontend
/**
* @var \ACP3\Core\Helpers\TableOfContents
*/
private $toc;
protected $toc;
/**
* @var \ACP3\Modules\ACP3\Articles\Model
*/
......
......@@ -14,7 +14,7 @@ class Model extends Core\Model
/**
* @return string
*/
private function _getPeriod()
protected function _getPeriod()
{
return '(start = end AND start <= :time OR start != end AND :time BETWEEN start AND end)';
}
......
......@@ -74,7 +74,7 @@ class Index extends Core\Modules\Controller\Admin
/**
* @param array $formData
*/
private function _createPost(array $formData)
protected function _createPost(array $formData)
{
try {
$file = [];
......@@ -178,7 +178,7 @@ class Index extends Core\Modules\Controller\Admin
* @param array $formData
* @param array $category
*/
private function _editPost(array $formData, array $category)
protected function _editPost(array $formData, array $category)
{
try {
$file = [];
......@@ -255,7 +255,7 @@ class Index extends Core\Modules\Controller\Admin
/**
* @param array $formData
*/
private function _settingsPost(array $formData)
protected function _settingsPost(array $formData)
{
try {
$this->categoriesValidator->validateSettings($formData, $this->lang);
......
......@@ -173,7 +173,7 @@ class Details extends Core\Modules\Controller\Admin
* @param array $formData
* @param array $comment
*/
private function _editPost(array $formData, array $comment)
protected function _editPost(array $formData, array $comment)
{
try {
$this->commentsValidator->validateEdit($formData);
......
......@@ -112,7 +112,7 @@ class Index extends Core\Modules\Controller\Admin
/**
* @param array $formData
*/
private function _settingsPost(array $formData)
protected function _settingsPost(array $formData)
{
try {
$this->commentsValidator->validateSettings($formData);
......
......@@ -52,19 +52,19 @@ class Index extends Core\Modules\Controller\Frontend
/**
* @var bool
*/
private $emoticonsActive;
protected $emoticonsActive;
/**
* @var array
*/
protected $commentsSettings;
/**
* @param \ACP3\Core\Context\Frontend $context
* @param \ACP3\Core\Date $date
* @param \ACP3\Core\Pagination $pagination
* @param \ACP3\Core\Context\Frontend $context
* @param \ACP3\Core\Date $date
* @param \ACP3\Core\Pagination $pagination
* @param \ACP3\Modules\ACP3\Comments\Model $commentsModel
* @param \ACP3\Modules\ACP3\Comments\Validator $commentsValidator
* @param \ACP3\Core\Helpers\Secure $secureHelper
* @param \ACP3\Core\Helpers\Secure $secureHelper
*/
public function __construct(
Core\Context\Frontend $context,
......@@ -80,9 +80,14 @@ class Index extends Core\Modules\Controller\Frontend
$this->pagination = $pagination;
$this->commentsModel = $commentsModel;
$this->commentsValidator = $commentsValidator;
$this->commentsSettings = $this->config->getSettings('comments');
$this->secureHelper = $secureHelper;
}
public function preDispatch()
{
parent::preDispatch();
$this->commentsSettings = $this->config->getSettings('comments');
$this->emoticonsActive = ($this->commentsSettings['emoticons'] == 1);
}
......@@ -206,7 +211,7 @@ class Index extends Core\Modules\Controller\Frontend
/**
* @param array $formData
*/
private function _createPost(array $formData)
protected function _createPost(array $formData)
{
try {
$ip = $_SERVER['REMOTE_ADDR'];
......
......@@ -52,7 +52,7 @@ class Index extends Core\Modules\Controller\Admin
/**
* @param array $formData
*/
private function _indexPost(array $formData)
protected function _indexPost(array $formData)
{
try {
$this->contactValidator->validateSettings($formData);
......
......@@ -98,7 +98,7 @@ class Index extends Core\Modules\Controller\Frontend
/**
* @param array $formData
*/
private function _indexPost(array $formData)
protected function _indexPost(array $formData)
{
try {
$seoSettings = $this->config->getSettings('seo');
......
......@@ -64,7 +64,7 @@ class Index extends Core\Modules\Controller\Admin
/**
* @param array $formData
*/
private function _createPost(array $formData)
protected function _createPost(array $formData)
{
try {
$file = [];
......@@ -146,7 +146,7 @@ class Index extends Core\Modules\Controller\Admin
* @param array $formData
* @param array $emoticon
*/
private function _editPost(array $formData, array $emoticon)
protected function _editPost(array $formData, array $emoticon)
{
try {
$file = [];
......@@ -217,7 +217,7 @@ class Index extends Core\Modules\Controller\Admin
/**
* @param array $formData
*/
private function _settingsPost(array $formData)
protected function _settingsPost(array $formData)
{
try {
$this->emoticonsValidator->validateSettings($formData);
......
......@@ -59,7 +59,7 @@ class Index extends Core\Modules\Controller\Admin
/**
* @param array $formData
*/
private function _indexPost(array $formData)
protected function _indexPost(array $formData)
{
try {