Add base class for syntax handlers
Some checks failed
DokuWiki Default Tasks / all (push) Has been cancelled

This commit is contained in:
2026-01-06 09:02:39 +01:00
parent 0ad43bcf9c
commit 490a483df1
5 changed files with 273 additions and 258 deletions

View File

@@ -1,5 +1,6 @@
<?php <?php
require_once(__DIR__ . '/syntax/AbstractSyntax.php');
require_once(__DIR__ . '/syntax/files.php'); require_once(__DIR__ . '/syntax/files.php');
/** /**

219
syntax/AbstractSyntax.php Normal file
View File

@@ -0,0 +1,219 @@
<?php
use dokuwiki\Extension\SyntaxPlugin;
use dokuwiki\plugin\luxtools\Crawler;
use dokuwiki\plugin\luxtools\Output;
use dokuwiki\plugin\luxtools\Path;
/**
* LuxTools Plugin: Abstract base class for file-listing syntax handlers.
*
* Provides shared functionality for directory, files, and images syntax.
*/
abstract class syntax_plugin_luxtools_abstract extends SyntaxPlugin
{
/**
* Returns the syntax keyword (e.g., 'files', 'directory', 'images').
* Used for pattern matching and plugin registration.
*
* @return string
*/
abstract protected function getSyntaxKeyword(): string;
/**
* Returns the default parameters for this syntax handler.
* Will be merged with the base defaults.
*
* @return array
*/
protected function getDefaultParams(): array
{
return [];
}
/**
* Process the parsed path/pattern. Override to customize path handling.
*
* @param string $path The cleaned path from the syntax
* @return array Data to be passed to render()
*/
abstract protected function processPath(string $path): array;
/**
* Perform the actual rendering. Override in each handler.
*
* @param string $format Output format (xhtml, odt, etc.)
* @param \Doku_Renderer $renderer The renderer instance
* @param array $pathData Path data from processPath()
* @param array $params Parsed parameters
* @return bool
*/
abstract protected function doRender(string $format, \Doku_Renderer $renderer, array $pathData, array $params): bool;
/** @inheritdoc */
public function getType()
{
return 'substition';
}
/** @inheritdoc */
public function getPType()
{
return 'block';
}
/** @inheritdoc */
public function getSort()
{
return 222;
}
/** @inheritdoc */
public function connectTo($mode)
{
$keyword = $this->getSyntaxKeyword();
$pattern = '\{\{' . $keyword . '>.+?\}\}';
$this->Lexer->addSpecialPattern($pattern, $mode, 'plugin_luxtools_' . $keyword);
}
/** @inheritdoc */
public function handle($match, $state, $pos, Doku_Handler $handler)
{
global $INPUT;
// Do not allow the syntax in discussion plugin comments
if (!$this->getConf('allow_in_comments') && $INPUT->has('comment')) {
return false;
}
$keyword = $this->getSyntaxKeyword();
$match = substr($match, strlen('{{' . $keyword . '>'), -2);
[$path, $flags] = array_pad(explode('&', $match, 2), 2, '');
$params = $this->parseFlags($flags);
$pathData = $this->processPath($path);
return ['pathData' => $pathData, 'params' => $params];
}
/** @inheritdoc */
public function render($format, Doku_Renderer $renderer, $data)
{
if ($data === false) {
return false;
}
if ($format !== 'xhtml' && $format !== 'odt') {
return false;
}
$pathData = $data['pathData'];
$params = $data['params'];
// Disable caching if requested
if ($params['cache'] === 0) {
$renderer->nocache();
}
return $this->doRender($format, $renderer, $pathData, $params);
}
/**
* Parse flags string into parameters array.
*
* @param string $flags The flags string from the syntax
* @return array Parsed parameters
*/
protected function parseFlags(string $flags): array
{
// Base defaults shared by all handlers
$baseDefaults = [
'sort' => 'name',
'order' => 'asc',
'style' => 'list',
'tableheader' => 0,
'recursive' => 0,
'titlefile' => '_title.txt',
'cache' => 0,
'randlinks' => 0,
'showsize' => 0,
'showdate' => 0,
'listsep' => ', ',
];
// Merge with handler-specific defaults
$params = array_merge($baseDefaults, $this->getDefaultParams());
// Load default config options and combine with provided flags
$flags = $this->getConf('defaults') . '&' . $flags;
$flagList = explode('&', $flags);
foreach ($flagList as $flag) {
if (empty(trim($flag))) {
continue;
}
[$name, $value] = sexplode('=', $flag, 2, '');
$params[trim($name)] = trim(trim($value), '"'); // quotes can be used to keep whitespace
}
return $params;
}
/**
* Get path info with error handling.
*
* @param string $basePath The base path to resolve
* @param \Doku_Renderer $renderer The renderer for error output
* @return array|false Path info array or false on error
*/
protected function getPathInfoSafe(string $basePath, \Doku_Renderer $renderer)
{
try {
$pathHelper = new Path($this->getConf('paths'));
return $pathHelper->getPathInfo($basePath);
} catch (\Exception $e) {
$this->renderError($renderer, 'error_outsidejail');
return false;
}
}
/**
* Create and configure a Crawler instance.
*
* @param array $params The parameters array
* @return Crawler
*/
protected function createCrawler(array $params): Crawler
{
$crawler = new Crawler($this->getConf('extensions'));
$crawler->setSortBy($params['sort']);
$crawler->setSortReverse($params['order'] === 'desc');
return $crawler;
}
/**
* Render an error message.
*
* @param \Doku_Renderer $renderer The renderer
* @param string $langKey The language key for the error message
*/
protected function renderError(\Doku_Renderer $renderer, string $langKey): void
{
$renderer->cdata('[n/a: ' . $this->getLang($langKey) . ']');
}
/**
* Separate a path into base directory and pattern.
*
* @param string $path The full path with pattern
* @return array [base, pattern]
*/
protected function separatePathAndPattern(string $path): array
{
$path = Path::cleanPath($path, false);
$parts = explode('/', $path);
$pattern = array_pop($parts);
$base = implode('/', $parts) . '/';
return [$base, $pattern];
}
}

