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 .= '' . hsc($text) . ''; 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 ?? ''; } }