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
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?
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.
Formularz dodawania komentarza Użytkownik podaje nick i treść komentarza.
Walidacja danych System sprawdza poprawność wprowadzonych danych - czy pola nie są puste, czy treść nie jest za długa.
Lista komentarzy Wszystkie komentarze są wyświetlane chronologicznie z informacją o autorze i dacie.
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" } ]}Wymagane funkcje:
Przykładowy scenariusz:
Ocena: 3.0Uż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.
Wszystko z wariantu A, plus:
htmlspecialchars() przy wyświetlaniuPrzykładowy scenariusz:
Ocena: 4.0-5.0Użytkownik dodaje komentarz, a jego nick jest zapamiętany w sesji na kolejne komentarze. Widzi licznik znaków przy pisaniu (max 500). Komentarze wyświetlają się od najnowszych.
Wszystko z wariantu B, plus:
Przykładowy scenariusz:
Ocena: 5.0-6.0Admin widzi panel moderacji że wszystkimi komentarzami. Może ukryć spam klikając “Ukryj” - komentarz znika dla użytkowników, ale zostaje w bazie. Może też przywrócić ukryty komentarz.
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 znakiif (!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;}
// Paginacjafunction 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:
Pracuj iteracyjnie - lepiej mieć działający wariant A niż niedokończony C!