- Classes migradas de inc/*.class.php (PluginButterfly*) para src/ com namespace GlpiPlugin\Butterfly (PSR-4 registrado pelo core; KB-033). Referências de core qualificadas (\Config, \Html, \Session, ...). - Editor de CSS custom agora usa o Monaco do core (GLPI.Monaco.createEditor + sync via formdata, mesmo padrão de custom_ui do core) com fallback para textarea se a lib não carregar. - pics/ movido para public/pics: no GLPI 11 só recursos em public/ são servidos estaticamente em /plugins/<key>/... (RequestRouterTrait). URLs do select2 de temas agora usam root_doc + encodeURIComponent. - MIN_GLPI_VERSION 11.0.0; removido bloco morto de GLPI <9.5 e css/login.base.css órfão. - config_page com glpi_tab urlencoded para a classe namespaced. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
64 lines
1.9 KiB
PHP
64 lines
1.9 KiB
PHP
<?php
|
|
/**
|
|
* Lightweight public endpoint to serve plugin pictures.
|
|
* Works without authentication and is compatible with GLPI 11 routing.
|
|
*/
|
|
|
|
// GLPI 11: o core já foi bootado pelo LegacyFileLoadController antes deste
|
|
// arquivo executar — não incluir inc/includes.php (KB-PLUGIN-028)
|
|
|
|
$rawPath = $_GET['path'] ?? '';
|
|
if ($rawPath === '') {
|
|
http_response_code(400);
|
|
die('Missing path parameter');
|
|
}
|
|
|
|
$theme = $_GET['theme'] ?? '';
|
|
|
|
// Basic sanitization while keeping subdirectories (needed for storage layout)
|
|
$path = preg_replace('/[^a-zA-Z0-9._\\-\\/]/', '', $rawPath);
|
|
$path = str_replace(['../', '..\\'], '', $path);
|
|
$theme = preg_replace('/[^a-zA-Z0-9_\\-]/', '', $theme);
|
|
|
|
if ($path === '') {
|
|
http_response_code(400);
|
|
die('Invalid path');
|
|
}
|
|
|
|
if ($theme !== '') {
|
|
$baseDir = \GlpiPlugin\Butterfly\Theme::getThemeFolder() . '/' . $theme;
|
|
} else {
|
|
$baseDir = \GlpiPlugin\Butterfly\Toolbox::getStorageBasePath();
|
|
}
|
|
|
|
$fullPath = $baseDir . '/' . ltrim($path, '/');
|
|
$realBase = is_dir($baseDir) ? realpath($baseDir) : false;
|
|
$realPath = realpath($fullPath);
|
|
|
|
if ($realBase === false || $realPath === false) {
|
|
http_response_code(404);
|
|
die('File not found');
|
|
}
|
|
|
|
$realBase = rtrim($realBase, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
|
if (strpos($realPath, $realBase) !== 0) {
|
|
http_response_code(403);
|
|
die('Access denied');
|
|
}
|
|
|
|
if (!is_file($realPath) || !is_readable($realPath)) {
|
|
http_response_code(404);
|
|
die('File not found');
|
|
}
|
|
|
|
$mimeType = mime_content_type($realPath) ?: 'application/octet-stream';
|
|
$fileSize = filesize($realPath);
|
|
$fileName = basename($realPath);
|
|
|
|
header('Content-Type: ' . $mimeType);
|
|
header('Content-Length: ' . $fileSize);
|
|
header('Content-Disposition: inline; filename="' . $fileName . '"');
|
|
header('Cache-Control: public, max-age=3600');
|
|
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($realPath)) . ' GMT');
|
|
|
|
readfile($realPath);
|