/path/to/image.jpg|Caption text}} * */ class syntax_plugin_luxtools_image extends SyntaxPlugin { /** @inheritdoc */ public function getType() { return 'substition'; } /** @inheritdoc */ public function getPType() { return 'block'; } /** @inheritdoc */ public function getSort() { return 315; // Same as imagebox plugin } /** @inheritdoc */ public function connectTo($mode) { $this->Lexer->addSpecialPattern('\{\{image>.+?\}\}', $mode, 'plugin_luxtools_image'); } /** @inheritdoc */ public function handle($match, $state, $pos, \Doku_Handler $handler) { // Remove the leading {{image> and trailing }} $match = substr($match, strlen('{{image>'), -2); // Split path and caption by | $parts = explode('|', $match, 2); $pathPart = trim($parts[0]); $caption = isset($parts[1]) ? trim($parts[1]) : ''; // Parse optional parameters from path (e.g., /path/image.jpg?200x150&right) // Supported formats: // ?200 - width only // ?200x150 - width and height // ?left - alignment only (left, right, center) // ?200&right - width and alignment // ?200x150¢er - full options $width = null; $height = null; $align = 'right'; // default alignment if (strpos($pathPart, '?') !== false) { [$pathPart, $paramStr] = explode('?', $pathPart, 2); $paramParts = explode('&', $paramStr); foreach ($paramParts as $param) { $param = trim($param); if ($param === '') continue; // Check if it's an alignment keyword if (in_array($param, ['left', 'right', 'center'], true)) { $align = $param; // Check if it's a size specification (digits, optionally with 'x' and more digits) } elseif (preg_match('/^(\d+)(?:x(\d+))?$/', $param, $m)) { $width = (int)$m[1]; if (isset($m[2]) && $m[2] !== '') { $height = (int)$m[2]; } } } } $path = Path::cleanPath($pathPart, false); return [ 'path' => $path, 'caption' => $caption, 'align' => $align, 'width' => $width, 'height' => $height, ]; } /** @inheritdoc */ public function render($format, \Doku_Renderer $renderer, $data) { if ($data === false || !is_array($data)) { return false; } if ($format !== 'xhtml') { // For non-XHTML formats, render caption as text if available. if (!empty($data['caption'])) { $renderer->cdata('[Image: ' . $data['caption'] . ']'); } return true; } try { $pathHelper = new Path($this->getConf('paths')); // Use addTrailingSlash=false since this is a file path, not a directory $pathInfo = $pathHelper->getPathInfo($data['path'], false); } catch (\Exception $e) { $renderer->cdata('[n/a: ' . $this->getLang('error_outsidejail') . ']'); return true; } $fullPath = $pathInfo['root'] . $pathInfo['local']; // Verify the file exists and is an image if (!is_file($fullPath)) { $renderer->cdata('[n/a: File not found]'); return true; } // Check if it's an image try { [, $mime,] = mimetype($fullPath, false); } catch (\Throwable $e) { $mime = null; } if (!is_string($mime) || !str_starts_with($mime, 'image/')) { $renderer->cdata('[n/a: Not an image]'); return true; } // Build the image URL using the plugin's file.php endpoint global $ID; $imageUrl = $this->buildFileUrl($pathInfo, $data['width'], $data['height']); // Build full-size URL for linking $fullUrl = $this->buildFileUrl($pathInfo, null, null); $this->renderImageBox($renderer, $imageUrl, $fullUrl, $data); return true; } /** * Build the file.php URL for the image. * * @param array $pathInfo Path info from Path helper * @param int|null $width Optional width * @param int|null $height Optional height * @return string */ protected function buildFileUrl(array $pathInfo, ?int $width, ?int $height): string { global $ID; $params = [ 'root' => $pathInfo['root'], 'file' => $pathInfo['local'], 'id' => $ID, ]; if ($width !== null) { $params['w'] = $width; } if ($height !== null) { $params['h'] = $height; } return DOKU_BASE . 'lib/plugins/luxtools/file.php?' . http_build_query($params, '', '&'); } /** * Render the imagebox HTML. * * @param \Doku_Renderer $renderer * @param string $imageUrl URL for the displayed image * @param string $fullUrl URL for the full-size image (on click) * @param array $data Parsed data from handle() */ protected function renderImageBox(\Doku_Renderer $renderer, string $imageUrl, string $fullUrl, array $data): void { $align = $data['align'] ?? 'right'; $caption = $data['caption'] ?? ''; $width = $data['width']; $height = $data['height']; // Alignment class $alignClass = 'tright'; // default if ($align === 'left') { $alignClass = 'tleft'; } elseif ($align === 'center') { $alignClass = 'tcenter'; } // Build width style for the outer container $outerStyle = ''; if ($width !== null) { // Add a few pixels for border/padding $outerStyle = ' style="width: ' . ($width + 10) . 'px;"'; } // Build image attributes $imgAttrs = 'class="media" loading="lazy" decoding="async"'; if ($width !== null) { $imgAttrs .= ' width="' . (int)$width . '"'; } if ($height !== null) { $imgAttrs .= ' height="' . (int)$height . '"'; } $imgAttrs .= ' alt="' . hsc($caption) . '"'; /** @var \Doku_Renderer_xhtml $renderer */ $renderer->doc .= '
'; $renderer->doc .= '
'; // Image with link to full size $renderer->doc .= ''; $renderer->doc .= ''; $renderer->doc .= ''; // Caption if ($caption !== '') { // Calculate max caption width $captionStyle = ''; if ($width !== null) { $captionStyle = ' style="max-width: ' . ($width - 6) . 'px;"'; } $renderer->doc .= '
'; $renderer->doc .= hsc($caption); $renderer->doc .= '
'; } $renderer->doc .= '
'; $renderer->doc .= '
'; } }