Przejdź do głównej zawartości

Prosty system komentarzy

Stworzysz Prosty system komentarzy - aplikację do dodawania i przeglądania komentarzy pod artykułem. System pozwala użytkownikom dodawać komentarze, a moderatorom zarządzać ich widocznością.

Czego się nauczysz?

  • Zabezpieczania aplikacji przed atakami XSS
  • Sanityzacji danych wejściowych i wyjściowych
  • Implementacji systemu moderacji treści
  • Pracy z sesjami użytkowników
  • Tworzenia interfejsu komentarzy

W prawdziwej pracy...

Systemy komentarzy są obecne na każdej stronie internetowej - od blogów, przez sklepy, po portale informacyjne. Umiejętność projektowania bezpiecznych systemów komentarzy z ochroną przed XSS i moderacją treści jest fundamentem dla każdego programisty aplikacji webowych.

  1. Formularz dodawania komentarza Użytkownik podaje nick i treść komentarza.

  2. Walidacja danych System sprawdza poprawność wprowadzonych danych - czy pola nie są puste, czy treść nie jest za długa.

  3. Lista komentarzy Wszystkie komentarze są wyświetlane chronologicznie z informacją o autorze i dacie.

  4. Bezpieczne wyświetlanie Treści są sanityzowane przed wyświetleniem, chroniąc przed atakami XSS.

Przykładowa struktura pliku JSON:

{
"comments": [
{
"id": 1,
"nick": "JanK",
"content": "Świetny artykuł! Bardzo pomocny.",
"status": "visible",
"ip": "192.168.1.100",
"created_at": "2026-02-10 14:30:00",
"moderated_at": null,
"moderated_by": null
},
{
"id": 2,
"nick": "Anna123",
"content": "Mam pytanie - czy to działa również z PHP 8?",
"status": "visible",
"ip": "192.168.1.101",
"created_at": "2026-02-10 15:45:00",
"moderated_at": null,
"moderated_by": null
},
{
"id": 3,
"nick": "Troll",
"content": "Spam spam spam",
"status": "hidden",
"ip": "192.168.1.102",
"created_at": "2026-02-10 16:00:00",
"moderated_at": "2026-02-10 16:05:00",
"moderated_by": "admin"
}
]
}
  • Foldersystem-komentarzy/
    • index.php (artykuł z komentarzami)
    • dodaj-komentarz.php (obsługa formularza)
    • moderacja.php (panel moderatora - wariant C)
    • Folderincludes/
      • config.php
      • functions.php
      • validation.php
      • security.php
      • auth.php (wariant C)
    • Folderdata/
      • comments.json
      • users.json (wariant C)
      • banned_words.json (opcjonalnie)
    • Foldercss/
      • style.css
    • Folderjs/
      • validation.js
      • comments.js

Wymagane funkcje:

  • Formularz komentarza: nick, treść
  • Walidacja PHP (pola wymagane, max długość)
  • Min. 1 walidacja JavaScript
  • Zapis komentarzy do pliku JSON
  • Lista komentarzy z datami
  • Komunikaty błędów i sukcesu

Przykładowy scenariusz:

Użytkownik wpisuje nick “JanK” i komentarz “Świetny artykuł!”. Po zapisie widzi swój komentarz na liście z datą dodania. Próba dodania pustego komentarza wyświetla błąd.

Ocena: 3.0

Walidacja komentarza:

$nick = trim($_POST['nick'] ?? '');
$content = trim($_POST['content'] ?? '');
$maxLength = 500;
if (empty($nick)) {
$errors[] = "Nick jest wymagany";
}
if (strlen($nick) < 2 || strlen($nick) > 30) {
$errors[] = "Nick musi mieć od 2 do 30 znaków";
}
if (empty($content)) {
$errors[] = "Treść komentarza jest wymagana";
}
if (strlen($content) > $maxLength) {
$errors[] = "Komentarz może mieć maksymalnie $maxLength znaków";
}
// Sprawdź czy nick zawiera tylko dozwolone znaki
if (!preg_match('/^[a-zA-Z0-9_ąćęłńóśźżĄĆĘŁŃÓŚŹŻ]+$/', $nick)) {
$errors[] = "Nick może zawierać tylko litery, cyfry i podkreślnik";
}

Bezpieczne wyświetlanie komentarza:

