Add directory listing syntax
Some checks failed
DokuWiki Default Tasks / all (push) Has been cancelled
Some checks failed
DokuWiki Default Tasks / all (push) Has been cancelled
This commit is contained in:
71
Crawler.php
71
Crawler.php
@@ -122,6 +122,77 @@ class Crawler
|
|||||||
return $this->sortItems($result);
|
return $this->sortItems($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List the direct children (files and directories) of a given local path.
|
||||||
|
*
|
||||||
|
* Unlike crawl(), this includes directories even when not recursing.
|
||||||
|
*
|
||||||
|
* @param string $root
|
||||||
|
* @param string $local
|
||||||
|
* @param string $titlefile
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function listDirectory($root, $local, $titlefile)
|
||||||
|
{
|
||||||
|
$path = $root . $local;
|
||||||
|
$path = rtrim($path, '/');
|
||||||
|
|
||||||
|
// do not list wiki or data directories
|
||||||
|
if (Path::isWikiControlled($path)) return [];
|
||||||
|
|
||||||
|
if (($dir = opendir($path)) === false) return [];
|
||||||
|
$result = [];
|
||||||
|
while (($file = readdir($dir)) !== false) {
|
||||||
|
if ($file[0] == '.' || $file == $titlefile) {
|
||||||
|
// ignore hidden, system and title files
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$filepath = $path . '/' . $file;
|
||||||
|
if (!is_readable($filepath)) continue;
|
||||||
|
|
||||||
|
$isDir = is_dir($filepath);
|
||||||
|
if (!$isDir && !$this->isExtensionAllowed($file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($this->isFileIgnored($file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get title file (directories only)
|
||||||
|
$filename = $file;
|
||||||
|
if ($isDir) {
|
||||||
|
$title = $filepath . '/' . $titlefile;
|
||||||
|
if (is_readable($title)) {
|
||||||
|
$filename = io_readFile($title, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build a local path consistent with crawl() (leading slash for root)
|
||||||
|
$self = rtrim($local, '/') . '/' . $file;
|
||||||
|
if ($self === '/' . $file) {
|
||||||
|
// keep the original behaviour when local is empty
|
||||||
|
$self = '/' . $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry = [
|
||||||
|
'name' => $filename,
|
||||||
|
'local' => $self,
|
||||||
|
'path' => $filepath,
|
||||||
|
'mtime' => filemtime($filepath),
|
||||||
|
'ctime' => filectime($filepath),
|
||||||
|
'size' => $isDir ? 0 : filesize($filepath),
|
||||||
|
'children' => false,
|
||||||
|
'treesize' => 1,
|
||||||
|
'isdir' => $isDir,
|
||||||
|
];
|
||||||
|
|
||||||
|
$result[] = $entry;
|
||||||
|
}
|
||||||
|
closedir($dir);
|
||||||
|
return $this->sortItems($result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort the given items by the current sortby and sortreverse settings
|
* Sort the given items by the current sortby and sortreverse settings
|
||||||
*
|
*
|
||||||
|
|||||||
84
Output.php
84
Output.php
@@ -91,6 +91,25 @@ class Output
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a flat list (files and/or directories) as a table.
|
||||||
|
*
|
||||||
|
* @param array $params
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function renderAsFlatTable($params)
|
||||||
|
{
|
||||||
|
if ($this->renderer instanceof \Doku_Renderer_xhtml) {
|
||||||
|
$this->renderer->doc .= '<div class="filetools-plugin">';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->renderTableItems($this->files, $params);
|
||||||
|
|
||||||
|
if ($this->renderer instanceof \Doku_Renderer_xhtml) {
|
||||||
|
$this->renderer->doc .= '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the files as a table, including details if configured that way.
|
* Renders the files as a table, including details if configured that way.
|
||||||
@@ -147,7 +166,11 @@ class Output
|
|||||||
|
|
||||||
if ($params['showsize']) {
|
if ($params['showsize']) {
|
||||||
$renderer->tablecell_open(1, 'right');
|
$renderer->tablecell_open(1, 'right');
|
||||||
$renderer->cdata(filesize_h($item['size']));
|
if (!empty($item['isdir'])) {
|
||||||
|
$renderer->cdata('');
|
||||||
|
} else {
|
||||||
|
$renderer->cdata(filesize_h($item['size']));
|
||||||
|
}
|
||||||
$renderer->tablecell_close();
|
$renderer->tablecell_close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +240,11 @@ class Output
|
|||||||
|
|
||||||
protected function renderItemLink($item, $cachebuster = false)
|
protected function renderItemLink($item, $cachebuster = false)
|
||||||
{
|
{
|
||||||
|
if (!empty($item['isdir'])) {
|
||||||
|
$this->renderDirectoryLink($item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->renderer instanceof \Doku_Renderer_xhtml) {
|
if ($this->renderer instanceof \Doku_Renderer_xhtml) {
|
||||||
$this->renderItemLinkXHTML($item, $cachebuster);
|
$this->renderItemLinkXHTML($item, $cachebuster);
|
||||||
} else {
|
} else {
|
||||||
@@ -224,6 +252,60 @@ class Output
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a directory like a normal media link, but with open behaviour.
|
||||||
|
*
|
||||||
|
* @param array $item
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function renderDirectoryLink($item)
|
||||||
|
{
|
||||||
|
$caption = $item['name'] ?? '';
|
||||||
|
$path = $item['path'] ?? '';
|
||||||
|
|
||||||
|
if ($caption === '') {
|
||||||
|
$caption = '[n/a]';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($this->renderer instanceof \Doku_Renderer_xhtml)) {
|
||||||
|
$this->renderer->cdata($caption);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_string($path) || $path === '') {
|
||||||
|
$this->renderer->cdata('[n/a]');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global $conf;
|
||||||
|
/** @var \Doku_Renderer_xhtml $renderer */
|
||||||
|
$renderer = $this->renderer;
|
||||||
|
|
||||||
|
$syntax = plugin_load('syntax', 'luxtools');
|
||||||
|
$serviceUrl = $syntax ? trim((string)$syntax->getConf('open_service_url')) : '';
|
||||||
|
$serviceToken = $syntax ? trim((string)$syntax->getConf('open_service_token')) : '';
|
||||||
|
|
||||||
|
// Prepare a DokuWiki-style media link with a folder icon class.
|
||||||
|
$link = [
|
||||||
|
'target' => $conf['target']['extern'],
|
||||||
|
'style' => '',
|
||||||
|
'pre' => '',
|
||||||
|
'suf' => '',
|
||||||
|
'name' => $caption,
|
||||||
|
'url' => '#',
|
||||||
|
'title' => $renderer->_xmlEntities($path),
|
||||||
|
'more' => ' class="filetools-open media mediafile mf_folder" data-path="' . hsc($path) . '"',
|
||||||
|
];
|
||||||
|
if ($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
|
||||||
|
if ($serviceUrl !== '') {
|
||||||
|
$link['more'] .= ' data-service-url="' . hsc($serviceUrl) . '"';
|
||||||
|
}
|
||||||
|
if ($serviceToken !== '') {
|
||||||
|
$link['more'] .= ' data-service-token="' . hsc($serviceToken) . '"';
|
||||||
|
}
|
||||||
|
$renderer->doc .= $renderer->_formatLink($link);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a file link on the XHTML renderer
|
* Render a file link on the XHTML renderer
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -233,4 +233,34 @@ class plugin_luxtools_test extends DokuWikiTest
|
|||||||
$this->assertStringContainsString('Open here', $xhtml);
|
$this->assertStringContainsString('Open here', $xhtml);
|
||||||
$this->assertStringContainsString('data-path="/tmp/somewhere"', $xhtml);
|
$this->assertStringContainsString('data-path="/tmp/somewhere"', $xhtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function checks that the directory syntax renders a flat table,
|
||||||
|
* listing both folders and files.
|
||||||
|
*/
|
||||||
|
public function test_directory_table_flat()
|
||||||
|
{
|
||||||
|
$instructions = p_get_instructions('{{directory>' . TMP_DIR . '/filelistdata/&direct=1}}');
|
||||||
|
$xhtml = p_render('xhtml', $instructions, $info);
|
||||||
|
|
||||||
|
$doc = new Document();
|
||||||
|
$doc->html($xhtml);
|
||||||
|
|
||||||
|
$structure = [
|
||||||
|
'div.filetools-plugin' => 1,
|
||||||
|
'div.filetools-plugin table' => 1,
|
||||||
|
'div.filetools-plugin table > tbody > tr' => 3,
|
||||||
|
'a.filetools-open' => 1,
|
||||||
|
];
|
||||||
|
$this->structureCheck($doc, $structure);
|
||||||
|
|
||||||
|
// Should list the top-level entries, but not recurse into exampledir
|
||||||
|
$this->assertStringContainsString('example.txt', $xhtml);
|
||||||
|
$this->assertStringContainsString('exampleimage.png', $xhtml);
|
||||||
|
$this->assertStringContainsString('exampledir', $xhtml);
|
||||||
|
$this->assertStringNotContainsString('example2.txt', $xhtml);
|
||||||
|
|
||||||
|
// Directory row should trigger the same behaviour as {{open>...}} for that folder
|
||||||
|
$this->assertStringContainsString('data-path="' . TMP_DIR . '/filelistdata/exampledir"', $xhtml);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
127
syntax/directory.php
Normal file
127
syntax/directory.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use dokuwiki\Extension\SyntaxPlugin;
|
||||||
|
use dokuwiki\plugin\luxtools\Crawler;
|
||||||
|
use dokuwiki\plugin\luxtools\Output;
|
||||||
|
use dokuwiki\plugin\luxtools\Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LuxTools Plugin: Directory syntax.
|
||||||
|
*
|
||||||
|
* Lists the direct children (folders and files) of a given path.
|
||||||
|
* Always renders as a table.
|
||||||
|
*/
|
||||||
|
class syntax_plugin_luxtools_directory extends SyntaxPlugin
|
||||||
|
{
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function getType()
|
||||||
|
{
|
||||||
|
return 'substition';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function getPType()
|
||||||
|
{
|
||||||
|
return 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @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);
|
||||||
|
|
||||||
|
return [$path, $params];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
public function render($format, Doku_Renderer $renderer, $data)
|
||||||
|
{
|
||||||
|
if ($data === false) return false;
|
||||||
|
[$path, $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($path);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$renderer->cdata('[n/a: ' . $this->getLang('error_outsidejail') . ']');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$crawler = new Crawler($this->getConf('extensions'));
|
||||||
|
$crawler->setSortBy($params['sort']);
|
||||||
|
$crawler->setSortReverse($params['order'] === 'desc');
|
||||||
|
|
||||||
|
$items = $crawler->listDirectory(
|
||||||
|
$pathInfo['root'],
|
||||||
|
$pathInfo['local'],
|
||||||
|
$params['titlefile']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($items == []) {
|
||||||
|
$renderer->cdata('[n/a: ' . $this->getLang('error_nomatch') . ']');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always render as table style
|
||||||
|
$params['style'] = 'table';
|
||||||
|
|
||||||
|
$output = new Output($renderer, $pathInfo['root'], $pathInfo['web'], $items);
|
||||||
|
$output->renderAsFlatTable($params);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user