diff --git a/README b/README
index a417c0b..b0cdb89 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-LuxTools plugin for DokuWiki
+luxtools plugin for DokuWiki
Lists files matching a given glob pattern.
diff --git a/_test/GeneralTest.php b/_test/GeneralTest.php
index 5b23e0b..2e46184 100644
--- a/_test/GeneralTest.php
+++ b/_test/GeneralTest.php
@@ -39,47 +39,43 @@ class GeneralTest extends DokuWikiTest
}
/**
- * Test to ensure that every conf['...'] entry in conf/default.php has a corresponding meta['...'] entry in
- * conf/metadata.php.
+ * luxtools settings are managed via the plugin's admin page, not via the Configuration Manager.
+ * Ensure default config exists and (when present) metadata.php does not expose any settings.
*/
public function testPluginConf(): void
{
$conf_file = __DIR__ . '/../conf/default.php';
$meta_file = __DIR__ . '/../conf/metadata.php';
- if (!file_exists($conf_file) && !file_exists($meta_file)) {
- self::markTestSkipped('No config files exist -> skipping test');
+ if (!file_exists($conf_file)) {
+ self::markTestSkipped('No config default.php exists -> skipping test');
}
- if (file_exists($conf_file)) {
- include($conf_file);
- }
- if (file_exists($meta_file)) {
- include($meta_file);
- }
+ $conf = null;
+ $meta = null;
- $this->assertEquals(
- gettype($conf),
- gettype($meta),
- 'Both ' . DOKU_PLUGIN . 'luxtools/conf/default.php and ' . DOKU_PLUGIN . 'luxtools/conf/metadata.php have to exist and contain the same keys.'
+ include($conf_file);
+ $this->assertIsArray(
+ $conf,
+ 'The ' . DOKU_PLUGIN . 'luxtools/conf/default.php file needs to define $conf as an array.'
);
- if ($conf !== null && $meta !== null) {
- foreach ($conf as $key => $value) {
- $this->assertArrayHasKey(
- $key,
- $meta,
- 'Key $meta[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'luxtools/conf/metadata.php'
- );
+ if (file_exists($meta_file)) {
+ include($meta_file);
+
+ if ($meta === null) {
+ // If the file exists but does not define $meta, treat it as empty.
+ $meta = [];
}
- foreach ($meta as $key => $value) {
- $this->assertArrayHasKey(
- $key,
- $conf,
- 'Key $conf[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'luxtools/conf/default.php'
- );
- }
+ $this->assertIsArray(
+ $meta,
+ 'The ' . DOKU_PLUGIN . 'luxtools/conf/metadata.php file needs to define $meta as an array.'
+ );
+ $this->assertEmpty(
+ $meta,
+ 'luxtools should not expose settings via the Configuration Manager.'
+ );
}
}
diff --git a/admin/main.php b/admin/main.php
new file mode 100644
index 0000000..4c86353
--- /dev/null
+++ b/admin/main.php
@@ -0,0 +1,195 @@
+getLang('menu');
+ }
+
+ public function getMenuSort()
+ {
+ // keep near other plugin tools
+ return 1011;
+ }
+
+ public function forAdminOnly()
+ {
+ return true;
+ }
+
+ public function handle()
+ {
+ global $INPUT;
+
+ if ($INPUT->str('luxtools_cmd') !== 'save') return;
+
+ if (!checkSecurityToken()) {
+ msg($this->getLang('err_security'), -1);
+ return;
+ }
+
+ $newConf = [];
+ // Normalize newlines to "\n" for consistent parsing
+ $paths = $INPUT->str('paths');
+ $paths = str_replace(["\r\n", "\r"], "\n", $paths);
+ $newConf['paths'] = $paths;
+ $newConf['allow_in_comments'] = (int)$INPUT->bool('allow_in_comments');
+ $newConf['defaults'] = $INPUT->str('defaults');
+ $newConf['extensions'] = $INPUT->str('extensions');
+ $newConf['thumb_placeholder'] = $INPUT->str('thumb_placeholder');
+ $newConf['gallery_thumb_scale'] = $INPUT->str('gallery_thumb_scale');
+ $newConf['open_service_url'] = $INPUT->str('open_service_url');
+
+ if ($this->savePluginLocalConf($newConf)) {
+ msg($this->getLang('saved'), 1);
+ } else {
+ msg($this->getLang('err_save'), -1);
+ }
+ }
+
+ public function html()
+ {
+ global $ID;
+
+ echo '
';
+ echo '
' . hsc($this->getLang('settings')) . '
';
+
+ echo '';
+
+ echo '';
+ }
+
+ /**
+ * Persist plugin settings to conf/plugins/luxtools.local.php.
+ *
+ * @param array $newConf
+ * @return bool
+ */
+ protected function savePluginLocalConf(array $newConf)
+ {
+ if (!defined('DOKU_CONF')) return false;
+
+ $plugin = 'luxtools';
+ $confDir = DOKU_CONF . 'plugins/';
+ $file = $confDir . $plugin . '.local.php';
+
+ if (function_exists('io_mkdir_p')) {
+ io_mkdir_p($confDir);
+ } elseif (!@is_dir($confDir)) {
+ @mkdir($confDir, 0777, true);
+ }
+
+ // Only write known keys; ignore extras.
+ $lines = ["configKeys as $key) {
+ if (!array_key_exists($key, $newConf)) continue;
+ $value = $newConf[$key];
+ $lines[] = '$conf[' . var_export($key, true) . '] = ' . $this->exportPhpValue($value, $key) . ';';
+ }
+ $lines[] = '';
+ $content = implode("\n", $lines);
+
+ if (function_exists('io_saveFile')) {
+ return (bool)io_saveFile($file, $content);
+ }
+
+ return @file_put_contents($file, $content, LOCK_EX) !== false;
+ }
+
+ /**
+ * Export a value to PHP code.
+ *
+ * We use nowdoc for multiline strings to safely preserve newlines.
+ *
+ * @param mixed $value
+ * @param string $key
+ * @return string
+ */
+ protected function exportPhpValue($value, string $key): string
+ {
+ if (is_bool($value) || is_int($value) || is_float($value) || $value === null) {
+ return var_export($value, true);
+ }
+
+ $value = (string)$value;
+
+ if (str_contains($value, "\n") || str_contains($value, "\r")) {
+ $marker = strtoupper('LUXTOOLS_' . preg_replace('/[^A-Z0-9_]/i', '_', $key) . '_EOT');
+ // Extremely unlikely, but avoid delimiter collision.
+ while (str_contains($value, $marker)) {
+ $marker .= '_X';
+ }
+ return "<<<'$marker'\n" . $value . "\n$marker";
+ }
+
+ return var_export($value, true);
+ }
+}
diff --git a/conf/metadata.php b/conf/metadata.php
index d1d95bf..cc383ec 100644
--- a/conf/metadata.php
+++ b/conf/metadata.php
@@ -2,19 +2,10 @@
/**
* Metadata for configuration manager plugin
- * Additions for the luxtools plugin
*
- * @author Gina Haeussge
+ * NOTE: luxtools settings are managed via the plugin's dedicated admin page
+ * (Admin -> Additional Plugins). Therefore, we intentionally do not expose
+ * any settings to the Configuration Manager.
*/
-$meta['paths'] = array('');
-$meta['allow_in_comments'] = array('onoff');
-$meta['defaults'] = array('string');
-$meta['extensions'] = array('string');
-
-$meta['thumb_placeholder'] = array('string');
-
-// Thumbnail generation scale factor for the {{images>...}} gallery.
-$meta['gallery_thumb_scale'] = array('string');
-
-$meta['open_service_url'] = array('string');
+$meta = [];
diff --git a/lang/de/lang.php b/lang/de/lang.php
index 2ba6eaf..a628f56 100644
--- a/lang/de/lang.php
+++ b/lang/de/lang.php
@@ -16,3 +16,19 @@ $lang['error_outsidejail'] = 'Zugriff verweigert';
$lang['empty_files'] = 'Keine Dateien';
$lang['empty_images'] = 'Keine Bilder';
+
+$lang['menu'] = 'luxtools';
+$lang['settings'] = 'luxtools-Einstellungen';
+$lang['legend'] = 'Einstellungen';
+$lang['btn_save'] = 'Speichern';
+$lang['saved'] = 'Einstellungen gespeichert.';
+$lang['err_save'] = 'Einstellungen konnten nicht gespeichert werden. Bitte Schreibrechte für conf/plugins/ prüfen.';
+$lang['err_security'] = 'Sicherheits-Token ungültig. Bitte erneut versuchen.';
+
+$lang['paths'] = 'Erlaubte Basis-Pfade (eine pro Zeile oder komma-separiert).';
+$lang['allow_in_comments'] = 'Files-Syntax in Kommentaren erlauben.';
+$lang['defaults'] = 'Standardoptionen (gleiche Syntax wie bei Inline-Konfiguration).';
+$lang['extensions'] = 'Kommagetrennte Liste erlaubter Dateiendungen.';
+$lang['thumb_placeholder'] = 'MediaManager-ID für den Platzhalter der Galerie-Thumbnails.';
+$lang['gallery_thumb_scale'] = 'Skalierungsfaktor für Galerie-Thumbnails. 2 erzeugt schärfere Thumbnails auf HiDPI-Displays (Anzeige bleibt 150×150).';
+$lang['open_service_url'] = 'URL des lokalen Client-Dienstes für {{open>...}} (z.B. http://127.0.0.1:8765).';
diff --git a/lang/de/settings.php b/lang/de/settings.php
index a27b5c0..de6e4e4 100644
--- a/lang/de/settings.php
+++ b/lang/de/settings.php
@@ -1,7 +1,11 @@
...}} button (e.g. http://127.0.0.1:8765).';
diff --git a/lang/en/settings.php b/lang/en/settings.php
index 3206f46..35f8575 100644
--- a/lang/en/settings.php
+++ b/lang/en/settings.php
@@ -1,5 +1,6 @@
...}} syntax implementation lives in syntax/files.php.
*/
diff --git a/syntax/AbstractSyntax.php b/syntax/AbstractSyntax.php
index 3367491..6935531 100644
--- a/syntax/AbstractSyntax.php
+++ b/syntax/AbstractSyntax.php
@@ -6,7 +6,7 @@ use dokuwiki\plugin\luxtools\Output;
use dokuwiki\plugin\luxtools\Path;
/**
- * LuxTools Plugin: Abstract base class for file-listing syntax handlers.
+ * luxtools Plugin: Abstract base class for file-listing syntax handlers.
*
* Provides shared functionality for directory, files, and images syntax.
*/
diff --git a/syntax/directory.php b/syntax/directory.php
index 916f081..5b7b7f6 100644
--- a/syntax/directory.php
+++ b/syntax/directory.php
@@ -6,7 +6,7 @@ use dokuwiki\plugin\luxtools\Path;
require_once(__DIR__ . '/AbstractSyntax.php');
/**
- * LuxTools Plugin: Directory syntax.
+ * luxtools Plugin: Directory syntax.
*
* Lists the direct children (folders and files) of a given path.
* Always renders as a table.
diff --git a/syntax/files.php b/syntax/files.php
index bf12b2e..c28b4ba 100644
--- a/syntax/files.php
+++ b/syntax/files.php
@@ -5,7 +5,7 @@ use dokuwiki\plugin\luxtools\Output;
require_once(__DIR__ . '/AbstractSyntax.php');
/**
- * LuxTools Plugin: Files syntax.
+ * luxtools Plugin: Files syntax.
*
* Lists files matching a given glob pattern.
*/
diff --git a/syntax/images.php b/syntax/images.php
index 5957b68..32b3179 100644
--- a/syntax/images.php
+++ b/syntax/images.php
@@ -5,7 +5,7 @@ use dokuwiki\plugin\luxtools\Output;
require_once(__DIR__ . '/AbstractSyntax.php');
/**
- * LuxTools Plugin: Image gallery syntax.
+ * luxtools Plugin: Image gallery syntax.
*
* Renders a thumbnail gallery of images matching a glob pattern.
*/
diff --git a/syntax/open.php b/syntax/open.php
index 8c94df0..46b3555 100644
--- a/syntax/open.php
+++ b/syntax/open.php
@@ -3,7 +3,7 @@
use dokuwiki\Extension\SyntaxPlugin;
/**
- * LuxTools Plugin: Open local path syntax.
+ * luxtools Plugin: Open local path syntax.
*
* Renders an inline button. Clicking it triggers client-side JS that attempts
* to open the configured path in the default file manager (best-effort).