293 lines
9.3 KiB
PHP
293 lines
9.3 KiB
PHP
<?php
|
|
|
|
use dokuwiki\Extension\SyntaxPlugin;
|
|
use dokuwiki\plugin\luxtools\Path;
|
|
use dokuwiki\plugin\luxtools\PageLinkTrait;
|
|
use dokuwiki\plugin\luxtools\ThumbnailHelper;
|
|
|
|
require_once(__DIR__ . '/../autoload.php');
|
|
|
|
/**
|
|
* luxtools Plugin: Image syntax.
|
|
*
|
|
* Renders a single image in an imagebox (similar to Wikipedia-style image boxes).
|
|
* Syntax: {{image>/path/to/image.jpg|Caption text}}
|
|
*
|
|
*/
|
|
class syntax_plugin_luxtools_image extends SyntaxPlugin
|
|
{
|
|
use PageLinkTrait;
|
|
/** @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 by | into: path, caption, options
|
|
// Format: {{image>path|caption|options}}
|
|
$parts = explode('|', $match, 3);
|
|
$pathPart = trim($parts[0]);
|
|
$caption = isset($parts[1]) ? trim($parts[1]) : '';
|
|
$optionStr = isset($parts[2]) ? trim($parts[2]) : '';
|
|
|
|
// Parse options from third part (e.g., "200x150&right")
|
|
$width = null;
|
|
$height = null;
|
|
$align = null;
|
|
|
|
if ($optionStr !== '') {
|
|
$optionParts = explode('&', $optionStr);
|
|
foreach ($optionParts as $param) {
|
|
$param = trim($param);
|
|
if ($param === '') continue;
|
|
|
|
if (in_array($param, ['left', 'right', 'center'], true)) {
|
|
$align = $param;
|
|
} elseif (preg_match('/^(\d+)(?:x(\d+))?$/', $param, $m)) {
|
|
$width = (int)$m[1];
|
|
if (isset($m[2]) && $m[2] !== '') {
|
|
$height = (int)$m[2];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$isRemote = ThumbnailHelper::isRemoteUrl($pathPart);
|
|
$path = $isRemote ? $pathPart : Path::cleanPath($pathPart, false);
|
|
|
|
return [
|
|
'path' => $path,
|
|
'is_remote' => $isRemote,
|
|
'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;
|
|
}
|
|
|
|
// Apply default settings if not explicitly specified
|
|
if ($data['width'] === null) {
|
|
$data['width'] = (int)$this->getConf('default_image_width');
|
|
if ($data['width'] <= 0) $data['width'] = 250;
|
|
}
|
|
if ($data['align'] === null) {
|
|
$data['align'] = (string)$this->getConf('default_image_align');
|
|
if (!in_array($data['align'], ['left', 'right', 'center'], true)) {
|
|
$data['align'] = 'right';
|
|
}
|
|
}
|
|
|
|
if (!empty($data['is_remote'])) {
|
|
if (empty($data['path']) || !ThumbnailHelper::isRemoteUrl($data['path'])) {
|
|
$renderer->cdata('[n/a: Invalid URL]');
|
|
return true;
|
|
}
|
|
|
|
// Remote images: link directly, no proxying or thumbnailing
|
|
$thumb = [
|
|
'url' => $data['path'],
|
|
'isFinal' => true,
|
|
'thumbUrl' => $data['path'],
|
|
];
|
|
|
|
$this->renderImageBox($renderer, $thumb, $data['path'], $data);
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
$blobsRoot = $this->resolveBlobsRoot();
|
|
if ($blobsRoot === '' && $this->isBlobsPath($data['path'] ?? '')) {
|
|
$this->renderPageNotLinked($renderer);
|
|
return true;
|
|
}
|
|
|
|
$pathHelper = $this->createPathHelperWithBlobs($blobsRoot);
|
|
// 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;
|
|
}
|
|
|
|
// Get thumbnail from helper - it handles everything
|
|
global $ID;
|
|
$placeholderId = trim((string)$this->getConf('thumb_placeholder'));
|
|
$thumb = ThumbnailHelper::getThumbnail(
|
|
$pathInfo['root'],
|
|
$pathInfo['local'],
|
|
$ID,
|
|
$data['width'] ?? 250,
|
|
$data['height'] ?? ($data['width'] ?? 250),
|
|
80,
|
|
$placeholderId !== '' ? $placeholderId : null
|
|
);
|
|
|
|
// Build full-size URL for linking
|
|
$fullUrl = $this->buildImageUrl($pathInfo, null, null, false);
|
|
|
|
$this->renderImageBox($renderer, $thumb, $fullUrl, $data);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Build the file.php URL for a local image.
|
|
*
|
|
* @param array $pathInfo Path info array from Path helper
|
|
* @param int|null $width Optional width
|
|
* @param int|null $height Optional height
|
|
* @param bool $thumbnail Whether to generate a thumbnail
|
|
* @return string
|
|
*/
|
|
protected function buildImageUrl(array $pathInfo, ?int $width, ?int $height, bool $thumbnail): string
|
|
{
|
|
global $ID;
|
|
|
|
$params = [
|
|
'root' => $pathInfo['root'],
|
|
'file' => $pathInfo['local'],
|
|
'id' => $ID,
|
|
];
|
|
|
|
if ($thumbnail && ($width !== null || $height !== null)) {
|
|
$params['thumb'] = 1;
|
|
$params['q'] = 80;
|
|
}
|
|
|
|
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 array $thumb Thumbnail info from ThumbnailHelper::getThumbnail()
|
|
* @param string $fullUrl URL for the full-size image (on click)
|
|
* @param array $data Parsed data from handle()
|
|
*/
|
|
protected function renderImageBox(\Doku_Renderer $renderer, array $thumb, 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;"';
|
|
}
|
|
|
|
// Use thumbnail metadata from helper
|
|
$dataThumbAttr = $thumb['isFinal'] ? '' : ' data-thumb-src="' . hsc($thumb['thumbUrl']) . '"';
|
|
|
|
// Build image attributes
|
|
$imgAttrs = 'class="media luxtools-thumb" 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 .= '<div class="luxtools-imagebox ' . $alignClass . '"' . $outerStyle . '>';
|
|
$renderer->doc .= '<div class="luxtools-imagebox-inner">';
|
|
|
|
// Image with link to full size
|
|
$renderer->doc .= '<a href="' . hsc($fullUrl) . '" class="media" target="_blank">';
|
|
$renderer->doc .= '<img src="' . hsc($thumb['url']) . '" ' . $imgAttrs . $dataThumbAttr . ' />';
|
|
$renderer->doc .= '</a>';;
|
|
|
|
// Caption
|
|
if ($caption !== '') {
|
|
// Calculate max caption width
|
|
$captionStyle = '';
|
|
if ($width !== null) {
|
|
$captionStyle = ' style="max-width: ' . ($width - 6) . 'px;"';
|
|
}
|
|
$renderer->doc .= '<div class="luxtools-imagebox-caption"' . $captionStyle . '>';
|
|
$renderer->doc .= hsc($caption);
|
|
$renderer->doc .= '</div>';
|
|
}
|
|
|
|
$renderer->doc .= '</div>';
|
|
$renderer->doc .= '</div>';
|
|
}
|
|
}
|