INF.03-01-21.06-SGCzerwiec 2021 · 35 kroków · ok. 73 min · 36 kryteriów z klucza
Rozwiązanie krok po kroku: Aplikacja internetowa restauracji (Nowoczesny kod (flex, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza baza z czterema zapytaniami (INSERT, dwa SELECT, UPDATE), strona restauracja.html z formularzem rezerwacji, style w styl_1.css i skrypt rezerwacja.php, który dopisuje rezerwację do bazy - i tak samo spełnia każde kryterium klucza. Inna jest droga:
flexbox zamiast float: panele lewy, prawy i dolny stoją we wspólnym znaczniku <main>, który ustawia je w rzędzie i zawija panel dolny do nowej linii - bez float i clear. Szerokość 60% zostaje na panelu lewym, tak jak ocenia ją klucz,
zmienne CSS dla kolorów z arkusza, panele nazwane po treści (#dish, #offer, #booking), wiersze formularza w blokach <div> zamiast <br>, pochylenie numeru zdającego z arkusza stylów,
PHP na PDO z wyjątkami: dane z formularza są sprawdzane (data w formacie rrrr-mm-dd, liczba osób co najmniej 1, numer telefonu), a do bazy trafiają zapytaniem przygotowanym,
bezpieczne wypisywanie (htmlspecialchars w funkcji e()), komunikaty przy błędnych danych i awarii bazy oraz wzorzec PRG - odświeżenie strony po rezerwacji nie dopisuje jej drugi raz.
Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i zdjęcie pochodzą z prawdziwego archiwum zad1, a podgląd skryptu to wynik prawdziwego uruchomienia kodu w PHP na bazie z arkusza, po wysłaniu formularza.
Jak liczą się punkty. Klucz ma 36 kryteriów (R.1.1 - R.5.6), każde za 1 punkt. Próg zdania to 75%, czyli 27 punktów. Przy każdym kroku jest napisane, które kryteria spełnia.
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-6 · po tym etapie masz 6 z 36 kryteriów (pkt)
Krok 1: Baza danych baza
Kryteria:
R.1.1 Wykonano import tabel do bazy danych o nazwie baza , czynność udokumentowano plikiem o nazwie zad1 w formacie JPEG
ok. 2 min
Polecenie z arkusza
Utwórz nową bazę danych o nazwie baza.
Rozpakuj zad1.zip z pulpitu (hasło Wykon@jTo) do folderu z numerem zdającego: baza.sql i menu.jpg. W phpMyAdmin Nowa → baza albo zapytaniem:
CREATE DATABASE baza CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;
Arkusz nie podaje porównywania, więc wybór należy do Ciebie. Tabele z baza.sql mają zestaw znaków (latin1) zapisany we własnej definicji, więc ustawienie bazy ich nie zmienia.
Typowe błędy
Nazwa jest nietypowa i łatwo ją „poprawić” na restauracja - skrypt łączy się z bazą baza (R.5.2).
Krok 2: Import tabel i zrzut zad1.jpg
Kryteria:
R.1.1 Wykonano import tabel do bazy danych o nazwie baza , czynność udokumentowano plikiem o nazwie zad1 w formacie JPEG
ok. 3 min
Polecenie z arkusza
Zaimportuj do bazy tabele z pliku baza.sql, z wcześniej rozpakowanego archiwum. Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie JPEG i nazwij zad1. Nie kadruj zrzutu. Zrzut powinien obejmować cały ekran, z widocznym paskiem zadań. Na zrzucie powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel.
Import → baza.sql → Wykonaj. Plik tworzy kilka tabel; zadanie korzysta z trzech z obrazu 1: dania (typ: 1 zupy, 2 mięsne, 3 przekąski, 4 napoje), pracownicy (stanowisko: 1 kucharz, 2 pomoc, 3 kelner, 4 barman) i pustej rezerwacje. Zrzut zapisz jako zad1.jpg - w tym arkuszu tak nazywa się zrzut importu.
Typowe błędy
import.jpg zamiast zad1.jpg - klucz R.1.1 sprawdza nazwę pliku.
Krok 3: Zapytanie 1: nowa rezerwacja (INSERT)
Kryteria: ,
R.1.2 Zapisano plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Utworzono zapytanie 1 wstawiające do tabeli rezerwacje rekord z danymi: 1, '2017-07-04', 4, 111222333. pole id jest nadawane automatycznie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO rezerwacje(`nr_stolika`, `data_rez`, `liczba_osob`, `telefon`) VALUES (1, "2017-07-04", 4, "111222333"); (możliwe również INSERT INTO rezerwacje VALUES (...) lub INSERT INTO rezerwacje SET (lista_przypisań) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne jest poprawne zapytanie lub tabela z dodanym rekordem
ok. 3 min
Polecenie z arkusza
Zapytanie 1: wstawiające do tabeli rezerwacje rekord z danymi: numer stolika = 1, data=„2017-07-04”, liczba osób = 4, numer telefonu = „111222333”, wartość pola id nadawana jest automatycznie.
INSERT INTO z jawną listą kolumn - po nazwie tabeli kolumny, po VALUES wartości w tej samej kolejności. Kolumny id nie ma na liście: ma AUTO_INCREMENT, więc numer nada baza. Data i telefon to teksty, więc w apostrofach; numer stolika i liczba osób - bez. Lista kolumn sprawia, że zapytanie nie zależy od kolejności kolumn w tabeli. Każda część w osobnej linii. Zrzut kw1.jpg pokazuje tabelę z nowym rekordem (id = 1), zapytanie trafia do kwerendy.txt.
Typowe błędy
Bez listy kolumn (INSERT INTO rezerwacje VALUES (1, ...)) pierwsza wartość trafi do id - stolik 1 stałby się identyfikatorem.
R.1.4 Utworzono zapytanie 2 wybierające z tabeli dania jedynie pola nazwa i cena dla dań mięsnych. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, cena FROM dania WHERE typ = 2; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne jest dokładnie 5 rekordów: Kaczka i owoc 30, Kurczak pieczony 40, wieprzowy przysmak 35, Mintaj w panierce 30, Alle kotlet 30
ok. 1 min
Polecenie z arkusza
Zapytanie 2: wybierające z tabeli dania jedynie pola nazwa i cena dla dań mięsnych.
Dania mięsne to typ = 2. Wynik (sprawdzony na bazie z archiwum): 5 dań, od „Kaczka i owoc” do „Alle kotlet” - jak w kluczu. Zrzut kw2.jpg.
R.1.5 Utworzono zapytanie 3 wybierające z tabeli dania jedynie pole nazwa dla przekąsek, których cena jest mniejsza od 15 zł. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa FROM dania WHERE typ = 3 AND cena < 15; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są jedynie nazwy: Orzechy i chipsy, Bukiet warzyw
ok. 2 min
Polecenie z arkusza
Zapytanie 3: wybierające z tabeli dania jedynie pole nazwa dla takich przekąsek, których cena jest mniejsza od 15 zł.
Dwa warunki z AND: przekąska (typ = 3) i cena mniejsza od 15 (< - przekąski za równe 15 zł odpadają). Wynik: „Orzechy i chipsy” i „Bukiet warzyw”. Zrzut kw3.jpg.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1INSERT INTO rezerwacje (nr_stolika, data_rez, liczba_osob, telefon)VALUES (1, '2017-07-04', 4, '111222333');-- Zapytanie 2SELECT nazwa, cenaFROM daniaWHERE typ = 2;-- Zapytanie 3SELECT nazwaFROM daniaWHERE typ = 3 AND cena < 15;
R.1.6 Utworzono zapytanie 4 aktualizujące dane w tabeli pracownicy . Dla wszystkich pracowników, którzy zatrudnieni są jako kelner (stanowisko = 3), należy dokonać zmiany stanowiska na barman (stanowisko = 4). W pliku z kwerendami lub na zrzucie istnieje zapis np.: UPDATE pracownicy SET stanowisko = 4 WHERE stanowisko = 3; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne jest prawidłowe zapytanie lub rekordy ze zmienionymi danymi stanowisko = 4 zmieniono dla rekordów z danymi Anna Przybylska, Maria Kowal oraz pozostało stanowisko = 4 dla Ewy Nowackiej
ok. 2 min
Polecenie z arkusza
Zapytanie 4: aktualizujące dane w tabeli pracownicy. Dla wszystkich pracowników, którzy zatrudnieni są jako kelner, należy dokonać zmiany stanowiska na barman.
UPDATE ... SET ... WHERE zmienia istniejące wiersze. Kelner to 3, barman to 4. Zmieniają się dwie osoby - Anna Przybylska i Maria Kowal; Ewa Nowacka już była barmanką (sprawdzone na bazie z archiwum). Zrzut kw4.jpg pokazuje tabelę po zmianie.
Typowe błędy
UPDATE bez WHERE zmienia stanowisko WSZYSTKIM pracownikom - phpMyAdmin wykona to bez pytania.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1INSERT INTO rezerwacje (nr_stolika, data_rez, liczba_osob, telefon)VALUES (1, '2017-07-04', 4, '111222333');-- Zapytanie 2SELECT nazwa, cenaFROM daniaWHERE typ = 2;-- Zapytanie 3SELECT nazwaFROM daniaWHERE typ = 3 AND cena < 15;-- Zapytanie 4UPDATE pracownicySET stanowisko = 4WHERE stanowisko = 3;
Podgląd strony po tym kroku
Etap 2: Strona HTML
Kroki 7-17 · po tym etapie masz 18 z 36 kryteriów (pkt)
R.2.1 Witrynę internetową zapisano w pliku restauracja.html oraz zastosowano prawidłowy standard kodowania polskich znaków lub umieszczono prolog języka HTML 5
ok. 2 min
Polecenie z arkusza
Wymagania dotyczące witryny: Nazwa pliku: restauracja.html. Zastosowany język HTML 5. Zastosowany właściwy standard kodowania polskich znaków.
<!DOCTYPE html>, <html lang="pl"> (przeglądarka i czytnik ekranu wiedzą, że treść jest po polsku) i <meta charset="UTF-8"> jako pierwszy element <head> (R.2.1). Strona jest zwykłym plikiem .html - kod PHP będzie w osobnym pliku.
R.2.2 Nadano tytuł strony: "Restauracja Wszystkie Smaki”
R.3.4 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl_1.css , formatowanie pochodzi jedynie z tego arkusza (poza pochyleniem numeru PESEL) 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
Tytuł strony widoczny na karcie przeglądarki: „Restauracja Wszystkie Smaki”. Arkusz stylów w pliku o nazwie styl_1.css prawidłowo połączony z kodem strony.
<title> (R.2.2), <link> do styl_1.css - z podkreśleniem i cyfrą - i viewport, żeby telefon nie pomniejszał strony do szerokości ekranu komputera. Całe formatowanie będzie w arkuszu stylów, bez atrybutów style w HTML-u (R.3.4).
Typowe błędy
Klucz odbiera R.3.4 także wtedy, gdy choć jeden selektor nie trafia w blok (np. kropka zamiast #).
restauracja.htmlHTMLzmienione: 3 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Restauracja Wszystkie Smaki</title> <linkrel="stylesheet"href="styl_1.css"></head><body></body></html>
Podgląd strony po tym kroku (restauracja.html)
Krok 9: Bloki: baner, trzy panele w main, stopka
Kryteria:
R.2.3 Układ strony zdefiniowano dzieląc stronę na baner, panele lewy i prawy, dolny oraz stopkę. Zastosowano znaczniki sekcji
ok. 2 min
Polecenie z arkusza
Podział strony na bloki: baner, panele lewy i prawy, dolny, stopka. Podział zrealizowany za pomocą znaczników sekcji tak, aby układ bloków strony po uruchomieniu w przeglądarce, był zgodny z Obrazem 2.
Baner to <header>, stopka <footer>. Trzy panele stoją we wspólnym <main> - to główna treść strony i zarazem pojemnik, który w CSS ułoży je zgodnie z obrazem 2:
R.2.7 W panelu prawym zapisano listę numerowaną z trzema elementami: Obiady od 40 zł; Przekąski od 10 zł; Kolacje od 20 zł, zastosowano znaczniki <ol>, <li> oraz ich zamknięcia
ok. 1 min
Polecenie z arkusza
Zawartość panelu prawego: Nagłówek czwartego stopnia o treści: „U nas dobrze zjesz!”. Lista numerowana z trzema elementami: Obiady od 40 zł; Przekąski od 10 zł; Kolacje od 20 zł.
<h4> i lista numerowana <ol> z trzema <li>, każdy z zamknięciem - klucz R.2.7 wymaga znaczników zamykających.
restauracja.htmlHTMLzmienione: 6 linii
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Restauracja Wszystkie Smaki</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <header> <h1>Witamy w restauracji "Wszystkie Smaki"</h1> </header> <main> <sectionid="dish"> <imgsrc="menu.jpg"alt="Nasze danie"> </section> <sectionid="offer"> <h4>U nas dobrze zjesz!</h4> <ol> <li>Obiady od 40 zł</li> <li>Przekąski od 10 zł</li> <li>Kolacje od 20 zł</li> </ol> </section> <sectionid="booking"> </section> </main> <footer> </footer></body></html>
Podgląd strony po tym kroku (restauracja.html)
Krok 13: Panel dolny: formularz wysyłany do rezerwacja.php
Kryteria:
R.2.4 W banerze zapisano nagłówek h1: "Witamy w restauracji "Wszystkie Smaki"”, w panelu prawym zapisano nagłówek h4: "U nas dobrze zjesz!", w panelu dolnym zapisano nagłówek h2: "Zarezerwuj stolik on-line"
ok. 2 min
Polecenie z arkusza
Zawartość panelu dolnego: Nagłówek drugiego stopnia o treści: „Zarezerwuj stolik on-line”. Formularz zgodny z obrazem 2, wysyłający dane do pliku rezerwacja.php metodą bezpieczną.
action="rezerwacja.php" - dokąd trafią dane,
method="post" - „metodę bezpieczną” arkusz rozumie jako POST: dane idą w treści żądania, a nie w adresie strony (w specyfikacji HTTP słowo „bezpieczna” znaczy co innego, ale klucz R.3.3 wymaga POST).
Ten krok domyka nagłówki z R.2.4: <h1> w banerze, <h4> w panelu prawym i <h2> tutaj.
restauracja.htmlHTMLzmienione: 3 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Restauracja Wszystkie Smaki</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <header> <h1>Witamy w restauracji "Wszystkie Smaki"</h1> </header> <main> <sectionid="dish"> <imgsrc="menu.jpg"alt="Nasze danie"> </section> <sectionid="offer"> <h4>U nas dobrze zjesz!</h4> <ol> <li>Obiady od 40 zł</li> <li>Przekąski od 10 zł</li> <li>Kolacje od 20 zł</li> </ol> </section> <sectionid="booking"> <h2>Zarezerwuj stolik on-line</h2> <formaction="rezerwacja.php"method="post"> </form> </section> </main> <footer> </footer></body></html>
Podgląd strony po tym kroku (restauracja.html)
Krok 14: Formularz: trzy pola z podpisami
ok. 3 min
Polecenie z arkusza
Zawiera podpisy i pola: „Data (format rrrr-mm-dd):”, poniżej pole edycyjne. „Ile osób?”, poniżej pole edycyjne typu numerycznego. „Twój numer telefonu:”, poniżej pole edycyjne.
Każda para podpis + pole stoi we własnym bloku <div class="field"> - zamiast <br> po podpisie i po polu. „Poniżej” ustawi za chwilę jedna reguła CSS.
<label for> wskazuje id pola: kliknięcie podpisu ustawia kursor w polu, a czytnik ekranu odczytuje podpis razem z polem,
name - pod tą nazwą wartość trafi do $_POST w skrypcie (date, people, phone),
data zostaje polem tekstowym (arkusz: pole edycyjne z podanym formatem), ale pattern z czterema, dwiema i dwiema cyframi przepuści tylko zapis rrrr-mm-dd, a placeholder podpowiada go w pustym polu,
liczba osób: type="number" (R.2.9) z min="1", telefon: type="tel" - wygląda jak zwykłe pole tekstowe, a na telefonie otwiera klawiaturę z cyframi,
required - przeglądarka nie wyśle formularza z pustym polem.
To wygoda dla użytkownika, a nie zabezpieczenie: dane da się wysłać z pominięciem formularza, dlatego skrypt sprawdzi je jeszcze raz.
Typowe błędy
Pole bez name w ogóle nie zostanie wysłane - skrypt nie znajdzie wartości w $_POST.
restauracja.htmlHTMLzmienione: 12 linii
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Restauracja Wszystkie Smaki</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <header> <h1>Witamy w restauracji "Wszystkie Smaki"</h1> </header> <main> <sectionid="dish"> <imgsrc="menu.jpg"alt="Nasze danie"> </section> <sectionid="offer"> <h4>U nas dobrze zjesz!</h4> <ol> <li>Obiady od 40 zł</li> <li>Przekąski od 10 zł</li> <li>Kolacje od 20 zł</li> </ol> </section> <sectionid="booking"> <h2>Zarezerwuj stolik on-line</h2> <formaction="rezerwacja.php"method="post"> <divclass="field"> <labelfor="date">Data (format rrrr-mm-dd):</label> <inputtype="text"id="date"name="date"placeholder="rrrr-mm-dd"pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"required> </div> <divclass="field"> <labelfor="people">Ile osób?</label> <inputtype="number"id="people"name="people"min="1"required> </div> <divclass="field"> <labelfor="phone">Twój numer telefonu:</label> <inputtype="tel"id="phone"name="phone"autocomplete="tel"required> </div> </form> </section> </main> <footer> </footer></body></html>
Podgląd strony po tym kroku (restauracja.html)
Krok 15: Formularz: pole wyboru zgody
Kryteria:
R.2.9 Na stronie umieszczono cztery pola z podpisami, kolejno: "Data (format rrrr-mm-dd):", pole edycyjne, "Ile osób?", pole numeryczne, "Twój numer telefonu:" pole edycyjne, pole wyboru (checkbox) z napisem: „Zgadzam się na przetwarzanie moich danych osobowych”
ok. 1 min
Polecenie z arkusza
Pole wyboru z napisem: „Zgadzam się na przetwarzanie moich danych osobowych”.
<input type="checkbox"> z podpisem za polem, jak na obrazie 2. Ten blok nie ma klasy field, więc podpis zostanie w tej samej linii co pole. required przy polu wyboru znaczy: musi być zaznaczone. Tu komplet czterech pól z podpisami (R.2.9).
restauracja.htmlHTMLzmienione: 4 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Restauracja Wszystkie Smaki</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <header> <h1>Witamy w restauracji "Wszystkie Smaki"</h1> </header> <main> <sectionid="dish"> <imgsrc="menu.jpg"alt="Nasze danie"> </section> <sectionid="offer"> <h4>U nas dobrze zjesz!</h4> <ol> <li>Obiady od 40 zł</li> <li>Przekąski od 10 zł</li> <li>Kolacje od 20 zł</li> </ol> </section> <sectionid="booking"> <h2>Zarezerwuj stolik on-line</h2> <formaction="rezerwacja.php"method="post"> <divclass="field"> <labelfor="date">Data (format rrrr-mm-dd):</label> <inputtype="text"id="date"name="date"placeholder="rrrr-mm-dd"pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"required> </div> <divclass="field"> <labelfor="people">Ile osób?</label> <inputtype="number"id="people"name="people"min="1"required> </div> <divclass="field"> <labelfor="phone">Twój numer telefonu:</label> <inputtype="tel"id="phone"name="phone"autocomplete="tel"required> </div> <div> <inputtype="checkbox"id="consent"name="consent"required> <labelfor="consent">Zgadzam się na przetwarzanie moich danych osobowych</label> </div> </form> </section> </main> <footer> </footer></body></html>
Podgląd strony po tym kroku (restauracja.html)
Krok 16: Formularz: przyciski WYCZYŚĆ i REZERWUJ
Kryteria: , ,
R.2.8 W panelu dolnym zapisano formularz zawierający dwa przyciski: WYCZYŚĆ oraz REZERWUJ, zastosowano znacznik <form> oraz jego zamknięcie
R.3.2 Przycisk WYCZYŚĆ powoduje w formularzu usunięcie wpisanych danych
R.3.3 Przycisk REZERWUJ wysyła dane do pliku rezerwacja.php metodą POST
ok. 2 min
Polecenie z arkusza
Przycisk „WYCZYŚĆ”, po jego wciśnięciu formularz jest czyszczony. Przycisk „REZERWUJ”, po jego wciśnięciu są wysyłane dane z formularza.
Działanie przycisku zależy od typu: type="reset" przywraca pola do wartości początkowych - czyści formularz bez jednej linii skryptu (R.3.2); type="submit" wysyła formularz pod adres z action metodą POST (R.2.8, R.3.3). Wpisz coś w podglądzie i kliknij WYCZYŚĆ.
Typowe błędy
Sam <button> bez type wewnątrz formularza wysyła dane - WYCZYŚĆ zamiast czyścić przeniósłby na rezerwacja.php.
restauracja.htmlHTMLzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Restauracja Wszystkie Smaki</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <header> <h1>Witamy w restauracji "Wszystkie Smaki"</h1> </header> <main> <sectionid="dish"> <imgsrc="menu.jpg"alt="Nasze danie"> </section> <sectionid="offer"> <h4>U nas dobrze zjesz!</h4> <ol> <li>Obiady od 40 zł</li> <li>Przekąski od 10 zł</li> <li>Kolacje od 20 zł</li> </ol> </section> <sectionid="booking"> <h2>Zarezerwuj stolik on-line</h2> <formaction="rezerwacja.php"method="post"> <divclass="field"> <labelfor="date">Data (format rrrr-mm-dd):</label> <inputtype="text"id="date"name="date"placeholder="rrrr-mm-dd"pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"required> </div> <divclass="field"> <labelfor="people">Ile osób?</label> <inputtype="number"id="people"name="people"min="1"required> </div> <divclass="field"> <labelfor="phone">Twój numer telefonu:</label> <inputtype="tel"id="phone"name="phone"autocomplete="tel"required> </div> <div> <inputtype="checkbox"id="consent"name="consent"required> <labelfor="consent">Zgadzam się na przetwarzanie moich danych osobowych</label> </div> <buttontype="reset">WYCZYŚĆ</button> <buttontype="submit">REZERWUJ</button> </form> </section> </main> <footer> </footer></body></html>
Podgląd strony po tym kroku (restauracja.html)
Krok 17: Stopka z numerem zdającego
ok. 1 min
Polecenie z arkusza
Zawartość stopki: napis o treści: „Stronę internetową opracował: ”, dalej wstawiony Twój numer PESEL, lub w przypadku jego braku, inny numer, którym został podpisany arkusz, numer powinien być zapisany pochyloną czcionką.
Numer stoi w <span class="author"> - pochylenie nada mu arkusz stylów, więc HTML opisuje tylko treść. Na egzaminie w miejscu nazwiska wpisujesz swój PESEL; tu stoi autor rozwiązania.
restauracja.htmlHTMLzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Restauracja Wszystkie Smaki</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <header> <h1>Witamy w restauracji "Wszystkie Smaki"</h1> </header> <main> <sectionid="dish"> <imgsrc="menu.jpg"alt="Nasze danie"> </section> <sectionid="offer"> <h4>U nas dobrze zjesz!</h4> <ol> <li>Obiady od 40 zł</li> <li>Przekąski od 10 zł</li> <li>Kolacje od 20 zł</li> </ol> </section> <sectionid="booking"> <h2>Zarezerwuj stolik on-line</h2> <formaction="rezerwacja.php"method="post"> <divclass="field"> <labelfor="date">Data (format rrrr-mm-dd):</label> <inputtype="text"id="date"name="date"placeholder="rrrr-mm-dd"pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"required> </div> <divclass="field"> <labelfor="people">Ile osób?</label> <inputtype="number"id="people"name="people"min="1"required> </div> <divclass="field"> <labelfor="phone">Twój numer telefonu:</label> <inputtype="tel"id="phone"name="phone"autocomplete="tel"required> </div> <div> <inputtype="checkbox"id="consent"name="consent"required> <labelfor="consent">Zgadzam się na przetwarzanie moich danych osobowych</label> </div> <buttontype="reset">WYCZYŚĆ</button> <buttontype="submit">REZERWUJ</button> </form> </section> </main> <footer> Stronę internetową opracował: <spanclass="author">Bartosz Bryniarski</span> </footer></body></html>
Podgląd strony po tym kroku (restauracja.html)
Etap 3: Styl CSS
Kroki 18-26 · po tym etapie masz 30 z 36 kryteriów (pkt)
Krok 18: Plik styl_1.css: zmienne, tło i krój całej strony
Kryteria:
R.4.1 Ustawiono krój czcionki Verdana dla całej strony (*, body, html lub kontener wszystkich sekcji)
ok. 2 min
Polecenie z arkusza
Plik CSS zawiera formatowanie: Całej strony: kolor tła #F6E5DC, krój czcionki Verdana.
Trzy kolory z arkusza w zmiennych - ciemny brąz #2F180C pojawi się w trzech regułach (baner, stopka, przyciski), więc zmiana odcienia to jedna linia. „Cała strona” to body: tło obejmie okno, a krój odziedziczą wszystkie elementy. Po Verdanie krój zapasowy sans-serif - gdy Verdany nie ma w systemie, przeglądarka weźmie dowolny krój bezszeryfowy (R.4.1).
R.4.4 Ustawiono kolor czcionki khaki dla banera i stopki
ok. 2 min
Polecenie z arkusza
Banera: kolor tła #2F180C, wyrównanie tekstu do środka, kolor czcionki khaki, wysokość 60 px. Stopki: kolor tła #2F180C, kolor czcionki khaki, wysokość 60 px.
Baner i stopka mają te same trzy właściwości, więc dostają jedną regułę z dwoma selektorami; wyśrodkowanie dotyczy tylko banera - na obrazie 2 tekst stopki stoi przy lewej krawędzi. khaki to nazwa koloru w CSS, wpisujesz ją wprost (R.4.4).
Selektory identyfikatorów (#). Szerokość zostaje na samym panelu lewym, tak jak ocenia ją klucz (R.4.6); panel prawy nie ma podanej szerokości - zajmie resztę rzędu (R.4.8).
Typowe błędy
.dish (kropka) zamiast #dish nie trafia w panel - klucz R.3.4 wprost wymienia ten błąd.
R.4.2 Ustawiono kolor tła #F6E5DC dla całej strony (*, body, html lub kontener wszystkich sekcji) , #2F180C dla banera i stopki oraz #D0B6A8 dla panelu dolnego
R.4.3 Ustawiono wyrównanie tekstu do środka dla banera i panelu dolnego
R.4.7 Ustawiono marginesy wewnętrzne (padding) dla panelu prawego na 70px oraz dla panelu dolnego na 30px
ok. 1 min
Polecenie z arkusza
Panelu dolnego: kolor tła #D0B6A8, wyrównanie tekstu do środka, marginesy wewnętrzne 30 px.
Ta reguła domyka kolory tła (R.4.2: strona, baner, stopka i panel dolny), wyśrodkowanie (R.4.3: baner i panel dolny) i marginesy wewnętrzne (R.4.7: 70 px w prawym, 30 px w dolnym).
R.2.10 W stopce zapisano napis o treści: „Stronę internetową opracował: ”, dalej wstawiony numer PESEL (lub innym, którym został podpisany arkusz), numer jest pochylony
ok. 1 min
Polecenie z arkusza
Numer powinien być zapisany pochyloną czcionką.
font-style: italic dla klasy author. Klucz dopuszcza też pochylenie znacznikiem HTML (<i>), ale z arkusza stylów jest spójniej - całe formatowanie w jednym pliku (R.2.10).
Formularz zgodny z obrazem 2, zawiera podpisy i pola: „Data (format rrrr-mm-dd):”, poniżej pole edycyjne, „Ile osób?”, poniżej pole edycyjne typu numerycznego, „Twój numer telefonu:”, poniżej pole edycyjne.
<label> i <input> są elementami liniowymi i stanęłyby obok siebie. display: block zamienia podpis w blok na całą szerokość - pole spada pod niego, a blok <div> zaczyna następną parę od nowej linii. Selektor .field label nie dotyka podpisu zgody, który ma zostać obok pola wyboru.
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu
ok. 3 min
Polecenie z arkusza
Podział zrealizowany za pomocą znaczników sekcji tak, aby układ bloków strony po uruchomieniu w przeglądarce, był zgodny z Obrazem 2.
display: flex na <main> stawia panele w rzędzie, a flex-wrap: wrap pozwala przenieść to, co się nie mieści, do następnej linii,
#offer { flex: 1; } - panel prawy rośnie i zajmuje resztę rzędu obok 60% panelu lewego. Przy wąskim oknie zwęża się (tekst się zawija), zamiast spaść pod zdjęcie - panel z float w wersji podstawowej spada pod nie przy ekranie 1024 px, bo ma szerokość swojej treści,
#booking { flex-basis: 100%; } - panel dolny chce całej szerokości, więc spada pod oba panele. box-sizing: border-box wlicza jego padding 30 px w te 100% - bez tego panel byłby o 60 px szerszy od strony i pojawiłby się poziomy pasek przewijania.
Baner i stopka są poza pojemnikiem, więc stoją nad nim i pod nim same z siebie - bez float nie ma też clear (R.3.1).
Dlaczego nie siatka? W siatce (grid) procent szerokości elementu liczy się od jego kolumny: panel z width: 60% w kolumnie szerokiej na 60% strony zająłby 36% strony. Szerokość trzeba by przenieść do grid-template-columns, a klucz z tego arkusza ocenia ją na panelu lewym. Flex liczy procenty od pojemnika, więc wymiar z arkusza działa wprost.
Typowe błędy
width: 100% zamiast flex-basis przy panelu z paddingiem bez box-sizing: border-box daje poziomy pasek przewijania.
Kroki 27-33 · po tym etapie masz 36 z 36 kryteriów (pkt)
Krok 27: Plik rezerwacja.php i komunikat
Kryteria: ,
R.5.1 Skrypt napisano w języku PHP, zawiera przynajmniej jedną poprawnie zapisaną instrukcję języka
R.5.4 Skrypt wyświetla tekst: "Dodano rezerwację do bazy"
ok. 3 min
Polecenie z arkusza
Wymagania dotyczące skryptu: Napisany w języku PHP, w pliku o nazwie rezerwacja.php. Jest uruchamiany po wypełnieniu formularza i wciśnięciu przycisku REZERWUJ. Nie jest wymagane sprawdzenie poprawności danych wpisanych w formularz. Wypisuje tekst „Dodano rezerwację do bazy”.
Utwórz rezerwacja.php w tym samym folderze co strona - tam wysyła formularz (action). Plik ma dwie części:
na górze blok PHP, który przygotuje komunikat - tu za chwilę dojdą dane, ich sprawdzenie i zapis do bazy,
pod nim zwykły dokument HTML5 z kodowaniem, tytułem i tym samym arkuszem stylów co strona; komunikat stoi w bloku #booking, więc dostaje tło i wyśrodkowanie panelu rezerwacji. Wypisuje go skrót <?= ... ?> (to samo co <?php echo ... ?>), a odnośnik prowadzi z powrotem do restauracji.
Logika jest w jednym miejscu, a w HTML-u zostaje tylko wypisywanie (R.5.1, R.5.4). Podgląd obok to wynik prawdziwego uruchomienia pliku po wysłaniu formularza.
rezerwacja.phpPHPnowy plik
<?php$message = 'Dodano rezerwację do bazy';?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Rezerwacja stolika</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <sectionid="booking"> <h2>Rezerwacja stolika</h2> <p><?= $message?></p> <p><ahref="restauracja.html">Wróć do strony restauracji</a></p> </section></body></html>
Podgląd strony po tym kroku (rezerwacja.php)
Krok 28: Połączenie z bazą w bloku try
Kryteria:
R.5.2 Skrypt wykonuje połączenie z serwerem bazy danych i wybór bazy o nazwie baza
ok. 3 min
Polecenie z arkusza
Łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie baza.
new PDO(...) - adres w jednym napisie: serwer localhost, baza baza, kodowanie utf8mb4; dalej użytkownik root i puste hasło (R.5.2),
ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, zamiast zwracać false, którego łatwo nie zauważyć,
try / catch (PDOException ...) - gdy baza nie działa, użytkownik zobaczy zwykłe zdanie zamiast komunikatu PHP ze szczegółami serwera. Komunikat o sukcesie ustawia się w try, czyli dopiero wtedy, gdy wszystko wcześniej się udało.
PDO zamiast mysqli z tabeli 1 - klucz przyjmuje obie biblioteki (uwaga przy R.5). Klucz każe sprawdzać skrypt w XAMPP 7.4.9, dlatego kod nie korzysta ze składni dodanej dopiero w PHP 8 (np. catch bez zmiennej).
rezerwacja.phpPHPzmienione: 10 linii
<?php$message = '';try {$pdo = new PDO('mysql:host=localhost;dbname=baza;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);$message = 'Dodano rezerwację do bazy';} catch (PDOException $exception) {$message = 'Nie udało się zapisać rezerwacji. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Rezerwacja stolika</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <sectionid="booking"> <h2>Rezerwacja stolika</h2> <p><?= $message?></p> <p><ahref="restauracja.html">Wróć do strony restauracji</a></p> </section></body></html>
Krok 29: Pobranie danych z formularza i ich sprawdzenie
Kryteria:
R.5.5 Skrypt prawidłowo pobiera dane z tablicy $_POST np. $telefon = $_POST['tel']; lub bezpośrednio do zapytania
ok. 4 min
Polecenie z arkusza
Pobiera dane z formularza.
Dane wysłane metodą POST są w tablicy $_POST, pod nazwami z atrybutów name formularza (R.5.5). Arkusz nie wymaga ich sprawdzania, ale w prawdziwej rezerwacji to podstawa:
?? '' - gdy pola nie ma (np. plik otwarty wprost z paska adresu), zmienna dostaje pusty napis zamiast ostrzeżenia „Undefined array key”; trim obcina spacje z brzegów,
data: DateTime::createFromFormat('!Y-m-d', ...) czyta napis w formacie rrrr-mm-dd, a porównanie z powrotem z tym samym formatem odrzuca daty, których nie ma - 2026-02-31 PHP po cichu zamieniłby na 3 marca,
liczba osób: filter_var z FILTER_VALIDATE_INT i min_range 1 zwraca liczbę całkowitą albo false (dla 0, -2, 2.5, abc),
telefon: wyrażenie regularne przepuszcza 9-15 cyfr, spacji, plusów i myślników.
Każdy błąd trafia do listy $errors. Połączenie z bazą powstaje teraz tylko dla poprawnych danych (if ($errors === [])), a przy błędach pod napisem „Rezerwacja nie została zapisana.” pojawia się lista zdań dla użytkownika. Sprawdzone na prawdziwym PHP: 17.10.2026, 0 osób i telefon abc dają trzy komunikaty.
Typowe błędy
$_POST['data'] przy polu name="date" daje pustą wartość - klucz w $_POST to dokładnie name pola. Klucz R.5.5 wymaga odczytu z tablicy $_POST, dlatego nie ma tu filter_input.
rezerwacja.phpPHPzmienione: 30 linii
<?php// dane z formularza i ich sprawdzenie$date = trim($_POST['date'] ?? '');$people = filter_var($_POST['people'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);$phone = trim($_POST['phone'] ?? '');$errors = [];$parsedDate = DateTime::createFromFormat('!Y-m-d', $date);if ($parsedDate === false || $parsedDate->format('Y-m-d') !== $date) {$errors[] = 'Podaj datę w formacie rrrr-mm-dd.';}if ($people === false) {$errors[] = 'Podaj liczbę osób - co najmniej 1.';}if (preg_match('/^[0-9 +-]{9,15}$/', $phone) !== 1) {$errors[] = 'Podaj numer telefonu.';}$message = 'Rezerwacja nie została zapisana.';if ($errors === []) {try {$pdo = new PDO('mysql:host=localhost;dbname=baza;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);$message = 'Dodano rezerwację do bazy'; } catch (PDOException $exception) {$message = 'Nie udało się zapisać rezerwacji. Spróbuj ponownie za chwilę.'; }}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Rezerwacja stolika</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <sectionid="booking"> <h2>Rezerwacja stolika</h2> <p><?= $message?></p><?phpforeach ($errorsas$error): ?> <prole="alert"><?= $error?></p><?phpendforeach; ?> <p><ahref="restauracja.html">Wróć do strony restauracji</a></p> </section></body></html>
R.5.6 Skrypt wysyła do bazy zapytanie wstawiające rekord w tabeli rezerwacje o wartościach podanych w formularzu (bez numeru stolika). Należy sprawdzić czy rekord został dodany do bazy w phpMyAdmin
ok. 4 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie wstawiające rekord do tabeli rezerwacje z danymi pobranymi z formularza bez wypełnionego numeru stolika (zmodyfikowane zapytanie 1, patrz punkt „Operacje na bazie danych”).
Zapytanie 1 bez numeru stolika: na liście kolumn tylko trzy pola, a w VALUESparametry:date, :people, :phone zamiast wartości. Numer stolika zostaje pusty (NULL), a id nada baza (R.5.6).
prepare wysyła do bazy samą treść zapytania, execute([...]) - osobno dane. Napis z formularza nigdy nie stanie się częścią kodu SQL, nawet gdyby zawierał apostrof albo średnik,
nie ma apostrofów wokół tekstów ani sklejania napisów - o typy i cudzysłowy dba PDO.
Sprawdzone na prawdziwej bazie: po wysłaniu formularza w tabeli rezerwacje jest nowy wiersz z datą 2026-10-17, 2 osobami i NULL w numerze stolika.
rezerwacja.phpPHPzmienione: 4 linie
<?php// dane z formularza i ich sprawdzenie$date = trim($_POST['date'] ?? '');$people = filter_var($_POST['people'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);$phone = trim($_POST['phone'] ?? '');$errors = [];$parsedDate = DateTime::createFromFormat('!Y-m-d', $date);if ($parsedDate === false || $parsedDate->format('Y-m-d') !== $date) {$errors[] = 'Podaj datę w formacie rrrr-mm-dd.';}if ($people === false) {$errors[] = 'Podaj liczbę osób - co najmniej 1.';}if (preg_match('/^[0-9 +-]{9,15}$/', $phone) !== 1) {$errors[] = 'Podaj numer telefonu.';}$message = 'Rezerwacja nie została zapisana.';if ($errors === []) {try {$pdo = new PDO('mysql:host=localhost;dbname=baza;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);$statement = $pdo->prepare('INSERT INTO rezerwacje (data_rez, liczba_osob, telefon) VALUES (:date, :people, :phone)' );$statement->execute(['date' => $date, 'people' => $people, 'phone' => $phone]);$message = 'Dodano rezerwację do bazy'; } catch (PDOException $exception) {$message = 'Nie udało się zapisać rezerwacji. Spróbuj ponownie za chwilę.'; }}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Rezerwacja stolika</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <sectionid="booking"> <h2>Rezerwacja stolika</h2> <p><?= $message?></p><?phpforeach ($errorsas$error): ?> <prole="alert"><?= $error?></p><?phpendforeach; ?> <p><ahref="restauracja.html">Wróć do strony restauracji</a></p> </section></body></html>
Krok 31: Zamknięcie połączenia i sprawdzenie w phpMyAdmin
Kryteria:
R.5.3 Ostatnią operacją na bazie jest jej zamknięcie (zgodne ze składnią)
ok. 2 min
Polecenie z arkusza
Na końcu działania skrypt zamyka połączenie z serwerem.
PDO nie ma funkcji „zamknij”: połączenie zamyka się, gdy znika ostatnie odwołanie do obiektu. Dlatego najpierw $statement = null (przygotowane zapytanie też trzyma połączenie), a potem $pdo = null - zaraz po zapisie, więc to ostatnia operacja na bazie (R.5.3).
Sprawdź całość: otwórz http://localhost/…/restauracja.html, wypełnij formularz i kliknij REZERWUJ - przeglądarka pokazuje „Dodano rezerwację do bazy”, a w phpMyAdmin w tabeli rezerwacje jest nowy wiersz (obok: stan tabeli z prawdziwej bazy po jednej rezerwacji z formularza i zapytaniu 1).
rezerwacja.phpPHPzmienione: 2 linie
<?php// dane z formularza i ich sprawdzenie$date = trim($_POST['date'] ?? '');$people = filter_var($_POST['people'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);$phone = trim($_POST['phone'] ?? '');$errors = [];$parsedDate = DateTime::createFromFormat('!Y-m-d', $date);if ($parsedDate === false || $parsedDate->format('Y-m-d') !== $date) {$errors[] = 'Podaj datę w formacie rrrr-mm-dd.';}if ($people === false) {$errors[] = 'Podaj liczbę osób - co najmniej 1.';}if (preg_match('/^[0-9 +-]{9,15}$/', $phone) !== 1) {$errors[] = 'Podaj numer telefonu.';}$message = 'Rezerwacja nie została zapisana.';if ($errors === []) {try {$pdo = new PDO('mysql:host=localhost;dbname=baza;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);$statement = $pdo->prepare('INSERT INTO rezerwacje (data_rez, liczba_osob, telefon) VALUES (:date, :people, :phone)' );$statement->execute(['date' => $date, 'people' => $people, 'phone' => $phone]);$statement = null;$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie$message = 'Dodano rezerwację do bazy'; } catch (PDOException $exception) {$message = 'Nie udało się zapisać rezerwacji. Spróbuj ponownie za chwilę.'; }}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Rezerwacja stolika</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <sectionid="booking"> <h2>Rezerwacja stolika</h2> <p><?= $message?></p><?phpforeach ($errorsas$error): ?> <prole="alert"><?= $error?></p><?phpendforeach; ?> <p><ahref="restauracja.html">Wróć do strony restauracji</a></p> </section></body></html>
Podgląd strony po tym kroku (rezerwacja.php)
Krok 32: Podsumowanie rezerwacji i bezpieczne wypisywanie
ok. 2 min
Dodatek spoza arkusza - klucz go nie ocenia. Pod komunikatem z arkusza skrypt powtarza, co zapisał: liczbę osób, datę i telefon (zmienna $saved mówi, że zapis się udał). To dane od użytkownika, więc przechodzą przez funkcję e() z góry pliku - htmlspecialchars z ENT_QUOTES zamienia <, >, & i cudzysłowy na encje. Znacznik wpisany w pole nie wykona się na stronie, tylko pokaże jako tekst.
Przez e() idą też komunikat i błędy - zasada „wszystko, co wypisujesz, przechodzi przez e()” jest prostsza do pilnowania niż rozstrzyganie przy każdej zmiennej, skąd pochodzi.
rezerwacja.phpPHPzmienione: 12 linii
<?phpfunctione($value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}// dane z formularza i ich sprawdzenie$date = trim($_POST['date'] ?? '');$people = filter_var($_POST['people'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);$phone = trim($_POST['phone'] ?? '');$errors = [];$parsedDate = DateTime::createFromFormat('!Y-m-d', $date);if ($parsedDate === false || $parsedDate->format('Y-m-d') !== $date) {$errors[] = 'Podaj datę w formacie rrrr-mm-dd.';}if ($people === false) {$errors[] = 'Podaj liczbę osób - co najmniej 1.';}if (preg_match('/^[0-9 +-]{9,15}$/', $phone) !== 1) {$errors[] = 'Podaj numer telefonu.';}$message = 'Rezerwacja nie została zapisana.';$saved = false;if ($errors === []) {try {$pdo = new PDO('mysql:host=localhost;dbname=baza;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);$statement = $pdo->prepare('INSERT INTO rezerwacje (data_rez, liczba_osob, telefon) VALUES (:date, :people, :phone)' );$statement->execute(['date' => $date, 'people' => $people, 'phone' => $phone]);$statement = null;$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie$saved = true;$message = 'Dodano rezerwację do bazy'; } catch (PDOException $exception) {$message = 'Nie udało się zapisać rezerwacji. Spróbuj ponownie za chwilę.'; }}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Rezerwacja stolika</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <sectionid="booking"> <h2>Rezerwacja stolika</h2> <p><?= e($message) ?></p><?phpif ($saved): ?> <p>Rezerwacja dla <?= e($people) ?> os. na dzień <?= e($date) ?>, telefon <?= e($phone) ?>.</p><?phpendif; ?><?phpforeach ($errorsas$error): ?> <prole="alert"><?= e($error) ?></p><?phpendforeach; ?> <p><ahref="restauracja.html">Wróć do strony restauracji</a></p> </section></body></html>
Podgląd strony po tym kroku (rezerwacja.php)
Krok 33: Odświeżenie nie dodaje rezerwacji drugi raz (PRG)
ok. 4 min
Dodatek spoza arkusza - klucz go nie ocenia. Strona z komunikatem jest wynikiem żądania POST: odświeżenie (F5) wysyła formularz jeszcze raz i w bazie są dwie takie same rezerwacje. Wzorzec PRG (Post/Redirect/Get) to usuwa, a napis z arkusza zostaje:
session_start() na samym początku pliku - sesja przechowa rezerwację między dwoma żądaniami,
cała obsługa formularza stoi w if ($_SERVER['REQUEST_METHOD'] === 'POST'). Po udanym zapisie skrypt wkłada rezerwację do $_SESSION i wysyła header('Location: rezerwacja.php'), a potem exit - header tylko ustawia nagłówek odpowiedzi i nie zatrzymuje skryptu,
przeglądarka wczytuje plik jeszcze raz, zwykłym żądaniem GET. Gałąź elseif odczytuje rezerwację z sesji, od razu ją usuwa (unset) i wypisuje „Dodano rezerwację do bazy” z podsumowaniem,
kolejne odświeżenie nie ma już czego pokazać - zostaje zdanie z odnośnikiem do formularza, bez drugiego zapisu.
Zmienne dostają wartości początkowe na górze pliku, więc każda gałąź ma z czego wypisać. Sprawdzone na prawdziwym PHP: wysłanie formularza (przekierowanie 302), komunikat po przekierowaniu, odświeżenie bez drugiego wiersza w tabeli, data 2026-13-01, telefon ze znacznikiem <b> i złe hasło do bazy - za każdym razem bez ostrzeżeń PHP.
Typowe błędy
header('Location: ...') po wypisaniu choćby jednego znaku (także pustej linii przed <?php) nie zadziała - nagłówki muszą wyjść przed treścią strony.
rezerwacja.phpPHPzmienione: 49 linii
<?phpsession_start();functione($value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}$date = '';$people = '';$phone = '';$errors = [];$message = 'Formularz rezerwacji jest na stronie restauracji.';$saved = false;if ($_SERVER['REQUEST_METHOD'] === 'POST') {// dane z formularza i ich sprawdzenie$date = trim($_POST['date'] ?? '');$people = filter_var($_POST['people'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);$phone = trim($_POST['phone'] ?? '');$parsedDate = DateTime::createFromFormat('!Y-m-d', $date);if ($parsedDate === false || $parsedDate->format('Y-m-d') !== $date) {$errors[] = 'Podaj datę w formacie rrrr-mm-dd.'; }if ($people === false) {$errors[] = 'Podaj liczbę osób - co najmniej 1.'; }if (preg_match('/^[0-9 +-]{9,15}$/', $phone) !== 1) {$errors[] = 'Podaj numer telefonu.'; }$message = 'Rezerwacja nie została zapisana.';if ($errors === []) {try {$pdo = new PDO('mysql:host=localhost;dbname=baza;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);$statement = $pdo->prepare('INSERT INTO rezerwacje (data_rez, liczba_osob, telefon) VALUES (:date, :people, :phone)' );$statement->execute(['date' => $date, 'people' => $people, 'phone' => $phone]);$statement = null;$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie// PRG: rezerwacja do sesji i przekierowanie - odświeżenie nie zapisze jej drugi raz$_SESSION['booking'] = ['date' => $date, 'people' => $people, 'phone' => $phone];header('Location: rezerwacja.php');exit; } catch (PDOException $exception) {$message = 'Nie udało się zapisać rezerwacji. Spróbuj ponownie za chwilę.'; } }} elseif (isset($_SESSION['booking'])) {// żądanie GET po przekierowaniu: rezerwacja z sesji, od razu usunięta ['date' => $date, 'people' => $people, 'phone' => $phone] = $_SESSION['booking'];unset($_SESSION['booking']);$saved = true;$message = 'Dodano rezerwację do bazy';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Rezerwacja stolika</title> <linkrel="stylesheet"href="styl_1.css"></head><body> <sectionid="booking"> <h2>Rezerwacja stolika</h2> <p><?= e($message) ?></p><?phpif ($saved): ?> <p>Rezerwacja dla <?= e($people) ?> os. na dzień <?= e($date) ?>, telefon <?= e($phone) ?>.</p><?phpendif; ?><?phpforeach ($errorsas$error): ?> <prole="alert"><?= e($error) ?></p><?phpendforeach; ?> <p><ahref="restauracja.html">Wróć do strony restauracji</a></p> </section></body></html>
Krok 34 · po tym etapie masz 36 z 36 kryteriów (pkt)
Krok 34: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 3 min
Obie wersje dają komplet 36 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ
float: left paneli i clear panelu dolnego
<main> z display: flex i flex-wrap
panele
#left, #right, #bottom
#dish, #offer, #booking w <main>
kolory
wpisane w każdą regułę
zmienne CSS
formularz
<br> po podpisach, pola bez ograniczeń
bloki <div>, pattern, min, required
pochylenie numeru
znacznik <i>
klasa i font-style w CSS
biblioteka
mysqli (tabela 1 arkusza)
PDO z wyjątkami
dane z formularza
wklejone do zapytania
sprawdzone i podane jako parametry
wynik
sam tekst
dokument HTML z podsumowaniem przez e()
odświeżenie po zapisie
dodaje rezerwację drugi raz
PRG - przekierowanie, napis z sesji
W prawdziwym projekcie dane logowania do bazy trzyma się zwykle w osobnym pliku (np. config.php), a formularz chroni się tokenem CSRF - klucz tego nie wymaga.
Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i łatwiej ją napisać bez dokumentacji pod ręką. Nowoczesna jest odporna na błędne i podmienione dane z formularza. Obie biblioteki klucz przyjmuje.
Krok 35 · po tym etapie masz 36 z 36 kryteriów (pkt)
Krok 35: Plik przeglądarka.txt i nagranie płyty
ok. 5 min
Polecenie z arkusza
Po zakończeniu pracy utwórz plik tekstowy. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowałeś poprawność działania witryny. Plik zapisz w folderze z Twoim numerem PESEL (lub innym, którym został podpisany arkusz) jako przeglądarka.txt. Nagraj płytę z rezultatami pracy. W folderze z numerem PESEL (lub innym, którym został podpisany arkusz) powinny się znajdować pliki: kw1.jpg, kw2.jpg, kw3.jpg, kw4.jpg, kwerendy.txt, menu.jpg, przeglądarka.txt, restauracja.html, rezerwacja.php, styl_1.css, zad1.jpg, ewentualnie inne przygotowane pliki.
W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była strona przez http://localhost/.... Sprawdź listę plików (zrzut importu nazywa się tu zad1.jpg), 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.