Commit dc990e43 authored by Tino Goratsch's avatar Tino Goratsch

made it possible to override translation inside the themes

...this includes to theme inheritance feature too!
parent 46e4cef8
......@@ -33,6 +33,7 @@ abstract class AbstractBootstrap implements BootstrapInterface
/**
* @param string $appMode
*
* @throws \Exception
*/
public function __construct($appMode)
......
......@@ -39,6 +39,7 @@ class ServiceContainerBuilder extends ContainerBuilder
/**
* ServiceContainerBuilder constructor.
*
* @param ApplicationPath $applicationPath
* @param SymfonyRequest $symfonyRequest
* @param string $applicationMode
......@@ -103,6 +104,7 @@ class ServiceContainerBuilder extends ContainerBuilder
* @param \ACP3\Core\Environment\ApplicationPath $applicationPath
* @param SymfonyRequest $symfonyRequest
* @param string $applicationMode
*
* @return ContainerBuilder
*/
public static function create(
......
<?php
/**
* Copyright (c) by the ACP3 Developers.
* See the LICENSE file at the top-level module directory for licensing details.
*/
namespace ACP3\Core\Environment;
use ACP3\Core\Settings\SettingsInterface;
use ACP3\Core\XML;
use ACP3\Modules\ACP3\System\Installer\Schema;
class Theme
{
/**
* @var \ACP3\Core\Settings\SettingsInterface
*/
private $settings;
/**
* @var \ACP3\Core\XML
*/
private $xml;
/**
* @var array
*/
private $availableThemes = [];
/**
* @var array
*/
private $sortedThemeDependencies = [];
/**
* Design constructor.
*
* @param \ACP3\Core\Settings\SettingsInterface $settings
* @param \ACP3\Core\XML $xml
*/
public function __construct(
SettingsInterface $settings,
XML $xml
) {
$this->settings = $settings;
$this->xml = $xml;
}
public function getAvailableThemes(): array
{
if (empty($this->availableThemes)) {
$this->setAvailableThemes();
}
return $this->availableThemes;
}
private function setAvailableThemes(): void
{
$designs = \glob(ACP3_ROOT_DIR . '/designs/*/info.xml');
if ($designs === false) {
return;
}
foreach ($designs as $design) {
$designInfo = $this->xml->parseXmlFile($design, '/design');
if (!empty($designInfo)) {
$identifier = $this->getThemeInternalName($design);
$this->availableThemes[$identifier] = $designInfo;
}
}
}
/**
* @param string $file
*
* @return string
*/
private function getThemeInternalName(string $file): string
{
$path = \dirname($file);
$pathParts = \explode('/', $path);
return $pathParts[\count($pathParts) - 1];
}
/**
* @return array
*/
public function getCurrentThemeDependencies(): array
{
$settings = $this->settings->getSettings(Schema::MODULE_NAME);
return $this->getThemeDependencies($settings['design']);
}
/**
* @param string $themeName
*
* @return array
*/
public function getThemeDependencies(string $themeName): array
{
if (!isset($this->sortedThemeDependencies[$themeName])) {
$this->setThemeDependencies($themeName);
}
return $this->sortedThemeDependencies[$themeName];
}
private function setThemeDependencies(string $themeName): void
{
$availableThemes = $this->getAvailableThemes();
if (!isset($availableThemes[$themeName]['parent'])) {
$this->sortedThemeDependencies[$themeName] = [$themeName];
return;
}
$currentTheme = $themeName;
$parents = [$themeName];
do {
$currentTheme = $availableThemes[$currentTheme]['parent'];
$parents[] = $currentTheme;
} while (isset($availableThemes[$currentTheme]['parent']));
$this->sortedThemeDependencies[$themeName] = $parents;
}
}
......@@ -9,6 +9,8 @@ namespace ACP3\Core\I18n;
use ACP3\Core\Cache;
use ACP3\Core\Environment\ApplicationPath;
use ACP3\Core\Environment\Theme;
use ACP3\Core\Modules;
use ACP3\Core\Modules\Vendor;
use Fisharebest\Localization\Locale;
......@@ -28,22 +30,36 @@ class DictionaryCache
* @var \ACP3\Core\Modules\Vendor
*/
protected $vendors;
/**
* @var \ACP3\Core\Environment\Theme
*/
private $theme;
/**
* @var \ACP3\Core\Modules
*/
private $modules;
/**
* DictionaryCache constructor.
*
* @param \ACP3\Core\Cache $cache
* @param \ACP3\Core\Environment\ApplicationPath $appPath
* @param \ACP3\Core\Modules $modules
* @param \ACP3\Core\Modules\Vendor $vendors
* @param \ACP3\Core\Environment\Theme $theme
*/
public function __construct(
Cache $cache,
ApplicationPath $appPath,
Vendor $vendors
Modules $modules,
Vendor $vendors,
Theme $theme
) {
$this->cache = $cache;
$this->appPath = $appPath;
$this->vendors = $vendors;
$this->theme = $theme;
$this->modules = $modules;
}
/**
......@@ -52,8 +68,11 @@ class DictionaryCache
* @param string $language
*
* @return array
*
* @throws \MJS\TopSort\CircularDependencyException
* @throws \MJS\TopSort\ElementNotFoundException
*/
public function getLanguageCache($language)
public function getLanguageCache(string $language): array
{
if ($this->cache->contains($language) === false) {
$this->saveLanguageCache($language);
......@@ -68,41 +87,84 @@ class DictionaryCache
* @param string $language
*
* @return bool
*
* @throws \MJS\TopSort\CircularDependencyException
* @throws \MJS\TopSort\ElementNotFoundException
*/
public function saveLanguageCache($language)
public function saveLanguageCache(string $language): bool
{
$data = [];
$locale = Locale::create($language);
$data = [
'info' => [
'direction' => $locale->script()->direction(),
],
'keys' => [],
];
foreach ($this->modules->getAllModulesTopSorted() as $module) {
$i18nFile = "{$this->appPath->getModulesDir()}{$module['vendor']}/{$module['dir']}/Resources/i18n/{$language}.xml";
if (\file_exists($i18nFile) === false) {
continue;
}
foreach ($this->vendors->getVendors() as $vendor) {
$languageFiles = \glob($this->appPath->getModulesDir() . $vendor . '/*/Resources/i18n/' . $language . '.xml');
$data['keys'] += $this->parseI18nFile($i18nFile, $module['dir']);
}
if ($languageFiles !== false) {
foreach ($languageFiles as $file) {
if (isset($data['info']['direction']) === false) {
$locale = Locale::create($this->getLanguagePackIsoCode($file));
$data['info']['direction'] = $locale->script()->direction();
}
$themeDependenciesReversed = \array_reverse($this->theme->getCurrentThemeDependencies());
foreach ($themeDependenciesReversed as $theme) {
$i18nFiles = \glob(ACP3_ROOT_DIR . "designs/{$theme}/*/i18n/{$language}.xml");
$module = $this->getModuleFromPath($file);
if ($i18nFiles === false) {
continue;
}
// Iterate over all language keys
$xml = \simplexml_load_file($file);
foreach ($xml->keys->item as $item) {
$data['keys'][\strtolower($module . (string) $item['key'])] = \trim((string) $item);
}
}
foreach ($i18nFiles as $i18nFile) {
$data['keys'] = \array_merge(
$data['keys'],
$this->parseI18nFile($i18nFile, $this->getModuleNameFromThemePath($i18nFile))
);
}
}
return $this->cache->save($language, $data);
}
/**
* @param string $i18nFile
* @param string $moduleName
*
* @return array
*/
private function parseI18nFile(string $i18nFile, string $moduleName): array
{
$data = [];
$xml = \simplexml_load_file($i18nFile);
foreach ($xml->keys->item as $item) {
$data[\strtolower($moduleName . (string) $item['key'])] = \trim((string) $item);
}
return $data;
}
/**
* @param string $filePath
*
* @return string
*/
private function getModuleNameFromThemePath(string $filePath): string
{
$pathArray = \explode('/', $filePath);
return $pathArray[\count($pathArray) - 3];
}
/**
* Gets the cache for all registered languages.
*
* @return array
*/
public function getLanguagePacksCache()
public function getLanguagePacksCache(): array
{
if ($this->cache->contains('language_packs') === false) {
$this->saveLanguagePacksCache();
......@@ -116,21 +178,20 @@ class DictionaryCache
*
* @return bool
*/
protected function saveLanguagePacksCache()
protected function saveLanguagePacksCache(): bool
{
$languagePacks = [];
$languageFiles = \glob($this->appPath->getModulesDir() . '*/*/Resources/i18n/*.xml');
foreach ($this->vendors->getVendors() as $vendors) {
$languageFiles = \glob($this->appPath->getModulesDir() . $vendors . '/*/Resources/i18n/*.xml');
if ($languageFiles !== false) {
foreach ($languageFiles as $file) {
$languagePack = $this->registerLanguagePack($file);
if ($languageFiles !== false) {
foreach ($languageFiles as $file) {
$languagePack = $this->registerLanguagePack($file);
if (!empty($languagePack)) {
$languagePacks += $languagePack;
}
if (empty($languagePack)) {
continue;
}
$languagePacks += $languagePack;
}
}
......@@ -142,7 +203,7 @@ class DictionaryCache
*
* @return array
*/
protected function registerLanguagePack($file)
protected function registerLanguagePack(string $file): array
{
$languageIso = $this->getLanguagePackIsoCode($file);
......
......@@ -14,7 +14,7 @@ trait ExtractFromPathTrait
*
* @return string
*/
protected function getLanguagePackIsoCode($filePath)
protected function getLanguagePackIsoCode(string $filePath): string
{
return \substr($filePath, \strrpos($filePath, '/') + 1, -4);
}
......@@ -24,7 +24,7 @@ trait ExtractFromPathTrait
*
* @return string
*/
protected function getModuleFromPath($filePath)
protected function getModuleFromPath(string $filePath): string
{
$pathArray = \explode('/', $filePath);
......
......@@ -35,6 +35,7 @@ class LoggerFactory
* @param string $level
*
* @return LoggerInterface
*
* @throws \Exception
*/
public function create($channel, $level = LogLevel::DEBUG)
......
......@@ -39,6 +39,10 @@ class Modules
* @var array
*/
private $allModules = [];
/**
* @var array
*/
private $allModulesTopSorted = [];
/**
* @param \ACP3\Core\Environment\ApplicationPath $appPath
......@@ -201,21 +205,25 @@ class Modules
* Returns an array with all modules which is sorted topologically.
*
* @return array
*
* @throws \MJS\TopSort\CircularDependencyException
* @throws \MJS\TopSort\ElementNotFoundException
*/
public function getAllModulesTopSorted(): array
{
$topSort = new StringSort();
if (empty($this->allModulesTopSorted)) {
$topSort = new StringSort();
$modules = $this->getAllModules();
foreach ($modules as $module) {
$topSort->add(\strtolower($module['dir']), $module['dependencies']);
}
$modules = $this->getAllModules();
foreach ($modules as $module) {
$topSort->add(\strtolower($module['dir']), $module['dependencies']);
}
$topSortedModules = [];
foreach ($topSort->sort() as $module) {
$topSortedModules[$module] = $modules[$module];
foreach ($topSort->sort() as $module) {
$this->allModulesTopSorted[$module] = $modules[$module];
}
}
return $topSortedModules;
return $this->allModulesTopSorted;
}
}
......@@ -10,7 +10,6 @@ namespace ACP3\Core\Modules;
use ACP3\Core\Cache;
use ACP3\Core\Environment\ApplicationPath;
use ACP3\Core\Filesystem;
use ACP3\Core\I18n\Translator;
use ACP3\Core\Model\Repository\ModuleAwareRepositoryInterface;
use ACP3\Core\XML;
......@@ -26,10 +25,6 @@ class ModuleInfoCache
* @var \ACP3\Core\Environment\ApplicationPath
*/
protected $appPath;
/**
* @var \ACP3\Core\I18n\Translator
*/
protected $translator;
/**
* @var \ACP3\Core\Modules\Vendor
*/
......@@ -48,7 +43,6 @@ class ModuleInfoCache
*
* @param Cache $cache
* @param ApplicationPath $appPath
* @param Translator $translator
* @param Vendor $vendors
* @param XML $xml
* @param ModuleAwareRepositoryInterface $systemModuleRepository
......@@ -56,14 +50,12 @@ class ModuleInfoCache
public function __construct(
Cache $cache,
ApplicationPath $appPath,
Translator $translator,
Vendor $vendors,
XML $xml,
ModuleAwareRepositoryInterface $systemModuleRepository
) {
$this->cache = $cache;
$this->appPath = $appPath;
$this->translator = $translator;
$this->vendors = $vendors;
$this->xml = $xml;
$this->systemModuleRepository = $systemModuleRepository;
......@@ -74,7 +66,7 @@ class ModuleInfoCache
*/
public function getCacheKey()
{
return 'infos_' . $this->translator->getLocale();
return 'modules_info';
}
/**
......@@ -153,10 +145,9 @@ class ModuleInfoCache
'installed' => (!empty($moduleInfoDb)),
'active' => (!empty($moduleInfoDb) && $moduleInfoDb['active'] == 1),
'schema_version' => !empty($moduleInfoDb) ? (int) $moduleInfoDb['version'] : 0,
'description' => $this->getModuleDescription($moduleInfo, $moduleName),
'author' => $moduleInfo['author'],
'version' => $moduleInfo['version'],
'name' => $this->getModuleName($moduleInfo, $moduleName),
'name' => $moduleName,
'categories' => isset($moduleInfo['categories']),
'protected' => isset($moduleInfo['protected']),
'installable' => !isset($moduleInfo['no_install']),
......@@ -169,36 +160,6 @@ class ModuleInfoCache
return [];
}
/**
* @param array $moduleInfo
* @param string $moduleName
*
* @return string
*/
protected function getModuleDescription(array $moduleInfo, $moduleName)
{
if (isset($moduleInfo['description']['lang']) && $moduleInfo['description']['lang'] === 'true') {
return $this->translator->t($moduleName, 'mod_description');
}
return $moduleInfo['description'];
}
/**
* @param array $moduleInfo
* @param string $moduleName
*
* @return string
*/
protected function getModuleName(array $moduleInfo, $moduleName)
{
if (isset($moduleInfo['name']['lang']) && $moduleInfo['name']['lang'] === 'true') {
return $this->translator->t($moduleName, $moduleName);
}
return $moduleInfo['name'];
}
/**
* @return XML
*/
......
......@@ -17,6 +17,8 @@ class SchemaInstaller extends SchemaHelper implements InstallerInterface
* @param \ACP3\Core\Modules\Installer\SchemaInterface $schema
*
* @return bool
*
* @throws \Doctrine\DBAL\DBALException
*/
public function install(SchemaInterface $schema)
{
......@@ -34,6 +36,8 @@ class SchemaInstaller extends SchemaHelper implements InstallerInterface
* @param SchemaInterface $schema
*
* @return bool
*
* @throws \Doctrine\DBAL\DBALException
*/
protected function moduleNeedsInstallation(SchemaInterface $schema)
{
......@@ -50,7 +54,7 @@ class SchemaInstaller extends SchemaHelper implements InstallerInterface
*
* @return bool
*/
protected function addToModulesTable($moduleName, $schemaVersion)
protected function addToModulesTable(string $moduleName, int $schemaVersion)
{
$insertValues = [
'id' => '',
......@@ -72,7 +76,7 @@ class SchemaInstaller extends SchemaHelper implements InstallerInterface
*
* @throws \Doctrine\DBAL\ConnectionException
*/
protected function installSettings($moduleName, array $settings)
protected function installSettings(string $moduleName, array $settings)
{
if (\count($settings) > 0) {
$this->db->getConnection()->beginTransaction();
......@@ -107,6 +111,8 @@ class SchemaInstaller extends SchemaHelper implements InstallerInterface
* @param \ACP3\Core\Modules\Installer\SchemaInterface $schema
*
* @return bool
*
* @throws \Doctrine\DBAL\ConnectionException
*/
public function uninstall(SchemaInterface $schema)
{
......@@ -121,7 +127,7 @@ class SchemaInstaller extends SchemaHelper implements InstallerInterface
*
* @return bool
*/
protected function removeFromModulesTable($moduleName)
protected function removeFromModulesTable(string $moduleName)
{
return $this->systemModuleRepository->delete((int) $this->getModuleId($moduleName)) !== false;
}
......
......@@ -19,6 +19,8 @@ class SchemaUpdater extends SchemaHelper
* @param \ACP3\Core\Modules\Installer\MigrationInterface $migration
*
* @return int
*
* @throws \Doctrine\DBAL\ConnectionException
*/
public function updateSchema(SchemaInterface $schema, MigrationInterface $migration)
{
......@@ -61,6 +63,8 @@ class SchemaUpdater extends SchemaHelper
* @param int $installedSchemaVersion
*
* @return int
*
* @throws \Doctrine\DBAL\ConnectionException
*/
protected function iterateOverSchemaUpdates(
$moduleName,
......@@ -94,7 +98,7 @@ class SchemaUpdater extends SchemaHelper
*
* @return bool
*/
public function updateSchemaVersion($moduleName, $schemaVersion)
public function updateSchemaVersion(string $moduleName, int $schemaVersion)
{
return $this->systemModuleRepository->update(['version' => (int) $schemaVersion], ['name' => $moduleName]) !== false;
}
......
......@@ -15,7 +15,9 @@ services:
arguments:
- '@core.cache.system'
- '@core.environment.application_path'
- '@core.modules'
- '@core.modules.vendors'
- '@core.environment.theme'
core.i18n.country_list:
class: ACP3\Core\I18n\CountryList
......
......@@ -36,7 +36,6 @@ services:
arguments:
- '@core.cache.system'
- '@core.environment.application_path'
- '@core.i18n.translator'
- '@core.modules.vendors'
- '@core.xml'
- '@system.model.modulerepository'
......
......@@ -26,6 +26,12 @@ services:
core.environment.application_path:
synthetic: true
core.environment.theme:
class: ACP3\Core\Environment\Theme
arguments:
- '@core.config'
- '@core.xml'
fast_image_size:
class: FastImageSize\FastImageSize
......
......@@ -5,7 +5,7 @@
<h4>{lang t="acp|access_to_modules"}</h4>
<ul>
{foreach $modules as $module}
<li><a href="{uri args="acp/`$module.dir`"}">{$module.name}</a></li>
<li><a href="{uri args="acp/`$module.dir`"}">{lang t="`$module.name`|`$module.name`"}</a></li>
{/foreach}
</ul>
</div>
......
......@@ -47,7 +47,7 @@
<select class="form-control" name="module" id="link-module">
<option value="">{lang t="system|pls_select"}</option>
{foreach $modules as $row}
<option value="{$row.dir|lower}"{$row.selected}>{$row.name}</option>
<option value="{$row.dir|lower}"{$row.selected}>{lang t="`$row.name`|`$row.name`"}</option>
{/foreach}
</select>
</div>
......
......@@ -28,7 +28,7 @@
<div class="row">
{/if}
<fieldset class="col-sm-6">
<legend>{$module}</legend>
<legend>{lang t="`$module`|`$module`"}</legend>
{foreach $values.privileges as $privilege}
<div class="form-group">
<label class="col-sm-2 control-label"{if !empty($privilege.description)} title="{$privilege.description}"{/if}>{$privilege.key}</label>
......
......@@ -14,7 +14,7 @@ trait AvailableDesignsTrait
/**
* @return array
*/
protected function getAvailableDesigns()
protected function getAvailableDesigns(): array
{
$designs = [];
foreach ($this->getDesignPaths() as $file) {
......@@ -42,7 +42,7 @@ trait AvailableDesignsTrait
/**
* @return array
*/
private function getDesignPaths()
private function getDesignPaths(): array
{
return \glob(ACP3_ROOT_DIR . 'designs/*/info.xml');
}
......@@ -52,7 +52,7 @@ trait AvailableDesignsTrait
*
* @return bool|string
*/
private function getDesignDirectory($file)
private function getDesignDirectory(string $file): string
{
$pathLength = \strlen(ACP3_ROOT_DIR . 'designs/');
$lastDS = \strrpos($file, '/');
......
......@@ -23,8 +23,8 @@
<tbody>
{foreach $installed_modules as $row}
<tr>
<td>{$row.name}</td>
<td>{$row.description}</td>
<td>{lang t="`$row.name`|`$row.name`"}</td>
<td>{lang t="`$row.name`|mod_description"}</td>
<td>{$row.version}</td>
<td>{$row.author}</td>
<td class="text-center">
......@@ -86,8 +86,8 @@
<tbody>
{foreach $new_modules as $row}
<tr>
<td>{$row.name}</td>
<td>{$row.description}</td>
<td>{lang t="`$row.name`|`$row.name`"}</td>
<td>{lang t="`$row.name`|mod_description"}</td>
<td>{$row.version}</td>
<td>{$row.author}</td>
<td class="text-center">
......
......@@ -16,7 +16,9 @@
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="menu-admin-label">
{foreach $user_sidebar.modules as $row}
<li{if $row.is_active} class="active"{/