Major Refactoring (will need some cleanup)
This refactors the plugin from one mega syntax file into multiple classes. The plugin now focuses on one usecase: listing files that are external to the wiki and making them downloadable. All other usecases have been dropped. Also a bunch of other options have been dropped. A new dispatcher makes it possible to deliver files without the need to have the webserver pointing at them.
This commit is contained in:
192
Crawler.php
Normal file
192
Crawler.php
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace dokuwiki\plugin\filelist;
|
||||||
|
|
||||||
|
class Crawler
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @var string regexp to check extensions */
|
||||||
|
protected $ext;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $sortby = 'name';
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
protected $sortreverse = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the crawler
|
||||||
|
*
|
||||||
|
* @param string $extensions The extensions to allow (comma separated list)
|
||||||
|
*/
|
||||||
|
public function __construct($extensions)
|
||||||
|
{
|
||||||
|
$this->ext = explode(',', $extensions);
|
||||||
|
$this->ext = array_map('trim', $this->ext);
|
||||||
|
$this->ext = array_map('preg_quote_cb', $this->ext);
|
||||||
|
$this->ext = implode('|', $this->ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSortBy($sortby)
|
||||||
|
{
|
||||||
|
$this->sortby = $sortby;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSortReverse($sortreverse)
|
||||||
|
{
|
||||||
|
$this->sortreverse = $sortreverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does a (recursive) crawl for finding files based on a given pattern.
|
||||||
|
* Based on a safe glob reimplementation using fnmatch and opendir.
|
||||||
|
*
|
||||||
|
* @param string $path the path to search in
|
||||||
|
* @param string $pattern the pattern to match to
|
||||||
|
* @param bool $recursive whether to search recursively
|
||||||
|
* @param string $titlefile the name of the title file
|
||||||
|
* @return array a hierarchical filelist or false if nothing could be found
|
||||||
|
*
|
||||||
|
* @see http://www.php.net/manual/en/function.glob.php#71083
|
||||||
|
*/
|
||||||
|
public function crawl($root, $local, $pattern, $recursive, $titlefile)
|
||||||
|
{
|
||||||
|
$path = $root . $local;
|
||||||
|
|
||||||
|
if (($dir = opendir($path)) === false) return [];
|
||||||
|
$result = [];
|
||||||
|
while (($file = readdir($dir)) !== false) {
|
||||||
|
if ($file[0] == '.' || $file == $titlefile) {
|
||||||
|
// ignore hidden, system and title files
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$self = $local . '/' . $file;
|
||||||
|
$filepath = $path . '/' . $file;
|
||||||
|
if (!is_readable($filepath)) continue;
|
||||||
|
|
||||||
|
if ($this->fnmatch($pattern, $file) || (is_dir($filepath) && $recursive)) {
|
||||||
|
if (!is_dir($filepath) && !$this->isExtensionAllowed($file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get title file
|
||||||
|
$filename = $file;
|
||||||
|
if (is_dir($filepath)) {
|
||||||
|
$title = $filepath . '/' . $titlefile;
|
||||||
|
if (is_readable($title)) {
|
||||||
|
$filename = io_readFile($title, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare entry
|
||||||
|
if (!is_dir($filepath) || $recursive) {
|
||||||
|
$entry = [
|
||||||
|
'name' => $filename,
|
||||||
|
'local' => $self,
|
||||||
|
'path' => $filepath,
|
||||||
|
'mtime' => filemtime($filepath),
|
||||||
|
'ctime' => filectime($filepath),
|
||||||
|
'size' => filesize($filepath),
|
||||||
|
'children' => ((is_dir($filepath) && $recursive) ?
|
||||||
|
$this->crawl($root, $self, $pattern, $recursive, $titlefile) :
|
||||||
|
false
|
||||||
|
),
|
||||||
|
'treesize' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
// calculate tree size
|
||||||
|
if ($entry['children'] !== false) {
|
||||||
|
foreach ($entry['children'] as $child) {
|
||||||
|
$entry['treesize'] += $child['treesize'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$entry['treesize'] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add entry to result
|
||||||
|
$result[] = $entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir($dir);
|
||||||
|
return $this->sortItems($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the given items by the current sortby and sortreverse settings
|
||||||
|
*
|
||||||
|
* @param array $items
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function sortItems($items)
|
||||||
|
{
|
||||||
|
$callback = [$this, 'compare' . ucfirst($this->sortby)];
|
||||||
|
if (!is_callable($callback)) return $items;
|
||||||
|
|
||||||
|
usort($items, $callback);
|
||||||
|
if ($this->sortreverse) {
|
||||||
|
$items = array_reverse($items);
|
||||||
|
}
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a file is allowed by the configured extensions
|
||||||
|
*
|
||||||
|
* @param string $file
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isExtensionAllowed($file)
|
||||||
|
{
|
||||||
|
if ($this->ext === '') return true; // no restriction
|
||||||
|
return preg_match('/(' . $this->ext . ')$/i', $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replacement for fnmatch() for windows systems.
|
||||||
|
*
|
||||||
|
* @author jk at ricochetsolutions dot com
|
||||||
|
* @link http://www.php.net/manual/en/function.fnmatch.php#71725
|
||||||
|
*/
|
||||||
|
protected function fnmatch($pattern, $string)
|
||||||
|
{
|
||||||
|
return preg_match(
|
||||||
|
"#^" . strtr(
|
||||||
|
preg_quote($pattern, '#'),
|
||||||
|
[
|
||||||
|
'\*' => '.*',
|
||||||
|
'\?' => '.',
|
||||||
|
'\[' => '[',
|
||||||
|
'\]' => ']'
|
||||||
|
]
|
||||||
|
) . "$#i",
|
||||||
|
$string
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compareName($a, $b)
|
||||||
|
{
|
||||||
|
return strcmp($a['name'], $b['name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compareIname($a, $b)
|
||||||
|
{
|
||||||
|
return strcmp(strtolower($a['name']), strtolower($b['name']));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compareCtime($a, $b)
|
||||||
|
{
|
||||||
|
return $a['ctime'] <=> $b['ctime'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compareMtime($a, $b)
|
||||||
|
{
|
||||||
|
return $a['mtime'] <=> $b['mtime'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compareSize($a, $b)
|
||||||
|
{
|
||||||
|
return $a['size'] <=> $b['size'];
|
||||||
|
}
|
||||||
|
}
|
||||||
283
Output.php
Normal file
283
Output.php
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace dokuwiki\plugin\filelist;
|
||||||
|
|
||||||
|
class Output
|
||||||
|
{
|
||||||
|
/** @var \Doku_Renderer */
|
||||||
|
protected $renderer;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $basedir;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $webdir;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $files;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(\Doku_Renderer $renderer, $basedir, $webdir, $files)
|
||||||
|
{
|
||||||
|
$this->renderer = $renderer;
|
||||||
|
$this->basedir = $basedir;
|
||||||
|
$this->webdir = $webdir;
|
||||||
|
$this->files = $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderAsList($params)
|
||||||
|
{
|
||||||
|
if ($this->renderer instanceof \Doku_Renderer_xhtml) {
|
||||||
|
$this->renderer->doc .= '<div class="filelist-plugin">';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->renderListItems($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.
|
||||||
|
*
|
||||||
|
* @param array $params the parameters of the filelist call
|
||||||
|
*/
|
||||||
|
public function renderAsTable($params)
|
||||||
|
{
|
||||||
|
if ($this->renderer instanceof \Doku_Renderer_xhtml) {
|
||||||
|
$this->renderer->doc .= '<div class="filelist-plugin">';
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = $this->flattenResultTree($this->files);
|
||||||
|
$this->renderTableItems($items, $params);
|
||||||
|
|
||||||
|
if ($this->renderer instanceof \Doku_Renderer_xhtml) {
|
||||||
|
$this->renderer->doc .= '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the files as a table, including details if configured that way.
|
||||||
|
*
|
||||||
|
* @param array $params the parameters of the filelist call
|
||||||
|
*/
|
||||||
|
protected function renderTableItems($items, $params)
|
||||||
|
{
|
||||||
|
|
||||||
|
$renderer = $this->renderer;
|
||||||
|
|
||||||
|
|
||||||
|
// count the columns
|
||||||
|
$columns = 1;
|
||||||
|
if ($params['showsize']) {
|
||||||
|
$columns++;
|
||||||
|
}
|
||||||
|
if ($params['showdate']) {
|
||||||
|
$columns++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$renderer->table_open($columns);
|
||||||
|
|
||||||
|
if ($params['tableheader']) {
|
||||||
|
$renderer->tablethead_open();
|
||||||
|
$renderer->tablerow_open();
|
||||||
|
|
||||||
|
$renderer->tableheader_open();
|
||||||
|
$renderer->cdata($this->getLang('filename'));
|
||||||
|
$renderer->tableheader_close();
|
||||||
|
|
||||||
|
if ($params['showsize']) {
|
||||||
|
$renderer->tableheader_open();
|
||||||
|
$renderer->cdata($this->getLang('filesize'));
|
||||||
|
$renderer->tableheader_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($params['showdate']) {
|
||||||
|
$renderer->tableheader_open();
|
||||||
|
$renderer->cdata($this->getLang('lastmodified'));
|
||||||
|
$renderer->tableheader_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$renderer->tablerow_close();
|
||||||
|
$renderer->tablethead_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$renderer->tabletbody_open();
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$renderer->tablerow_open();
|
||||||
|
$renderer->tablecell_open();
|
||||||
|
$this->renderItemLink($item, $params['randlinks']);
|
||||||
|
$renderer->tablecell_close();
|
||||||
|
|
||||||
|
if ($params['showsize']) {
|
||||||
|
$renderer->tablecell_open(1, 'right');
|
||||||
|
$renderer->cdata(filesize_h($item['size']));
|
||||||
|
$renderer->tablecell_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($params['showdate']) {
|
||||||
|
$renderer->tablecell_open();
|
||||||
|
$renderer->cdata(dformat($item['mtime']));
|
||||||
|
$renderer->tablecell_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$renderer->tablerow_close();
|
||||||
|
}
|
||||||
|
$renderer->tabletbody_close();
|
||||||
|
$renderer->table_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively renders a tree of files as list items.
|
||||||
|
*
|
||||||
|
* @param array $items the files to render
|
||||||
|
* @param array $params the parameters of the filelist call
|
||||||
|
* @param int $level the level to render
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function renderListItems($items, $params, $level = 1)
|
||||||
|
{
|
||||||
|
if ($params['style'] == 'olist') {
|
||||||
|
$this->renderer->listo_open();
|
||||||
|
} else {
|
||||||
|
$this->renderer->listu_open();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($items as $file) {
|
||||||
|
if ($file['children'] === false && $file['treesize'] === 0) continue; // empty directory
|
||||||
|
|
||||||
|
$this->renderer->listitem_open($level);
|
||||||
|
$this->renderer->listcontent_open();
|
||||||
|
|
||||||
|
if ($file['children'] !== false && $file['treesize'] > 0) {
|
||||||
|
// render the directory and its subtree
|
||||||
|
$this->renderer->cdata($file['name']);
|
||||||
|
$this->renderListItems($file['children'], $params, $level + 1);
|
||||||
|
} elseif ($file['children'] === false) {
|
||||||
|
// render the file link
|
||||||
|
$this->renderItemLink($file, $params['randlinks']);
|
||||||
|
|
||||||
|
// render filesize
|
||||||
|
if ($params['showsize']) {
|
||||||
|
$this->renderer->cdata($params['listsep'] . filesize_h($file['size']));
|
||||||
|
}
|
||||||
|
// render lastmodified
|
||||||
|
if ($params['showdate']) {
|
||||||
|
$this->renderer->cdata($params['listsep'] . dformat($file['mtime']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->renderer->listcontent_close();
|
||||||
|
$this->renderer->listitem_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($params['style'] == 'olist') {
|
||||||
|
$this->renderer->listo_close();
|
||||||
|
} else {
|
||||||
|
$this->renderer->listu_close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderItemLink($item, $cachebuster = false)
|
||||||
|
{
|
||||||
|
if ($this->renderer instanceof \Doku_Renderer_xhtml) {
|
||||||
|
$this->renderItemLinkXHTML($item, $cachebuster);
|
||||||
|
} else {
|
||||||
|
$this->renderItemLinkAny($item, $cachebuster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a file link on the XHTML renderer
|
||||||
|
*/
|
||||||
|
protected function renderItemLinkXHTML($item, $cachebuster = false)
|
||||||
|
{
|
||||||
|
global $conf;
|
||||||
|
/** @var \Doku_Renderer_xhtml $renderer */
|
||||||
|
$renderer = $this->renderer;
|
||||||
|
|
||||||
|
//prepare for formating
|
||||||
|
$link['target'] = $conf['target']['extern'];
|
||||||
|
$link['style'] = '';
|
||||||
|
$link['pre'] = '';
|
||||||
|
$link['suf'] = '';
|
||||||
|
$link['more'] = '';
|
||||||
|
$link['url'] = $this->itemWebUrl($item, $cachebuster);
|
||||||
|
$link['name'] = $item['name'];
|
||||||
|
$link['title'] = $renderer->_xmlEntities($link['url']);
|
||||||
|
if ($conf['relnofollow']) $link['more'] .= ' rel="nofollow"';
|
||||||
|
[$ext,] = mimetype(basename($item['local']));
|
||||||
|
$link['class'] .= ' mediafile mf_' . $ext;
|
||||||
|
$renderer->doc .= $renderer->_formatLink($link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a file link on any Renderer
|
||||||
|
* @param array $item
|
||||||
|
* @param bool $cachebuster
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function renderItemLinkAny($item, $cachebuster = false)
|
||||||
|
{
|
||||||
|
$this->renderer->externalmedialink($this->itemWebUrl($item, $cachebuster), $item['name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the Web URL for a given item
|
||||||
|
*
|
||||||
|
* @param array $item The item data as returned by the Crawler
|
||||||
|
* @param bool $cachbuster add a cachebuster to the URL?
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function itemWebUrl($item, $cachbuster = false)
|
||||||
|
{
|
||||||
|
if (str_ends_with($this->webdir, '=')) {
|
||||||
|
$url = $this->webdir . rawurlencode($item['local']);
|
||||||
|
} else {
|
||||||
|
$url = $this->webdir . $item['local'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cachbuster) {
|
||||||
|
if (strpos($url, '?') === false) {
|
||||||
|
$url .= '?t=' . $item['mtime'];
|
||||||
|
} else {
|
||||||
|
$url .= '&t=' . $item['mtime'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens the filelist by recursively walking through all subtrees and
|
||||||
|
* merging them with a prefix attached to the filenames.
|
||||||
|
*
|
||||||
|
* @param array $items the tree to flatten
|
||||||
|
* @param string $prefix the prefix to attach to all processed nodes
|
||||||
|
* @return array a flattened representation of the tree
|
||||||
|
*/
|
||||||
|
protected function flattenResultTree($items, $prefix = '')
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($items as $file) {
|
||||||
|
if ($file['children'] !== false) {
|
||||||
|
$result = array_merge(
|
||||||
|
$result,
|
||||||
|
$this->flattenResultTree($file['children'], $prefix . $file['name'] . '/')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$file['name'] = $prefix . $file['name'];
|
||||||
|
$result[] = $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLang($key)
|
||||||
|
{
|
||||||
|
$syntax = plugin_load('syntax', 'filelist');
|
||||||
|
return $syntax->getLang($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
144
Path.php
Normal file
144
Path.php
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace dokuwiki\plugin\filelist;
|
||||||
|
|
||||||
|
class Path
|
||||||
|
{
|
||||||
|
protected $paths = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $pathConfig The path configuration ftom the plugin settings
|
||||||
|
*/
|
||||||
|
public function __construct($pathConfig)
|
||||||
|
{
|
||||||
|
$this->paths = $this->parsePathConfig($pathConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the path configuration into an internal array
|
||||||
|
*
|
||||||
|
* roots (and aliases) are always saved with a trailing slash
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function parsePathConfig($pathConfig)
|
||||||
|
{
|
||||||
|
$paths = [];
|
||||||
|
$lines = explode("\n", $pathConfig);
|
||||||
|
$lastRoot = '';
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$line = trim($line);
|
||||||
|
if (empty($line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($line, 'A>')) {
|
||||||
|
// this is an alias for the last read root
|
||||||
|
$line = trim(substr($line, 2));
|
||||||
|
if (!isset($paths[$lastRoot])) continue; // no last root, no alias
|
||||||
|
$alias = $this->cleanPath($line);
|
||||||
|
$paths[$lastRoot]['alias'] = $alias;
|
||||||
|
$paths[$alias] = &$paths[$lastRoot]; // alias references the original
|
||||||
|
} elseif (str_starts_with($line, 'W>')) {
|
||||||
|
// this is a web path for the last read root
|
||||||
|
$line = trim(substr($line, 2));
|
||||||
|
if (!isset($paths[$lastRoot])) continue; // no last path, no web path
|
||||||
|
$paths[$lastRoot]['web'] = $line;
|
||||||
|
} else {
|
||||||
|
// this is a new path
|
||||||
|
$line = $this->cleanPath($line);
|
||||||
|
$lastRoot = $line;
|
||||||
|
$paths[$line] = [
|
||||||
|
'root' => $line,
|
||||||
|
'web' => DOKU_BASE . 'lib/plugins/filelist/file.php?root=' . rawurlencode($line).'&file=',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given path is listable and return it's configuration
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @param bool $addTrailingSlash
|
||||||
|
* @return array
|
||||||
|
* @throws \Exception if the given path is not allowed
|
||||||
|
*/
|
||||||
|
public function getPathInfo($path, $addTrailingSlash = true)
|
||||||
|
{
|
||||||
|
$path = $this->cleanPath($path, $addTrailingSlash);
|
||||||
|
|
||||||
|
$paths = $this->paths;
|
||||||
|
$allowed = array_keys($paths);
|
||||||
|
usort($allowed, function ($a, $b) {
|
||||||
|
return strlen($a) - strlen($b);
|
||||||
|
});
|
||||||
|
$allowed = array_map('preg_quote_cb', $allowed);
|
||||||
|
$regex = '/^(' . implode('|', $allowed) . ')/';
|
||||||
|
|
||||||
|
if (!preg_match($regex, $path, $matches)) {
|
||||||
|
throw new \Exception('Path not allowed: ' . $path);
|
||||||
|
}
|
||||||
|
$match = $matches[1];
|
||||||
|
|
||||||
|
$pathInfo = $paths[$match];
|
||||||
|
$pathInfo['local'] = substr($path, strlen($match));
|
||||||
|
$pathInfo['path'] = $pathInfo['root'] . $pathInfo['local'];
|
||||||
|
|
||||||
|
|
||||||
|
return $pathInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean a path for better comparison
|
||||||
|
*
|
||||||
|
* Converts all backslashes to forward slashes
|
||||||
|
* Keeps leading double backslashes for UNC paths
|
||||||
|
* Ensure a single trailing slash unless disabled
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function cleanPath($path, $addTrailingSlash = true)
|
||||||
|
{
|
||||||
|
if (str_starts_with($path, '\\\\')) {
|
||||||
|
$unc = '\\\\';
|
||||||
|
} else {
|
||||||
|
$unc = '';
|
||||||
|
}
|
||||||
|
$path = ltrim($path, '\\');
|
||||||
|
$path = str_replace('\\', '/', $path);
|
||||||
|
$path = self::realpath($path);
|
||||||
|
if ($addTrailingSlash) {
|
||||||
|
$path = rtrim($path, '/');
|
||||||
|
$path .= '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $unc . $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canonicalizes a given path. A bit like realpath, but without the resolving of symlinks.
|
||||||
|
*
|
||||||
|
* @author anonymous
|
||||||
|
* @see <http://www.php.net/manual/en/function.realpath.php#73563>
|
||||||
|
*/
|
||||||
|
public static function realpath($path)
|
||||||
|
{
|
||||||
|
$path = explode('/', $path);
|
||||||
|
$output = [];
|
||||||
|
$counter = count($path);
|
||||||
|
for ($i = 0; $i < $counter; $i++) {
|
||||||
|
if ('.' == $path[$i]) continue;
|
||||||
|
if ('' === $path[$i] && $i > 0) continue;
|
||||||
|
if ('..' == $path[$i] && '..' != $output[count($output) - 1]) {
|
||||||
|
array_pop($output);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$output[] = $path[$i];
|
||||||
|
}
|
||||||
|
return implode('/', $output);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
* Options for the filelist plugin
|
* Options for the filelist plugin
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
$conf['paths'] = '';
|
||||||
|
|
||||||
$conf['allow_in_comments'] = 0;
|
$conf['allow_in_comments'] = 0;
|
||||||
$conf['allowed_absolute_paths'] = DOKU_INC;
|
$conf['allowed_absolute_paths'] = DOKU_INC;
|
||||||
$conf['web_paths'] = DOKU_URL;
|
$conf['web_paths'] = DOKU_URL;
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
* @author Gina Haeussge <osd@foosel.net>
|
* @author Gina Haeussge <osd@foosel.net>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
$meta['paths'] = array('');
|
||||||
|
|
||||||
$meta['allow_in_comments'] = array('onoff');
|
$meta['allow_in_comments'] = array('onoff');
|
||||||
$meta['allowed_absolute_paths'] = array('');
|
$meta['allowed_absolute_paths'] = array('');
|
||||||
$meta['web_paths'] = array('');
|
$meta['web_paths'] = array('');
|
||||||
|
|||||||
40
file.php
Normal file
40
file.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use dokuwiki\plugin\filelist\Path;
|
||||||
|
|
||||||
|
if (!defined('DOKU_INC')) define('DOKU_INC', __DIR__ . '/../../../');
|
||||||
|
if (!defined('NOSESSION')) define('NOSESSION', true); // we do not use a session or authentication here (better caching)
|
||||||
|
if (!defined('DOKU_DISABLE_GZIP_OUTPUT')) define('DOKU_DISABLE_GZIP_OUTPUT', 1); // we gzip ourself here
|
||||||
|
require_once(DOKU_INC . 'inc/init.php');
|
||||||
|
|
||||||
|
global $INPUT;
|
||||||
|
|
||||||
|
$syntax = plugin_load('syntax', 'filelist');
|
||||||
|
if (!$syntax) die('plugin disabled?');
|
||||||
|
|
||||||
|
$pathUtil = new Path($syntax->getConf('paths'));
|
||||||
|
$path = $INPUT->str('root') . $INPUT->str('file');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pathInfo = $pathUtil->getPathInfo($path, false);
|
||||||
|
if (!is_readable($pathInfo['path'])) {
|
||||||
|
header('Content-Type: text/plain');
|
||||||
|
http_status(404);
|
||||||
|
echo 'Path not readable: ' . $pathInfo['path'];
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
[$ext, $mime, $download] = mimetype($pathInfo['path'], false);
|
||||||
|
$basename = basename($pathInfo['path']);
|
||||||
|
header('Content-Type: ' . $mime);
|
||||||
|
if ($download) {
|
||||||
|
header('Content-Disposition: attachment; filename="' . $basename . '"');
|
||||||
|
}
|
||||||
|
http_sendfile($pathInfo['path']);
|
||||||
|
readfile($pathInfo['path']);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
header('Content-Type: text/plain');
|
||||||
|
http_status(403);
|
||||||
|
echo $e->getMessage();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
1087
syntax.php
1087
syntax.php
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user