View File

@@ -1,111 +1,41 @@
<?php <?php
use dokuwiki\Extension\SyntaxPlugin;
use dokuwiki\plugin\luxtools\Crawler;
use dokuwiki\plugin\luxtools\Output; use dokuwiki\plugin\luxtools\Output;
use dokuwiki\plugin\luxtools\Path; 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. * Lists the direct children (folders and files) of a given path.
* Always renders as a table. * Always renders as a table.
*/ */
class syntax_plugin_luxtools_directory extends SyntaxPlugin class syntax_plugin_luxtools_directory extends syntax_plugin_luxtools_abstract
{ {
/** @inheritdoc */ /** @inheritdoc */
public function getType() protected function getSyntaxKeyword(): string
{ {
return 'substition'; return 'directory';
} }
/** @inheritdoc */ /** @inheritdoc */
public function getPType() protected function processPath(string $path): array
{ {
return 'block'; // Directory path (no glob/pattern)
}
/** @inheritdoc */
public function getSort()
{
return 222;
}
/** @inheritdoc */
public function connectTo($mode)
{
$this->Lexer->addSpecialPattern('\{\{directory>.+?\}\}', $mode, 'plugin_luxtools_directory');
}
/** @inheritdoc */
public function handle($match, $state, $pos, Doku_Handler $handler)
{
global $INPUT;
// do not allow the syntax in discussion plugin comments
if (!$this->getConf('allow_in_comments') && $INPUT->has('comment')) {
return false;
}
$match = substr($match, strlen('{{directory>'), -2);
[$path, $flags] = explode('&', $match, 2);
// load default config options
$flags = $this->getConf('defaults') . '&' . $flags;
$flags = explode('&', $flags);
$params = [
'sort' => 'name',
'order' => 'asc',
'style' => 'list',
'tableheader' => 0,
'recursive' => 0,
'titlefile' => '_title.txt',
'cache' => 0,
'randlinks' => 0,
'showsize' => 0,
'showdate' => 0,
'listsep' => ', ',
];
foreach ($flags as $flag) {
[$name, $value] = sexplode('=', $flag, 2, '');
$params[trim($name)] = trim(trim($value), '"'); // quotes can be used to keep whitespace
}
// directory path (no glob/pattern)
$path = Path::cleanPath($path, true); $path = Path::cleanPath($path, true);
return ['path' => $path];
return [$path, $params];
} }
/** @inheritdoc */ /** @inheritdoc */
public function render($format, Doku_Renderer $renderer, $data) protected function doRender(string $format, \Doku_Renderer $renderer, array $pathData, array $params): bool
{ {
if ($data === false) return false; $pathInfo = $this->getPathInfoSafe($pathData['path'], $renderer);
[$path, $params] = $data; if ($pathInfo === false) {
if ($format != 'xhtml' && $format != 'odt') {
return false;
}
// disable caching
if ($params['cache'] === 0) {
$renderer->nocache();
}
try {
$pathHelper = new Path($this->getConf('paths'));
$pathInfo = $pathHelper->getPathInfo($path);
} catch (Exception $e) {
$renderer->cdata('[n/a: ' . $this->getLang('error_outsidejail') . ']');
return true; return true;
} }
$crawler = new Crawler($this->getConf('extensions')); $crawler = $this->createCrawler($params);
$crawler->setSortBy($params['sort']);
$crawler->setSortReverse($params['order'] === 'desc');
$items = $crawler->listDirectory( $items = $crawler->listDirectory(
$pathInfo['root'], $pathInfo['root'],
$pathInfo['local'], $pathInfo['local'],
@@ -113,7 +43,7 @@ class syntax_plugin_luxtools_directory extends SyntaxPlugin
); );
if ($items == []) { if ($items == []) {
$renderer->cdata('[n/a: ' . $this->getLang('error_nomatch') . ']'); $this->renderError($renderer, 'error_nomatch');
return true; return true;
} }

View File

@@ -1,124 +1,48 @@
<?php <?php
use dokuwiki\Extension\SyntaxPlugin;
use dokuwiki\plugin\luxtools\Crawler;
use dokuwiki\plugin\luxtools\Output; use dokuwiki\plugin\luxtools\Output;
use dokuwiki\plugin\luxtools\Path;
require_once(__DIR__ . '/AbstractSyntax.php');
/** /**
* LuxTools Plugin: Files syntax. * LuxTools Plugin: Files syntax.
* *
* Lists files matching a given glob pattern. * Lists files matching a given glob pattern.
*/ */
class syntax_plugin_luxtools_files extends SyntaxPlugin class syntax_plugin_luxtools_files extends syntax_plugin_luxtools_abstract
{ {
/** @inheritdoc */ /** @inheritdoc */
public function getType() protected function getSyntaxKeyword(): string
{ {
return 'substition'; return 'files';
} }
/** @inheritdoc */ /** @inheritdoc */
public function getPType() protected function processPath(string $path): array
{ {
return 'block'; [$base, $pattern] = $this->separatePathAndPattern($path);
return ['base' => $base, 'pattern' => $pattern];
} }
/** @inheritdoc */ /** @inheritdoc */
public function getSort() protected function doRender(string $format, \Doku_Renderer $renderer, array $pathData, array $params): bool
{ {
return 222; $pathInfo = $this->getPathInfoSafe($pathData['base'], $renderer);
} if ($pathInfo === false) {
/** @inheritdoc */
public function connectTo($mode)
{
$this->Lexer->addSpecialPattern('\{\{files>.+?\}\}', $mode, 'plugin_luxtools_files');
}
/** @inheritdoc */
public function handle($match, $state, $pos, Doku_Handler $handler)
{
global $INPUT;
// do not allow the syntax in discussion plugin comments
if (!$this->getConf('allow_in_comments') && $INPUT->has('comment')) {
return false;
}
$match = substr($match, strlen('{{files>'), -2);
[$path, $flags] = explode('&', $match, 2);
// load default config options
$flags = $this->getConf('defaults') . '&' . $flags;
$flags = explode('&', $flags);
$params = [
'sort' => 'name',
'order' => 'asc',
'style' => 'list',
'tableheader' => 0,
'recursive' => 0,
'titlefile' => '_title.txt',
'cache' => 0,
'randlinks' => 0,
'showsize' => 0,
'showdate' => 0,
'listsep' => ', ',
];
foreach ($flags as $flag) {
[$name, $value] = sexplode('=', $flag, 2, '');
$params[trim($name)] = trim(trim($value), '"'); // quotes can be use to keep whitespace
}
// separate path and pattern
$path = Path::cleanPath($path, false);
$parts = explode('/', $path);
$pattern = array_pop($parts);
$base = implode('/', $parts) . '/';
return [$base, $pattern, $params];
}
/**
* Create output
*/
public function render($format, Doku_Renderer $renderer, $data)
{
[$base, $pattern, $params] = $data;
if ($format != 'xhtml' && $format != 'odt') {
return false;
}
// disable caching
if ($params['cache'] === 0) {
$renderer->nocache();
}
try {
$pathHelper = new Path($this->getConf('paths'));
$pathInfo = $pathHelper->getPathInfo($base);
} catch (Exception $e) {
$renderer->cdata('[n/a: ' . $this->getLang('error_outsidejail') . ']');
return true; return true;
} }
$crawler = new Crawler($this->getConf('extensions')); $crawler = $this->createCrawler($params);
$crawler->setSortBy($params['sort']);
$crawler->setSortReverse($params['order'] === 'desc');
$result = $crawler->crawl( $result = $crawler->crawl(
$pathInfo['root'], $pathInfo['root'],
$pathInfo['local'], $pathInfo['local'],
$pattern, $pathData['pattern'],
$params['recursive'], $params['recursive'],
$params['titlefile'] $params['titlefile']
); );
// if we got nothing back, display a message
if ($result == []) { if ($result == []) {
$renderer->cdata('[n/a: ' . $this->getLang('error_nomatch') . ']'); $this->renderError($renderer, 'error_nomatch');
return true; return true;
} }

View File

@@ -1,113 +1,55 @@
<?php <?php
use dokuwiki\Extension\SyntaxPlugin;
use dokuwiki\plugin\luxtools\Crawler;
use dokuwiki\plugin\luxtools\Output; use dokuwiki\plugin\luxtools\Output;
use dokuwiki\plugin\luxtools\Path;
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. * Renders a thumbnail gallery of images matching a glob pattern.
*/ */
class syntax_plugin_luxtools_images extends SyntaxPlugin class syntax_plugin_luxtools_images extends syntax_plugin_luxtools_abstract
{ {
/** @inheritdoc */ /** @inheritdoc */
public function getType() protected function getSyntaxKeyword(): string
{ {
return 'substition'; return 'images';
} }
/** @inheritdoc */ /** @inheritdoc */
public function getPType() protected function getDefaultParams(): array
{ {
return 'block'; // Images syntax doesn't use some of the common params
} return [
'style' => null,
/** @inheritdoc */ 'tableheader' => null,
public function getSort() 'showsize' => null,
{ 'showdate' => null,
return 222; 'listsep' => null,
}
/** @inheritdoc */
public function connectTo($mode)
{
$this->Lexer->addSpecialPattern('\{\{images>.+?\}\}', $mode, 'plugin_luxtools_images');
}
/** @inheritdoc */
public function handle($match, $state, $pos, Doku_Handler $handler)
{
global $INPUT;
// do not allow the syntax in discussion plugin comments
if (!$this->getConf('allow_in_comments') && $INPUT->has('comment')) {
return false;
}
$match = substr($match, strlen('{{images>'), -2);
[$path, $flags] = explode('&', $match, 2);
// load default config options
$flags = $this->getConf('defaults') . '&' . $flags;
$flags = explode('&', $flags);
$params = [
'sort' => 'name',
'order' => 'asc',
'recursive' => 0,
'titlefile' => '_title.txt',
'cache' => 0,
'randlinks' => 0,
]; ];
foreach ($flags as $flag) {
[$name, $value] = sexplode('=', $flag, 2, '');
$params[trim($name)] = trim(trim($value), '"'); // quotes can be use to keep whitespace
}
// separate path and pattern
$path = Path::cleanPath($path, false);
$parts = explode('/', $path);
$pattern = array_pop($parts);
$base = implode('/', $parts) . '/';
return [$base, $pattern, $params];
} }
/** /** @inheritdoc */
* Create output protected function processPath(string $path): array
*/
public function render($format, Doku_Renderer $renderer, $data)
{ {
[$base, $pattern, $params] = $data; [$base, $pattern] = $this->separatePathAndPattern($path);
return ['base' => $base, 'pattern' => $pattern];
}
if ($format != 'xhtml' && $format != 'odt') { /** @inheritdoc */
return false; protected function doRender(string $format, \Doku_Renderer $renderer, array $pathData, array $params): bool
} {
$pathInfo = $this->getPathInfoSafe($pathData['base'], $renderer);
// disable caching if ($pathInfo === false) {
if ($params['cache'] === 0) {
$renderer->nocache();
}
try {
$pathHelper = new Path($this->getConf('paths'));
$pathInfo = $pathHelper->getPathInfo($base);
} catch (Exception $e) {
$renderer->cdata('[n/a: ' . $this->getLang('error_outsidejail') . ']');
return true; return true;
} }
$crawler = new Crawler($this->getConf('extensions')); $crawler = $this->createCrawler($params);
$crawler->setSortBy($params['sort']);
$crawler->setSortReverse($params['order'] === 'desc');
$result = $crawler->crawl( $result = $crawler->crawl(
$pathInfo['root'], $pathInfo['root'],
$pathInfo['local'], $pathInfo['local'],
$pattern, $pathData['pattern'],
$params['recursive'], $params['recursive'],
$params['titlefile'] $params['titlefile']
); );
@@ -115,9 +57,8 @@ class syntax_plugin_luxtools_images extends SyntaxPlugin
$items = $this->flattenResultTree($result); $items = $this->flattenResultTree($result);
$items = $this->filterImages($items); $items = $this->filterImages($items);
// if we got nothing back, display a message
if ($items == []) { if ($items == []) {
$renderer->cdata('[n/a: ' . $this->getLang('error_nomatch') . ']'); $this->renderError($renderer, 'error_nomatch');
return true; return true;
} }