Eine Bildergalerie für einen Rich-Text-Editor in PHP

Der Dialog „Insert Gallery“ des Editors kann die Bilder anzeigen, die bereits auf Ihrem Server liegen. So wählen Benutzer ein Bild aus, statt dasselbe noch einmal hochzuladen. Dafür braucht er nur eine URL, die mit JSON antwortet. Diese Seite liefert Ihnen diesen Endpoint in reinem PHP 8: ein Skript, das einen Ordner unter uploads/ mit scandir() liest und seine Unterordner und Bilder auflistet.
Die Galerie einschalten
Setzen Sie galleryEndpoint, wenn Sie den Editor erstellen. Der Dialog fragt dann bei dieser URL jedes Mal eine Liste ab, wenn er geöffnet wird oder den Ordner wechselt.
<link rel="stylesheet" href="/richtexteditor/rte_theme_default.css">
<script src="/richtexteditor/rte.js"></script>
<script src="/richtexteditor/plugins/all_plugins.js"></script>
<script src="/rte-files.js"></script>
<script>
new RichTextEditor("#editor", {
toolbar: "full",
galleryEndpoint: "/rte/gallery",
maxUploadFileSize: 4000000 // same limit as rte/upload.php, checked before sending
});
</script>rte-files.js ist die kleine Browserdatei, die Uploads aus dem Dialog an /rte/upload sendet. Die Anleitung zum Bild-Upload zeigt die Serverseite dazu.
Die Route
GET /rte/gallery?folder=blog gibt die Liste eines Ordners zurück. Das Skript ist kurz, weil die Arbeit in zwei Hilfsfunktionen steckt:
if ($method === 'GET') {
return rte_listing(rte_resolve_folder($_GET['folder'] ?? ''));
}rte_resolve_folder() wandelt den virtuellen Ordner aus dem Query-String in ein reales Verzeichnis innerhalb des Stammordners um oder antwortet mit 400 oder 404. Die Anleitung zu Ordnern erklärt die Funktion. rte_listing() baut das JSON.
Was die Galerie erhält
Der Dialog braucht den aktuellen Ordner, seinen übergeordneten Ordner (null im Stammordner), die Unterordner und die Bilder mit ihren URLs und Größen:

