v2.6.1d (Lyra): Plugin i18n, plugin editor vernieuwd, media invoegen
Plugin internationalisatie: plugins hebben eigen language/ mappen. Systeem plugins volgen admin taal (admin.php), content plugins volgen content taal (site.php). Fallback chain: geselecteerd -> plugin default_language -> CMS default. PluginManager/AdminPluginAPI/CMSAPI uitgebreid met getPluginTranslations()/t(). plugin.json settings ondersteunen label_key/help_key/option_label_key. Plugin uniformiteit: alle 6 plugins hebben uniforme structuur (README.md, assets/.gitkeep, language/nl|en/). plugins/README.md herschreven. guide plugin-development.md (NL+EN) volledig herschreven. Plugin editor vernieuwd: geneste bestandsbrowser zijbalk, nieuw bestand aanmaken, uploaden naar assets/, verwijderen en verplaatsen. Nieuwe routes: plugins-file-upload, plugins-file-delete, plugins-file-move. Path-traversal bescherming + protected plugins geblokkeerd. Media invoegen in editor: nieuw /admin/media-list JSON endpoint + herbruikbare _media-modal.twig include. Plugin-context scant assets/ map. editor-toolbar.js modeMap uitgebreid voor css/scss/js/json. Plugin overzicht knoppen: alleen iconen met title/aria-label. Tests: pentest 30/30, WCAG 2.1 AA 25/25.
This commit is contained in:
@@ -8,13 +8,24 @@ class PluginManager
|
||||
private array $enabledPlugins = [];
|
||||
private array $actions = [];
|
||||
private array $filters = [];
|
||||
private string $siteDefaultLanguage = 'nl';
|
||||
|
||||
public function __construct(string $pluginsPath, array $enabledPlugins = [])
|
||||
public function __construct(string $pluginsPath, array $enabledPlugins = [], string $siteDefaultLanguage = 'nl')
|
||||
{
|
||||
$this->pluginsPath = $pluginsPath;
|
||||
$this->enabledPlugins = $enabledPlugins;
|
||||
$this->siteDefaultLanguage = $siteDefaultLanguage !== '' ? $siteDefaultLanguage : 'nl';
|
||||
$this->loadPlugins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CMS site default language. Used as a fallback when a plugin
|
||||
* does not declare its own `default_language` in plugin.json.
|
||||
*/
|
||||
public function setSiteDefaultLanguage(string $lang): void
|
||||
{
|
||||
$this->siteDefaultLanguage = $lang !== '' ? $lang : 'nl';
|
||||
}
|
||||
|
||||
public function setAPI($api): void
|
||||
{
|
||||
@@ -124,6 +135,123 @@ class PluginManager
|
||||
return in_array($pluginName, $this->enabledPlugins, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the runtime config for a plugin: defaults from plugin.json `settings`
|
||||
* merged with overrides from the plugin's config.json.
|
||||
*
|
||||
* @param string $pluginName Plugin name (directory name)
|
||||
* @return array Resolved config: [key => value, ...]
|
||||
*/
|
||||
public function getPluginConfig(string $pluginName): array
|
||||
{
|
||||
$pluginDir = $this->pluginsPath . '/' . $pluginName;
|
||||
$pluginJsonFile = $pluginDir . '/plugin.json';
|
||||
$configJsonFile = $pluginDir . '/config.json';
|
||||
|
||||
$defaults = [];
|
||||
if (file_exists($pluginJsonFile)) {
|
||||
$pluginJson = json_decode(file_get_contents($pluginJsonFile), true) ?? [];
|
||||
foreach ($pluginJson['settings'] ?? [] as $setting) {
|
||||
if (isset($setting['key'])) {
|
||||
$defaults[$setting['key']] = $setting['default'] ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$overrides = [];
|
||||
if (file_exists($configJsonFile)) {
|
||||
$overrides = json_decode(file_get_contents($configJsonFile), true) ?? [];
|
||||
}
|
||||
|
||||
return array_merge($defaults, $overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default (fallback) language declared by a plugin.
|
||||
*
|
||||
* Resolved from (in order):
|
||||
* 1. plugin.json `default_language`
|
||||
* 2. CMS site config `language.default`
|
||||
* 3. 'nl'
|
||||
*
|
||||
* @param string $pluginName Plugin directory name
|
||||
* @return string Language code (e.g. 'nl', 'en')
|
||||
*/
|
||||
public function getPluginDefaultLanguage(string $pluginName): string
|
||||
{
|
||||
$pluginJsonFile = $this->pluginsPath . '/' . $pluginName . '/plugin.json';
|
||||
if (file_exists($pluginJsonFile)) {
|
||||
$pluginJson = json_decode(file_get_contents($pluginJsonFile), true) ?? [];
|
||||
if (!empty($pluginJson['default_language'])) {
|
||||
return (string)$pluginJson['default_language'];
|
||||
}
|
||||
}
|
||||
return $this->siteDefaultLanguage ?? 'nl';
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the translations for a plugin for the requested language.
|
||||
*
|
||||
* Fallback chain: requested language -> plugin default language -> empty array.
|
||||
*
|
||||
* @param string $pluginName Plugin directory name
|
||||
* @param string $lang Requested language code
|
||||
* @param string $context Translation context: 'admin' or 'site' (front-end).
|
||||
* Defaults to 'admin' for system plugins, 'site' for content plugins.
|
||||
* @return array Translations [key => value]
|
||||
*/
|
||||
public function getPluginTranslations(string $pluginName, string $lang, string $context = 'admin'): array
|
||||
{
|
||||
$langDir = $this->pluginsPath . '/' . $pluginName . '/language';
|
||||
if (!is_dir($langDir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Try requested language first
|
||||
$file = $langDir . '/' . $lang . '/' . $context . '.php';
|
||||
if (file_exists($file)) {
|
||||
$t = include $file;
|
||||
if (is_array($t)) {
|
||||
return $t;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to the plugin's default language
|
||||
$defaultLang = $this->getPluginDefaultLanguage($pluginName);
|
||||
if ($defaultLang !== $lang) {
|
||||
$fallbackFile = $langDir . '/' . $defaultLang . '/' . $context . '.php';
|
||||
if (file_exists($fallbackFile)) {
|
||||
$t = include $fallbackFile;
|
||||
if (is_array($t)) {
|
||||
return $t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect translations for every loaded plugin for the requested language.
|
||||
*
|
||||
* Returns an associative array keyed by plugin name:
|
||||
* ['Statistics' => [...], 'Logs' => [...], ...]
|
||||
*
|
||||
* @param string $lang Requested language code
|
||||
* @param string $context 'admin' or 'site'
|
||||
* @return array Plugin translations keyed by plugin name
|
||||
*/
|
||||
public function getAllPluginTranslations(string $lang, string $context = 'admin'): array
|
||||
{
|
||||
$all = [];
|
||||
foreach ($this->plugins as $pluginName => $plugin) {
|
||||
// System plugins follow the admin language; content plugins follow the content language.
|
||||
// The caller decides the context; here we just load for every plugin.
|
||||
$all[$pluginName] = $this->getPluginTranslations($pluginName, $lang, $context);
|
||||
}
|
||||
return $all;
|
||||
}
|
||||
|
||||
public function isPluginViewable(object $plugin): bool
|
||||
{
|
||||
if (method_exists($plugin, 'getConfig')) {
|
||||
@@ -236,7 +364,7 @@ class PluginManager
|
||||
* Find which plugin handles a given admin route.
|
||||
*
|
||||
* @param string $route The admin route (e.g. 'statistics' or 'statistics/details')
|
||||
* @return array|null ['plugin' => name, 'action' => action] or null
|
||||
* @return array|null ['plugin' => name, 'action' => action, 'permission' => required permission] or null
|
||||
*/
|
||||
public function resolveAdminRoute(string $route): ?array
|
||||
{
|
||||
@@ -248,6 +376,7 @@ class PluginManager
|
||||
return [
|
||||
'plugin' => $item['plugin'] ?? '',
|
||||
'action' => $action,
|
||||
'permission' => $item['permission'] ?? 'plugins',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user