Serwis używa plików cookies do analizy ruchu i poprawy działania. Szczegóły
EE.09-01-22.06-SGCzerwiec 2022 · 45 kroków · ok. 87 min · 42 kryteria z klucza
Rozwiązanie krok po kroku: Forum miłośników psów (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza forumpsy z czterema zapytaniami, avatar z przezroczystym tłem 100 × 100 px i strona index.php forum o psach z filmem, formularzem i listą odpowiedzi - i tak samo spełnia każde kryterium klucza. Inna jest droga:
siatka CSS zamiast float: oba bloki stoją w znaczniku <main>, a ich szerokości 40% i 60% opisuje grid-template-columns - stopka nie potrzebuje clear,
zmienne CSS dla trzech kolorów z arkusza - każdy jest wpisany raz,
PHP na PDO na górze pliku index.php, przed HTML-em: najpierw zapis nowej odpowiedzi, potem odczyt pytania i listy. W HTML-u zostają tylko pętla i wypisanie danych,
każda wartość z bazy przechodzi przez htmlspecialchars, odpowiedź jest przycinana z białych znaków, a zapis idzie zapytaniem przygotowanym - apostrof w odpowiedzi ani wpisany skrypt niczego nie psują,
po dodaniu odpowiedzi przekierowanie - odświeżenie strony nie doda tej samej odpowiedzi drugi raz.
To arkusz EE.09 z czerwca 2022 - kwalifikacji formuły 2017, którą zastąpiła INF.03. Baza, avatar i film pochodzą z oryginalnego archiwum CKE zad1.zip (załącznik na stronie arkusza), zapytania i zrzuty są policzone na MariaDB 10.4, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 (klucz każe sprawdzać w XAMPP 8.1.2 - użyte konstrukcje działają w obu wersjach).
Jak liczą się punkty. Klucz ma 42 kryteria (R.1.1 - R.5.8), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 32 punkty. Przy każdym kroku jest napisane, które kryteria spełnia; klucz przyjmuje zarówno mysqli, jak i PDO.
Suwak i strzałki obok niego - albo ←→ na klawiaturze - przechodzą krok po kroku; wyjaśnienie, pliki i podgląd zmieniają się razem z krokiem.
Lista „Etap” przenosi od razu do wybranego etapu.
„Rozwiązuję samodzielnie” chowa rozwiązanie kroku - najpierw własna próba, potem odsłonięcie.
„Tryb prezentacji” pokazuje same kroki, bez menu strony - na rzutnik.
Etap 1: Baza danych
Kroki 1-7 · po tym etapie masz 6 z 42 kryteriów (pkt)
Krok 1: Folder zdającego i archiwum zad1.zip
ok. 2 min
Polecenie z arkusza
Na pulpicie znajduje się archiwum o nazwie zad1.zip zabezpieczone hasłem: 3&PsY#@ Archiwum należy rozpakować. Na pulpicie konta Egzamin należy utworzyć folder. Jako nazwy folderu należy użyć numeru zdającego, którym został podpisany arkusz. Rozpakowane pliki należy umieścić w tym folderze.
Folder na pulpicie nazwany numerem PESEL (w tym rozwiązaniu w jego miejscu stoi Bartosz Bryniarski), do niego archiwum zad1.zip rozpakowane z hasłem _3&PsY#@_. W środku: baza.sql, Avatar.jpg i video.mp4. Do tego folderu trafiają wszystkie rezultaty pracy, a stronę uruchamiasz z kopii w C:\xampp\htdocs.
Krok 2: XAMPP i pusta baza forumpsy
ok. 2 min
Polecenie z arkusza
Za pomocą narzędzia phpMyAdmin należy wykonać operacje na bazie danych: Utworzenie bazy danych o nazwie: forumpsy z zestawem polskich znaków (np. utf8_unicode_ci)
Start przy Apache i MySQL, potem http://localhost/phpmyadmin, Nowa, nazwa forumpsy, porównywanie utf8_unicode_ci, Utwórz.
Krok 3: Import pliku baza.sql i zrzut import.jpg
Kryteria:
R.1.1 Wykonano import tabel do bazy danych forumpsy , czynność udokumentowano plikiem o nazwie import w formacie JPEG
ok. 3 min
Polecenie z arkusza
Import tabel do bazy forumpsy z pliku baza.sql. Czynność należy udokumentować zrzutem ekranu, na którym powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel. Zrzut należy zapisać w formacie JPEG, pod nazwą import. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań.
Baza forumpsy, karta Import, plik baza.sql, Wykonaj. Po imporcie są trzy tabele: konta (10 rekordów), odpowiedzi (7) i pytania (4). Zrzut całego ekranu z komunikatem o sukcesie i listą tabel - import.jpg (JPEG, w odróżnieniu od zrzutów zapytań).
Podgląd strony po tym kroku
Krok 4: Zapytanie 1: nowa odpowiedź na pytanie 1
Kryteria: ,
R.1.2 Zapisano plik tekstowy o nazwie kwerendy zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Utworzono zapytanie 1 wstawiające do tabeli odpowiedzi wiersz, o treści „Przyda Ci się także wysoki stół, na którym postawisz pieska.”, pytania_id = 1, konta_id = 5. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO `odpowiedzi` (`Pytania_id`, `konta_id`, `odpowiedz`) VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.'); możliwe inne formy zapytania INSERT INTO oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest komunikat o poprawnie wykonanym zapytaniu lub tabela ze wstawionym rekordem
ok. 3 min
Polecenie z arkusza
Zapytania SQL na bazie forumpsy zapisane do pliku kwerendy.txt. Zapytanie 1: wstawiające do tabeli odpowiedzi wiersz, o treści „Przyda Ci się także wysoki stół, na którym postawisz pieska.”. Jest on odpowiedzią na pytanie o id równym 1, udzieloną przez użytkownika o id równym 5.
Lista kolumn bez id - numer nada AUTO_INCREMENT (nowa odpowiedź dostaje 8), potem wartości w tej samej kolejności: pytanie 1, użytkownik 5, treść w apostrofach. Zapytanie trafia do kwerendy.txt (R.1.2), zrzut - do kw1.png. Skrypt wykona to samo zapytanie z parametrem w miejscu treści.
kwerendy.txtTekstnowy plik
-- Zapytanie 1INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz)VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.');
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: ile lat na forum
Kryteria:
R.1.4 Utworzono zapytanie 2 wybierające z tabeli konta jedynie pole nick oraz liczące ile lat dany użytkownik jest zarejestrowany na forum – alias latNaForum, dla rekordów, których nick jest równy Jacek, Tomek, Monika. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nick, YEAR(CURRENT_DATE())-rejestracja AS latNaForum FROM konta WHERE nick IN ('Jacek', 'Tomek', 'Monika'); (możliwe inne funkcje zamiast CURRENT_DATE()aktualny rok zwracany funkcją lub warunek z OR) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są jedynie 3 rekordy, jedynie pola nick i latNaForum wartości w kolumnie latNaForum są obliczone dla Jacka 3, Tomka 6, Moniki 2
ok. 3 min
Polecenie z arkusza
Zapytanie 2: z tabeli konta dla rekordów, których nick to Jacek, Tomek, Monika wybierające jedynie pole nick oraz liczące ile lat dany użytkownik jest zarejestrowany na forum – kolumnę należy nazwać (alias) latNaForum. Zapytanie jest uniwersalne, gdy zmieni się aktualny rok, też powinno być liczone poprawnie
YEAR(CURDATE()) - rok z dzisiejszej daty, więc zapytanie nie ma roku wpisanego na sztywno; od niego odejmujesz rok rejestracji i nazywasz wynik aliasem latNaForum. IN (...) zastępuje trzy warunki z OR.
Wynik w 2026 roku: Jacek 7, Tomek 10, Monika 6. Zrzut: kw2.png.
⚠️ Rozbieżność arkusz-klucz: z danych w 2026 roku wychodzi 7, 10 i 6, a klucz CKE podaje 3, 6 i 2 - wynik z czerwca 2022, roku egzaminu. Klucz sam wymaga zapytania uniwersalnego, więc punkt daje zapytanie z YEAR(CURDATE()), a liczby zależą od roku, w którym się je wykonuje.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz)VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.');-- Zapytanie 2SELECT nick, YEAR(CURDATE()) - rejestracja AS latNaForumFROM kontaWHERE nick IN ('Jacek', 'Tomek', 'Monika');
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: autor pytania nr 1
Kryteria:
R.1.5 Utworzono zapytanie 3 wybierające jedynie nick i liczbę postów użytkownika z tabeli konta oraz odpowiadające im pole pytanie z tabeli pytania dla pytania, którego id jest równe 1. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = konta_id WHERE pytania.id = 1; (lub bez JOIN i porównanie kluczy po WHERE) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest dokładnie jeden rekord o danych Jacek, 50, "Mam pytanie w jaki sposób dbać ..."
ok. 3 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie nick i liczbę postów użytkownika z tabeli konta oraz odpowiadające im pole pytanie z tabeli pytania dla pytania, którego id jest równe 1. Należy posłużyć się relacją.
JOIN pytania ON konta.id = pytania.konta_id - relacja z obrazu 1: każde pytanie z kontem autora. WHERE pytania.id = 1 z nazwą tabeli, bo kolumnę id mają obie. Wynik: Jacek, 50 i pytanie o sierść sznaucera. Każda klauzula w osobnej linii - przy złączeniach łatwiej wtedy czytać zapytanie. Zrzut: kw3.png.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz)VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.');-- Zapytanie 2SELECT nick, YEAR(CURDATE()) - rejestracja AS latNaForumFROM kontaWHERE nick IN ('Jacek', 'Tomek', 'Monika');-- Zapytanie 3SELECT nick, postow, pytanieFROM kontaJOIN pytania ON konta.id = pytania.konta_idWHERE pytania.id = 1;
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: odpowiedzi na pytanie nr 1
Kryteria:
R.1.6 Utworzono zapytanie 4 wybierające jedynie id odpowiedzi oraz odpowiedź z tabeli odpowiedzi oraz odpowiadające im pole nick z tabeli konta dla id pytania równego 1. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON konta_id = konta.id WHERE Pytania_id = 1; (lub bez JOIN i porównanie kluczy po WHERE) oraz wynik działania zapytania udokumentowano zrzutem, na którym widać tylko rekordy z id równym 1, 2, 3; pola id, odpowiedz i nick lub dodatkowo inne rekordy powstałe wyłącznie na skutek działania zapytania 1
ok. 2 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie id odpowiedzi oraz odpowiedź z tabeli odpowiedzi oraz odpowiadające im pole nick z tabeli konta dla id pytania równego 1. Należy posłużyć się relacją.
Złączenie odpowiedzi z kontami ich autorów i warunek odpowiedzi.Pytania_id = 1. Wynik: odpowiedzi 1, 2, 3 (Anna, Gosia, Tomek) i 8 (Gosia) - tę ostatnią dodało zapytanie 1, a klucz przyjmuje ją wprost. Zrzut: kw4.png.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz)VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.');-- Zapytanie 2SELECT nick, YEAR(CURDATE()) - rejestracja AS latNaForumFROM kontaWHERE nick IN ('Jacek', 'Tomek', 'Monika');-- Zapytanie 3SELECT nick, postow, pytanieFROM kontaJOIN pytania ON konta.id = pytania.konta_idWHERE pytania.id = 1;-- Zapytanie 4SELECT odpowiedzi.id, odpowiedz, nickFROM odpowiedziJOIN konta ON odpowiedzi.konta_id = konta.idWHERE odpowiedzi.Pytania_id = 1;
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 8-25 · po tym etapie masz 20 z 42 kryteriów (pkt)
Krok 8: Avatar: przezroczyste tło
ok. 3 min
Polecenie z arkusza
Cechy grafiki z pliku Avatar: Tło przezroczyste
GIMP: Warstwa → Przezroczystość → Dodaj kanał alfa, potem Zaznaczanie rozmytego obszaru kliknięte w białe tło i Delete. Różdżka zaznacza tylko obszar połączony z klikniętym punktem, więc białe odblaski w oczach zostają - Kolor na alfę zrobiłby je przezroczystymi.
Podgląd strony po tym kroku
Krok 9: Avatar: 100 × 100 px w formacie PNG
Kryteria:
R.2.1 Obraz w pliku Avatar ma wymiary 100 px na 100 px, tło przezroczyste i jest zapisany w formacie obsługującym przezroczystość
ok. 2 min
Polecenie z arkusza
Rozmiary 100 px na 100 px. Grafika zapisana pod nazwą Avatar w formacie obsługującym przezroczystość
Obraz → Skaluj obraz z 640 × 640 do 100 × 100 px, potem Eksportuj jakoAvatar.png - PNG zachowuje przezroczystość, JPEG nie (R.2.1).
Podgląd strony po tym kroku
Krok 10: Plik index.php z deklaracją HTML5
Kryteria:
R.2.2 Witrynę internetową zapisano w pliku o nazwie index.php
ok. 1 min
Polecenie z arkusza
Cechy witryny: Składa się ze strony o nazwie index.php
Plik index.php w folderze zdającego, od razu z deklaracją <!DOCTYPE html> w pierwszej linii (arkusz wymaga HTML5) i szkieletem <html>, <head>, <body>. Kod PHP dojdzie na samą górę pliku, przed deklarację.
Jawnie zastosowany właściwy standard kodowania polskich znaków
<meta charset="UTF-8"> jako pierwszy element <head>, a plik zapisany w UTF-8 bez BOM - znak BOM przed <?php byłby „wypisany” przed nagłówkiem przekierowania i header() przestałby działać.
R.2.3 Zapisano jawnie standard kodowania polskich znaków i deklarację HTML5 <!DOCTYPE HTML> oraz zadeklarowano dla witryny język polski (np. lang = 'pl')
ok. 1 min
Polecenie z arkusza
Zadeklarowany język zawartości witryny: polski
lang="pl" w znaczniku <html> - deklaracja HTML5, kodowanie i język są gotowe (R.2.3).
Tytuł strony widoczny na karcie przeglądarki: „Forum o psach”
<title> z tytułem z arkusza (R.2.4). Obok <meta name="viewport"> - bez niej telefon pokazuje stronę jak pomniejszony ekran komputera. Arkusz tego nie wymaga, ale to stały element każdej współczesnej strony.
index.phpPHPzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title></head><body></body></html>
Podgląd strony po tym kroku (index.php)
Krok 14: Podpięcie arkusza stylów styl.css
Kryteria:
R.3.8 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.css , formatowanie pochodzi jedynie z tego arkusza Uwaga: kryterium to nie jest spełnione również, gdy zdający źle przypisał co najmniej jeden selektor do bloku (np. kropka zamiast #)
ok. 1 min
Polecenie z arkusza
Arkusz stylów w pliku o nazwie styl.css prawidłowo połączony z kodem strony
<link rel="stylesheet" href="styl.css"> - całe formatowanie strony przyjdzie z tego pliku (R.3.8).
index.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body></body></html>
Podgląd strony po tym kroku (index.php)
Krok 15: Bloki: baner, lewy, prawy i stopka
Kryteria:
R.2.5 Wygląd strony zdefiniowano dzieląc ją na bloki: baner, lewy, prawy, stopka. Zastosowano znaczniki sekcji
ok. 2 min
Polecenie z arkusza
Podział strony na: blok banera, obok siebie blok lewy i prawy, stopka. Podział zrealizowany za pomocą znaczników sekcji
<header>, dwie sekcje i <footer> (R.2.5). Sekcje są opakowane w <main> - znacznik głównej treści strony, który w CSS stanie się siatką z dwiema kolumnami. Opakowanie ma sens także bez stylów: czytnik ekranu pozwala przeskoczyć prosto do <main>, z pominięciem banera.
Krok 17: Blok lewy: avatar z tekstem alternatywnym
Kryteria:
R.2.8 W bloku lewym umieszczono obraz Avatar z tekstem alternatywnym "Użytkownik forum"
ok. 1 min
Polecenie z arkusza
Zawartość bloku lewego: Obraz o nazwie Avatar i tekście alternatywnym „Użytkownik forum”
<img> z plikiem Avatar.png i tekstem alternatywnym (R.2.8). Atrybuty width i height z prawdziwymi wymiarami pliku pozwalają przeglądarce zarezerwować miejsce, zanim obraz się wczyta - tekst pod nim nie „skacze” w dół.
R.2.10 W bloku lewym umieszczono film video.mp4 (np. znaczniki video i source lub jedynie video z atrybutem src lub znacznik object lub embed)
R.3.5 Video odtwarza się w pętli
R.3.6 Na filmie widoczne są przyciski sterujące
ok. 2 min
Polecenie z arkusza
Film o nazwie video.mp4 z przyciskami do nawigacji, odtwarzający się w pętli
<video> z atrybutami logicznymi controls (przyciski - R.3.6) i loop (odtwarzanie w pętli - R.3.5). Między avatarem a filmem PHP wstawi dane autora pytania.
Krok 19: Blok prawy: formularz wysyłany metodą POST
Kryteria:
R.3.3 Zdefiniowano wysyłanie danych z formularza do tego samego pliku metodą POST (poprawne również bez atrybutu action)
ok. 1 min
Polecenie z arkusza
Zawartość bloku prawego: Formularz wysyłający dane metodą bezpieczną do tego samego pliku
<form method="post"> bez atrybutu action - dane wracają do index.php (R.3.3). POST, bo odpowiedź nie powinna trafiać do adresu strony ani historii przeglądarki.
<textarea> z rows="4" i cols="40", nazwa pola odpowiedz - pod tym kluczem PHP odczyta treść. Dwa dodatki:
maxlength="1000" - przeglądarka nie pozwoli wpisać dłuższej odpowiedzi; serwer i tak sprawdzi to sam, bo formularz da się ominąć,
aria-label - pole nie ma widocznej etykiety (obraz 2 jej nie pokazuje), więc czytnik ekranu dostaje nazwę pola z tego atrybutu zamiast samego „pole edycyjne”.
R.2.9 W bloku prawym umieszczono formularz z polem wielowierszowym (<textarea>) i przyciskiem, zastosowano znacznik <form>
R.3.4 Pole wielowierszowe ma 4 wiersze (rows) i 40 kolumn (cols) oraz przycisk wysyła dane z formularza (input lub button z typem submit lub button bez typu)
ok. 1 min
Polecenie z arkusza
Przycisk o treści „Dodaj odpowiedź”, którego wciśnięcie powoduje wysłanie danych do skryptu 2
<button type="submit"> wysyła formularz. Formularz, pole i przycisk (R.2.9) oraz 4 wiersze, 40 kolumn i wysyłanie (R.3.4) są gotowe.
Krok 25: Stopka: odnośnik otwierany w nowej karcie
Kryteria:
R.3.2 Na stronie umieszczono odnośnik, którego kliknięcie inicjuje otwarcie strony http://mojestrony.pl/ w nowej karcie przeglądarki
ok. 1 min
Polecenie z arkusza
Za numerem zdającego wstawiony odnośnik do strony http://mojestrony.pl/ otwierający się w osobnej karcie przeglądarki i podpisany „Zobacz nasze realizacje”
Pełny adres z http:// i target="_blank" (R.3.2). Dodatek rel="noopener": otwarta w nowej karcie strona nie dostaje dostępu do karty forum (obiektu window.opener), więc nie może jej po cichu podmienić na fałszywą. Nowe przeglądarki robią to już domyślnie, ale atrybut zostaje dla starszych.
Kroki 26-35 · po tym etapie masz 31 z 42 kryteriów (pkt)
Krok 26: Plik styl.css: kolory jako zmienne i krój Garamond
Kryteria:
R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (*): krój czcionki: Garamond, serif (czcionka Garamond jako pierwsza)
ok. 2 min
Polecenie z arkusza
Styl CSS zdefiniowany w całości w zewnętrznym pliku o nazwie styl.css. Ustawione domyślne wartości dla wszystkich selektorów stylu CSS: krój czcionki Garamond, w przypadku jego braku – serif
Na początku pliku zmienne CSS w :root: trzy kolory z arkusza dostają nazwy --green-dark, --green-light i --text-dark. Kolor #97b498 występuje w arkuszu pięć razy (dwa gradienty, przycisk, :hover), więc zmiana odcienia to jedna linia zamiast pięciu.
Selektor * z krojem Garamond, serif (R.4.1) - obejmuje też pole tekstowe i przycisk, które kroju po rodzicu nie dziedziczą.
R.4.7 Ustawiono wyrównanie tekstu do środka dla banera
ok. 2 min
Polecenie z arkusza
Dla bloku banera: tło będące pionowym gradientem liniowym od koloru #97b498 do #c8e6c9, biały kolor czcionki, wyrównanie tekstu do środka, marginesy wewnętrzne 5 px
linear-gradient bez kierunku biegnie z góry na dół, czyli pionowo; zmienne działają także wewnątrz funkcji. Wyśrodkowanie (R.4.7), biały tekst i padding: 5px.
R.4.3 Ustawiono gradient liniowy pionowy od koloru #97b498 do #c8e6c9 dla banera np. background: linear-gradient(#97b498, #c8e6c9) i odwrotnej kolejności dla stopki np. background: linear-gradient(#c8e6c9, #97b498)
ok. 1 min
Polecenie z arkusza
Dla stopki: tło będące pionowym gradientem liniowym od koloru #c8e6c9 do #97b498, biały kolor czcionki, marginesy wewnętrzne 12 px
Te same zmienne w odwrotnej kolejności. Gradienty banera i stopki są gotowe (R.4.3). Druga reguła zeruje domyślny margines paragrafu w stopce - bez niej stopka byłaby wyraźnie wyższa niż na obrazie 2.
Krok 31: Układ bloków: siatka z kolumnami 40% i 60%
Kryteria:
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z Obrazem 2 w arkuszu egzaminacyjnym (prawidłowo zastosowano właściwości CSS układające bloki na stronie, np. float i clear albo display flex albo grid). Nie należy brać pod uwagę umiejscowienia filmu
ok. 3 min
Polecenie z arkusza
Podział zrealizowany za pomocą znaczników sekcji tak, aby wygląd układu bloków strony po uruchomieniu w przeglądarce, był zgodny z Obrazem 2
<main> staje się siatką: display: grid i grid-template-columns: 40% 60% - dwie kolumny o szerokościach z arkusza, a każda sekcja zajmuje kolejną komórkę. Bloki stoją obok siebie bez float, a stopka po prostu stoi pod <main>, bez clear (R.3.1).
Szerokość bloku to szerokość jego kolumny - klucz przyjmuje grid-template-columns jako ustawienie 40% i 60% (R.4.6, razem z filmem w kroku niżej). Kolumny w procentach liczą się od szerokości <main>, a bloki nie mają marginesów, więc suma 100% mieści się dokładnie.
Typowe błędy
Odstęp gap między kolumnami przy 40% + 60% wypycha siatkę poza ekran (pojawia się poziomy pasek przewijania strony) - przy procentach bez odstępu albo kolumny 2fr 3fr, które same odejmą gap.
R.4.6 Ustawiono szerokość 40% dla bloku lewego i 60% dla prawego (width lub grid- template-columns lub flex) oraz 100% dla selektora osadzającego film (np. video, object, embed)
ok. 1 min
Polecenie z arkusza
Dla selektora filmu: szerokość 100%
Selektor znacznika video (klucz nie przyjmuje klasy) - film wypełnia kolumnę bloku lewego. Szerokości bloków z siatki i filmu dają R.4.6.
Krok 35: Przycisk po najechaniu kursorem i z klawiatury
Kryteria:
R.4.10 W momencie, gdy kursor myszy znajdzie się na przycisku jego kolory są zmienione na: biały kolor tła, kolor czcionki #97b498 (np. za pomocą pseudoklasy :hover)
ok. 1 min
Polecenie z arkusza
W momencie, gdy kursor myszy znajdzie się na przycisku kolory tego przycisku są zmienione na: biały kolor tła, kolor czcionki #97b498
:hover zamienia kolory pod kursorem (R.4.10). Ten sam wygląd dostaje :focus-visible - przycisk wybrany klawiszem Tab. Osoba, która nie używa myszy, też widzi, który element jest aktywny.
Kroki 36-43 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 36: Kod PHP na górze pliku i połączenie z bazą forumpsy
Kryteria:
R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy forumpsy
ok. 4 min
Polecenie z arkusza
Skrypt łączy się z serwerem bazy danych na localhost, użytkownik root bez hasła, baza danych o nazwie forumpsy
Trzy skrypty arkusza mają wspólną część - bazę. Cały kod PHP stoi więc na samej górze index.php, przed <!DOCTYPE html>: najpierw komunikacja z bazą, potem HTML, który tylko wypisuje przygotowane dane. W HTML-u przybyły trzy linie pod formularzem: jeśli jest komunikat, wypisz go w paragrafie.
e() - krótka funkcja z htmlspecialchars: każdy tekst wypisywany na stronie zamienia <, >, & i cudzysłowy na encje, więc odpowiedź ze znacznikami nie zamieni się w kod strony,
new PDO(...) - serwer localhost, baza forumpsy i kodowanie w jednym napisie, potem root i puste hasło (R.5.1). ERRMODE_EXCEPTION - każdy błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze przychodzą jako tablice z nazwami kolumn ($row['nick'] zamiast $row[2]),
try / catch - gdy baza nie działa, użytkownik dostaje zwykłe zdanie, a nie komunikat PHP z danymi serwera.
PDO zamiast funkcji mysqli z tabeli 1 - klucz przyjmuje obie biblioteki (uwaga przy R.5). Od tego kroku podgląd to wynik prawdziwego uruchomienia strony.
Krok 37: Skrypt 1: zapytanie 3 i nagłówek z nickiem
Kryteria:
R.5.3 Skrypt 1 wysyła do bazy zapytanie 3 lub skrypt 3 wysyła do bazy zapytanie 4
ok. 3 min
Polecenie z arkusza
Działanie skryptu 1: Wysyła do bazy zapytanie 3. Dane zwrócone zapytaniem wyświetlane są według wzoru, gdzie w nawiasach <> zapisano wartości pobrane z bazy: „Użytkownik: <nick>” w nagłówku czwartego stopnia
$pdo->query(...) - zapytanie 3 bez zmian (R.5.3); nie ma w nim danych od użytkownika, więc wystarczy zwykłe query,
->fetch() - jeden wiersz, bo pytanie o id 1 jest jedno. Gdy pytania by nie było, fetch zwraca false - dlatego zmienna $question jest na początku ustawiona na false, a HTML wypisuje nagłówek w warunku if ($question),
w HTML-u <?= e($question['nick']) ?> - skrót <?= ?> to echo, a e() zabezpiecza nick.
Podgląd: „Użytkownik: Jacek” pod avatarem.
index.phpPHPzmienione: 8 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$question = false;try {$pdo = new PDO('mysql:host=localhost;dbname=forumpsy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// skrypt 1 - zapytanie 3: autor pytania nr 1$question = $pdo->query('SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;') ->fetch();} catch (PDOException $exception) {$message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <main> <sectionid="left"> <imgsrc="Avatar.png"alt="Użytkownik forum"width="100"height="100"><?phpif ($question): ?> <h4>Użytkownik: <?= e($question['nick']) ?></h4><?phpendif; ?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"maxlength="1000"aria-label="Treść odpowiedzi"></textarea><br> <buttontype="submit">Dodaj odpowiedź</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> <h2>Odpowiedzi na pytanie</h2> <ol> </ol> </section> </main> <footer> <p> Autor: Bartosz Bryniarski <ahref="http://mojestrony.pl/"target="_blank"rel="noopener">Zobacz nasze realizacje</a> </p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 38: Skrypt 1: liczba postów i treść pytania w paragrafach
Kryteria: ,
R.5.4 Skrypt 1 wyświetla dane wg wzoru: w h4: "Użytkownik: <nick>", w paragrafie: "<liczba postów> postów na forum”, w paragrafie <pytanie>, gdzie w nawiasach <> zapisano wartości pobrane z bazy
R.2.6 W bloku banera zapisano nagłówek h1: "Forum miłośników psów", w bloku prawym h2: "Odpowiedzi na pytanie" oraz zapisano przynajmniej jeden paragraf (w skrypcie lub HTML)
ok. 2 min
Polecenie z arkusza
„<liczba postów> postów na forum” w paragrafie. <pytanie> w paragrafie
Dwa paragrafy w tym samym warunku (R.5.4, R.2.6). Liczba postów przechodzi przez (int) - liczba całkowita nie zawiera znaczników, więc nie potrzebuje e(); treść pytania - przez e().
index.phpPHPzmienione: 2 linie
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$question = false;try {$pdo = new PDO('mysql:host=localhost;dbname=forumpsy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// skrypt 1 - zapytanie 3: autor pytania nr 1$question = $pdo->query('SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;') ->fetch();} catch (PDOException $exception) {$message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <main> <sectionid="left"> <imgsrc="Avatar.png"alt="Użytkownik forum"width="100"height="100"><?phpif ($question): ?> <h4>Użytkownik: <?= e($question['nick']) ?></h4> <p><?= (int) $question['postow'] ?> postów na forum</p> <p><?= e($question['pytanie']) ?></p><?phpendif; ?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"maxlength="1000"aria-label="Treść odpowiedzi"></textarea><br> <buttontype="submit">Dodaj odpowiedź</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> <h2>Odpowiedzi na pytanie</h2> <ol> </ol> </section> </main> <footer> <p> Autor: Bartosz Bryniarski <ahref="http://mojestrony.pl/"target="_blank"rel="noopener">Zobacz nasze realizacje</a> </p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 39: Skrypt 2: odpowiedź z formularza zapytaniem przygotowanym
Kryteria: ,
R.5.5 Skrypt 2 wysyła do bazy zmodyfikowane zapytanie 1 ten sposób, że w polu odpowiedz znajduje się wartość z pola tekstowego, należy sprawdzić w phpMyAdmin
R.5.6 Jeżeli pole tekstowe jest puste, skrypt 2 nie wysyła danych do bazy (w skrypcie 2 znajduje się warunek sprawdzający czy pole jest puste)
ok. 4 min
Polecenie z arkusza
Działanie skryptu 2: Jeżeli wpisano dane do pola wielowierszowego i dane nie są puste, skrypt wysyła do bazy zmodyfikowane zapytanie 1, w ten sposób, że w polu odpowiedz znajduje się wartość z pola wielowierszowego
Skrypt 2 stoi przed zapytaniami 3 i 4 - nowa odpowiedź jest już w bazie, gdy skrypt 3 będzie czytał listę:
$_SERVER['REQUEST_METHOD'] === 'POST' - tylko po wysłaniu formularza,
trim($_POST['odpowiedz'] ?? '') - ?? daje pusty napis, gdy pola nie wysłano, a trim zdejmuje spacje i entery z brzegów: odpowiedź z samych spacji jest pusta,
=== '' - pusta odpowiedź nie trafia do bazy (R.5.6), a pod formularzem pojawia się komunikat. empty() uznałby za puste także odpowiedź „0”,
mb_strlen(...) > 1000 - ten sam limit co maxlength w polu, sprawdzony na serwerze,
prepare z parametrem :odpowiedz i execute z treścią - zapytanie 1, w którym w miejscu treści stoi wartość z pola (R.5.5). Baza dostaje dane osobno od zapytania, więc apostrof w odpowiedzi niczego nie psuje.
Sprawdzenie, jak każe klucz: po wysłaniu formularza w tabeli jest wiersz 9 z treścią z formularza. Podgląd: odpowiedź „Do sznaucera kup trymer…” wysłana - lista jeszcze pusta, wypełni ją skrypt 3.
index.phpPHPzmienione: 14 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$question = false;try {$pdo = new PDO('mysql:host=localhost;dbname=forumpsy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// skrypt 2 - nowa odpowiedź z formularza, przed odczytem listy (od razu będzie na liście)if ($_SERVER['REQUEST_METHOD'] === 'POST') {$answer = trim($_POST['odpowiedz'] ?? '');if ($answer === '') {$message = 'Wpisz treść odpowiedzi - pusta odpowiedź nie została dodana.'; } elseif (mb_strlen($answer) > 1000) {$message = 'Odpowiedź może mieć najwyżej 1000 znaków.'; } else {$statement = $pdo->prepare('INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, :odpowiedz);');$statement->execute(['odpowiedz' => $answer]); } }// skrypt 1 - zapytanie 3: autor pytania nr 1$question = $pdo->query('SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;') ->fetch();} catch (PDOException $exception) {$message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <main> <sectionid="left"> <imgsrc="Avatar.png"alt="Użytkownik forum"width="100"height="100"><?phpif ($question): ?> <h4>Użytkownik: <?= e($question['nick']) ?></h4> <p><?= (int) $question['postow'] ?> postów na forum</p> <p><?= e($question['pytanie']) ?></p><?phpendif; ?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"maxlength="1000"aria-label="Treść odpowiedzi"></textarea><br> <buttontype="submit">Dodaj odpowiedź</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> <h2>Odpowiedzi na pytanie</h2> <ol> </ol> </section> </main> <footer> <p> Autor: Bartosz Bryniarski <ahref="http://mojestrony.pl/"target="_blank"rel="noopener">Zobacz nasze realizacje</a> </p> </footer></body></html>
R.2.7 W bloku prawym umieszczono listę numerowaną, zastosowano znaczniki <ol>, <li> (w skrypcie lub HTML)
ok. 3 min
Polecenie z arkusza
Działanie skryptu 3: Wysyła do bazy zapytanie 4. Każdy wiersz zwrócony zapytaniem jest wyświetlany w elemencie listy
fetchAll() - od razu tablica wszystkich wierszy zapytania 4, bez pętli while przy bazie,
w HTML-u pętla foreach w składni alternatywnej (foreach (...): … endforeach;) - przy przeplataniu PHP z HTML-em łatwiej ją czytać niż klamry,
każdy wiersz w <li> z treścią odpowiedzi (R.5.8, R.2.7).
Podgląd: cztery ponumerowane odpowiedzi, w tym ta z zapytania 1.
index.phpPHPzmienione: 8 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$question = false;$answers = [];try {$pdo = new PDO('mysql:host=localhost;dbname=forumpsy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// skrypt 2 - nowa odpowiedź z formularza, przed odczytem listy (od razu będzie na liście)if ($_SERVER['REQUEST_METHOD'] === 'POST') {$answer = trim($_POST['odpowiedz'] ?? '');if ($answer === '') {$message = 'Wpisz treść odpowiedzi - pusta odpowiedź nie została dodana.'; } elseif (mb_strlen($answer) > 1000) {$message = 'Odpowiedź może mieć najwyżej 1000 znaków.'; } else {$statement = $pdo->prepare('INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, :odpowiedz);');$statement->execute(['odpowiedz' => $answer]); } }// skrypt 1 - zapytanie 3: autor pytania nr 1$question = $pdo->query('SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;') ->fetch();// skrypt 3 - zapytanie 4: odpowiedzi na pytanie nr 1$answers = $pdo->query('SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id WHERE odpowiedzi.Pytania_id = 1;') ->fetchAll();} catch (PDOException $exception) {$message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <main> <sectionid="left"> <imgsrc="Avatar.png"alt="Użytkownik forum"width="100"height="100"><?phpif ($question): ?> <h4>Użytkownik: <?= e($question['nick']) ?></h4> <p><?= (int) $question['postow'] ?> postów na forum</p> <p><?= e($question['pytanie']) ?></p><?phpendif; ?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"maxlength="1000"aria-label="Treść odpowiedzi"></textarea><br> <buttontype="submit">Dodaj odpowiedź</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> <h2>Odpowiedzi na pytanie</h2> <ol><?phpforeach ($answersas$row): ?> <li><?= e($row['odpowiedz']) ?></li><?phpendforeach; ?> </ol> </section> </main> <footer> <p> Autor: Bartosz Bryniarski <ahref="http://mojestrony.pl/"target="_blank"rel="noopener">Zobacz nasze realizacje</a> </p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 41: Skrypt 3: nick w znaczniku em i linia pozioma
Kryteria: ,
R.5.7 Skrypt 3 wyświetla pola odpowiedz i nick każdego zwróconego wiersza
R.3.7 W bloku prawym nicki osób publikujących odpowiedzi są wyróżnione znacznikiem <em> </em> (w skrypcie lub HTML)
ok. 2 min
Polecenie z arkusza
Wyświetlane jest pole odpowiedz, pole nick oraz linia pozioma. Nick jest zapisany za pomocą znacznika semantycznego pochylającego tekst i oznaczającego tekst wyróżniony
Nick w <em> (tekst wyróżniony, domyślnie kursywą - R.3.7), pod odpowiedzią <hr>. Odpowiedź i nick, oba przez e() (R.5.7).
index.phpPHPzmienione: 1 linia
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$question = false;$answers = [];try {$pdo = new PDO('mysql:host=localhost;dbname=forumpsy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// skrypt 2 - nowa odpowiedź z formularza, przed odczytem listy (od razu będzie na liście)if ($_SERVER['REQUEST_METHOD'] === 'POST') {$answer = trim($_POST['odpowiedz'] ?? '');if ($answer === '') {$message = 'Wpisz treść odpowiedzi - pusta odpowiedź nie została dodana.'; } elseif (mb_strlen($answer) > 1000) {$message = 'Odpowiedź może mieć najwyżej 1000 znaków.'; } else {$statement = $pdo->prepare('INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, :odpowiedz);');$statement->execute(['odpowiedz' => $answer]); } }// skrypt 1 - zapytanie 3: autor pytania nr 1$question = $pdo->query('SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;') ->fetch();// skrypt 3 - zapytanie 4: odpowiedzi na pytanie nr 1$answers = $pdo->query('SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id WHERE odpowiedzi.Pytania_id = 1;') ->fetchAll();} catch (PDOException $exception) {$message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <main> <sectionid="left"> <imgsrc="Avatar.png"alt="Użytkownik forum"width="100"height="100"><?phpif ($question): ?> <h4>Użytkownik: <?= e($question['nick']) ?></h4> <p><?= (int) $question['postow'] ?> postów na forum</p> <p><?= e($question['pytanie']) ?></p><?phpendif; ?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"maxlength="1000"aria-label="Treść odpowiedzi"></textarea><br> <buttontype="submit">Dodaj odpowiedź</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> <h2>Odpowiedzi na pytanie</h2> <ol><?phpforeach ($answersas$row): ?> <li><?= e($row['odpowiedz']) ?> <em><?= e($row['nick']) ?></em><hr></li><?phpendforeach; ?> </ol> </section> </main> <footer> <p> Autor: Bartosz Bryniarski <ahref="http://mojestrony.pl/"target="_blank"rel="noopener">Zobacz nasze realizacje</a> </p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 42: Zamknięcie połączenia z bazą
Kryteria:
R.5.2 Ostatnią operacją na bazie jest jej zamknięcie (zgodne ze składnią)
ok. 2 min
Polecenie z arkusza
Po wykonaniu operacji na bazie danych skrypt zamyka połączenie z serwerem.
PDO nie ma funkcji close - połączenie zamyka się, gdy znika ostatnie odwołanie do obiektu: $statement = null i $pdo = null. Stoją w bloku finally, który wykona się zawsze po try - po udanym zapisie, po pustej odpowiedzi i po wyjątku (R.5.2).
Dane pytania i lista odpowiedzi są już w zmiennych $question i $answers, więc HTML wypisuje je po zamknięciu połączenia. Podgląd: wysłana odpowiedź jest piąta na liście, podpisana nickiem Gosia (użytkownik nr 5).
index.phpPHPzmienione: 4 linie
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$question = false;$answers = [];$statement = null;try {$pdo = new PDO('mysql:host=localhost;dbname=forumpsy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// skrypt 2 - nowa odpowiedź z formularza, przed odczytem listy (od razu będzie na liście)if ($_SERVER['REQUEST_METHOD'] === 'POST') {$answer = trim($_POST['odpowiedz'] ?? '');if ($answer === '') {$message = 'Wpisz treść odpowiedzi - pusta odpowiedź nie została dodana.'; } elseif (mb_strlen($answer) > 1000) {$message = 'Odpowiedź może mieć najwyżej 1000 znaków.'; } else {$statement = $pdo->prepare('INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, :odpowiedz);');$statement->execute(['odpowiedz' => $answer]); } }// skrypt 1 - zapytanie 3: autor pytania nr 1$question = $pdo->query('SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;') ->fetch();// skrypt 3 - zapytanie 4: odpowiedzi na pytanie nr 1$answers = $pdo->query('SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id WHERE odpowiedzi.Pytania_id = 1;') ->fetchAll();} catch (PDOException $exception) {$message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';} finally {$statement = null;$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <main> <sectionid="left"> <imgsrc="Avatar.png"alt="Użytkownik forum"width="100"height="100"><?phpif ($question): ?> <h4>Użytkownik: <?= e($question['nick']) ?></h4> <p><?= (int) $question['postow'] ?> postów na forum</p> <p><?= e($question['pytanie']) ?></p><?phpendif; ?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"maxlength="1000"aria-label="Treść odpowiedzi"></textarea><br> <buttontype="submit">Dodaj odpowiedź</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> <h2>Odpowiedzi na pytanie</h2> <ol><?phpforeach ($answersas$row): ?> <li><?= e($row['odpowiedz']) ?> <em><?= e($row['nick']) ?></em><hr></li><?phpendforeach; ?> </ol> </section> </main> <footer> <p> Autor: Bartosz Bryniarski <ahref="http://mojestrony.pl/"target="_blank"rel="noopener">Zobacz nasze realizacje</a> </p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 43: Odświeżenie po dodaniu odpowiedzi nie dodaje jej drugi raz
ok. 3 min
Dodatek spoza arkusza - klucz go nie ocenia. Strona po wysłaniu formularza jest wynikiem żądania POST, więc odświeżenie (F5) wysyła tę samą odpowiedź jeszcze raz - na liście pojawia się dubel. Wzorzec PRG (Post/Redirect/Get):
po udanym zapisie $answerAdded = true,
po bloku finally (połączenie jest już zamknięte) header('Location: index.php') każe przeglądarce wczytać stronę jeszcze raz zwykłym żądaniem GET, a exit kończy skrypt - header tylko ustawia nagłówek i nie zatrzymuje kodu,
przy pustej odpowiedzi przekierowania nie ma - strona zostaje z komunikatem.
Nagłówek musi wyjść przed pierwszym znakiem HTML - dlatego kod PHP stoi na samej górze pliku. Sprawdzone na prawdziwym PHP: wysłanie formularza daje odpowiedź 302, potem lista z nową odpowiedzią, a odświeżenie nie dodaje kolejnego wiersza. Podgląd: stan po przekierowaniu.
index.phpPHPzmienione: 8 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$question = false;$answers = [];$statement = null;$answerAdded = false;try {$pdo = new PDO('mysql:host=localhost;dbname=forumpsy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// skrypt 2 - nowa odpowiedź z formularza, przed odczytem listy (od razu będzie na liście)if ($_SERVER['REQUEST_METHOD'] === 'POST') {$answer = trim($_POST['odpowiedz'] ?? '');if ($answer === '') {$message = 'Wpisz treść odpowiedzi - pusta odpowiedź nie została dodana.'; } elseif (mb_strlen($answer) > 1000) {$message = 'Odpowiedź może mieć najwyżej 1000 znaków.'; } else {$statement = $pdo->prepare('INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, :odpowiedz);');$statement->execute(['odpowiedz' => $answer]);$answerAdded = true; } }// skrypt 1 - zapytanie 3: autor pytania nr 1$question = $pdo->query('SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;') ->fetch();// skrypt 3 - zapytanie 4: odpowiedzi na pytanie nr 1$answers = $pdo->query('SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id WHERE odpowiedzi.Pytania_id = 1;') ->fetchAll();} catch (PDOException $exception) {$message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';} finally {$statement = null;$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie}// po dodaniu odpowiedzi przekierowanie (PRG) - odświeżenie strony nie wyśle formularza ponownieif ($answerAdded) {header('Location: index.php');exit;}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <main> <sectionid="left"> <imgsrc="Avatar.png"alt="Użytkownik forum"width="100"height="100"><?phpif ($question): ?> <h4>Użytkownik: <?= e($question['nick']) ?></h4> <p><?= (int) $question['postow'] ?> postów na forum</p> <p><?= e($question['pytanie']) ?></p><?phpendif; ?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"maxlength="1000"aria-label="Treść odpowiedzi"></textarea><br> <buttontype="submit">Dodaj odpowiedź</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> <h2>Odpowiedzi na pytanie</h2> <ol><?phpforeach ($answersas$row): ?> <li><?= e($row['odpowiedz']) ?> <em><?= e($row['nick']) ?></em><hr></li><?phpendforeach; ?> </ol> </section> </main> <footer> <p> Autor: Bartosz Bryniarski <ahref="http://mojestrony.pl/"target="_blank"rel="noopener">Zobacz nasze realizacje</a> </p> </footer></body></html>
Krok 44 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 44: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 3 min
Obie wersje dają komplet 42 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ
float: left sekcji, clear stopki
<main> z siatką
szerokości 40% i 60%
width na blokach
grid-template-columns
kolory
wpisane w każdej regule
zmienne --green-dark, --green-light, --text-dark
biblioteka
mysqli (tabela 1 arkusza)
PDO z wyjątkami
gdzie jest kod
trzy bloki PHP w HTML-u
na górze pliku, w HTML-u tylko wypisanie
odczyt wierszy
mysqli_fetch_row, $row[2]
fetch, fetchAll, $row['nick']
pusta odpowiedź
!empty(), odpowiedź ze spacji przechodzi
trim i === '', komunikat
INSERT
treść wklejona w zapytanie
parametr :odpowiedz
wypisywanie danych
echo wprost
htmlspecialchars przy każdej wartości
zamknięcie bazy
mysqli_close po liście
$pdo = null w finally
po wysłaniu
F5 dodaje odpowiedź drugi raz
przekierowanie
W prawdziwym forum odpowiedź byłaby zapisywana jako zalogowany użytkownik, a nie zawsze jako konto nr 5 - arkusz upraszcza ten fragment, więc obie wersje trzymają się zapytania 1.
Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie wywraca się na apostrofie, nie wykonuje wpisanych skryptów i nie dubluje odpowiedzi po odświeżeniu - a klucz przyjmuje obie.
Krok 45 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 45: Plik przeglądarka.txt i nagranie płyty
ok. 5 min
Polecenie z arkusza
UWAGA: po zakończeniu pracy utwórz w folderze z numerem, którym został podpisany arkusz, plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowałeś poprawność działania witryny. Nagraj płytę z rezultatami pracy.
W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była strona. W folderze z numerem zdającego: Avatar.png, import.jpg, index.php, kw1.png - kw4.png, kwerendy.txt, przeglądarka.txt, styl.css i video.mp4. Nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego.
Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.
To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.
Autor rozwiązania: Bartosz Bryniarski. Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.