Compare commits

...

48 Commits

Author SHA1 Message Date
niklas.ortmann a785d862d8 Update DatabaseArticleManager.php 2026-07-19 16:10:08 +02:00
niklas.ortmann a728a8a556 Update showArticle-controller.php 2026-07-19 16:08:55 +02:00
niklas.ortmann ae34afda15 Update article-validator.php 2026-07-19 16:08:06 +02:00
niklas.ortmann 59e0b9f111 Update like-controller.php 2026-07-19 16:05:52 +02:00
niklas.ortmann cd50840b19 Update add-comment.php 2026-07-19 16:05:05 +02:00
niklas.ortmann 3bce65a1a5 Update deleteComment-controller.php 2026-07-19 16:01:42 +02:00
niklas.ortmann 50b29c96d6 Update updateComment-controller.php 2026-07-19 16:01:06 +02:00
niklas.ortmann d5fc5f3065 Update updateArticle-controller.php 2026-07-19 16:00:07 +02:00
niklas.ortmann 0427d52a58 Update index.php 2026-07-19 15:58:32 +02:00
niklas.ortmann cc804757ff Update createArticle-controller.php 2026-07-19 15:57:36 +02:00
niklas.ortmann b55a6592f0 Update logout-controller.php 2026-07-19 15:55:54 +02:00
niklas.ortmann e12a98cc98 Update deleteArticle-controller.php 2026-07-19 15:55:09 +02:00
niklas.ortmann 320141855a Update deleteAccount-controller.php 2026-07-19 15:54:15 +02:00
niklas.ortmann 12801344f1 Update profile-controller.php 2026-07-19 15:42:28 +02:00
niklas.ortmann 83387de103 Update createArticle.php 2026-07-19 15:41:37 +02:00
niklas.ortmann dfb8e1dc89 Update updateArticle.php 2026-07-19 15:41:35 +02:00
niklas.ortmann bdbc38a339 Update profile.php 2026-07-19 15:40:16 +02:00
niklas.ortmann e6356525b9 Update comments.js 2026-07-19 15:34:50 +02:00
niklas.ortmann ca74690952 Update showArticle.php 2026-07-19 15:33:13 +02:00
niklas.ortmann 75f9fdbeb8 Update navbar.php 2026-07-19 15:31:02 +02:00
niklas.ortmann 81a27135d5 Update navbar.css 2026-07-19 15:28:08 +02:00
niklas.ortmann a58576637a Update navbar.css 2026-07-19 15:26:53 +02:00
niklas.ortmann 997957a5d3 Update logout-controller.php 2026-07-19 15:25:15 +02:00
niklas.ortmann ec794db706 logout-fix 2026-07-19 15:23:46 +02:00
niklas.ortmann ba749bca68 debugging 2026-07-19 15:21:16 +02:00
niklas.ortmann 12a6af55b0 debugging 2026-07-19 15:19:47 +02:00
niklas.ortmann 400f018104 debugging 2026-07-19 15:17:32 +02:00
niklas.ortmann 80f4b623f9 debugging 2026-07-19 15:16:22 +02:00
niklas.ortmann 6546d264b0 debugging 2026-07-19 15:15:30 +02:00
niklas.ortmann e7e120e5b8 debugging 2026-07-19 15:15:03 +02:00
niklas.ortmann ec66889a1f debugging 2026-07-19 15:13:44 +02:00
niklas.ortmann f1703a476e Update index.php 2026-07-19 15:09:22 +02:00
niklas.ortmann bac296b58a Update logout-controller.php 2026-07-19 15:07:09 +02:00
niklas.ortmann bfb2d2b0d8 navbar logout mit post 2026-07-19 14:59:45 +02:00
niklas.ortmann ad9a00fd49 csrf.php 2026-07-19 14:57:52 +02:00
niklas.ortmann 42faab17e8 Create csrf.php 2026-07-19 14:56:26 +02:00
niklas.ortmann 1da4842847 Update dataSources.local.xml 2026-07-19 14:56:25 +02:00
niklas.ortmann afd90086d8 Update profile.php 2026-07-19 14:20:43 +02:00
niklas.ortmann a7a54f877c Merge pull request 'Beitragseditor funktioniert mit und ohne JS (+Bilder löschen)' (#57) from BeitragseditorOhneJS into dev
Reviewed-on: #57
2026-07-19 13:45:33 +02:00
NOrtmann1 9b9f71d21c Update DatabaseInitializer.php 2026-07-19 13:44:27 +02:00
NOrtmann1 4b84f0ab09 Update updateArticle-controller.php 2026-07-19 13:30:39 +02:00
NOrtmann1 8c8b148bf4 Update updateArticle-controller.php 2026-07-19 13:15:23 +02:00
NOrtmann1 6bf335ce46 Update createArticle.php 2026-07-19 13:15:20 +02:00
NOrtmann1 144a1015f1 Create article-block-helper.php 2026-07-19 13:15:17 +02:00
NOrtmann1 c0a94b2eee Update createArticle-controller.php 2026-07-19 13:15:14 +02:00
NOrtmann1 f1a32120d6 Update editor.js 2026-07-19 12:48:41 +02:00
NOrtmann1 97fa1de999 Update updateArticle-controller.php 2026-07-19 12:48:38 +02:00
NOrtmann1 7341fe7733 Update updateArticle.php 2026-07-19 12:48:33 +02:00
27 changed files with 837 additions and 446 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="dataSourceStorageLocal" created-in="IU-261.24374.151"> <component name="dataSourceStorageLocal" created-in="IU-261.25134.95">
<data-source name="articles" uuid="315cb5c9-2b0f-435b-b602-59823b160908"> <data-source name="articles" uuid="315cb5c9-2b0f-435b-b602-59823b160908">
<database-info product="SQLite" version="3.51.1" jdbc-version="4.2" driver-name="SQLite JDBC" driver-version="3.51.1.0" dbms="SQLITE" exact-version="3.51.1" exact-driver-version="3.51"> <database-info product="SQLite" version="3.51.1" jdbc-version="4.2" driver-name="SQLite JDBC" driver-version="3.51.1.0" dbms="SQLITE" exact-version="3.51.1" exact-driver-version="3.51">
<identifier-quote-string>&quot;</identifier-quote-string> <identifier-quote-string>&quot;</identifier-quote-string>
+57 -17
View File
@@ -4,12 +4,30 @@ if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login"); header("Location: index.php?pfad=login");
exit(); exit();
} }
// -------------------------------------------------------------------------
// Aktuelle Blockliste ermitteln: Session-Fallback (nach Validierungsfehler
// oder Zwischen-Submit "add_block"/"delete_block") oder leeres Array (Start).
// -------------------------------------------------------------------------
$rawContent = '[]';
if (isset($_SESSION['old_content']) && !empty($_SESSION['old_content'])) {
$rawContent = $_SESSION['old_content'];
unset($_SESSION['old_content']);
}
$blocks = json_decode($rawContent, true);
if (!is_array($blocks)) {
$blocks = [];
}
$blocks = array_values($blocks); // sequentielle Indizes 0..n-1 sicherstellen
?> ?>
<!-- <!--
Seite: Beitrag erstellen Seite: Beitrag erstellen
Inhalt: Formular für die Erstellung eines neuen Beitrags Inhalt: Formular für die Erstellung eines neuen Beitrags
--> -->
<form method="post" action="php/controller/createArticle-controller.php" id="editor-form" class="article-editor-scope.editor-container article-editor-scope editor-container"> <form method="post" action="php/controller/createArticle-controller.php" id="editor-form" enctype="multipart/form-data" class="article-editor-scope.editor-container article-editor-scope editor-container">
<?php csrf_field(); ?>
<main class="editor-main"> <main class="editor-main">
<?php include_once "includes/alertMessages.php"?> <?php include_once "includes/alertMessages.php"?>
@@ -18,27 +36,49 @@ if (!isset($_SESSION["user"])) {
value="<?php echo htmlspecialchars($_SESSION['old_title'] ?? ''); unset($_SESSION['old_title']); ?>" value="<?php echo htmlspecialchars($_SESSION['old_title'] ?? ''); unset($_SESSION['old_title']); ?>"
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], blocks[i][text] bzw. blocks[i][image]). 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']);
} else {
echo '[]'; // Standardmäßig ein leeres JSON-Array
}
?></textarea>
</main> </main>
<!-- Seitenleiste --> <!-- Seitenleiste -->
+5 -1
View File
@@ -18,6 +18,8 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
<form method="post" action="index.php?pfad=profile"> <form method="post" action="index.php?pfad=profile">
<?php csrf_field(); ?>
<label class="input-label">Vorname</label> <label class="input-label">Vorname</label>
<input type="text" <input type="text"
name="vorname" name="vorname"
@@ -82,9 +84,10 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
<br> <br>
<form action="php/controller/deleteAccount-controller.php" method="POST"> <form action="php/controller/deleteAccount-controller.php" method="POST">
<?php csrf_field(); ?>
<button type="submit" <button type="submit"
class="button" class="button"
onclick="return confirm('Möchtest du deinen Account wirklich unwiderruflich löschen?');"> onclick="return confirm('Möchtest du deinen Account und alle deine Beiträge wirklich unwiderruflich löschen?');">
Account löschen Account löschen
</button> </button>
</form> </form>
@@ -155,6 +158,7 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
</a> </a>
<form action="php/controller/deleteArticle-controller.php" method="POST"> <form action="php/controller/deleteArticle-controller.php" method="POST">
<?php csrf_field(); ?>
<input type="hidden" <input type="hidden"
name="id" name="id"
value="<?php echo htmlspecialchars($userArticle->getID()); ?>"> value="<?php echo htmlspecialchars($userArticle->getID()); ?>">
+17 -2
View File
@@ -56,9 +56,14 @@ if ($replyAuthor === null) {
<span>❤️ <span class="like-count"><?php echo $articleObj->getLikeCount(); ?></span></span> <span>❤️ <span class="like-count"><?php echo $articleObj->getLikeCount(); ?></span></span>
<?php if (isset($_SESSION["user_email"])): ?> <?php if (isset($_SESSION["user_email"])): ?>
<a href="php/controller/like-controller.php?id=<?php echo $articleObj->getId(); ?>" class="like-toggle-btn"> <form method="post"
action="php/controller/like-controller.php?id=<?php echo $articleObj->getId(); ?>"
class="like-toggle-form">
<?php csrf_field(); ?>
<button type="submit" class="like-toggle-btn">
<?php echo $articleObj->hasLiked($_SESSION["user_email"]) ? '👎 Gefällt mir nicht mehr' : '👍 Gefällt mir'; ?> <?php echo $articleObj->hasLiked($_SESSION["user_email"]) ? '👎 Gefällt mir nicht mehr' : '👍 Gefällt mir'; ?>
</a> </button>
</form>
<?php else: ?> <?php else: ?>
<span class="login-hint">(Anmelden zum Liken)</span> <span class="login-hint">(Anmelden zum Liken)</span>
<?php endif; ?> <?php endif; ?>
@@ -202,6 +207,8 @@ if ($replyAuthor === null) {
action="index.php?pfad=updateComment" action="index.php?pfad=updateComment"
class="edit-comment-form"> class="edit-comment-form">
<?php csrf_field(); ?>
<input type="hidden" <input type="hidden"
name="comment_id" name="comment_id"
value="<?php echo htmlspecialchars( value="<?php echo htmlspecialchars(
@@ -239,6 +246,8 @@ if ($replyAuthor === null) {
action="index.php?pfad=deleteComment" action="index.php?pfad=deleteComment"
class="delete-comment-form"> class="delete-comment-form">
<?php csrf_field(); ?>
<input type="hidden" <input type="hidden"
name="comment_id" name="comment_id"
value="<?php echo htmlspecialchars( value="<?php echo htmlspecialchars(
@@ -334,6 +343,8 @@ if ($replyAuthor === null) {
action="index.php?pfad=updateComment" action="index.php?pfad=updateComment"
class="edit-comment-form"> class="edit-comment-form">
<?php csrf_field(); ?>
<input type="hidden" <input type="hidden"
name="comment_id" name="comment_id"
value="<?php echo htmlspecialchars( value="<?php echo htmlspecialchars(
@@ -371,6 +382,8 @@ if ($replyAuthor === null) {
action="index.php?pfad=deleteComment" action="index.php?pfad=deleteComment"
class="delete-comment-form"> class="delete-comment-form">
<?php csrf_field(); ?>
<input type="hidden" <input type="hidden"
name="comment_id" name="comment_id"
value="<?php echo htmlspecialchars( value="<?php echo htmlspecialchars(
@@ -412,6 +425,8 @@ if ($replyAuthor === null) {
method="post" method="post"
action="php/ajax/add-comment.php"> action="php/ajax/add-comment.php">
<?php csrf_field(); ?>
<input type="hidden" <input type="hidden"
name="article_id" name="article_id"
value="<?php echo htmlspecialchars( value="<?php echo htmlspecialchars(
+57 -23
View File
@@ -5,13 +5,30 @@ 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">
<?php csrf_field(); ?>
<main class="editor-main"> <main class="editor-main">
<?php include_once "includes/alertMessages.php"?> <?php include_once "includes/alertMessages.php"?>
@@ -27,32 +44,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 -->
+26
View File
@@ -285,6 +285,32 @@ CSS für die navbar
border-bottom: 1px solid #333d43; border-bottom: 1px solid #333d43;
} }
.nav__logout-form {
display: contents;
margin: 0;
}
.nav__logout-form .nav__button {
width: 100%;
height: 100%;
}
.nav__mobile-logout-button {
color: #fff;
text-decoration: none;
font-size: 1.2rem;
font-weight: 600;
display: block;
width: 100%;
text-align: left;
padding: 0.5rem 1rem;
border: none;
border-bottom: 1px solid #333d43;
background: none;
cursor: pointer;
font-family: inherit;
}
.nav__mobile-submenu { .nav__mobile-submenu {
display: block; display: block;
list-style: none; list-style: none;
+6
View File
@@ -89,6 +89,12 @@
Es ist ein Datenbankfehler aufgetreten. Bitte versuche es erneut. Es ist ein Datenbankfehler aufgetreten. Bitte versuche es erneut.
</p> </p>
<?php endif; ?> <?php endif; ?>
<?php if (isset($_SESSION["message"]) && $_SESSION["message"] == "invalid_csrf_token"): ?>
<p class="alert-message is-error">
Deine Sitzung ist abgelaufen oder die Anfrage konnte nicht überprüft werden.
Bitte lade die Seite neu und versuche es erneut.
</p>
<?php endif; ?>
<?php <?php
unset($_SESSION["message"]); unset($_SESSION["message"]);
?> ?>
+69
View File
@@ -0,0 +1,69 @@
<?php
/**
* 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/Veröffentlichen), damit neu ausgewählte Bilder in jedem
* Fall persistiert werden, bevor PHP die temporäre Upload-Datei nach
* Request-Ende verwirft.
*
* Wird sowohl vom createArticle- als auch vom updateArticle-Controller genutzt.
*
* @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;
}
+88
View File
@@ -0,0 +1,88 @@
<?php
/**
* CSRF-Schutz nach dem Synchronizer-Token-Pattern.
*
* Pro Session wird ein einziges, zufälliges Token erzeugt,
* das in jedem Formular als verstecktes Feld mitgeschickt und bei jeder
* zustandsändernden Anfrage serverseitig mit dem Session-Token verglichen
* wird.
*
* @author Niklas Ortmann
*/
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
/**
* Gibt das aktuelle CSRF-Token der Session zurück.
*
* Existiert noch kein Token, wird eines erzeugt und in der Session
* gespeichert.
*
* @return string Das CSRF-Token
*/
function csrf_token(): string
{
if (empty($_SESSION["csrf_token"]) || !is_string($_SESSION["csrf_token"])) {
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
}
return $_SESSION["csrf_token"];
}
/**
* Gibt ein verstecktes Formularfeld mit dem aktuellen CSRF-Token aus.
*
* Wird in jedem Formular benötigt, das eine zustandsändernde
* Aktion auslöst.
*
* @return void
*/
function csrf_field(): void
{
echo '<input type="hidden" name="csrf_token" value="'
. htmlspecialchars(csrf_token())
. '">';
}
/**
* Prüft, ob das per POST gesendete CSRF-Token zum Session-Token passt.
*
* Der Vergleich erfolgt zeitkonstant über hash_equals(), um
* Timing-Angriffe auf den Vergleich selbst auszuschließen.
*
* @return bool true, wenn das Token gültig ist
*/
function csrf_verify(): bool
{
$sentToken = $_POST["csrf_token"] ?? "";
$sessionToken = $_SESSION["csrf_token"] ?? "";
if (!is_string($sentToken) || $sentToken === "" || $sessionToken === "") {
return false;
}
return hash_equals($sessionToken, $sentToken);
}
/**
* Bricht die Anfrage ab und leitet mit einer Fehlermeldung um,
* wenn das mitgesendete CSRF-Token ungültig oder nicht vorhanden ist.
*
* Muss am Anfang jeder zustandsändernden POST-Aktion aufgerufen werden,
* bevor irgendeine Änderung an Daten vorgenommen wird.
*
* @param string $redirectTo Ziel-URL, zu der bei ungültigem Token
* weitergeleitet wird
* @return void
*/
function csrf_require_valid(string $redirectTo = "index.php"): void
{
if (!csrf_verify()) {
http_response_code(403);
$_SESSION["message"] = "invalid_csrf_token";
header("Location: " . $redirectTo);
exit();
}
}
+1
View File
@@ -2,6 +2,7 @@
if (session_status() === PHP_SESSION_NONE) { if (session_status() === PHP_SESSION_NONE) {
session_start(); session_start();
} }
include_once "includes/csrf.php";
include_once "php/controller/index-controller.php"; include_once "php/controller/index-controller.php";
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
+24
View File
@@ -109,6 +109,12 @@ document.addEventListener("DOMContentLoaded", function () {
action="index.php?pfad=updateComment" action="index.php?pfad=updateComment"
class="edit-comment-form" class="edit-comment-form"
> >
<input
type="hidden"
name="csrf_token"
value="${escapeHtml(formData.get("csrf_token"))}"
>
<input <input
type="hidden" type="hidden"
name="comment_id" name="comment_id"
@@ -137,6 +143,12 @@ document.addEventListener("DOMContentLoaded", function () {
action="index.php?pfad=deleteComment" action="index.php?pfad=deleteComment"
class="delete-comment-form" class="delete-comment-form"
> >
<input
type="hidden"
name="csrf_token"
value="${escapeHtml(formData.get("csrf_token"))}"
>
<input <input
type="hidden" type="hidden"
name="comment_id" name="comment_id"
@@ -187,6 +199,12 @@ document.addEventListener("DOMContentLoaded", function () {
action="index.php?pfad=updateComment" action="index.php?pfad=updateComment"
class="edit-comment-form" class="edit-comment-form"
> >
<input
type="hidden"
name="csrf_token"
value="${escapeHtml(formData.get("csrf_token"))}"
>
<input <input
type="hidden" type="hidden"
name="comment_id" name="comment_id"
@@ -215,6 +233,12 @@ document.addEventListener("DOMContentLoaded", function () {
action="index.php?pfad=deleteComment" action="index.php?pfad=deleteComment"
class="delete-comment-form" class="delete-comment-form"
> >
<input
type="hidden"
name="csrf_token"
value="${escapeHtml(formData.get("csrf_token"))}"
>
<input <input
type="hidden" type="hidden"
name="comment_id" name="comment_id"
+84 -123
View File
@@ -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"); 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.maxWidth = "200px";
imgPreview.style.display = "block"; imgPreview.style.display = "none";
imgPreview.style.marginTop = "10px"; imgPreview.style.marginTop = "10px";
blockDiv.insertBefore(imgPreview, fileInput.nextSibling);
if (value && typeof value === 'string') {
if (value.startsWith('uploads/') || value.startsWith('data:image/')) {
imgPreview.src = value;
blockDiv.setAttribute("data-value", value);
}
} }
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);
}
} }
} }
+49
View File
@@ -4,6 +4,8 @@ if (session_status() === PHP_SESSION_NONE) {
} }
require_once "../model/CommentManager.php"; require_once "../model/CommentManager.php";
require_once "../model/ArticleManager.php";
require_once "../../includes/csrf.php";
/** /**
* Prüft, ob die Anfrage durch JavaScript per AJAX gesendet wurde. * Prüft, ob die Anfrage durch JavaScript per AJAX gesendet wurde.
@@ -96,6 +98,17 @@ if (!isset($_SESSION["user_email"])) {
); );
} }
/*
* CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird.
*/
if (!csrf_verify()) {
sendCommentResponse(
false,
"Deine Sitzung ist abgelaufen. Bitte lade die Seite neu und versuche es erneut.",
$articleId !== false ? $articleId : null
);
}
/* /*
* Weitere Formulardaten einlesen. * Weitere Formulardaten einlesen.
*/ */
@@ -128,6 +141,18 @@ if ($articleId === false || $articleId === null) {
); );
} }
/*
* Der Beitrag muss tatsächlich existieren.
*/
$existingArticle = ArticleManager::getInstance()->getArticle($articleId);
if ($existingArticle === null) {
sendCommentResponse(
false,
"Der zugehörige Beitrag wurde nicht gefunden.",
null
);
}
if ($content === "") { if ($content === "") {
sendCommentResponse( sendCommentResponse(
false, false,
@@ -152,6 +177,30 @@ if (
); );
} }
/*
* Falls eine Eltern-ID angegeben wurde, muss dieser Kommentar
* tatsächlich existieren und zum selben Beitrag gehören.
*/
if ($parentCommentId !== null) {
$existingComments = CommentManager::getInstance()->getCommentsByArticle($articleId);
$parentExists = false;
foreach ($existingComments as $existingComment) {
if ($existingComment->getId() === $parentCommentId) {
$parentExists = true;
break;
}
}
if (!$parentExists) {
sendCommentResponse(
false,
"Der ausgewählte Kommentar wurde nicht gefunden.",
$articleId
);
}
}
try { try {
$commentManager = CommentManager::getInstance(); $commentManager = CommentManager::getInstance();
+52 -58
View File
@@ -5,24 +5,68 @@ if (session_status() === PHP_SESSION_NONE) {
require_once '../model/LocalArticleManager.php'; require_once '../model/LocalArticleManager.php';
require_once '../model/ArticleManager.php'; require_once '../model/ArticleManager.php';
require_once '../validator/article-validator.php'; require_once '../validator/article-validator.php';
require_once '../../includes/article-block-helper.php';
require_once '../../includes/csrf.php';
if (!isset($_SESSION["user"])) { if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login"); header("Location: index.php?pfad=login");
exit(); exit();
} }
if ($_SERVER["REQUEST_METHOD"] === "POST") {
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
if (!csrf_verify()) {
$_SESSION["message"] = "invalid_csrf_token";
header("location: ../../index.php?pfad=createArticle");
exit();
}
$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_title"] = $_POST["title"] ?? '';
$_SESSION["old_content"] = $_POST["content"] ?? '';
$_SESSION["old_category"] = $_POST["category"] ?? ''; $_SESSION["old_category"] = $_POST["category"] ?? '';
$_SESSION["old_tags"] = $_POST["tags"] ?? ''; $_SESSION["old_tags"] = $_POST["tags"] ?? '';
if(!isset($_POST["title"]) ||!isset($_POST["content"]) || !isset($_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: ../../index.php?pfad=createArticle");
exit();
}
// ---------------------------------------------------------------------
// Echtes Veröffentlichen
// ---------------------------------------------------------------------
if (!isset($_POST["title"]) || !isset($_POST["category"])) {
$_SESSION["message"] = "missing_parameters"; $_SESSION["message"] = "missing_parameters";
header("location: ../../index.php?pfad=createArticle"); header("location: ../../index.php?pfad=createArticle");
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'] ?? '';
@@ -63,67 +107,17 @@ if (!isset($_SESSION["user"])) {
$cleanedTags = implode(',', $cleanedTags); $cleanedTags = implode(',', $cleanedTags);
} }
// ----------------- Base64-Bilder verarbeiten und auf Server speichern -----------------
$blocks = json_decode($content, true);
$uploadDir = __DIR__ . '/../../uploads/';
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
if (is_array($blocks)) {
foreach ($blocks as &$block) {
// sicherstellen, dass 'type' und 'value' existieren:
if (isset($block['type']) && isset($block['value']) && $block['type'] === 'image' && str_starts_with($block['value'], 'data:image/')) {
// Base64-String zerlegen
$parts = explode(',', $block['value']);
// falls der String korrupt ist und kein Komma hat
if (count($parts) < 2) {
continue;
}
$metadata = $parts[0];
$base64Data = $parts[1];
// Dateiendung ermitteln
preg_match('/data:image\/(?<extension>.*?);/', $metadata, $matches);
$extension = $matches['extension'] ?? 'jpg';
if ($extension === 'jpeg') {
$extension = 'jpg';
}
// Eindeutigen Dateinamen generieren
$fileName = 'img_' . uniqid() . '.' . $extension;
$filePath = $uploadDir . $fileName;
// Datei im /uploads speichern:
if (file_put_contents($filePath, base64_decode($base64Data)) !== false) {
// temporären Base64-String durch den echten Pfad ersetzen
$block['value'] = 'uploads/' . $fileName;
} else {
$_SESSION["message"] = "image_upload_error";
header("location: ../../index.php?pfad=createArticle");
exit();
}
}
}
unset($block);
}
// Aktualisiertes Array wieder in JSON konvertieren
$finalContent = json_encode($blocks, JSON_UNESCAPED_UNICODE);
// ----------------- Übertragung der validierten Daten in ArticleManager: --------------------------- // ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
try { try {
$articleManager = ArticleManager::getInstance(); $articleManager = ArticleManager::getInstance();
// $content enthält bereits die finalen "uploads/..."-Pfade (kein Base64 mehr),
// da rebuildBlocksFromPost() Datei-Uploads sofort verarbeitet.
$articleManager->addArticle($title, $content, $author, $category, $cleanedTags); $articleManager->addArticle($title, $content, $author, $category, $cleanedTags);
// Formulardaten nach erfolgreichem Erstellen aus der Session löschen // Formulardaten nach erfolgreichem Erstellen aus der Session löschen
unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]); unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
} catch (\Throwable $e){ } catch (\Throwable $e) {
$_SESSION["message"] = "internal_error"; $_SESSION["message"] = "internal_error";
header("location: ../../index.php?pfad=createArticle"); header("location: ../../index.php?pfad=createArticle");
exit(); exit();
@@ -134,5 +128,5 @@ if (!isset($_SESSION["user"])) {
header("location: ../../index.php"); header("location: ../../index.php");
exit(); exit();
} }
} }
?> ?>
@@ -5,12 +5,24 @@ if (session_status() === PHP_SESSION_NONE) {
require_once __DIR__ . "/../model/UserManager.php"; require_once __DIR__ . "/../model/UserManager.php";
require_once __DIR__ . "/../model/ArticleManager.php"; require_once __DIR__ . "/../model/ArticleManager.php";
require_once __DIR__ . "/../../includes/csrf.php";
if (!isset($_SESSION["user"])) { if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login"); header("Location: index.php?pfad=login");
exit(); exit();
} }
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("Location: ../../index.php?pfad=profile");
exit();
}
if (!csrf_verify()) {
$_SESSION["message"] = "invalid_csrf_token";
header("Location: ../../index.php?pfad=profile");
exit();
}
/* /*
Deregistrierung Deregistrierung
Funktion: Entfernt User aus der Datenbank und beendet die Session Funktion: Entfernt User aus der Datenbank und beendet die Session
+12 -3
View File
@@ -4,6 +4,7 @@ if (session_status() === PHP_SESSION_NONE) {
} }
require_once __DIR__ . "/../model/ArticleManager.php"; require_once __DIR__ . "/../model/ArticleManager.php";
require_once __DIR__ . "/../../includes/csrf.php";
if (!isset($_SESSION["user"])) { if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login"); header("Location: index.php?pfad=login");
@@ -12,6 +13,13 @@ if (!isset($_SESSION["user"])) {
if ($_SERVER["REQUEST_METHOD"] === "POST") { if ($_SERVER["REQUEST_METHOD"] === "POST") {
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
if (!csrf_verify()) {
$_SESSION["message"] = "invalid_csrf_token";
header("location: ../../index.php?pfad=profile");
exit();
}
if (isset($_SESSION["user_email"])) { if (isset($_SESSION["user_email"])) {
$user = $_SESSION["user_email"]; $user = $_SESSION["user_email"];
} else { } else {
@@ -22,9 +30,10 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
exit(); exit();
} }
if (isset($_POST["id"]) && !empty($_POST["id"])) { // Die Beitrags-ID muss eine gültige numerische ID sein.
$id = $_POST["id"]; $id = filter_input(INPUT_POST, "id", FILTER_VALIDATE_INT);
} else {
if ($id === false || $id === null) {
$_SESSION["message"] = "missing_id"; $_SESSION["message"] = "missing_id";
header("location: ../../index.php?pfad=profile"); header("location: ../../index.php?pfad=profile");
exit(); exit();
@@ -5,6 +5,7 @@ if (session_status() === PHP_SESSION_NONE) {
} }
require_once __DIR__ . "/../model/CommentManager.php"; require_once __DIR__ . "/../model/CommentManager.php";
require_once __DIR__ . "/../../includes/csrf.php";
/* /*
* Kommentare dürfen nur über ein POST-Formular gelöscht werden. * Kommentare dürfen nur über ein POST-Formular gelöscht werden.
@@ -44,6 +45,23 @@ if (!isset($_SESSION["user_email"])) {
exit(); exit();
} }
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
if (!csrf_verify()) {
$_SESSION["comment_message"] = "Deine Sitzung ist abgelaufen. Bitte lade die Seite neu.";
$_SESSION["comment_message_type"] = "error";
if ($articleId !== false && $articleId !== null) {
header(
"Location: index.php?pfad=showArticle&id="
. urlencode((string) $articleId)
. "#comments"
);
} else {
header("Location: index.php");
}
exit();
}
/* /*
* Kommentar-ID und Beitrags-ID müssen gültige Zahlen sein. * Kommentar-ID und Beitrags-ID müssen gültige Zahlen sein.
*/ */
+13
View File
@@ -5,6 +5,12 @@ if (session_status() === PHP_SESSION_NONE) {
require_once __DIR__ . '/../model/Article.php'; require_once __DIR__ . '/../model/Article.php';
require_once __DIR__ . '/../model/ArticleManager.php'; require_once __DIR__ . '/../model/ArticleManager.php';
require_once __DIR__ . '/../../includes/csrf.php';
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("Location: ../../index.php");
exit();
}
// 2. Prüfen, ob eine gültige Artikel-ID übergeben wurde // 2. Prüfen, ob eine gültige Artikel-ID übergeben wurde
if (isset($_GET["id"]) && !empty($_GET["id"])) { if (isset($_GET["id"]) && !empty($_GET["id"])) {
@@ -17,6 +23,13 @@ if (isset($_GET["id"]) && !empty($_GET["id"])) {
exit(); exit();
} }
// CSRF-Token prüfen, bevor der Like-Status verändert wird
if (!csrf_verify()) {
$_SESSION["message"] = "invalid_csrf_token";
header("Location: ../../index.php?pfad=showArticle&id=" . $articleId);
exit();
}
try { try {
$articleManager = ArticleManager::getInstance(); $articleManager = ArticleManager::getInstance();
$articleManager->toggleLike($articleId, $userEmail); $articleManager->toggleLike($articleId, $userEmail);
+4
View File
@@ -1,4 +1,8 @@
<?php <?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$_SESSION = []; $_SESSION = [];
session_destroy(); session_destroy();
+8 -1
View File
@@ -5,6 +5,7 @@ require_once "php/model/Article.php";
require_once "php/model/ArticleManager.php"; require_once "php/model/ArticleManager.php";
require_once "php/model/CommentManager.php"; require_once "php/model/CommentManager.php";
require_once "php/validator/user-validator.php"; require_once "php/validator/user-validator.php";
require_once "includes/csrf.php";
$error = null; $error = null;
@@ -27,6 +28,7 @@ try {
} }
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["saveProfile"])) { if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["saveProfile"])) {
$oldEmail = $_SESSION["user_email"]; $oldEmail = $_SESSION["user_email"];
$newEmail = trim($_POST["email"] ?? ""); $newEmail = trim($_POST["email"] ?? "");
@@ -34,7 +36,12 @@ try {
$nachname = trim($_POST["nachname"] ?? ""); $nachname = trim($_POST["nachname"] ?? "");
$password = $_POST["password"] ?? ""; $password = $_POST["password"] ?? "";
if (!userEmailValidator($newEmail)) { if (!csrf_verify()) {
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
$error = "Deine Sitzung ist abgelaufen. Bitte lade die Seite neu und versuche es erneut.";
$_GET["edit"] = "1";
} elseif (!userEmailValidator($newEmail)) {
$error = "Bitte gib eine gültige E-Mail-Adresse ein."; $error = "Bitte gib eine gültige E-Mail-Adresse ein.";
$_GET["edit"] = "1"; $_GET["edit"] = "1";
+7 -3
View File
@@ -7,10 +7,14 @@ require_once 'php/model/Article.php';
require_once 'php/model/ArticleManager.php'; require_once 'php/model/ArticleManager.php';
require_once 'php/model/UserManager.php'; require_once 'php/model/UserManager.php';
require_once 'php/model/CommentManager.php'; require_once 'php/model/CommentManager.php';
require_once 'php/validator/article-validator.php';
if (isset($_GET["id"]) && !empty($_GET["id"])){ // Die übergebene ID muss eine gültige, positive Zahl sein, bevor sie
// weiterverwendet wird. Vorher wurde jeder nicht-leere Wert akzeptiert.
$id = isset($_GET["id"]) ? articleIdValidator($_GET["id"]) : false;
if ($id !== false) {
try { try {
$id = $_GET["id"];
$articleManager = ArticleManager::getInstance(); $articleManager = ArticleManager::getInstance();
$article = $articleManager->getArticle($id); $article = $articleManager->getArticle($id);
if($article != null){ if($article != null){
@@ -38,7 +42,7 @@ if (isset($_GET["id"]) && !empty($_GET["id"])){
} }
$commentManager = CommentManager::getInstance(); $commentManager = CommentManager::getInstance();
$comments = $commentManager->getCommentsByArticle($_GET["id"]); $comments = $commentManager->getCommentsByArticle($id); // NEU: validierte ID statt rohem $_GET["id"]
foreach ($comments as $comment) { foreach ($comments as $comment) {
if ($comment->isReply()) { if ($comment->isReply()) {
+97 -78
View File
@@ -7,21 +7,27 @@ require_once '../model/LocalArticleManager.php';
require_once '../model/ArticleManager.php'; require_once '../model/ArticleManager.php';
require_once '../model/Article.php'; require_once '../model/Article.php';
require_once '../validator/article-validator.php'; require_once '../validator/article-validator.php';
require_once '../../includes/article-block-helper.php';
require_once '../../includes/csrf.php'; // NEU: CSRF-Schutz
if (!isset($_SESSION["user_email"])) { if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login"); header("Location: index.php?pfad=login");
exit(); exit();
} }
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"])) { // CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
$id = $_GET["id"]; if (!csrf_verify()) {
} else { $_SESSION["message"] = "invalid_csrf_token";
header("location: ../../index.php?pfad=updateArticle");
exit();
}
// Die Beitrags-ID muss eine gültige numerische ID sein
$id = filter_input(INPUT_GET, "id", FILTER_VALIDATE_INT);
if ($id === false || $id === null) {
$_SESSION["message"] = "missing_id"; $_SESSION["message"] = "missing_id";
header("location: ../../index.php?pfad=updateArticle"); header("location: ../../index.php?pfad=updateArticle");
exit(); exit();
@@ -30,6 +36,14 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
try { try {
$articleManager = ArticleManager::getInstance(); $articleManager = ArticleManager::getInstance();
$article = $articleManager->getArticle($id); $article = $articleManager->getArticle($id);
// Existenz des Beitrags prüfen, bevor auf $article zugegriffen wird.
if ($article === null) {
$_SESSION["message"] = "missing_id";
header("location: ../../index.php?pfad=updateArticle");
exit();
}
if ($article->getAuthor() != $_SESSION["user_email"]) { if ($article->getAuthor() != $_SESSION["user_email"]) {
$_SESSION["message"] = "unauthorized_access"; $_SESSION["message"] = "unauthorized_access";
header("location: ../../index.php"); header("location: ../../index.php");
@@ -41,13 +55,53 @@ 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);
// ---------------------------------------------------------------------
// Zwischen-Schritt: Block hinzufügen oder entfernen (kein echtes Speichern).
// Wird bei aktivem JavaScript per preventDefault() abgefangen und lokal im
// DOM erledigt (js/editor.js) ohne JS läuft dieser Server-Roundtrip.
// ---------------------------------------------------------------------
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: ../../index.php?pfad=updateArticle&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,91 +142,56 @@ 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.
// Hinweis/TODO: Bilder, die innerhalb derselben Bearbeitungs-Sitzung neu
if (!file_exists($uploadDir)) { // hochgeladen und noch vor dem finalen Speichern wieder entfernt wurden,
mkdir($uploadDir, 0755, true); // 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
// regelmäßiger Cleanup-Job anbieten, der verwaiste Dateien im uploads/-Ordner
// ----------------- Gelöschte Bilder über die JS-Löschliste entfernen ----------------- TODO: Gelöschte Bilder über die JS-Löschliste entfernen // mit den in der Datenbank referenzierten Pfaden abgleicht.
/*if (isset($_POST['deleted_images'])) { $oldBlocks = json_decode($article->getContent(), true);
$deletedImages = json_decode($_POST['deleted_images'], true); $oldImagePaths = [];
if (is_array($oldBlocks)) {
// Wir ermitteln den physisch echten, absoluten Pfad zum uploads-Ordner auf der Festplatte foreach ($oldBlocks as $oldBlock) {
$uploadDir = realpath(__DIR__ . '/../../uploads') . DIRECTORY_SEPARATOR; if (($oldBlock['type'] ?? '') === 'image'
&& !empty($oldBlock['value'])
if (is_array($deletedImages)) { && is_string($oldBlock['value'])
foreach ($deletedImages as $imagePath) { && str_starts_with($oldBlock['value'], 'uploads/')) {
// Nur den reinen Dateinamen heraustrennen (z.B. img_65a123.jpg) $oldImagePaths[] = $oldBlock['value'];
$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);
unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]); unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
} catch (\Throwable $e){ } catch (\Throwable $e) {
$_SESSION["message"] = $e->getMessage(); $_SESSION["message"] = $e->getMessage();
header("location: ../../index.php?pfad=updateArticle&id=$id"); header("location: ../../index.php?pfad=updateArticle&id=$id");
exit(); exit();
@@ -5,6 +5,7 @@ if (session_status() === PHP_SESSION_NONE) {
} }
require_once "php/model/CommentManager.php"; require_once "php/model/CommentManager.php";
require_once "includes/csrf.php";
if ($_SERVER["REQUEST_METHOD"] !== "POST") { if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("Location: index.php"); header("Location: index.php");
@@ -16,6 +17,14 @@ if (!isset($_SESSION["user_email"])) {
exit(); exit();
} }
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
if (!csrf_verify()) {
$_SESSION["comment_message"] = "Deine Sitzung ist abgelaufen. Bitte lade die Seite neu.";
$_SESSION["comment_message_type"] = "error";
header("Location: index.php");
exit();
}
$commentId = filter_input( $commentId = filter_input(
INPUT_POST, INPUT_POST,
"comment_id", "comment_id",
+10 -45
View File
@@ -63,12 +63,9 @@ class DatabaseArticleManager implements ArticleManagerDAO {
VALUES (:title, :content, :author, :category, :tags);"; VALUES (:title, :content, :author, :category, :tags);";
$command = $db->prepare($sql); $command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
// Verknüpft die übergebenen Parameter exakt mit den SQL-Platzhaltern // Verknüpft die übergebenen Parameter exakt mit den SQL-Platzhaltern
$success = $command->execute([ $command->execute([
":title" => $title, ":title" => $title,
":content" => $content, ":content" => $content,
":author" => $author, ":author" => $author,
@@ -76,14 +73,14 @@ class DatabaseArticleManager implements ArticleManagerDAO {
":tags" => $tags ":tags" => $tags
]); ]);
if (!$success) {
throw new InternalServerErrorException("internal_error");
}
return intval($db->lastInsertId()); return intval($db->lastInsertId());
} catch (PDOException $e) { } catch (PDOException $e) {
throw new InternalServerErrorException($e->getMessage()); // NEU: Die rohe PDO-Fehlermeldung wird nicht mehr direkt in die
// eigene Exception übernommen (Kapselung), sondern durch eine
// generische, sprechende Meldung ersetzt - analog zu den übrigen
// Methoden dieser Klasse und zu DatabaseUserManager.
throw new InternalServerErrorException("internal_error");
} }
} }
@@ -106,11 +103,8 @@ class DatabaseArticleManager implements ArticleManagerDAO {
WHERE id = :id;"; WHERE id = :id;";
$command = $db->prepare($sql); $command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
$success = $command->execute([ $command->execute([
":id" => $id, ":id" => $id,
":title" => $article->getTitle(), ":title" => $article->getTitle(),
":content" => $article->getContent(), ":content" => $article->getContent(),
@@ -120,7 +114,7 @@ class DatabaseArticleManager implements ArticleManagerDAO {
]); ]);
// rowCount() prüft, ob eine Zeile mit dieser ID existierte und geändert werden konnte // rowCount() prüft, ob eine Zeile mit dieser ID existierte und geändert werden konnte
if (!$success || $command->rowCount() === 0) { if ($command->rowCount() === 0) {
// Falls die ID nicht existiert, prüfen wir, ob sie überhaupt da ist // Falls die ID nicht existiert, prüfen wir, ob sie überhaupt da ist
if (!$this->getArticle($id)) { if (!$this->getArticle($id)) {
throw new NotFoundException("missing_id"); throw new NotFoundException("missing_id");
@@ -148,13 +142,7 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$sql = "DELETE FROM articles WHERE id = :id;"; $sql = "DELETE FROM articles WHERE id = :id;";
$command = $db->prepare($sql); $command = $db->prepare($sql);
if (!$command) { $command->execute([":id" => $id]);
throw new InternalServerErrorException("internal_error");
}
if (!$command->execute([":id" => $id])) {
throw new InternalServerErrorException("internal_error");
}
} catch (PDOException $exc) { } catch (PDOException $exc) {
throw new InternalServerErrorException("internal_error"); throw new InternalServerErrorException("internal_error");
} }
@@ -167,10 +155,6 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$sql = "SELECT * FROM articles WHERE id = :id;"; $sql = "SELECT * FROM articles WHERE id = :id;";
$command = $db->prepare($sql); $command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
$command->execute([":id" => $id]); $command->execute([":id" => $id]);
$row = $command->fetch(PDO::FETCH_ASSOC); $row = $command->fetch(PDO::FETCH_ASSOC);
@@ -202,10 +186,6 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$sql = "SELECT * FROM articles;"; $sql = "SELECT * FROM articles;";
$command = $db->query($sql); $command = $db->query($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
$rows = $command->fetchAll(PDO::FETCH_ASSOC); $rows = $command->fetchAll(PDO::FETCH_ASSOC);
$articles = []; $articles = [];
@@ -234,10 +214,6 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$sql = "SELECT * FROM articles WHERE author = :author;"; $sql = "SELECT * FROM articles WHERE author = :author;";
$command = $db->prepare($sql); $command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
$command->execute([":author" => $author]); $command->execute([":author" => $author]);
$rows = $command->fetchAll(PDO::FETCH_ASSOC); $rows = $command->fetchAll(PDO::FETCH_ASSOC);
$filteredArticles = []; $filteredArticles = [];
@@ -270,10 +246,6 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$sql = "SELECT * FROM articles WHERE category = :category;"; $sql = "SELECT * FROM articles WHERE category = :category;";
$command = $db->prepare($sql); $command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
$command->execute([":category" => $category]); $command->execute([":category" => $category]);
$rows = $command->fetchAll(PDO::FETCH_ASSOC); $rows = $command->fetchAll(PDO::FETCH_ASSOC);
$filteredArticles = []; $filteredArticles = [];
@@ -318,21 +290,14 @@ class DatabaseArticleManager implements ArticleManagerDAO {
OR tags LIKE :keyword"; OR tags LIKE :keyword";
$command = $db->prepare($sql); $command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
// Wildcards für die Suche hinzufügen // Wildcards für die Suche hinzufügen
$searchParam = '%' . $cleankeyword . '%'; $searchParam = '%' . $cleankeyword . '%';
$success = $command->execute([ $command->execute([
":keyword" => $searchParam ":keyword" => $searchParam
]); ]);
if (!$success) {
throw new InternalServerErrorException("internal_error");
}
$rows = $command->fetchAll(PDO::FETCH_ASSOC); $rows = $command->fetchAll(PDO::FETCH_ASSOC);
$filteredArticles = []; $filteredArticles = [];
+6 -1
View File
@@ -234,9 +234,14 @@ class DatabaseInitializer {
// Bestimmt per Zufall einen Autor aus dem Pool der gültigen E-Mails // Bestimmt per Zufall einen Autor aus dem Pool der gültigen E-Mails
$randomAuthor = $availableEmails[array_rand($availableEmails)]; $randomAuthor = $availableEmails[array_rand($availableEmails)];
// Text in (blocks[i][type]/[text]/[image])-Format umwandeln:
$content = json_encode([
['type' => 'text', 'value' => $article[1]]
], JSON_UNESCAPED_UNICODE);
$articleInsertStmt->execute([ $articleInsertStmt->execute([
':title' => $article[0], ':title' => $article[0],
':content' => $article[1], ':content' => $content,
':author' => $randomAuthor, ':author' => $randomAuthor,
':category' => $article[2], ':category' => $article[2],
':tags' => $article[3] ':tags' => $article[3]
+15
View File
@@ -1,5 +1,20 @@
<?php <?php
/**
* NEU: Prüft, ob ein übergebener Wert eine gültige, positive
* Beitrags-ID ist. Wird überall dort verwendet, wo eine Artikel-ID
* aus $_GET oder $_POST entgegengenommen wird
*
* @param mixed $id
* @return int|false Die validierte ID als int, oder false bei Ungültigkeit
*/
function articleIdValidator($id)
{
$options = ["options" => ["min_range" => 1]];
return filter_var($id, FILTER_VALIDATE_INT, $options);
}
/** /**
* Prüft, ob der Titel die folgenden Bedingungen erfüllt: * Prüft, ob der Titel die folgenden Bedingungen erfüllt:
* Buchstaben von a-z; A-Z * Buchstaben von a-z; A-Z