Compare commits

..

45 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
26 changed files with 625 additions and 287 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<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>
+59 -19
View File
@@ -4,12 +4,30 @@ if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login");
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
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">
<?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']); ?>"
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); ?>">
<!-- Plus-Button und das Pop-up-Menü -->
<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>
<div id="block-popup" class="article-editor-scope block-popup hidden">
<button type="button" data-type="text">Textblock</button>
<button type="button" data-type="image">Bild einfügen</button>
</div>
<?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>
<!-- 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>
<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>
<div id="block-popup" class="article-editor-scope block-popup">
<button type="submit" name="editor_action" value="add_text" data-type="text">Textblock</button>
<button type="submit" name="editor_action" value="add_image" data-type="image">Bild einfügen</button>
</div>
</div>
</main>
<!-- 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">
<?php csrf_field(); ?>
<label class="input-label">Vorname</label>
<input type="text"
name="vorname"
@@ -82,9 +84,10 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
<br>
<form action="php/controller/deleteAccount-controller.php" method="POST">
<?php csrf_field(); ?>
<button type="submit"
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
</button>
</form>
@@ -155,6 +158,7 @@ $isEditMode = (isset($_GET["edit"]) && $_GET["edit"] === "1") || !empty($error);
</a>
<form action="php/controller/deleteArticle-controller.php" method="POST">
<?php csrf_field(); ?>
<input type="hidden"
name="id"
value="<?php echo htmlspecialchars($userArticle->getID()); ?>">
+32 -17
View File
@@ -45,26 +45,31 @@ if ($replyAuthor === null) {
<!-- Metadaten & Titel -->
<div class="article-view-top-section">
<div class="category-and-likes-row">
<?php if (isset($category) && !empty($category)): ?>
<span class="article-view-category"><?php echo htmlspecialchars($category); ?></span>
<?php endif; ?>
<div class="category-and-likes-row">
<?php if (isset($category) && !empty($category)): ?>
<span class="article-view-category"><?php echo htmlspecialchars($category); ?></span>
<?php endif; ?>
<!-- Like-Anzeige und dynamischer Like-Button -->
<?php if (isset($articleObj) && $articleObj !== null): ?>
<div class="article-view-likes">
<span>❤️ <span class="like-count"><?php echo $articleObj->getLikeCount(); ?></span></span>
<!-- Like-Anzeige und dynamischer Like-Button -->
<?php if (isset($articleObj) && $articleObj !== null): ?>
<div class="article-view-likes">
<span>❤️ <span class="like-count"><?php echo $articleObj->getLikeCount(); ?></span></span>
<?php if (isset($_SESSION["user_email"])): ?>
<a href="php/controller/like-controller.php?id=<?php echo $articleObj->getId(); ?>" class="like-toggle-btn">
<?php if (isset($_SESSION["user_email"])): ?>
<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'; ?>
</a>
<?php else: ?>
<span class="login-hint">(Anmelden zum Liken)</span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</button>
</form>
<?php else: ?>
<span class="login-hint">(Anmelden zum Liken)</span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<h1 class="article-view-title">
<?php if (isset($title)) { echo htmlspecialchars($title); } ?>
@@ -202,6 +207,8 @@ if ($replyAuthor === null) {
action="index.php?pfad=updateComment"
class="edit-comment-form">
<?php csrf_field(); ?>
<input type="hidden"
name="comment_id"
value="<?php echo htmlspecialchars(
@@ -239,6 +246,8 @@ if ($replyAuthor === null) {
action="index.php?pfad=deleteComment"
class="delete-comment-form">
<?php csrf_field(); ?>
<input type="hidden"
name="comment_id"
value="<?php echo htmlspecialchars(
@@ -334,6 +343,8 @@ if ($replyAuthor === null) {
action="index.php?pfad=updateComment"
class="edit-comment-form">
<?php csrf_field(); ?>
<input type="hidden"
name="comment_id"
value="<?php echo htmlspecialchars(
@@ -371,6 +382,8 @@ if ($replyAuthor === null) {
action="index.php?pfad=deleteComment"
class="delete-comment-form">
<?php csrf_field(); ?>
<input type="hidden"
name="comment_id"
value="<?php echo htmlspecialchars(
@@ -412,6 +425,8 @@ if ($replyAuthor === null) {
method="post"
action="php/ajax/add-comment.php">
<?php csrf_field(); ?>
<input type="hidden"
name="article_id"
value="<?php echo htmlspecialchars(
+2
View File
@@ -27,6 +27,8 @@ $blocks = array_values($blocks);
-->
<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">
<?php include_once "includes/alertMessages.php"?>
+26
View File
@@ -285,6 +285,32 @@ CSS für die navbar
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 {
display: block;
list-style: none;
+6
View File
@@ -89,6 +89,12 @@
Es ist ein Datenbankfehler aufgetreten. Bitte versuche es erneut.
</p>
<?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
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) {
session_start();
}
include_once "includes/csrf.php";
include_once "php/controller/index-controller.php";
?>
<!DOCTYPE html>
+24
View File
@@ -109,6 +109,12 @@ document.addEventListener("DOMContentLoaded", function () {
action="index.php?pfad=updateComment"
class="edit-comment-form"
>
<input
type="hidden"
name="csrf_token"
value="${escapeHtml(formData.get("csrf_token"))}"
>
<input
type="hidden"
name="comment_id"
@@ -137,6 +143,12 @@ document.addEventListener("DOMContentLoaded", function () {
action="index.php?pfad=deleteComment"
class="delete-comment-form"
>
<input
type="hidden"
name="csrf_token"
value="${escapeHtml(formData.get("csrf_token"))}"
>
<input
type="hidden"
name="comment_id"
@@ -187,6 +199,12 @@ document.addEventListener("DOMContentLoaded", function () {
action="index.php?pfad=updateComment"
class="edit-comment-form"
>
<input
type="hidden"
name="csrf_token"
value="${escapeHtml(formData.get("csrf_token"))}"
>
<input
type="hidden"
name="comment_id"
@@ -215,6 +233,12 @@ document.addEventListener("DOMContentLoaded", function () {
action="index.php?pfad=deleteComment"
class="delete-comment-form"
>
<input
type="hidden"
name="csrf_token"
value="${escapeHtml(formData.get("csrf_token"))}"
>
<input
type="hidden"
name="comment_id"
+49
View File
@@ -4,6 +4,8 @@ if (session_status() === PHP_SESSION_NONE) {
}
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.
@@ -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.
*/
@@ -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 === "") {
sendCommentResponse(
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 {
$commentManager = CommentManager::getInstance();
+108 -114
View File
@@ -5,134 +5,128 @@ if (session_status() === PHP_SESSION_NONE) {
require_once '../model/LocalArticleManager.php';
require_once '../model/ArticleManager.php';
require_once '../validator/article-validator.php';
require_once '../../includes/article-block-helper.php';
require_once '../../includes/csrf.php';
if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login");
exit();
}
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($_POST["title"]) ||!isset($_POST["content"]) || !isset($_POST["category"])){
$_SESSION["message"] = "missing_parameters";
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_category"] = $_POST["category"] ?? '';
$_SESSION["old_tags"] = $_POST["tags"] ?? '';
$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";
header("location: ../../index.php?pfad=createArticle");
exit();
} else {
$title = $_POST["title"];
$content = json_encode($blocks, JSON_UNESCAPED_UNICODE);
$author = $_SESSION["user_email"];
$category = $_POST["category"];
$tags = $_POST['tags'] ?? '';
// -------------------------------- Validierung der Daten: -------------------------
if (!articleTitleValidator($title)) {
$_SESSION["message"] = "invalid_title";
header("location: ../../index.php?pfad=createArticle");
exit();
}
if (!articleContentValidator($content)) {
$_SESSION["message"] = "invalid_content";
header("location: ../../index.php?pfad=createArticle");
exit();
}
if (!articleCategoryValidator($category)) {
$_SESSION["message"] = "invalid_category";
header("location: ../../index.php?pfad=createArticle");
exit();
}
if (!articleTagValidator($tags)) {
$_SESSION["message"] = "invalid_tags";
header("location: ../../index.php?pfad=createArticle");
exit();
} else {
$title = $_POST["title"];
$content = $_POST["content"];
$author = $_SESSION["user_email"];
$category = $_POST["category"];
$tags = $_POST['tags'] ?? '';
// -------------------------------- Validierung der Daten: -------------------------
if (!articleTitleValidator($title)) {
$_SESSION["message"] = "invalid_title";
header("location: ../../index.php?pfad=createArticle");
exit();
$cleanedTags = [];
$rawTags = explode(',', $tags);
foreach ($rawTags as $rawTag) {
// Leerzeichen am Anfang/Ende des einzelnen Tags entfernen:
$tag = trim($rawTag);
$cleanedTags[] = $tag;
}
// Duplikate entfernen:
$cleanedTags = array_unique($cleanedTags);
$cleanedTags = implode(',', $cleanedTags);
}
if (!articleContentValidator($content)) {
$_SESSION["message"] = "invalid_content";
header("location: ../../index.php?pfad=createArticle");
exit();
}
// ----------------- Übertragung der validierten Daten in ArticleManager: ---------------------------
try {
$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);
if (!articleCategoryValidator($category)) {
$_SESSION["message"] = "invalid_category";
header("location: ../../index.php?pfad=createArticle");
exit();
}
// Formulardaten nach erfolgreichem Erstellen aus der Session löschen
unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
if (!articleTagValidator($tags)) {
$_SESSION["message"] = "invalid_tags";
header("location: ../../index.php?pfad=createArticle");
exit();
} else {
$cleanedTags = [];
$rawTags = explode(',', $tags);
foreach ($rawTags as $rawTag) {
// Leerzeichen am Anfang/Ende des einzelnen Tags entfernen:
$tag = trim($rawTag);
$cleanedTags[] = $tag;
}
// Duplikate entfernen:
$cleanedTags = array_unique($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: ---------------------------
try {
$articleManager = ArticleManager::getInstance();
$articleManager->addArticle($title, $content, $author, $category, $cleanedTags);
// Formulardaten nach erfolgreichem Erstellen aus der Session löschen
unset($_SESSION["old_title"], $_SESSION["old_content"], $_SESSION["old_category"], $_SESSION["old_tags"]);
} catch (\Throwable $e){
$_SESSION["message"] = "internal_error";
header("location: ../../index.php?pfad=createArticle");
exit();
}
$_SESSION["message"] = "new_article";
// Weiterleitung zur Homepage
header("location: ../../index.php");
} catch (\Throwable $e) {
$_SESSION["message"] = "internal_error";
header("location: ../../index.php?pfad=createArticle");
exit();
}
$_SESSION["message"] = "new_article";
// Weiterleitung zur Homepage
header("location: ../../index.php");
exit();
}
}
?>
@@ -5,12 +5,24 @@ if (session_status() === PHP_SESSION_NONE) {
require_once __DIR__ . "/../model/UserManager.php";
require_once __DIR__ . "/../model/ArticleManager.php";
require_once __DIR__ . "/../../includes/csrf.php";
if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login");
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
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__ . "/../../includes/csrf.php";
if (!isset($_SESSION["user"])) {
header("Location: index.php?pfad=login");
@@ -12,6 +13,13 @@ if (!isset($_SESSION["user"])) {
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"])) {
$user = $_SESSION["user_email"];
} else {
@@ -22,9 +30,10 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
exit();
}
if (isset($_POST["id"]) && !empty($_POST["id"])) {
$id = $_POST["id"];
} else {
// Die Beitrags-ID muss eine gültige numerische ID sein.
$id = filter_input(INPUT_POST, "id", FILTER_VALIDATE_INT);
if ($id === false || $id === null) {
$_SESSION["message"] = "missing_id";
header("location: ../../index.php?pfad=profile");
exit();
@@ -5,6 +5,7 @@ if (session_status() === PHP_SESSION_NONE) {
}
require_once __DIR__ . "/../model/CommentManager.php";
require_once __DIR__ . "/../../includes/csrf.php";
/*
* Kommentare dürfen nur über ein POST-Formular gelöscht werden.
@@ -44,6 +45,23 @@ if (!isset($_SESSION["user_email"])) {
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.
*/
+13
View File
@@ -5,6 +5,12 @@ if (session_status() === PHP_SESSION_NONE) {
require_once __DIR__ . '/../model/Article.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
if (isset($_GET["id"]) && !empty($_GET["id"])) {
@@ -17,6 +23,13 @@ if (isset($_GET["id"]) && !empty($_GET["id"])) {
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 {
$articleManager = ArticleManager::getInstance();
$articleManager->toggleLike($articleId, $userEmail);
+4
View File
@@ -1,4 +1,8 @@
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$_SESSION = [];
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/CommentManager.php";
require_once "php/validator/user-validator.php";
require_once "includes/csrf.php";
$error = null;
@@ -27,6 +28,7 @@ try {
}
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["saveProfile"])) {
$oldEmail = $_SESSION["user_email"];
$newEmail = trim($_POST["email"] ?? "");
@@ -34,7 +36,12 @@ try {
$nachname = trim($_POST["nachname"] ?? "");
$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.";
$_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/UserManager.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 {
$id = $_GET["id"];
$articleManager = ArticleManager::getInstance();
$article = $articleManager->getArticle($id);
if($article != null){
@@ -38,7 +42,7 @@ if (isset($_GET["id"]) && !empty($_GET["id"])){
}
$commentManager = CommentManager::getInstance();
$comments = $commentManager->getCommentsByArticle($_GET["id"]);
$comments = $commentManager->getCommentsByArticle($id); // NEU: validierte ID statt rohem $_GET["id"]
foreach ($comments as $comment) {
if ($comment->isReply()) {
+32 -73
View File
@@ -7,83 +7,27 @@ require_once '../model/LocalArticleManager.php';
require_once '../model/ArticleManager.php';
require_once '../model/Article.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");
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 (isset($_GET["id"]) && !empty($_GET["id"])) {
$id = $_GET["id"];
} else {
// CSRF-Token prüfen, bevor irgendeine Änderung vorgenommen wird
if (!csrf_verify()) {
$_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";
header("location: ../../index.php?pfad=updateArticle");
exit();
@@ -92,6 +36,14 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
try {
$articleManager = ArticleManager::getInstance();
$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"]) {
$_SESSION["message"] = "unauthorized_access";
header("location: ../../index.php");
@@ -118,7 +70,9 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
$_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
// ---------------------------------------------------------------------
// Zwischenspeichern
// 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'];
@@ -134,7 +88,7 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
}
$_SESSION["old_content"] = json_encode($blocks, JSON_UNESCAPED_UNICODE);
header("location: ../../content/updateArticle.php?id=$id");
header("location: ../../index.php?pfad=updateArticle&id=$id");
exit();
}
@@ -191,7 +145,12 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
// ----------------- Verwaiste Bilder aufräumen -----------------
// Bilder, die im alten (gespeicherten) Content vorkamen, im neuen aber nicht
// 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
// Hinweis/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
// regelmäßiger Cleanup-Job anbieten, der verwaiste Dateien im uploads/-Ordner
// mit den in der Datenbank referenzierten Pfaden abgleicht.
$oldBlocks = json_decode($article->getContent(), true);
$oldImagePaths = [];
if (is_array($oldBlocks)) {
@@ -5,6 +5,7 @@ if (session_status() === PHP_SESSION_NONE) {
}
require_once "php/model/CommentManager.php";
require_once "includes/csrf.php";
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("Location: index.php");
@@ -16,6 +17,14 @@ if (!isset($_SESSION["user_email"])) {
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(
INPUT_POST,
"comment_id",
+10 -45
View File
@@ -63,12 +63,9 @@ class DatabaseArticleManager implements ArticleManagerDAO {
VALUES (:title, :content, :author, :category, :tags);";
$command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
// Verknüpft die übergebenen Parameter exakt mit den SQL-Platzhaltern
$success = $command->execute([
$command->execute([
":title" => $title,
":content" => $content,
":author" => $author,
@@ -76,14 +73,14 @@ class DatabaseArticleManager implements ArticleManagerDAO {
":tags" => $tags
]);
if (!$success) {
throw new InternalServerErrorException("internal_error");
}
return intval($db->lastInsertId());
} 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;";
$command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
$success = $command->execute([
$command->execute([
":id" => $id,
":title" => $article->getTitle(),
":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
if (!$success || $command->rowCount() === 0) {
if ($command->rowCount() === 0) {
// Falls die ID nicht existiert, prüfen wir, ob sie überhaupt da ist
if (!$this->getArticle($id)) {
throw new NotFoundException("missing_id");
@@ -148,13 +142,7 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$sql = "DELETE FROM articles WHERE id = :id;";
$command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
if (!$command->execute([":id" => $id])) {
throw new InternalServerErrorException("internal_error");
}
$command->execute([":id" => $id]);
} catch (PDOException $exc) {
throw new InternalServerErrorException("internal_error");
}
@@ -167,10 +155,6 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$sql = "SELECT * FROM articles WHERE id = :id;";
$command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
$command->execute([":id" => $id]);
$row = $command->fetch(PDO::FETCH_ASSOC);
@@ -202,10 +186,6 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$sql = "SELECT * FROM articles;";
$command = $db->query($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
$rows = $command->fetchAll(PDO::FETCH_ASSOC);
$articles = [];
@@ -234,10 +214,6 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$sql = "SELECT * FROM articles WHERE author = :author;";
$command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
$command->execute([":author" => $author]);
$rows = $command->fetchAll(PDO::FETCH_ASSOC);
$filteredArticles = [];
@@ -270,10 +246,6 @@ class DatabaseArticleManager implements ArticleManagerDAO {
$sql = "SELECT * FROM articles WHERE category = :category;";
$command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
$command->execute([":category" => $category]);
$rows = $command->fetchAll(PDO::FETCH_ASSOC);
$filteredArticles = [];
@@ -318,21 +290,14 @@ class DatabaseArticleManager implements ArticleManagerDAO {
OR tags LIKE :keyword";
$command = $db->prepare($sql);
if (!$command) {
throw new InternalServerErrorException("internal_error");
}
// Wildcards für die Suche hinzufügen
$searchParam = '%' . $cleankeyword . '%';
$success = $command->execute([
$command->execute([
":keyword" => $searchParam
]);
if (!$success) {
throw new InternalServerErrorException("internal_error");
}
$rows = $command->fetchAll(PDO::FETCH_ASSOC);
$filteredArticles = [];
+6 -1
View File
@@ -234,9 +234,14 @@ class DatabaseInitializer {
// Bestimmt per Zufall einen Autor aus dem Pool der gültigen E-Mails
$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([
':title' => $article[0],
':content' => $article[1],
':content' => $content,
':author' => $randomAuthor,
':category' => $article[2],
':tags' => $article[3]
+15
View File
@@ -1,5 +1,20 @@
<?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:
* Buchstaben von a-z; A-Z