function displayComment(array $comment): string {
$nick = htmlspecialchars($comment['nick'], ENT_QUOTES, 'UTF-8');
$content = nl2br(htmlspecialchars($comment['content'], ENT_QUOTES, 'UTF-8'));
$date = htmlspecialchars($comment['created_at'], ENT_QUOTES, 'UTF-8');
return sprintf(
'<div class="comment">
<div class="comment-header">
<strong>%s</strong>
<span class="date">%s</span>
</div>
<div class="comment-content">%s</div>
</div>',
$nick,
$date,
$content
);
}

Dodawanie komentarza:

function addComment(array &$comments, array $data): int {
$newId = empty($comments) ? 1 : max(array_column($comments, 'id')) + 1;
$newComment = [
'id' => $newId,
'nick' => $data['nick'],
'content' => $data['content'],
'status' => 'visible',
'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
'created_at' => date('Y-m-d H:i:s'),
'moderated_at' => null,
'moderated_by' => null,
];
$comments[] = $newComment;
return $newId;
}

Funkcje moderacji:

function hideComment(array &$comments, int $commentId, string $moderator): bool {
foreach ($comments as &$comment) {
if ($comment['id'] === $commentId) {
$comment['status'] = 'hidden';
$comment['moderated_at'] = date('Y-m-d H:i:s');
$comment['moderated_by'] = $moderator;
return true;
}
}
return false;
}
function showComment(array &$comments, int $commentId): bool {
foreach ($comments as &$comment) {
if ($comment['id'] === $commentId) {
$comment['status'] = 'visible';
return true;
}
}
return false;
}
function deleteComment(array &$comments, int $commentId): bool {
foreach ($comments as $key => $comment) {
if ($comment['id'] === $commentId) {
unset($comments[$key]);
return true;
}
}
return false;
}

Filtrowanie widocznych komentarzy:

function getVisibleComments(array $comments): array {
return array_filter($comments, fn($c) => $c['status'] === 'visible');
}
function sortByDateDesc(array $comments): array {
usort($comments, fn($a, $b) => strcmp($b['created_at'], $a['created_at']));
return $comments;
}
// Paginacja
function paginateComments(array $comments, int $page, int $perPage = 10): array {
$offset = ($page - 1) * $perPage;
return array_slice($comments, $offset, $perPage);
}
function getTotalPages(array $comments, int $perPage = 10): int {
return (int) ceil(count($comments) / $perPage);
}

Filtr słów zakazanych (wariant C):

function filterBadWords(string $content, array $bannedWords): string {
foreach ($bannedWords as $word) {
$replacement = str_repeat('*', strlen($word));
$content = preg_replace('/\b' . preg_quote($word, '/') . '\b/iu', $replacement, $content);
}
return $content;
}
function containsBadWords(string $content, array $bannedWords): bool {
foreach ($bannedWords as $word) {
if (preg_match('/\b' . preg_quote($word, '/') . '\b/iu', $content)) {
return true;
}
}
return false;
}

JavaScript - licznik znaków:

function setupCharCounter(textareaId, counterId, maxLength) {
const textarea = document.getElementById(textareaId);
const counter = document.getElementById(counterId);
function updateCounter() {
const remaining = maxLength - textarea.value.length;
counter.textContent = remaining + ' znaków pozostało';
if (remaining < 50) {
counter.classList.add('warning');
} else {
counter.classList.remove('warning');
}
if (remaining < 0) {
counter.classList.add('error');
} else {
counter.classList.remove('error');
}
}
textarea.addEventListener('input', updateCounter);
updateCounter();
}
// Użycie: setupCharCounter('content', 'charCounter', 500);

Sesje - zapamiętanie nicka:

session_start();
// Zapisz nick w sesji po dodaniu komentarza
$_SESSION['nick'] = $nick;
// Pobierz zapisany nick dla formularza
$savedNick = $_SESSION['nick'] ?? '';
// W formularzu:
// <input type="text" name="nick" value="<?= htmlspecialchars($savedNick) ?>">

Wykorzystaj lekcje!

Cotygodniowe spotkania podczas lekcji to idealny moment, by:

  • Pokazać postępy - nawet małe kroki się liczą
  • Wyjaśnić wątpliwości - pytaj, nie zgaduj
  • Skonsultować rozwiązania - feedback pomoże Ci się rozwijać

Pracuj iteracyjnie - lepiej mieć działający wariant A niż niedokończony C!