226 lines
7.0 KiB
PHP
226 lines
7.0 KiB
PHP
<?php
|
|
|
|
use dokuwiki\Extension\SyntaxPlugin;
|
|
use dokuwiki\plugin\luxtools\PageLink;
|
|
use dokuwiki\plugin\luxtools\Path;
|
|
|
|
require_once(__DIR__ . '/../autoload.php');
|
|
|
|
/**
|
|
* luxtools Plugin: Open local path syntax.
|
|
*
|
|
* Renders an inline link. Clicking it triggers client-side JS that attempts
|
|
* to open the configured path in the default file manager (best-effort).
|
|
*/
|
|
class syntax_plugin_luxtools_open extends SyntaxPlugin
|
|
{
|
|
/** @inheritdoc */
|
|
public function getType()
|
|
{
|
|
return 'substition';
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function getPType()
|
|
{
|
|
// inline
|
|
return 'normal';
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function getSort()
|
|
{
|
|
return 222;
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function connectTo($mode)
|
|
{
|
|
$this->Lexer->addSpecialPattern('\{\{open>.+?\}\}', $mode, 'plugin_luxtools_open');
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function handle($match, $state, $pos, Doku_Handler $handler)
|
|
{
|
|
$match = substr($match, strlen('{{open>'), -2);
|
|
[$path, $caption] = array_pad(explode('|', $match, 2), 2, '');
|
|
|
|
$path = trim($path);
|
|
$caption = trim($caption);
|
|
if ($caption === '') $caption = $path !== '' ? $path : 'Open';
|
|
|
|
// Basic scheme filtering to avoid javascript: style injections.
|
|
// Allow either file:// URLs, or plain paths (Windows/UNC/Linux style).
|
|
if (preg_match('/^[a-zA-Z][a-zA-Z0-9+.-]*:/', $path)) {
|
|
if (!str_starts_with(strtolower($path), 'file://')) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return [$path, $caption];
|
|
}
|
|
|
|
/** @inheritdoc */
|
|
public function render($format, Doku_Renderer $renderer, $data)
|
|
{
|
|
if ($data === false) return false;
|
|
[$path, $caption] = $data;
|
|
|
|
if ($format !== 'xhtml') {
|
|
// no meaningful representation in non-browser formats
|
|
$renderer->cdata($caption);
|
|
return true;
|
|
}
|
|
|
|
if ($path === '') {
|
|
$renderer->cdata('[n/a]');
|
|
return true;
|
|
}
|
|
|
|
// Resolve blobs alias to the linked folder (if available)
|
|
if ($this->isBlobsPath($path)) {
|
|
$blobsRoot = $this->resolveBlobsRoot();
|
|
if ($blobsRoot === '') {
|
|
$this->renderPageNotLinked($renderer);
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
$pathConfig = (string)$this->getConf('paths');
|
|
$pathConfig = rtrim($pathConfig) . "\n" . $blobsRoot . "\nA> blobs";
|
|
$pathHelper = new Path($pathConfig);
|
|
$resolvedPath = $path;
|
|
$isBlobsRoot = (rtrim($resolvedPath, '/') === 'blobs');
|
|
if ($isBlobsRoot) {
|
|
$resolvedPath = rtrim($resolvedPath, '/') . '/';
|
|
}
|
|
$pathInfo = $pathHelper->getPathInfo($resolvedPath, $isBlobsRoot);
|
|
$path = $pathInfo['path'];
|
|
} catch (\Exception $e) {
|
|
$renderer->cdata('[n/a: ' . $this->getLang('error_outsidejail') . ']');
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Map local paths back to their configured aliases before opening.
|
|
if (!preg_match('/^[a-zA-Z][a-zA-Z0-9+.-]*:/', $path)) {
|
|
try {
|
|
$pathHelper = new Path((string)$this->getConf('paths'));
|
|
$path = $pathHelper->mapToAliasPath($path);
|
|
} catch (\Exception $e) {
|
|
// ignore mapping failures
|
|
}
|
|
}
|
|
|
|
$serviceUrl = trim((string)$this->getConf('open_service_url'));
|
|
$serviceToken = trim((string)$this->getConf('open_service_token'));
|
|
|
|
if (!($renderer instanceof \Doku_Renderer_xhtml)) {
|
|
$renderer->cdata($caption);
|
|
return true;
|
|
}
|
|
|
|
global $conf;
|
|
/** @var \Doku_Renderer_xhtml $renderer */
|
|
|
|
// Render like a normal DokuWiki link with an icon in front.
|
|
// Use the same folder icon class as directory listings.
|
|
$link = [
|
|
'target' => $conf['target']['extern'],
|
|
'style' => '',
|
|
'pre' => '',
|
|
'suf' => '',
|
|
'name' => $caption,
|
|
'url' => '#',
|
|
'title' => $renderer->_xmlEntities($path),
|
|
'more' => '',
|
|
'class' => 'luxtools-open media mediafile mf_folder',
|
|
];
|
|
|
|
$link['more'] .= ' data-path="' . hsc($path) . '"';
|
|
if (!empty($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);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if the given path uses the blobs alias.
|
|
*/
|
|
protected function isBlobsPath(string $path): bool
|
|
{
|
|
$trimmed = ltrim($path, '/');
|
|
return preg_match('/^blobs(\/|$)/', $trimmed) === 1;
|
|
}
|
|
|
|
/**
|
|
* Resolve the current page's pagelink folder for the blobs alias.
|
|
*/
|
|
protected function resolveBlobsRoot(): string
|
|
{
|
|
global $ID;
|
|
$pageId = is_string($ID) ? $ID : '';
|
|
if ($pageId === '') return '';
|
|
|
|
if (function_exists('cleanID')) {
|
|
$pageId = (string)cleanID($pageId);
|
|
}
|
|
if ($pageId === '') return '';
|
|
|
|
$depth = (int)$this->getConf('pagelink_search_depth');
|
|
if ($depth < 0) $depth = 0;
|
|
|
|
$pageLink = new PageLink((string)$this->getConf('paths'), $depth);
|
|
$uuid = $pageLink->getPageUuid($pageId);
|
|
if ($uuid === null) return '';
|
|
|
|
$linkInfo = $pageLink->resolveUuid($uuid);
|
|
$folder = $linkInfo['folder'] ?? '';
|
|
if (!is_string($folder) || $folder === '') return '';
|
|
|
|
return $folder;
|
|
}
|
|
|
|
/**
|
|
* Render the "Page not linked" message with copy ID affordance.
|
|
*/
|
|
protected function renderPageNotLinked(\Doku_Renderer $renderer): void
|
|
{
|
|
$uuid = $this->getPageUuidSafe();
|
|
$text = (string)$this->getLang('pagelink_unlinked');
|
|
|
|
if ($renderer instanceof \Doku_Renderer_xhtml) {
|
|
$renderer->doc .= '<a href="#" class="luxtools-pagelink-copy" data-luxtools-pagelink-copy="1"'
|
|
. ' data-uuid="' . hsc($uuid) . '"'
|
|
. '>' . hsc($text) . '</a>';
|
|
return;
|
|
}
|
|
|
|
$renderer->cdata('[n/a: ' . $text . ']');
|
|
}
|
|
|
|
/**
|
|
* Read the current page UUID (if any).
|
|
*/
|
|
protected function getPageUuidSafe(): string
|
|
{
|
|
global $ID;
|
|
$pageId = is_string($ID) ? $ID : '';
|
|
if ($pageId === '') return '';
|
|
|
|
if (function_exists('cleanID')) {
|
|
$pageId = (string)cleanID($pageId);
|
|
}
|
|
if ($pageId === '') return '';
|
|
|
|
$depth = (int)$this->getConf('pagelink_search_depth');
|
|
if ($depth < 0) $depth = 0;
|
|
|
|
$pageLink = new PageLink((string)$this->getConf('paths'), $depth);
|
|
$uuid = $pageLink->getPageUuid($pageId);
|
|
return $uuid ?? '';
|
|
}
|
|
}
|