getSyntaxKeyword(); $pattern = '\{\{' . $keyword . '>.+?\}\}'; $this->Lexer->addSpecialPattern($pattern, $mode, 'plugin_luxtools_' . $keyword); } /** @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; } $keyword = $this->getSyntaxKeyword(); $match = substr($match, strlen('{{' . $keyword . '>'), -2); [$path, $flags] = array_pad(explode('&', $match, 2), 2, ''); $params = $this->parseFlags($flags); $pathData = $this->processPath($path); return ['pathData' => $pathData, 'params' => $params]; } /** @inheritdoc */ public function render($format, Doku_Renderer $renderer, $data) { if ($data === false) { return false; } // DokuWiki caches the $data returned by handle(). When the shape changes // between plugin versions, old cached instructions can feed unexpected // structures (or even null) into render(). Be defensive to avoid fatals. if (!is_array($data)) { return false; } if ($format !== 'xhtml' && $format !== 'odt') { return false; } // New format: ['pathData' => array, 'params' => array] // Back-compat: numeric list or older associative keys. $pathData = $data['pathData'] ?? ($data[0] ?? null); $params = $data['params'] ?? ($data[1] ?? null); // Some older cached instructions may pass the raw path as string. if (is_string($pathData)) { $pathData = $this->processPath($pathData); } // If params are missing (older cache), fall back to defaults. if (!is_array($params)) { $params = $this->parseFlags(''); } // If pathData was inlined (e.g. ['base'=>..,'pattern'=>..,'params'=>..]), extract it. if (!is_array($pathData)) { $candidate = $data; unset($candidate['params']); if (isset($candidate['base']) || isset($candidate['pattern']) || isset($candidate['path'])) { $pathData = $candidate; } } if (!is_array($pathData)) { return false; } // Disable caching if requested if (($params['cache'] ?? 0) == 0) { $renderer->nocache(); } return $this->doRender($format, $renderer, $pathData, $params); } /** * Parse flags string into parameters array. * * @param string $flags The flags string from the syntax * @return array Parsed parameters */ protected function parseFlags(string $flags): array { // Base defaults shared by all handlers $baseDefaults = [ 'sort' => (string)$this->getConf('default_sort'), 'order' => (string)$this->getConf('default_order'), 'style' => (string)$this->getConf('default_style'), 'tableheader' => (int)$this->getConf('default_tableheader'), 'foldersfirst' => (int)$this->getConf('default_foldersfirst'), 'recursive' => (int)$this->getConf('default_recursive'), 'titlefile' => (string)$this->getConf('default_titlefile'), 'cache' => (int)$this->getConf('default_cache'), 'randlinks' => (int)$this->getConf('default_randlinks'), 'showsize' => (int)$this->getConf('default_showsize'), 'showdate' => (int)$this->getConf('default_showdate'), 'listsep' => (string)$this->getConf('default_listsep'), 'maxheight' => (int)$this->getConf('default_maxheight'), ]; // Merge with handler-specific defaults $params = array_merge($baseDefaults, $this->getDefaultParams()); // Legacy (advanced): allow additional default flags in the old combined string. // This is not exposed in the admin UI anymore, but existing installs may // still have it in conf/local.php. $legacyDefaults = trim((string)$this->getConf('defaults')); // Combine default flags and provided flags. $flags = ($legacyDefaults !== '' ? ($legacyDefaults . '&') : '') . $flags; $flagList = explode('&', $flags); foreach ($flagList as $flag) { if (empty(trim($flag))) { continue; } [$name, $value] = sexplode('=', $flag, 2, ''); $params[trim($name)] = trim(trim($value), '"'); // quotes can be used to keep whitespace } return $params; } /** * Get path info with error handling. * * @param string $basePath The base path to resolve * @param \Doku_Renderer $renderer The renderer for error output * @return array|false Path info array or false on error */ protected function getPathInfoSafe(string $basePath, \Doku_Renderer $renderer) { try { $pathHelper = new Path($this->getConf('paths')); return $pathHelper->getPathInfo($basePath); } catch (\Exception $e) { $this->renderError($renderer, 'error_outsidejail'); return false; } } /** * Create and configure a Crawler instance. * * @param array $params The parameters array * @return Crawler */ protected function createCrawler(array $params): Crawler { $crawler = new Crawler($this->getConf('extensions')); $crawler->setSortBy($params['sort']); $crawler->setSortReverse($params['order'] === 'desc'); $crawler->setFoldersFirst(($params['foldersfirst'] ?? 0) != 0); return $crawler; } /** * Render an error message. * * @param \Doku_Renderer $renderer The renderer * @param string $langKey The language key for the error message */ protected function renderError(\Doku_Renderer $renderer, string $langKey): void { $renderer->cdata('[n/a: ' . $this->getLang($langKey) . ']'); } /** * Render a muted empty-state message (used when a listing has no results). * * @param \Doku_Renderer $renderer * @param string $langKey * @return void */ protected function renderEmptyState(\Doku_Renderer $renderer, string $langKey): void { $text = (string)$this->getLang($langKey); if ($renderer instanceof \Doku_Renderer_xhtml) { $renderer->doc .= '