renderer = $renderer;
$this->basedir = $basedir;
$this->webdir = $webdir;
$this->files = $files;
}
public function renderAsList($params)
{
$this->openContainer($params);
$this->renderListItems($this->files, $params);
$this->closeContainer();
}
/**
* Render a thumbnail gallery (XHTML only).
*
* Expects a flat list of file items in $this->files.
* Clicking a thumbnail opens the original image.
*
* @param array $params
* @return void
*/
public function renderAsGallery($params)
{
if (!($this->renderer instanceof \Doku_Renderer_xhtml)) {
$params['style'] = 'list';
$this->renderAsList($params);
return;
}
$thumbW = 150;
$thumbH = 150;
$thumbQ = 80;
$placeholderUrl = DOKU_BASE . 'lib/images/blank.gif';
$syntax = plugin_load('syntax', 'luxtools');
$placeholderId = $syntax ? trim((string)$syntax->getConf('thumb_placeholder')) : '';
if ($placeholderId !== '' && function_exists('ml')) {
// ml() builds a fetch.php URL for a MediaManager item
$placeholderUrl = ml($placeholderId, ['w' => $thumbW, 'h' => $thumbH], true, '&');
}
/** @var \Doku_Renderer_xhtml $renderer */
$renderer = $this->renderer;
$renderer->doc .= '
';
}
/**
* Append query parameters to a URL.
*
* Preserves existing query and fragment and uses RFC3986 encoding.
*
* @param string $url
* @param array $params
* @return string
*/
protected function withQueryParams(string $url, array $params): string
{
if ($params === []) return $url;
$fragment = '';
$hashPos = strpos($url, '#');
if ($hashPos !== false) {
$fragment = substr($url, $hashPos);
$url = substr($url, 0, $hashPos);
}
$glue = (strpos($url, '?') === false) ? '?' : '&';
return $url . $glue . http_build_query($params, '', '&', PHP_QUERY_RFC3986) . $fragment;
}
/**
* Compute the expected thumbnail cache path for an item.
*
* Mirrors the hashing scheme in file.php so we can detect whether a thumb
* already exists and can be used immediately.
*
* @param array $item
* @param int $w
* @param int $h
* @param int $q
* @return string|null
*/
protected function thumbCachePathForItem(array $item, int $w, int $h, int $q): ?string
{
if (!isset($item['path']) || !is_string($item['path']) || $item['path'] === '') return null;
if (!isset($item['mtime'])) return null;
$path = $item['path'];
$mtime = (int)$item['mtime'];
// Decide output format the same way file.php does.
try {
[, $mime,] = mimetype($path, false);
} catch (\Throwable $e) {
return null;
}
if (!is_string($mime) || !str_starts_with($mime, 'image/')) return null;
$dstFormat = ($mime === 'image/png' || $mime === 'image/gif') ? 'png' : 'jpg';
global $conf;
if (!isset($conf['cachedir']) || !is_string($conf['cachedir']) || trim($conf['cachedir']) === '') return null;
$hash = sha1($path . '|' . $mtime . '|w=' . $w . '|h=' . $h . '|q=' . $q . '|f=' . $dstFormat);
$sub = substr($hash, 0, 2);
$cacheDir = rtrim($conf['cachedir'], '/');
return $cacheDir . '/luxtools/thumbs/' . $sub . '/' . $hash . '.' . $dstFormat;
}
/**
* 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)
{
$this->openContainer($params);
$items = $this->flattenResultTree($this->files);
$this->renderTableItems($items, $params);
$this->closeContainer();
}
/**
* Render a flat list (files and/or directories) as a table.
*
* @param array $params
* @return void
*/
public function renderAsFlatTable($params)
{
$this->openContainer($params);
$this->renderTableItems($this->files, $params);
$this->closeContainer();
}
/**
* Open the wrapping container with an optional max-height and scroll behaviour.
*/
protected function openContainer($params): void
{
if (!($this->renderer instanceof \Doku_Renderer_xhtml)) {
return;
}
$style = $this->containerStyle($params);
$this->renderer->doc .= '';
}
/**
* Close the wrapping container if XHTML renderer is in use.
*/
protected function closeContainer(): void
{
if (!($this->renderer instanceof \Doku_Renderer_xhtml)) {
return;
}
$this->renderer->doc .= '
';
}
/**
* Build the inline style attribute for the container based on the maxheight param.
*/
protected function containerStyle($params): string
{
if (!isset($params['maxheight'])) {
return '';
}
$maxHeight = (int)$params['maxheight'];
if ($maxHeight < 0) {
return '';
}
return ' style="max-height: ' . $maxHeight . 'px; overflow-y: auto;"';
}
/**
* 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');
if (!empty($item['isdir'])) {
$renderer->cdata('');
} else {
$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 (!empty($item['isdir'])) {
$this->renderDirectoryLink($item);
return;
}
if ($this->renderer instanceof \Doku_Renderer_xhtml) {
$this->renderItemLinkXHTML($item, $cachebuster);
} else {
$this->renderItemLinkAny($item, $cachebuster);
}
}
/**
* 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;
}
$path = $this->mapOpenPath($path);
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);
}
/**
* Map a filesystem path to an alias path (if configured).
*
* @param string $path
* @return string
*/
protected function mapOpenPath($path)
{
if ($this->openPathMapper === false) return $path;
if ($this->openPathMapper === null) {
$syntax = plugin_load('syntax', 'luxtools');
$pathConfig = $syntax ? (string)$syntax->getConf('paths') : '';
if (trim($pathConfig) === '') {
$this->openPathMapper = false;
return $path;
}
$this->openPathMapper = new Path($pathConfig);
}
return $this->openPathMapper->mapToAliasPath($path);
}
/**
* 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'] = 'media 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', 'luxtools');
return $syntax->getLang($key);
}
}