Compare commits
3 Commits
5b0528a914
...
f1a32120d6
| Author | SHA1 | Date | |
|---|---|---|---|
| f1a32120d6 | |||
| 97fa1de999 | |||
| 7341fe7733 |
+55
-23
@@ -5,10 +5,25 @@ if (!isset($_SESSION["user"])) {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
include_once 'php/controller/showArticle-controller.php';
|
include_once 'php/controller/showArticle-controller.php';
|
||||||
|
|
||||||
|
// Aktuelle Blockliste ermitteln:
|
||||||
|
$rawContent = '[]';
|
||||||
|
if (isset($_SESSION['old_content']) && !empty($_SESSION['old_content'])) {
|
||||||
|
$rawContent = $_SESSION['old_content'];
|
||||||
|
unset($_SESSION['old_content']);
|
||||||
|
} elseif (isset($content) && !empty($content)) {
|
||||||
|
$rawContent = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
$blocks = json_decode($rawContent, true);
|
||||||
|
if (!is_array($blocks)) {
|
||||||
|
$blocks = [];
|
||||||
|
}
|
||||||
|
$blocks = array_values($blocks);
|
||||||
?>
|
?>
|
||||||
<!--
|
<!--
|
||||||
Seite: Beitrag erstellen
|
Seite: Beitrag bearbeiten
|
||||||
Inhalt: Formular für die Erstellung eines neuen Beitrags
|
Inhalt: Formular für die Bearbeitung eines Beitrags
|
||||||
-->
|
-->
|
||||||
<form method="post" action="php/controller/updateArticle-controller.php?id=<?php if(isset($id) && !empty($id)){echo htmlspecialchars($id);}else{$_SESSION["message"] = "missing_id";} ?>" id="editor-form" enctype="multipart/form-data" class="article-editor-scope.editor-container article-editor-scope editor-container">
|
<form method="post" action="php/controller/updateArticle-controller.php?id=<?php if(isset($id) && !empty($id)){echo htmlspecialchars($id);}else{$_SESSION["message"] = "missing_id";} ?>" id="editor-form" enctype="multipart/form-data" class="article-editor-scope.editor-container article-editor-scope editor-container">
|
||||||
|
|
||||||
@@ -27,32 +42,49 @@ include_once 'php/controller/showArticle-controller.php';
|
|||||||
?>"
|
?>"
|
||||||
placeholder="Titel hier eingeben" required>
|
placeholder="Titel hier eingeben" required>
|
||||||
|
|
||||||
<!-- Hier werden die dynamischen divs via JavaScript eingefügt -->
|
<!--
|
||||||
<div id="block-container"></div>
|
Content-Blöcke: werden serverseitig als echte, benannte Formularfelder gerendert
|
||||||
|
(blocks[i][type]...). Dadurch funktioniert das Hinzufügen/Entfernen von Blöcken und
|
||||||
|
der Bild-Upload auch ganz ohne JavaScript über einen normalen Formular-Submit.
|
||||||
|
Ist JavaScript aktiv, fängt js/editor.js diese Submits ab und erledigt dieselbe
|
||||||
|
Änderung lokal im DOM, ohne den Server zu belasten.
|
||||||
|
-->
|
||||||
|
<div id="block-container">
|
||||||
|
<?php foreach ($blocks as $i => $block): ?>
|
||||||
|
<?php
|
||||||
|
$blockType = $block['type'] ?? '';
|
||||||
|
if ($blockType !== 'text' && $blockType !== 'image') {
|
||||||
|
continue; // unbekannter/kaputter Block wird übersprungen
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="editor-block article-editor-scope" data-index="<?php echo (int)$i; ?>">
|
||||||
|
<input type="hidden" name="blocks[<?php echo (int)$i; ?>][type]" value="<?php echo htmlspecialchars($blockType); ?>">
|
||||||
|
|
||||||
|
<?php if ($blockType === 'text'): ?>
|
||||||
|
<textarea name="blocks[<?php echo (int)$i; ?>][text]"
|
||||||
|
placeholder="Schreibe deinen Textblock..."><?php echo htmlspecialchars($block['value'] ?? ''); ?></textarea>
|
||||||
|
<?php else: /* image */ ?>
|
||||||
|
<?php if (!empty($block['value'])): ?>
|
||||||
|
<img src="<?php echo htmlspecialchars($block['value']); ?>"
|
||||||
|
class="block-image-preview"
|
||||||
|
style="max-width:200px;display:block;margin-top:10px;">
|
||||||
|
<input type="hidden" name="blocks[<?php echo (int)$i; ?>][existing_image]" value="<?php echo htmlspecialchars($block['value']); ?>">
|
||||||
|
<?php endif; ?>
|
||||||
|
<input type="file" name="blocks[<?php echo (int)$i; ?>][image]" accept="image/*">
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<button type="submit" name="editor_action" value="delete_block:<?php echo (int)$i; ?>" class="delete-block-btn">✕</button>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Plus-Button und das Pop-up-Menü -->
|
|
||||||
<div id="add-block-control" class="article-editor-scope add-block-control">
|
<div id="add-block-control" class="article-editor-scope add-block-control">
|
||||||
<button type="button" id="plus-button" class="article-editor-scope plus-button">+</button>
|
<button type="button" id="plus-button" class="article-editor-scope plus-button">+</button>
|
||||||
<div id="block-popup" class="article-editor-scope block-popup hidden">
|
<div id="block-popup" class="article-editor-scope block-popup">
|
||||||
<button type="button" data-type="text">Textblock</button>
|
<button type="submit" name="editor_action" value="add_text" data-type="text">Textblock</button>
|
||||||
<button type="button" data-type="image">Bild einfügen</button>
|
<button type="submit" name="editor_action" value="add_image" data-type="image">Bild einfügen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Unsichtbares Textfeld, das die JSON-Daten hält und an den Controller postet -->
|
|
||||||
<textarea id="content" name="content" style="display:none;"><?php
|
|
||||||
if (isset($_SESSION['old_content']) && !empty($_SESSION['old_content'])){
|
|
||||||
echo htmlspecialchars($_SESSION['old_content']);
|
|
||||||
unset($_SESSION['old_content']);
|
|
||||||
}elseif (isset($content) && !empty($content)){
|
|
||||||
echo htmlspecialchars($content);
|
|
||||||
} else {
|
|
||||||
echo '[]';
|
|
||||||
}
|
|
||||||
?></textarea>
|
|
||||||
|
|
||||||
<!-- unsichtbares Input, um die zu löschenden Bilder zu übergeben-->
|
|
||||||
<input type="hidden" id="deleted-images" name="deleted_images" value="[]">
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Seitenleiste -->
|
<!-- Seitenleiste -->
|
||||||
|
|||||||
+85
-124
@@ -1,172 +1,133 @@
|
|||||||
console.log("Die JavaScript-Datei wurde erfolgreich geladen!");
|
//console.log("editor.js wurde erfolgreich geladen!");
|
||||||
|
|
||||||
function initEditor() {
|
function initEditor() {
|
||||||
const form = document.getElementById("editor-form");
|
const form = document.getElementById("editor-form");
|
||||||
|
|
||||||
if (!form) {
|
if (!form) {
|
||||||
console.error("Skript abgebrochen: Formular nicht gefunden!");
|
console.error("editor.js abgebrochen: Formular nicht gefunden!");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
console.log("Formular gefunden und Editor initialisiert:", form);
|
console.log("Formular gefunden und editor.js initialisiert:", form);
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = document.getElementById("block-container");
|
const container = document.getElementById("block-container");
|
||||||
const plusButton = document.getElementById("plus-button");
|
const plusButton = document.getElementById("plus-button");
|
||||||
const popup = document.getElementById("block-popup");
|
const popup = document.getElementById("block-popup");
|
||||||
const hiddenContentInput = document.getElementById("content");
|
|
||||||
|
|
||||||
const initialImages = [];
|
// Fortlaufender Zähler für eindeutige Block-Indizes. Wird nie wiederverwendet
|
||||||
|
// (auch nicht nach dem Löschen eines Blocks), damit sich neue und übrig
|
||||||
|
// gebliebene Blöcke beim finalen Submit nie einen Namen teilen.
|
||||||
|
let blockIndex = container.querySelectorAll(".editor-block").length;
|
||||||
|
|
||||||
|
// Ohne JS sind Textblock-/Bild-Button im Pop-up immer sichtbar und ganz normale
|
||||||
|
// Submit-Buttons. Erst mit JS blenden wir das Pop-up standardmäßig aus und
|
||||||
|
// steuern die Sichtbarkeit über den Plus-Button.
|
||||||
|
popup.classList.add("hidden");
|
||||||
|
|
||||||
// Pop-up umschalten bei Klick auf das Plus
|
|
||||||
plusButton.addEventListener("click", () => {
|
plusButton.addEventListener("click", () => {
|
||||||
popup.classList.toggle("hidden");
|
popup.classList.toggle("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Klick auf eine Block-Option im Pop-up
|
// Klick auf "Textblock" / "Bild einfügen": lokal im DOM anlegen statt zum
|
||||||
popup.querySelectorAll("button").forEach(btn => {
|
// Server zu submitten (entlastet den Server, kein Page-Reload nötig).
|
||||||
btn.addEventListener("click", function() {
|
popup.querySelectorAll('[name="editor_action"]').forEach(btn => {
|
||||||
|
btn.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
const type = this.getAttribute("data-type");
|
const type = this.getAttribute("data-type");
|
||||||
addBlockElement(type, "");
|
addBlockElement(type, "");
|
||||||
popup.classList.add("hidden");
|
popup.classList.add("hidden");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Erstellt ein visuelles HTML-Element im Editor
|
// Bereits vom Server gerenderte Blöcke (z.B. beim Bearbeiten eines bestehenden
|
||||||
function addBlockElement(type, value = "") {
|
// Artikels oder nach einem Validierungsfehler) ebenfalls mit JS-Verhalten ausstatten.
|
||||||
const blockDiv = document.createElement("div");
|
container.querySelectorAll(".editor-block").forEach(blockDiv => {
|
||||||
blockDiv.classList.add("editor-block");
|
bindDeleteButton(blockDiv);
|
||||||
blockDiv.setAttribute("data-type", type);
|
bindImageInput(blockDiv);
|
||||||
|
|
||||||
// Wenn es ein existierendes Server-Bild beim Laden ist, Pfad im globalen Array sichern
|
|
||||||
if (type === "image" && value && typeof value === 'string' && value.startsWith('uploads/')) {
|
|
||||||
initialImages.push(value);
|
|
||||||
blockDiv.setAttribute("data-value", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Löschen-Button
|
|
||||||
const deleteBtn = document.createElement("button");
|
|
||||||
deleteBtn.type = "button";
|
|
||||||
deleteBtn.innerHTML = "✕";
|
|
||||||
deleteBtn.classList.add("delete-block-btn");
|
|
||||||
deleteBtn.addEventListener("click", () => {
|
|
||||||
// ANPASSUNG 2B: Logik hier komplett geleert. Das '✕' entfernt den Block jetzt nur noch sicher aus dem HTML.
|
|
||||||
blockDiv.remove();
|
|
||||||
});
|
});
|
||||||
blockDiv.appendChild(deleteBtn);
|
|
||||||
|
// Erstellt einen neuen Block inkl. echter, benannter Formularfelder
|
||||||
|
// (blocks[i][type], blocks[i][text] bzw. blocks[i][image]). Diese Felder werden
|
||||||
|
// beim finalen Submit ganz normal vom Browser als multipart/form-data verschickt –
|
||||||
|
// es ist kein manuelles Zusammenbauen von JSON beim Absenden mehr nötig.
|
||||||
|
function addBlockElement(type, value = "") {
|
||||||
|
const index = blockIndex++;
|
||||||
|
|
||||||
|
const blockDiv = document.createElement("div");
|
||||||
|
blockDiv.classList.add("editor-block", "article-editor-scope");
|
||||||
|
blockDiv.setAttribute("data-index", String(index));
|
||||||
|
|
||||||
|
const typeInput = document.createElement("input");
|
||||||
|
typeInput.type = "hidden";
|
||||||
|
typeInput.name = `blocks[${index}][type]`;
|
||||||
|
typeInput.value = type;
|
||||||
|
blockDiv.appendChild(typeInput);
|
||||||
|
|
||||||
if (type === "text") {
|
if (type === "text") {
|
||||||
const textarea = document.createElement("textarea");
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.name = `blocks[${index}][text]`;
|
||||||
textarea.placeholder = "Schreibe deinen Textblock...";
|
textarea.placeholder = "Schreibe deinen Textblock...";
|
||||||
textarea.value = value;
|
textarea.value = value;
|
||||||
blockDiv.appendChild(textarea);
|
blockDiv.appendChild(textarea);
|
||||||
} else if (type === "image") {
|
} else if (type === "image") {
|
||||||
const fileInput = document.createElement("input");
|
const fileInput = document.createElement("input");
|
||||||
fileInput.type = "file";
|
fileInput.type = "file";
|
||||||
|
fileInput.name = `blocks[${index}][image]`;
|
||||||
fileInput.accept = "image/*";
|
fileInput.accept = "image/*";
|
||||||
|
blockDiv.appendChild(fileInput);
|
||||||
const imgPreview = document.createElement("img");
|
|
||||||
imgPreview.style.maxWidth = "200px";
|
|
||||||
imgPreview.style.display = "block";
|
|
||||||
imgPreview.style.marginTop = "10px";
|
|
||||||
|
|
||||||
if (value && typeof value === 'string') {
|
|
||||||
if (value.startsWith('uploads/') || value.startsWith('data:image/')) {
|
|
||||||
imgPreview.src = value;
|
|
||||||
blockDiv.setAttribute("data-value", value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteBtn = document.createElement("button");
|
||||||
|
deleteBtn.type = "submit";
|
||||||
|
deleteBtn.name = "editor_action";
|
||||||
|
deleteBtn.value = `delete_block:${index}`;
|
||||||
|
deleteBtn.classList.add("delete-block-btn");
|
||||||
|
deleteBtn.innerHTML = "✕";
|
||||||
|
blockDiv.appendChild(deleteBtn);
|
||||||
|
|
||||||
|
container.appendChild(blockDiv);
|
||||||
|
|
||||||
|
bindDeleteButton(blockDiv);
|
||||||
|
bindImageInput(blockDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Löschen-Button eines Blocks lokal abfangen: entfernt den Block nur aus dem DOM,
|
||||||
|
// statt das Formular zum Server zu senden.
|
||||||
|
function bindDeleteButton(blockDiv) {
|
||||||
|
const btn = blockDiv.querySelector(".delete-block-btn");
|
||||||
|
if (!btn) return;
|
||||||
|
btn.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
blockDiv.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zeigt bei Auswahl einer Bilddatei sofort eine Vorschau an. Rein optisch –
|
||||||
|
// der eigentliche Datei-Upload läuft nativ über das <input type="file">.
|
||||||
|
function bindImageInput(blockDiv) {
|
||||||
|
const fileInput = blockDiv.querySelector('input[type="file"]');
|
||||||
|
if (!fileInput) return;
|
||||||
|
|
||||||
|
let imgPreview = blockDiv.querySelector(".block-image-preview");
|
||||||
|
if (!imgPreview) {
|
||||||
|
imgPreview = document.createElement("img");
|
||||||
|
imgPreview.classList.add("block-image-preview");
|
||||||
|
imgPreview.style.maxWidth = "200px";
|
||||||
|
imgPreview.style.display = "none";
|
||||||
|
imgPreview.style.marginTop = "10px";
|
||||||
|
blockDiv.insertBefore(imgPreview, fileInput.nextSibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInput.addEventListener("change", function () {
|
fileInput.addEventListener("change", function () {
|
||||||
if (this.files && this.files[0]) {
|
if (this.files && this.files[0]) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = function(e) {
|
reader.onload = (e) => {
|
||||||
imgPreview.src = e.target.result;
|
imgPreview.src = e.target.result;
|
||||||
blockDiv.setAttribute("data-value", e.target.result);
|
imgPreview.style.display = "block";
|
||||||
}
|
};
|
||||||
reader.readAsDataURL(this.files[0]);
|
reader.readAsDataURL(this.files[0]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
blockDiv.appendChild(fileInput);
|
|
||||||
blockDiv.appendChild(imgPreview);
|
|
||||||
}
|
|
||||||
|
|
||||||
container.appendChild(blockDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
// beim Abschicken verbleibende Blöcke auslesen UND gelöschte Bilder ermitteln
|
|
||||||
form.addEventListener("submit", function(e) {
|
|
||||||
const blocks = [];
|
|
||||||
const currentImages = [];
|
|
||||||
|
|
||||||
// alle aktuell im Formular verbliebenen Blöcke scannen
|
|
||||||
container.querySelectorAll(".editor-block").forEach(blockDiv => {
|
|
||||||
const type = blockDiv.getAttribute("data-type");
|
|
||||||
let value = "";
|
|
||||||
|
|
||||||
if (type === "text") {
|
|
||||||
value = blockDiv.querySelector("textarea").value;
|
|
||||||
} else if (type === "image") {
|
|
||||||
|
|
||||||
const imgTag = blockDiv.querySelector("img");
|
|
||||||
if (imgTag) {
|
|
||||||
const srcValue = imgTag.getAttribute("src") || "";
|
|
||||||
// Wenn es ein neues Bild ist, nutzen wir das data-value (Base64)
|
|
||||||
if (srcValue.startsWith('data:image/')) {
|
|
||||||
value = blockDiv.getAttribute("data-value") || "";
|
|
||||||
} else {
|
|
||||||
value = srcValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pfade sammeln, die der Nutzer NICHT gelöscht hat (für den Abgleich)
|
|
||||||
if (value && value.startsWith('uploads/')) {
|
|
||||||
currentImages.push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks.push({ type: type, value: value });
|
|
||||||
});
|
|
||||||
|
|
||||||
// das reguläre unsichtbare Content-Feld befüllen
|
|
||||||
hiddenContentInput.value = JSON.stringify(blocks);
|
|
||||||
|
|
||||||
// Differenz berechnen: Welche Bilder aus 'initialImages' fehlen in 'currentImages' ?
|
|
||||||
const deletedImages = initialImages.filter(img => !currentImages.includes(img));
|
|
||||||
|
|
||||||
// das 'deleted_images'-Feld dynamisch befüllen und an den Controller senden
|
|
||||||
let deletedInput = document.getElementById("deleted-images");
|
|
||||||
if (!deletedInput) {
|
|
||||||
deletedInput = document.createElement("input");
|
|
||||||
deletedInput.type = "hidden";
|
|
||||||
deletedInput.id = "deleted-images";
|
|
||||||
deletedInput.name = "deleted_images";
|
|
||||||
form.appendChild(deletedInput);
|
|
||||||
}
|
|
||||||
deletedInput.value = JSON.stringify(deletedImages);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Existierende Blöcke laden (stellt alte Daten aus der Session wieder her)
|
|
||||||
try {
|
|
||||||
const initialBlocks = JSON.parse(hiddenContentInput.value.trim());
|
|
||||||
if (Array.isArray(initialBlocks)) {
|
|
||||||
initialBlocks.forEach(b => {
|
|
||||||
if (b.type === "image" && b.value && typeof b.value === 'string' && !b.value.startsWith('data:image/')) {
|
|
||||||
let cleanPath = b.value.trim().replace(/\\\//g, '/'); // Verwandelt \/ in /
|
|
||||||
|
|
||||||
initialImages.push(cleanPath);
|
|
||||||
addBlockElement(b.type, cleanPath);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
addBlockElement(b.type, b.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log("Erfolgreich registrierte Start-Bilder:", initialImages);
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
if (hiddenContentInput.value.trim() !== "") {
|
|
||||||
addBlockElement("text", hiddenContentInput.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,73 @@ if (!isset($_SESSION["user_email"])) {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut die Blockliste aus den POST-Daten (blocks[i][type], blocks[i][text],
|
||||||
|
* blocks[i][existing_image]) und ggf. hochgeladenen Dateien (blocks[i][image])
|
||||||
|
* zusammen. Läuft bei JEDEM Submit (Zwischen-Schritt "Block hinzufügen/löschen"
|
||||||
|
* UND finales Speichern), damit neu ausgewählte Bilder in jedem Fall persistiert
|
||||||
|
* werden, bevor PHP die temporäre Upload-Datei nach Request-Ende verwirft.
|
||||||
|
*
|
||||||
|
* @param array $postBlocks $_POST['blocks'] ?? []
|
||||||
|
* @param array $fileBlocks $_FILES['blocks'] ?? []
|
||||||
|
* @param string $uploadDir absoluter Pfad zum uploads-Verzeichnis (mit trailing slash)
|
||||||
|
* @return array Liste von ['type' => 'text'|'image', 'value' => string]
|
||||||
|
*/
|
||||||
|
function rebuildBlocksFromPost(array $postBlocks, array $fileBlocks, string $uploadDir): array {
|
||||||
|
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
|
|
||||||
|
$keys = array_keys($postBlocks);
|
||||||
|
if (isset($fileBlocks['name']) && is_array($fileBlocks['name'])) {
|
||||||
|
$keys = array_unique(array_merge($keys, array_keys($fileBlocks['name'])));
|
||||||
|
}
|
||||||
|
sort($keys, SORT_NUMERIC);
|
||||||
|
|
||||||
|
$blocks = [];
|
||||||
|
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$type = $postBlocks[$key]['type'] ?? null;
|
||||||
|
|
||||||
|
if ($type === 'text') {
|
||||||
|
$blocks[] = [
|
||||||
|
'type' => 'text',
|
||||||
|
'value' => $postBlocks[$key]['text'] ?? '',
|
||||||
|
];
|
||||||
|
} elseif ($type === 'image') {
|
||||||
|
// Vorbelegung: bereits vorhandenes Server-Bild (falls Datei nicht ersetzt wird)
|
||||||
|
$value = $postBlocks[$key]['existing_image'] ?? '';
|
||||||
|
|
||||||
|
$hasUpload = isset($fileBlocks['error'][$key]['image'])
|
||||||
|
&& $fileBlocks['error'][$key]['image'] === UPLOAD_ERR_OK;
|
||||||
|
|
||||||
|
if ($hasUpload) {
|
||||||
|
$tmpName = $fileBlocks['tmp_name'][$key]['image'];
|
||||||
|
$originalName = $fileBlocks['name'][$key]['image'];
|
||||||
|
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||||
|
if (!in_array($extension, $allowedExtensions, true)) {
|
||||||
|
$extension = 'jpg';
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = 'img_' . uniqid() . '.' . $extension;
|
||||||
|
$destination = $uploadDir . $fileName;
|
||||||
|
|
||||||
|
if (move_uploaded_file($tmpName, $destination)) {
|
||||||
|
$value = 'uploads/' . $fileName;
|
||||||
|
}
|
||||||
|
// Bei Fehler: alter Wert (falls vorhanden) bleibt erhalten, Block wird nicht verworfen
|
||||||
|
}
|
||||||
|
|
||||||
|
$blocks[] = [
|
||||||
|
'type' => 'image',
|
||||||
|
'value' => $value,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// unbekannter/fehlender type -> Block wird ignoriert
|
||||||
|
}
|
||||||
|
|
||||||
|
return $blocks;
|
||||||
|
}
|
||||||
|
|
||||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
$_SESSION["old_title"] = $_POST["title"] ?? '';
|
|
||||||
$_SESSION["old_content"] = $_POST["content"] ?? '';
|
|
||||||
$_SESSION["old_category"] = $_POST["category"] ?? '';
|
|
||||||
$_SESSION["old_tags"] = $_POST["tags"] ?? '';
|
|
||||||
|
|
||||||
if (isset($_GET["id"]) && !empty($_GET["id"])) {
|
if (isset($_GET["id"]) && !empty($_GET["id"])) {
|
||||||
$id = $_GET["id"];
|
$id = $_GET["id"];
|
||||||
@@ -41,13 +103,51 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isset($_POST["title"]) ||!isset($_POST["content"]) || !isset($_POST["category"])){
|
$uploadDir = __DIR__ . '/../../uploads/';
|
||||||
|
if (!file_exists($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formularzustand (Titel/Tags/Kategorie/Blöcke) immer sichern, damit er nach einem
|
||||||
|
// Redirect (PRG-Pattern oder Validierungsfehler) wieder angezeigt werden kann.
|
||||||
|
$_SESSION["old_title"] = $_POST["title"] ?? '';
|
||||||
|
$_SESSION["old_tags"] = $_POST["tags"] ?? '';
|
||||||
|
$_SESSION["old_category"] = $_POST["category"] ?? '';
|
||||||
|
|
||||||
|
$blocks = rebuildBlocksFromPost($_POST['blocks'] ?? [], $_FILES['blocks'] ?? [], $uploadDir);
|
||||||
|
$_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Zwischenspeichern
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
if (isset($_POST['editor_action']) && $_POST['editor_action'] !== '') {
|
||||||
|
$action = $_POST['editor_action'];
|
||||||
|
|
||||||
|
if ($action === 'add_text') {
|
||||||
|
$blocks[] = ['type' => 'text', 'value' => ''];
|
||||||
|
} elseif ($action === 'add_image') {
|
||||||
|
$blocks[] = ['type' => 'image', 'value' => ''];
|
||||||
|
} elseif (str_starts_with($action, 'delete_block:')) {
|
||||||
|
$deleteIndex = (int) substr($action, strlen('delete_block:'));
|
||||||
|
unset($blocks[$deleteIndex]);
|
||||||
|
$blocks = array_values($blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||||
|
header("location: ../../content/updateArticle.php?id=$id");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Echtes Speichern
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
if (!isset($_POST["title"]) || !isset($_POST["category"])) {
|
||||||
$_SESSION["message"] = "missing_parameters";
|
$_SESSION["message"] = "missing_parameters";
|
||||||
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
||||||
exit();
|
exit();
|
||||||
} else {
|
} else {
|
||||||
$title = $_POST["title"];
|
$title = $_POST["title"];
|
||||||
$content = $_POST["content"];
|
$content = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
||||||
$author = $_SESSION["user_email"];
|
$author = $_SESSION["user_email"];
|
||||||
$category = $_POST["category"];
|
$category = $_POST["category"];
|
||||||
$tags = $_POST['tags'] ?? '';
|
$tags = $_POST['tags'] ?? '';
|
||||||
@@ -88,84 +188,44 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
|||||||
$cleanedTags = implode(',', $cleanedTags);
|
$cleanedTags = implode(',', $cleanedTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------- Base64-Bilder speichern ---------------------------------------------
|
// ----------------- Verwaiste Bilder aufräumen -----------------
|
||||||
$blocks = json_decode($content, true);
|
// Bilder, die im alten (gespeicherten) Content vorkamen, im neuen aber nicht
|
||||||
$uploadDir = __DIR__ . '/../../uploads/';
|
// mehr referenziert werden, wurden vom Nutzer entfernt oder ersetzt -> löschen.
|
||||||
|
// TODO: Bilder, die innerhalb derselben Bearbeitungs-Sitzung neu hochgeladen und noch vor dem finalen Speichern wieder entfernt wurden, werden hierüber nicht erfasst (sie tauchten nie im alten Content auf) und bleiben als Datei liegen. Für eine vollständige Bereinigung würde sich ein
|
||||||
if (!file_exists($uploadDir)) {
|
$oldBlocks = json_decode($article->getContent(), true);
|
||||||
mkdir($uploadDir, 0755, true);
|
$oldImagePaths = [];
|
||||||
}
|
if (is_array($oldBlocks)) {
|
||||||
|
foreach ($oldBlocks as $oldBlock) {
|
||||||
// ----------------- Gelöschte Bilder über die JS-Löschliste entfernen ----------------- TODO: Gelöschte Bilder über die JS-Löschliste entfernen
|
if (($oldBlock['type'] ?? '') === 'image'
|
||||||
/*if (isset($_POST['deleted_images'])) {
|
&& !empty($oldBlock['value'])
|
||||||
$deletedImages = json_decode($_POST['deleted_images'], true);
|
&& is_string($oldBlock['value'])
|
||||||
|
&& str_starts_with($oldBlock['value'], 'uploads/')) {
|
||||||
// Wir ermitteln den physisch echten, absoluten Pfad zum uploads-Ordner auf der Festplatte
|
$oldImagePaths[] = $oldBlock['value'];
|
||||||
$uploadDir = realpath(__DIR__ . '/../../uploads') . DIRECTORY_SEPARATOR;
|
|
||||||
|
|
||||||
if (is_array($deletedImages)) {
|
|
||||||
foreach ($deletedImages as $imagePath) {
|
|
||||||
// Nur den reinen Dateinamen heraustrennen (z.B. img_65a123.jpg)
|
|
||||||
$filename = basename($imagePath);
|
|
||||||
$fullDeletePath = $uploadDir . $filename;
|
|
||||||
|
|
||||||
// Debugging & Löschen:
|
|
||||||
if (file_exists($fullDeletePath)) {
|
|
||||||
// Versuchen zu löschen. Wenn es fehlschlägt, Fehlermeldung erzwingen
|
|
||||||
if (!@unlink($fullDeletePath)) {
|
|
||||||
$error = error_get_last();
|
|
||||||
die("Datei existiert, aber PHP darf sie nicht löschen! Grund: " . $error['message']);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Wenn PHP die Datei an diesem Pfad nicht findet, brechen wir zum Debuggen ab
|
|
||||||
// die("PHP findet die Datei nicht unter dem Pfad: " . $fullDeletePath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}*/
|
|
||||||
|
|
||||||
// ----------------------- NEU hinzugefügte Base64-Bilder: --------------------------
|
$newImagePaths = [];
|
||||||
if (is_array($blocks)) {
|
foreach ($blocks as $block) {
|
||||||
foreach ($blocks as &$block) {
|
if (($block['type'] ?? '') === 'image' && !empty($block['value'])) {
|
||||||
// Prüfen, ob der Block ein Bild ist und ein NEUES Bild (Base64-Format) enthält
|
$newImagePaths[] = $block['value'];
|
||||||
if (isset($block['type']) && isset($block['value']) && $block['type'] === 'image' && is_string($block['value'])) {
|
|
||||||
|
|
||||||
if (str_starts_with($block['value'], 'data:image/')) {
|
|
||||||
$parts = explode(',', $block['value']);
|
|
||||||
if (count($parts) >= 2) {
|
|
||||||
$metadata = $parts[0];
|
|
||||||
$base64Data = $parts[1];
|
|
||||||
|
|
||||||
preg_match('/data:image\/(?<extension>.*?);/', $metadata, $matches);
|
|
||||||
$extension = $matches['extension'] ?? 'jpg';
|
|
||||||
if ($extension === 'jpeg') { $extension = 'jpg'; }
|
|
||||||
|
|
||||||
$fileName = 'img_' . uniqid() . '.' . $extension;
|
|
||||||
$filePath = $uploadDir . $fileName;
|
|
||||||
|
|
||||||
if (file_put_contents($filePath, base64_decode($base64Data)) !== false) {
|
|
||||||
$block['value'] = 'uploads/' . $fileName;
|
|
||||||
} else {
|
|
||||||
$_SESSION["message"] = "image_upload_error";
|
|
||||||
header("location: ../../index.php?pfad=updateArticle&id=$id");
|
|
||||||
exit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unset($block);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aktualisiertes Array wieder in JSON konvertieren
|
$orphanedImages = array_diff($oldImagePaths, $newImagePaths);
|
||||||
$finalContent = json_encode($blocks, JSON_UNESCAPED_UNICODE);
|
foreach ($orphanedImages as $orphanedImage) {
|
||||||
|
$absolutePath = __DIR__ . '/../../' . $orphanedImage;
|
||||||
|
if (is_file($absolutePath)) {
|
||||||
|
@unlink($absolutePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
|
// ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
|
||||||
try {
|
try {
|
||||||
$articleManager = ArticleManager::getInstance();
|
$articleManager = ArticleManager::getInstance();
|
||||||
$article = $articleManager->getArticle($id);
|
$article = $articleManager->getArticle($id);
|
||||||
$article->setTitle($title);
|
$article->setTitle($title);
|
||||||
$article->setContent($finalContent);
|
$article->setContent($content);
|
||||||
$article->setCategory($category);
|
$article->setCategory($category);
|
||||||
$article->setTags($cleanedTags);
|
$article->setTags($cleanedTags);
|
||||||
$articleManager->updateArticle($id, $article, $author);
|
$articleManager->updateArticle($id, $article, $author);
|
||||||
|
|||||||
Reference in New Issue
Block a user