Dieser Code erzeugt sie:
// The listing for one folder, in the shape the gallery dialog expects.
function rte_listing(array $folder): array
{
$folders = [];
$images = [];
$names = scandir($folder['dir']);
if ($names === false) {
throw new RteHttpError(500, 'The folder cannot be read.');
}
foreach ($names as $name) {
if ($name === '' || $name[0] === '.') {
continue; // ".", "..", and hidden files such as .htaccess
}
$path = $folder['dir'] . DIRECTORY_SEPARATOR . $name;
// Skip symlinks and Windows junctions. is_link() alone is not enough: PHP 8.2 on
// Windows returns false for a junction, so also require realpath() to be unchanged.
$real = realpath($path);
$same = DIRECTORY_SEPARATOR === '\\' ? strcasecmp($real ?: '', $path) === 0 : $real === $path;
if (is_link($path) || !$same) {
continue;
}
$segments = [...$folder['segments'], $name];
if (is_dir($path)) {
if (rte_is_valid_name($name) && !rte_is_reserved($segments)) { // only folders the routes accept back
$folders[] = ['folder' => implode('/', $segments), 'name' => $name];
}
} elseif (is_file($path) && in_array(rte_extension($name), RTE_IMAGE_EXTENSIONS, true)) {
$images[] = [
'url' => rte_url($folder['root'], $segments),
'name' => $name,
'folder' => $folder['virtual'],
'size' => (int)filesize($path),
];
}
}
$byName = fn(array $a, array $b): int => strcasecmp($a['name'], $b['name']) ?: strcmp($a['name'], $b['name']);
usort($folders, $byName);
usort($images, $byName);
$virtual = $folder['virtual'];
$parent = null;
if ($virtual !== '') {
$cut = strrpos($virtual, '/');
$parent = $cut === false ? '' : substr($virtual, 0, $cut);
}
return [
'currentFolder' => $virtual,
'currentFolderDisplay' => '/' . $virtual,
'parentFolder' => $parent,
'folders' => $folders,
'images' => $images,
];
}Einige Entscheidungen darin sollten Sie kennen:
- Namen, die mit einem Punkt beginnen, werden übersprungen, sodass
uploads/.htaccessnie erscheint. - Aufgeführt werden nur
.jpg .jpeg .png .gif .webp-Dateien. Alles andere im Ordner bleibt unsichtbar. - Symlinks und Windows-Junctions werden übersprungen. Unter Windows gab
is_link()in PHP 8.2 in unserem Test für eine Junctionfalsezurück, deshalb prüft der Code zusätzlich, dassrealpath()den Pfad unverändert lässt. - Jeder Teil einer Bild-URL läuft durch
rawurlencode(), sodassmy photo.jpgals/uploads/products/my%20photo.jpgaufgeführt wird. - Ordner und Bilder werden mit
strcasecmp()ohne Rücksicht auf Groß- und Kleinschreibung nach Namen sortiert.
Formate, die der Dialog anbietet und der Server akzeptiert
Der ausgelieferte Galerie-Dialog nennt „JPG, JPEG, JFIF, PNG, GIF, WEBP, BMP, AVIF“ als akzeptierte Formate, und seine Dateiauswahl bietet diese an. Dieser Server akzeptiert fünf davon: .jpg .jpeg .png .gif .webp. Wählt jemand eine .bmp-Datei, sendet der Dialog sie, der Server antwortet mit 400 {"error":"Only .jpg, .jpeg, .png, .gif and .webp images can be uploaded."}, und der Dialog zeigt „Upload failed for photo.bmp.“ an. Der Grund des Servers steht im Body der Antwort, den Sie in den Entwicklertools des Browsers sehen können.
Bei der Größe ist es anders: Mit maxUploadFileSize: 4000000 lehnt der Dialog eine große Datei ab, bevor er sie sendet. Eine Datei mit 4.5 MB ergab „big.jpg is 4.3 MB. The maximum allowed size is 3.8 MB.“ (der Dialog rechnet in binären Megabytes).
Die Bilder ausliefern
Die Liste verweist auf /uploads/..., also müssen diese URLs die Dateien zurückgeben. Im Beispielprojekt liefert router.php sie für den eingebauten Server von PHP aus. Er sendet nur Bild-Dateiendungen, mit festem Content-Type und X-Content-Type-Options: nosniff, und führt nie etwas aus diesem Ordner aus. Unter Apache oder nginx liefert der Webserver sie direkt aus. Behalten Sie dessen Regel bei, die das Ausführen verhindert, wie in der Anleitung zu sicheren Uploads gezeigt.
Testen
Starten Sie das Beispielprojekt zu dieser Anleitung mit php -d upload_max_filesize=5M -d display_errors=0 -d log_errors=1 -S localhost:5201 router.php und fragen Sie dann eine Liste ab:
curl "http://localhost:5201/rte/gallery?folder=team"
{"currentFolder":"team","currentFolderDisplay":"/team","parentFolder":"","folders":[],"images":[]}
curl "http://localhost:5201/rte/gallery?folder=blog/2027"
{"error":"Folder not found."}Der zweite Request erhält HTTP 404. Öffnen Sie http://localhost:5201/ und klicken Sie auf Insert Gallery: Der Dialog zeigt die Ordner und Bilder des Stammordners, und ein Klick auf einen Ordner öffnet ihn.
Dieselbe Anleitung für andere Plattformen
JavaScript · Next.js · ASP.NET Core · Razor Pages · Blazor · MVC 5 · Web Forms