INF.03-10-25.06-SGCzerwiec 2025 · 49 kroków · ok. 85 min · 39 kryteriów z klucza
Rozwiązanie krok po kroku: Portal szkoleniowy z bazą danych (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza szkolenia, cztery zapytania, zdjęcie przeskalowane do 150 px, strona index.php z tabelą kursów i formularzem zapisu, style w styl.css - i tak samo spełnia każde kryterium klucza. Inna jest droga:
układ na siatce CSS (display: grid i grid-template-columns) zamiast flex, kolory z arkusza w zmiennych CSS, formularz ułożony siatką bez znaczników <br>,
PHP na PDO zamiast funkcji mysqli_*: zapytanie przygotowane z parametrami nazwanymi dla INSERT, wyjątki zamiast sprawdzania błędów ręcznie, dane logowania w osobnym pliku config.php,
logika przed HTML-em: wszystkie operacje na bazie wykonują się na początku pliku, a niżej jest już tylko szablon strony, który wypisuje gotowe dane przez funkcję e() (escapowanie),
PRG po zapisie: po dodaniu uczestnika strona przekierowuje sama na siebie, więc odświeżenie nie doda go drugi raz.
Wersja podstawowa (zakładka obok) pisze kod najprostszymi funkcjami z tabeli 1 arkusza i dopiero na końcu go poprawia; tu kod jest od razu taki, jak w prawdziwym projekcie. Na końcu jest krótkie porównanie obu wersji i podpowiedź, którą wybrać na egzaminie.
Baza i zdjęcia pochodzą z prawdziwego archiwum pliki9 (załącznik na stronie arkusza, hasło w treści zadania), a podgląd każdego kroku z PHP to wynik prawdziwego uruchomienia strony na serwerze z tą bazą.
Jak liczą się punkty. Klucz ma 39 kryteriów (R.1.1 - R.5.10), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 30 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-7 · po tym etapie masz 6 z 39 kryteriów (pkt)
Krok 1: Baza danych szkolenia
Kryteria:
R.1.1 Wykonany import tabel do bazy danych szkolenia , czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie szkolenia, z zestawem polskich znaków (np. utf8_polish_ci).
W phpMyAdmin (http://localhost/phpmyadmin) kliknij Nowa, wpisz nazwę szkolenia i wybierz metodę porównywania utf8_polish_ci. To samo w SQL:
CREATE DATABASE szkolenia CHARACTER SET utf8 COLLATE utf8_polish_ci;
O kolejności sortowania decyduje jednak porównywanie zapisane w definicji tabel z szkolimy.sql (utf8_polish_ci, np. „ł” po „l”) - ustawienie bazy dziedziczą tylko tabele zakładane później bez własnego ustawienia. Baza nazywa się szkolenia, plik do importu szkolimy.sql - to różne słowa.
Typowe błędy
Literówka w nazwie bazy odcina też punkty za skrypty - połączenie w PHP podaje nazwę bazy (R.5.1).
Krok 2: Import tabel z pliku szkolimy.sql
Kryteria:
R.1.1 Wykonany import tabel do bazy danych szkolenia , czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Z rozpakowanego archiwum zaimportuj tabele z pliku szkolimy.sql do utworzonej bazy.
Zaznacz bazę szkolenia w lewym panelu, karta Import, plik szkolimy.sql, Wykonaj. Powstają trzy tabele z ilustracji 1: kursy (8 kursów, kod 1-8 to zarazem nazwa pliku zdjęcia), uczestnicy (29 osób, klucz id_uczestnika z AUTO_INCREMENT) i tabela łącząca kursy_uczestnicy (46 zapisów).
Typowe błędy
Import bez zaznaczonej bazy szkolenia tworzy tabele w innej bazie - strona ich nie znajdzie.
Krok 3: Zrzut ekranu import.png
Kryteria:
R.1.1 Wykonany import tabel do bazy danych szkolenia , czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie PNG pod nazwą import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań. Na zrzucie powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel.
Cały ekran (Print Screen), z komunikatem o udanym imporcie albo listą trzech tabel, zapis jako import.png. W tym arkuszu zrzut importu jest w PNG, a zrzuty zapytań w JPEG.
Typowe błędy
Wykadrowany zrzut (bez paska zadań) nie jest oceniany.
Krok 4: Zapytanie 1: kursy od najtańszego
Kryteria: ,
R.1.2 Zapisany plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Wykonane zapytanie 1 wybierające jedynie kod, nazwę i cenę kursów posortowane rosnąco według ceny. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT kod, nazwa, cena FROM kursy ORDER BY cena; (ASC opcjonalne) oraz wynik działania zapytania udokumentowany zrzutem, na którym widocznych jest dokładnie 8 rekordów dla wszystkich kursów, pierwszy rekord 5 gastronomia 1000, ostatni 7 spawanie 3200
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie kod, nazwę i cenę kursów posortowane rosnąco według ceny.
Każda klauzula w osobnej linii - tak zapytanie czyta się od góry do dołu: co (SELECT kod, nazwa, cena), skąd (FROM kursy), w jakiej kolejności (ORDER BY cena ASC). ASC jest domyślne, ale wpisane wprost mówi czytelnikowi, że kierunek jest zamierzony.
Wynik: 8 kursów, od 5 gastronomia 1000 do 7 spawanie 3200 (klucz R.1.3). Zapytanie do kwerendy.txt, zrzut kw1.jpg.
Typowe błędy
SELECT * łamie słowo „jedynie” - zwraca też max_liczba_miejsc.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT kod, nazwa, cenaFROM kursyORDER BY cena ASC;
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: nazwy wszystkich kursów
Kryteria:
R.1.4 Wykonane zapytanie 2 wybierające jedynie nazwy wszystkich kursów. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa FROM kursy; oraz wynik działania zapytania udokumentowany zrzutem, na którym widocznych jest dokładnie 8 rekordów: pierwszy jest język angielski, ostatni rysunek techniczny
ok. 1 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie nazwy wszystkich kursów.
Jedna kolumna, bez warunku i bez sortowania - 8 nazw w kolejności z tabeli, od języka angielskiego do rysunku technicznego (klucz R.1.4). Zrzut kw2.jpg. W tej samej kolejności kursy pojawią się w liście rozwijanej (ilustracja 4).
kwerendy.txtTekstzmienione: 4 linie
-- Zapytanie 1SELECT kod, nazwa, cenaFROM kursyORDER BY cena ASC;-- Zapytanie 2SELECT nazwaFROM kursy;
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: nowy uczestnik Tadeusz Wysocki
Kryteria:
R.1.5 Wykonane zapytanie 3 wstawiające rekord do tabeli uczestnicy o danych: Tadeusz Wysocki, wiek 36 lat. Klucz główny nadawany automatycznie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO `uczestnicy`(`imie`, `nazwisko`, `wiek`) VALUES ('Tadeusz','Wysocki','36'); oraz wynik działania zapytania udokumentowany zrzutem, na którym widoczny jest komunikat o poprawnym wstawieniu wiersza lub tabela ze wstawionym wierszem
ok. 2 min
Polecenie z arkusza
Zapytanie 3: wstawiające rekord do tabeli uczestnicy o danych: Tadeusz Wysocki, wiek 36 lat. Klucz główny nadawany automatycznie.
Lista kolumn podana jawnie (imie, nazwisko, wiek), bez id_uczestnika: kolumna ma AUTO_INCREMENT, więc baza nada numer 30 sama. Jawna lista kolumn ma też drugą zaletę - zapytanie działa dalej, gdy ktoś dopisze do tabeli nową kolumnę.
Komunikat „1 wiersz wstawiony” to zrzut kw3.jpg (klucz R.1.5).
Typowe błędy
Wpisanie własnego id_uczestnika nie spełnia „klucz główny nadawany automatycznie”.
kwerendy.txtTekstzmienione: 4 linie
-- Zapytanie 1SELECT kod, nazwa, cenaFROM kursyORDER BY cena ASC;-- Zapytanie 2SELECT nazwaFROM kursy;-- Zapytanie 3INSERT INTO uczestnicy (imie, nazwisko, wiek)VALUES ('Tadeusz', 'Wysocki', 36);
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: liczba zapisanych na każdy kurs
Kryteria:
R.1.6 Wykonane zapytanie 4 tworzące zestawienie, na którym widoczne są nazwy wszystkich kursów oraz liczby osób zapisanych na te kursy. Nazwa kolumny z liczbą osób (alias): „Zapisanych”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT COUNT(kod_kursu) AS 'Zapisanych', nazwa FROM kursy JOIN kursy_uczestnicy ON kod = kod_kursu GROUP BY nazwa; (AS opcjonalne) oraz wynik działania zapytania udokumentowany zrzutem, na którym widoczne są dane: 6 florystyka, 12 gastronomia, 7 informatyka, 2 język angielski, 6 język francuski, 6 język niemiecki, 4 rysunek techniczny, 3 spawanie
ok. 4 min
Polecenie z arkusza
Zapytanie 4: tworzące zestawienie, na którym widoczne są nazwy wszystkich kursów oraz liczby osób zapisanych na te kursy. Nazwa kolumny z liczbą osób (alias): „Zapisanych”.
Złączenie z aliasami tabel (kursy AS k, kursy_uczestnicy AS ku) - każda kolumna mówi, z której tabeli pochodzi, więc zapytanie nie zależy od tego, że nazwy kolumn przypadkiem się nie powtarzają:
JOIN … ON ku.kod_kursu = k.kod - do każdego zapisu dołączony kurs,
COUNT(ku.id_uczestnika) AS Zapisanych - liczba osób w grupie, z aliasem z arkusza,
GROUP BY k.kod, k.nazwa - grupa = jeden kurs (po kluczu, więc dwa kursy o tej samej nazwie by się nie zlały),
ORDER BY k.nazwa - kolejność alfabetyczna podana wprost; samo GROUP BY nie gwarantuje kolejności.
Wynik: florystyka 6, gastronomia 12, informatyka 7, język angielski 2, francuski 6, niemiecki 6, rysunek techniczny 4, spawanie 3 - jak w kluczu R.1.6. Zrzut kw4.jpg.
Typowe błędy
COUNT bez GROUP BY daje jeden wiersz z liczbą wszystkich zapisów (46).
kwerendy.txtTekstzmienione: 7 linii
-- Zapytanie 1SELECT kod, nazwa, cenaFROM kursyORDER BY cena ASC;-- Zapytanie 2SELECT nazwaFROM kursy;-- Zapytanie 3INSERT INTO uczestnicy (imie, nazwisko, wiek)VALUES ('Tadeusz', 'Wysocki', 36);-- Zapytanie 4SELECT k.nazwa, COUNT(ku.id_uczestnika) AS ZapisanychFROM kursy AS kJOIN kursy_uczestnicy AS ku ON ku.kod_kursu = k.kodGROUP BY k.kod, k.nazwaORDER BY k.nazwa;
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 8-24 · po tym etapie masz 17 z 39 kryteriów (pkt)
Krok 8: Grafika 1.jpg: szerokość 150 px
Kryteria:
R.2.1 Grafika 1.jpg przeskalowana z zachowaniem proporcji do szerokości 150 px, odpowiadająca temu wysokość wynosi 100 px (± 5 px)
ok. 3 min
Polecenie z arkusza
Cechy grafiki 1.jpg: przeskalowana z zachowaniem proporcji do szerokości 150 px.
1.jpg ma 450 × 300 px; Obraz → Skaluj obraz w GIMP-ie, szerokość 150 px przy zamkniętym łańcuchu proporcji - wysokość wyjdzie 100 px (klucz: 100 ±5). Pozostałe zdjęcia już mają 150 × 100. Skalowanie w edytorze, a nie atrybutem width, zmniejsza też plik - przeglądarka nie pobiera trzy razy większego obrazu.
Podgląd strony po tym kroku
Krok 9: Plik index.php w HTML5
Kryteria:
R.2.2 W pliku index.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków
ok. 1 min
Polecenie z arkusza
Składa się ze strony o nazwie index.php. Zapisana w języku HTML5.
Plik index.php w folderze zdającego. <!DOCTYPE html> włącza tryb standardów HTML5, dalej szkielet: <html>, <head> i <body>. Rozszerzenie .php, bo na początku pliku stanie kod PHP.
R.2.2 W pliku index.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków
ok. 1 min
Polecenie z arkusza
Zadeklarowany polski język zawartości witryny.
lang="pl" w <html> - czytnik ekranu czyta polską wymową, przeglądarka dobiera słownik i dzielenie wyrazów.
R.2.2 W pliku index.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków
ok. 1 min
Polecenie z arkusza
Jawnie zastosowany właściwy standard kodowania polskich znaków.
<meta charset="UTF-8"> jako pierwszy element <head>. Ten sam standard ustawi połączenie z bazą (charset=utf8mb4 w PDO), więc polskie litery przejdą bez zmian całą drogę: baza → PHP → przeglądarka.
<title>Szkolenia i kursy</title> - napis na karcie przeglądarki (klucz R.2.3). Obok <meta name="viewport">: bez niego telefon pokazałby stronę pomniejszoną jak na monitorze. Arkusz tego nie wymaga, a w każdym współczesnym szablonie ta linia jest.
index.phpPHPzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Szkolenia i kursy</title></head><body></body></html>
Podgląd strony po tym kroku (index.php)
Krok 13: Podpięcie arkusza stylów
Kryteria:
R.3.2 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.css formatowanie CSS pochodzi jedynie z tego arkusza
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"> - nazwa dokładnie z arkusza (R.3.2). Cały wygląd będzie w tym pliku, w HTML nie ma ani jednego atrybutu style.
Typowe błędy
Zła nazwa w href to brak stylów, a wtedy cały rezultat R.4 przepada.
index.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Szkolenia i kursy</title> <linkrel="stylesheet"href="styl.css"></head><body></body></html>
Podgląd strony po tym kroku (index.php)
Krok 14: Bloki semantyczne
Kryteria:
R.2.4 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML 5: header, main->section+section, footer
ok. 2 min
Polecenie z arkusza
Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 3. Zawartość bloku głównego: sekcja lewa i sekcja prawa.
Ilustracja 3 przekłada się na znaczniki z klucza R.2.4: <header>, <main> z dwiema <section>, <footer>. Każdy znacznik mówi, czym jest blok - czytnik ekranu pozwala przeskoczyć prosto do treści głównej, a wyszukiwarka wie, co jest nawigacyjnym dodatkiem. Sekcje dostają identyfikatory left i right, bo arkusz formatuje każdą inaczej.
Typowe błędy
<div> w miejscu <section> nie jest znacznikiem semantycznym - klucz wymaga main → section + section.
Krok 15: Blok nagłówkowy: nagłówek pierwszego stopnia
Kryteria:
R.2.5 Umieszczony w bloku nagłówkowym <h1>: "SZKOLENIA”, w sekcji prawej <h2>: "Zapisy na kursy" oraz paragraf z numerem zdającego w stopce
ok. 1 min
Polecenie z arkusza
Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści: „SZKOLENIA”.
<h1>SZKOLENIA</h1> w <header>. Rozstrzelenie liter z ilustracji 2 zrobi CSS (letter-spacing) - spacje między literami w HTML czytnik ekranu przeczytałby jako osobne litery.
Krok 16: Sekcja lewa: tabela z wierszem nagłówkowym
Kryteria: ,
R.2.7 W sekcji lewej umieszczona tabela o 3 kolumnach (w skrypcie lub HTML) , zastosowane znaczniki <table>, <tr>, <td>
R.2.8 Pierwszy wiersz tabeli zawiera komórki nagłówkowe, zastosowane znaczniki <tr>, <th>
ok. 2 min
Polecenie z arkusza
Zawartość sekcji lewej: tabela o trzech kolumnach. Pierwszy wiersz zawiera komórki nagłówkowe: Kurs, Nazwa, Cena. Tabela wypełniona jest przez skrypt 1.
Tabela podzielona na <thead> (wiersz z komórkami nagłówkowymi <th> - klucz R.2.8) i <tbody> (wiersze danych, na razie puste - wypełni je skrypt 1). Podział nie zmienia wyglądu, ale mówi przeglądarce, który wiersz jest nagłówkiem: przy wydruku długiej tabeli <thead> powtarza się na każdej stronie, a w CSS można formatować osobno nagłówek i dane.
R.2.9 W sekcji prawej umieszczony formularz z 4 etykietami, 3 polami edycyjnymi, listą rozwijaną i przyciskiem. Przynajmniej jedna etykieta powiązana jest z kontrolką
R.3.3 Formularz wysyła dane metodą POST do tego samego pliku (poprawne również bez atrybutu action)
ok. 1 min
Polecenie z arkusza
Formularz zgodny z ilustracją 2, wysyłający dane do tego samego pliku metodą bezpieczną z polami: etykiety, pola edycyjne, lista rozwijana i przycisk.
method="post" - dane w treści żądania, nie w adresie. Bez atrybutu action formularz wysyła się na adres bieżącej strony (R.3.3). Etykiety staną nad polami bez znaczników <br> - ułoży je siatka w CSS.
Typowe błędy
Brak method to GET - dane trafią do adresu, a skrypt czytający $_POST nic nie dostanie.
R.2.9 W sekcji prawej umieszczony formularz z 4 etykietami, 3 polami edycyjnymi, listą rozwijaną i przyciskiem. Przynajmniej jedna etykieta powiązana jest z kontrolką
ok. 1 min
Polecenie z arkusza
Etykieta dla pierwszego pola edycyjnego: „Imię”. Pole edycyjne.
<label for="name"> wskazuje pole o id="name" - kliknięcie etykiety przenosi kursor do pola, a czytnik ekranu odczytuje etykietę przy wejściu w pole (R.2.9). name="name" to klucz w $_POST. autocomplete="given-name" podpowiada przeglądarce, że to imię - wypełni je z zapamiętanych danych.
Typowe błędy
Pole bez name nie jest wysyłane - skrypt nie dostanie imienia.
R.2.9 W sekcji prawej umieszczony formularz z 4 etykietami, 3 polami edycyjnymi, listą rozwijaną i przyciskiem. Przynajmniej jedna etykieta powiązana jest z kontrolką
ok. 1 min
Polecenie z arkusza
Etykieta dla drugiego pola edycyjnego: „Nazwisko”. Pole edycyjne.
Ten sam wzorzec: etykieta z for="surname", pole z id i name równymi surname, podpowiedź family-name.
Krok 21: Formularz: etykieta „Wiek” i pole liczbowe
Kryteria: ,
R.2.9 W sekcji prawej umieszczony formularz z 4 etykietami, 3 polami edycyjnymi, listą rozwijaną i przyciskiem. Przynajmniej jedna etykieta powiązana jest z kontrolką
R.3.4 Pole edycyjne do wpisania wieku jest typu number oraz przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu)
ok. 1 min
Polecenie z arkusza
Etykieta dla trzeciego pola edycyjnego: „Wiek”. Pole edycyjne typu numerycznego.
type="number" (R.3.4) z zakresem min="1" max="120" - przeglądarka nie przyjmie liter ani wieku spoza zakresu. To tylko wygoda dla użytkownika: ten sam zakres sprawdzi PHP, bo żądanie można wysłać z pominięciem formularza.
Krok 22: Formularz: etykieta „Rodzaj kursu” i lista rozwijana
Kryteria:
R.2.9 W sekcji prawej umieszczony formularz z 4 etykietami, 3 polami edycyjnymi, listą rozwijaną i przyciskiem. Przynajmniej jedna etykieta powiązana jest z kontrolką
ok. 1 min
Polecenie z arkusza
Etykieta dla listy rozwijanej: „Rodzaj kursu”. Lista rozwijana wypełniona przez skrypt 2.
Czwarta etykieta i <select> bez opcji - wstawi je skrypt 2. Zapytanie 3 zapisuje tylko imię, nazwisko i wiek, więc wybranego kursu skrypt 3 nie czyta.
R.2.9 W sekcji prawej umieszczony formularz z 4 etykietami, 3 polami edycyjnymi, listą rozwijaną i przyciskiem. Przynajmniej jedna etykieta powiązana jest z kontrolką
R.3.4 Pole edycyjne do wpisania wieku jest typu number oraz przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu)
ok. 1 min
Polecenie z arkusza
Przycisk „Dodaj dane” wysyłający dane z formularza do skryptu 3.
<button type="submit"> - typ wpisany jawnie, bo <button> wewnątrz formularza bez typu też wysyła, ale czytający kod musiałby to wiedzieć (R.3.4). Formularz ma komplet z R.2.9: 4 etykiety, 3 pola, listę i przycisk.
R.2.5 Umieszczony w bloku nagłówkowym <h1>: "SZKOLENIA”, w sekcji prawej <h2>: "Zapisy na kursy" oraz paragraf z numerem zdającego w stopce
ok. 1 min
Polecenie z arkusza
Zawartość stopki: paragraf o treści „Stronę wykonał: ”, dalej wstawiony numer zdającego.
Paragraf w <footer>. Tu stoi nazwisko autora rozwiązania - na egzaminie po dwukropku wpisujesz swój numer zdającego (PESEL), bo tego sprawdza klucz R.2.5.
Kroki 25-33 · po tym etapie masz 28 z 39 kryteriów (pkt)
Krok 25: Plik styl.css: kolory z arkusza w zmiennych
ok. 2 min
Polecenie z arkusza
Styl CSS zdefiniowany jest w całości w zewnętrznym pliku o nazwie styl.css.
Na początku pliku trzy kolory z arkusza jako zmienne CSS (custom properties) w :root. Bordowy #880E4F występuje w arkuszu w trzech blokach - zmienna --accent trzyma go w jednym miejscu, a jego zmiana to jedna linia. Odwołanie to var(--accent).
Typowe błędy
Egzaminator sprawdza style w inspektorze przeglądarki, który pokazuje wartość obliczoną (rgb(136, 14, 79) = #880E4F). Jeśli wolisz nie ryzykować sporu przy czytaniu samego pliku CSS, wpisz kolory wprost - wersja podstawowa tak robi.
R.4.1 Ustawiony dla selektora body: krój czcionki "Century Gothic", sans-serif (tylko taka kolejność) , wyrównanie tekstu do środka, kolor tła #FCE4EC
ok. 1 min
Polecenie z arkusza
Dla selektora ciała strony: krój czcionki Century Gothic, w przypadku braku sans-serif, wyrównanie tekstu do środka, kolor tła #FCE4EC.
Selektor body - uwaga arkusza wymaga formatowania ciała strony wyłącznie selektorem znacznika. Krój dwuwyrazowy w cudzysłowie, po nim rodzina zapasowa sans-serif (R.4.1 - tylko ta kolejność). text-align: center dziedziczą wszystkie bloki.
R.4.2 Ustawiony kolor tła dla bloku nagłówkowego, prawej sekcji i stopki #880E4F
R.4.3 Ustawiony biały kolor czcionki dla bloku nagłówkowego, prawej sekcji i stopki
R.4.6 Ustawione marginesy wewnętrzne (padding) dla bloku nagłówkowego i stopki 10px oraz ustawione marginesy zewnętrzne (margin): dla tabeli auto, dla prawej sekcji 35px i dla przynajmniej jednej kontrolki 5px
R.4.7 Ustawiona dla bloku nagłówkowego i stopki odległość między literami 20px
ok. 2 min
Polecenie z arkusza
Dla bloku nagłówkowego i stopki: kolor tła #880E4F, biały kolor czcionki, marginesy wewnętrzne 10 px, odległość między literami 20 px.
Jedna reguła dla dwóch selektorów (każdy w osobnej linii - łatwiej dopisać trzeci). letter-spacing: 20px rozstrzela litery jak na ilustracji 2 (R.4.7).
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 3 w arkuszu egzaminacyjnym (prawidłowo zastosowane właściwości CSS decydujące o układzie bloków na stronie, np. float i clear albo display flex albo grid albo position)
ok. 2 min
Polecenie z arkusza
Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 3.
<main> staje się siatką o dwóch kolumnach: grid-template-columns: 50% 40% - pierwsza kolumna dla sekcji lewej, druga dla prawej. Szerokości sekcji z arkusza są więc zapisane w jednym miejscu, przy układzie, a nie w każdej sekcji osobno - klucz R.4.4 wymienia grid-template-columns obok width. Stopka jest poza <main>, więc zostaje pod spodem bez clear (R.3.1).
Różnica wobec flex z wersji podstawowej: w siatce margines zewnętrzny sekcji mieści się w jej kolumnie, więc prawa sekcja z marginesem 35 px będzie o 70 px węższa niż kolumna. Kolumna nadal ma 40%, a układ zgadza się z ilustracją 3.
Typowe błędy
width: 50% w sekcji umieszczonej w kolumnie siatki liczy się od kolumny, nie od strony - w siatce szerokości podaje się w grid-template-columns, nie w obu miejscach naraz.
Krok 29: Sekcja lewa: wysokość i paski przewijania
Kryteria:
R.4.9 Ustawiony dla lewej sekcji styl overflow: auto;
ok. 1 min
Polecenie z arkusza
Dla lewej sekcji: szerokość 50%, wysokość 600 px, paski przewijania tylko w momencie, gdy zawartość nie mieści się w bloku.
Szerokość 50% ma już kolumna siatki. Tu wysokość i overflow: auto - pasek pojawi się dopiero wtedy, gdy osiem wierszy ze zdjęciami przekroczy 600 px (R.4.9).
Typowe błędy
overflow: scroll pokazuje paski zawsze, także przy krótkiej zawartości - arkusz mówi „tylko w momencie”.
R.4.2 Ustawiony kolor tła dla bloku nagłówkowego, prawej sekcji i stopki #880E4F
R.4.3 Ustawiony biały kolor czcionki dla bloku nagłówkowego, prawej sekcji i stopki
R.4.4 Ustawiona wysokość sekcji lewej 600px, prawej 500px oraz szerokość sekcji lewej 50%, prawej 40% (width lub grid-template-columns lub flex)
R.4.6 Ustawione marginesy wewnętrzne (padding) dla bloku nagłówkowego i stopki 10px oraz ustawione marginesy zewnętrzne (margin): dla tabeli auto, dla prawej sekcji 35px i dla przynajmniej jednej kontrolki 5px
R.4.8 Ustawiony dla prawej sekcji cień box-shadow: 10px 10px 15px DimGray;
ok. 2 min
Polecenie z arkusza
Dla prawej sekcji: kolor tła #880E4F, biały kolor czcionki, szerokość 40%, wysokość 500 px, marginesy zewnętrzne 35 px, cień bloku o przesunięciu 10 px w obu osiach, rozmyciu 15 px i kolorze DimGray.
Tło z tej samej zmiennej co nagłówek. Szerokość 40% daje kolumna siatki, wysokości obu sekcji są już ustawione (R.4.4). Cień bloku z tabeli 2 arkusza: box-shadow: 10px 10px 15px DimGray - przesunięcie w prawo, w dół, rozmycie, kolor (R.4.8).
R.4.5 Ustawiona szerokość selektora table oraz wszystkich kontrolek (pola edycyjne, lista rozwijana, przycisk) 70%
R.4.6 Ustawione marginesy wewnętrzne (padding) dla bloku nagłówkowego i stopki 10px oraz ustawione marginesy zewnętrzne (margin): dla tabeli auto, dla prawej sekcji 35px i dla przynajmniej jednej kontrolki 5px
ok. 1 min
Polecenie z arkusza
Dla selektora tabeli: marginesy zewnętrzne automatycznie wyliczane przez przeglądarkę, szerokość 70%.
Selektor znacznika table (uwaga arkusza). margin: auto przy ustalonej szerokości dzieli wolne miejsce po równo na obie strony - tabela staje na środku sekcji (R.4.5, R.4.6).
Krok 32: Kontrolki formularza: margines i szerokość
Kryteria: ,
R.4.5 Ustawiona szerokość selektora table oraz wszystkich kontrolek (pola edycyjne, lista rozwijana, przycisk) 70%
R.4.6 Ustawione marginesy wewnętrzne (padding) dla bloku nagłówkowego i stopki 10px oraz ustawione marginesy zewnętrzne (margin): dla tabeli auto, dla prawej sekcji 35px i dla przynajmniej jednej kontrolki 5px
ok. 2 min
Polecenie z arkusza
Dla wszystkich kontrolek (pola edycyjne, lista rozwijana, przycisk) marginesy zewnętrzne 5 px, szerokość 70%.
input, select, button - margines 5 px i szerokość 70% (R.4.5, R.4.6). Dodatkowo formularz jest siatką o jednej kolumnie (display: grid i justify-items: center): każdy element - etykieta, pole, lista, przycisk - dostaje własny wiersz i staje na środku. To zastępuje znaczniki <br> z wersji podstawowej: o układzie decyduje CSS, a HTML zostaje samą treścią.
Kroki 34-47 · po tym etapie masz 39 z 39 kryteriów (pkt)
Krok 34: Dane połączenia w pliku config.php
Kryteria:
R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy szkolenia
ok. 2 min
Polecenie z arkusza
Łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie szkolenia.
Dane logowania do bazy w osobnym pliku config.php, który tylko zwraca tablicę (return [...]). W prawdziwym projekcie hasło do bazy jest inne na każdym serwerze i nie trafia do repozytorium razem z kodem strony - wystarczy podmienić jeden plik. Arkusz dopuszcza dodatkowe pliki („ewentualnie inne przygotowane pliki”).
DSN (mysql:host=localhost;dbname=szkolenia;charset=utf8mb4) to adres bazy dla PDO w jednym napisie: rodzaj serwera, host, nazwa bazy i kodowanie połączenia. Plik PHP bez HTML-u nie ma końcowego ?> - nic po nim nie wypisze się przypadkiem.
Typowe błędy
Egzaminator ocenia R.5.1 także w kodzie: jeśli dane połączenia są w config.php, ten plik musi być w folderze z rezultatami - bez niego strona się nie połączy.
config.phpPHPnowy plik
<?php// Dane połączenia z bazą - w jednym miejscu, poza plikiem stronyreturn ['dsn' => 'mysql:host=localhost;dbname=szkolenia;charset=utf8mb4','user' => 'root','password' => '',];
R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy szkolenia
R.5.10 W skryptach zastosowana przynajmniej jedna zmienna oraz nazwy wszystkich zmiennych są w języku polskim lub angielskim i odzwierciedlają cel zastosowania
ok. 3 min
Polecenie z arkusza
Napisane w języku PHP. Łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie szkolenia.
Kod PHP stoi na samym początkuindex.php, przed <!DOCTYPE html>: najpierw wszystkie operacje na bazie, potem szablon strony. Klucz R.5 przyjmuje PDO tak samo jak MySQLi.
require 'config.php' - dane z poprzedniego kroku trafiają do $config,
new PDO(...) - połączenie i wybór bazy szkolenia (R.5.1); opcje: ERRMODE_EXCEPTION - każdy błąd SQL zatrzymuje skrypt wyjątkiem, zamiast po cichu zwracać false, a FETCH_ASSOC - wiersze jako tablice z nazwami kolumn ($course['nazwa'] zamiast $row[1]),
try … catch (PDOException $error) - gdy baza nie działa, użytkownik widzi zwykły komunikat, a nie treść błędu z hasłem, ścieżką i nazwą serwera; http_response_code(500) mówi przeglądarce, że to błąd serwera.
Od tego kroku podgląd to prawdziwy wynik strony z serwera - na razie bez zmian.
Typowe błędy
catch wypisujący $error->getMessage() pokazałby użytkownikowi szczegóły serwera - to wskazówka dla atakującego. Szczegóły zapisuje się do dziennika błędów, nie na ekran.
Krok 36: Funkcja e(): bezpieczne wypisywanie tekstu
ok. 2 min
Polecenie z arkusza
Należy stosować znaczące nazewnictwo zmiennych i funkcji w języku polskim lub angielskim.
Każdy napis z bazy albo z formularza, który trafi do HTML-u, przejdzie przez e() (od escape). htmlspecialchars zamienia <, >, & i - dzięki ENT_QUOTES - oba cudzysłowy na encje, więc wpisane w formularzu <script> wyświetli się jako tekst, a nie wykona (to ochrona przed XSS). Escapowanie robi się na wyjściu, tuż przed wypisaniem: do bazy idą dane surowe.
Krótka nazwa to świadomy wyjątek od „znaczącego nazewnictwa” - e() występuje w szablonie kilkanaście razy i jest powszechnie znaną konwencją (tak samo nazywa się w Laravelu). Typy string przy parametrze i wyniku mówią, czego funkcja oczekuje.
R.5.3 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1 lub Skrypt 2 zawiera instrukcję wysyłającą do bazy danych zapytanie 2
ok. 2 min
Polecenie z arkusza
Skrypt 1: wysyła do bazy danych zapytanie 1.
Zapytanie 1 trafia do bloku try - wyjątek z dowolnego zapytania obsłuży ten sam catch, więc blok try obejmuje teraz połączenie i wszystkie operacje na bazie. $db->query(...) wysyła zapytanie (R.5.3), a fetchAll() od razu odbiera wszystkie wiersze do tablicy $courses. W zapytaniu nie ma danych od użytkownika, więc nie trzeba go przygotowywać - wystarcza query.
Dane są gotowe, zanim zacznie się HTML; szablon niżej tylko je wypisze.
index.phpPHPzmienione: 2 linie
<?php$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$courses = $db->query('SELECT kod, nazwa, cena FROM kursy ORDER BY cena')->fetchAll();} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Szkolenia i kursy</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>SZKOLENIA</h1> </header> <main> <sectionid="left"> <table> <thead> <tr> <th>Kurs</th> <th>Nazwa</th> <th>Cena</th> </tr> </thead> <tbody> </tbody> </table> </section> <sectionid="right"> <h2>Zapisy na kursy</h2> <formmethod="post"> <labelfor="name">Imię</label> <inputtype="text"id="name"name="name"autocomplete="given-name"> <labelfor="surname">Nazwisko</label> <inputtype="text"id="surname"name="surname"autocomplete="family-name"> <labelfor="age">Wiek</label> <inputtype="number"id="age"name="age"min="1"max="120"> <labelfor="course">Rodzaj kursu</label> <selectid="course"name="course"> </select> <buttontype="submit">Dodaj dane</button> </form> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 38: Skrypt 1: wiersz z obrazem dla każdego kursu
Kryteria: ,
R.2.6 W sekcji lewej umieszczona przynajmniej jedna grafika z tekstem alternatywnym "Kurs" (w skrypcie lub HTML)
R.5.5 Skrypt 1 wyświetla w I kolumnie tabeli wszystkie obrazy, których nazwy to kody zwrócone z bazy, a rozszerzenia to .jpg
ok. 3 min
Polecenie z arkusza
Dla każdego zwróconego przez bazę rekordu, w kolejnych wierszach tabeli wstawiane są: do pierwszej komórki: obraz o nazwie utworzonej ze zwróconego przez bazę kodu i rozszerzenia .jpg z tekstem alternatywnym: „kurs” (dla kodu 1 jest to 1.jpg).
W <tbody> pętla w składni szablonu: foreach (...): i endforeach; zamiast nawiasów klamrowych. HTML wiersza zostaje zwykłym HTML-em z wcięciami, a PHP wstawia tylko wartości przez <?= ... ?> (skrót od <?php echo ... ?>). Nazwa pliku to kod z bazy i dopisane .jpg - dla kodu 5 wychodzi 5.jpg (R.5.5), tekst alternatywny kurs (R.2.6).
Podgląd: osiem zdjęć, od gastronomii (najtańszej) do spawania.
Typowe błędy
$course['nazwa'] w miejscu kodu dałby gastronomia.jpg - plików nazwanych od kursów nie ma.
Krok 39: Skrypt 1: nazwa i cena w kolejnych komórkach
Kryteria:
R.5.4 Skrypt 1 dla każdego zwróconego przez bazę rekordu, wyświetla w II i III kolumnie tabeli kolejne nazwy i ceny kursów
ok. 1 min
Polecenie z arkusza
Do kolejnych komórek: zwrócone z bazy nazwa i cena kursu.
Dwie komórki, każda z wartością po nazwie kolumny - kolejność kolumn w SELECT przestaje mieć znaczenie (R.5.4). Nazwa przechodzi przez e(); cena to liczba, ale e() na każdej wartości to prostsza reguła niż zastanawianie się, która wartość jest bezpieczna. Cena zostaje w zapisie z bazy (1000, jak na ilustracji 2).
Tabela wygląda jak na ilustracji 2, a pasek przewijania pojawia się sam - osiem wierszy przekracza 600 px.
R.5.3 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1 lub Skrypt 2 zawiera instrukcję wysyłającą do bazy danych zapytanie 2
ok. 1 min
Polecenie z arkusza
Skrypt 2: wysyła do bazy danych zapytanie 2.
Zapytanie 2 obok zapytania 1. PDO::FETCH_COLUMN zwraca zamiast listy wierszy listę wartości pierwszej kolumny - $courseNames to od razu ['język angielski', 'język niemiecki', …].
index.phpPHPzmienione: 1 linia
<?php$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$courses = $db->query('SELECT kod, nazwa, cena FROM kursy ORDER BY cena')->fetchAll();$courseNames = $db->query('SELECT nazwa FROM kursy')->fetchAll(PDO::FETCH_COLUMN);} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Szkolenia i kursy</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>SZKOLENIA</h1> </header> <main> <sectionid="left"> <table> <thead> <tr> <th>Kurs</th> <th>Nazwa</th> <th>Cena</th> </tr> </thead> <tbody><?phpforeach ($coursesas$course): ?> <tr> <td><imgsrc="<?= e($course['kod']) ?>.jpg"alt="kurs"></td> <td><?= e($course['nazwa']) ?></td> <td><?= e($course['cena']) ?></td> </tr><?phpendforeach; ?> </tbody> </table> </section> <sectionid="right"> <h2>Zapisy na kursy</h2> <formmethod="post"> <labelfor="name">Imię</label> <inputtype="text"id="name"name="name"autocomplete="given-name"> <labelfor="surname">Nazwisko</label> <inputtype="text"id="surname"name="surname"autocomplete="family-name"> <labelfor="age">Wiek</label> <inputtype="number"id="age"name="age"min="1"max="120"> <labelfor="course">Rodzaj kursu</label> <selectid="course"name="course"> </select> <buttontype="submit">Dodaj dane</button> </form> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
R.5.6 Skrypt 2 wyświetla nazwy kursów jako elementy listy rozwijanej w znaczniku <option>
ok. 1 min
Polecenie z arkusza
Wszystkie zwrócone nazwy kursów są zapisywane jako elementy listy rozwijanej.
Ta sama składnia szablonu co w tabeli - jeden <option> na nazwę (R.5.6). Lista ma 8 kursów od języka angielskiego, jak na ilustracji 4.
index.phpPHPzmienione: 3 linie
<?php$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$courses = $db->query('SELECT kod, nazwa, cena FROM kursy ORDER BY cena')->fetchAll();$courseNames = $db->query('SELECT nazwa FROM kursy')->fetchAll(PDO::FETCH_COLUMN);} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Szkolenia i kursy</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>SZKOLENIA</h1> </header> <main> <sectionid="left"> <table> <thead> <tr> <th>Kurs</th> <th>Nazwa</th> <th>Cena</th> </tr> </thead> <tbody><?phpforeach ($coursesas$course): ?> <tr> <td><imgsrc="<?= e($course['kod']) ?>.jpg"alt="kurs"></td> <td><?= e($course['nazwa']) ?></td> <td><?= e($course['cena']) ?></td> </tr><?phpendforeach; ?> </tbody> </table> </section> <sectionid="right"> <h2>Zapisy na kursy</h2> <formmethod="post"> <labelfor="name">Imię</label> <inputtype="text"id="name"name="name"autocomplete="given-name"> <labelfor="surname">Nazwisko</label> <inputtype="text"id="surname"name="surname"autocomplete="family-name"> <labelfor="age">Wiek</label> <inputtype="number"id="age"name="age"min="1"max="120"> <labelfor="course">Rodzaj kursu</label> <selectid="course"name="course"><?phpforeach ($courseNamesas$courseName): ?> <option><?= e($courseName) ?></option><?phpendforeach; ?> </select> <buttontype="submit">Dodaj dane</button> </form> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 42: Skrypt 3: dane po kliknięciu „Dodaj dane”
Kryteria:
R.5.9 Skrypt zawiera instrukcję pobierającą dane z tablicy globalnej odpowiadającej zastosowanej metodzie formularza ($_POST, $_GET, $_REQUEST)
ok. 3 min
Polecenie z arkusza
Skrypt 3: wykonywany po kliknięciu przycisku „Dodaj dane”.
„Po kliknięciu przycisku” to żądanie metodą POST - sprawdza to $_SERVER['REQUEST_METHOD'] === 'POST'. Przy zwykłym wejściu na stronę (GET) blok się nie wykonuje.
Wartości przychodzą oczyszczone już przy wejściu:
imię i nazwisko z $_POST (R.5.9) przez trim() - spacje na brzegach nie trafią do bazy, a pole z samymi spacjami staje się puste; ?? '' daje pusty napis, gdy pola w żądaniu w ogóle nie ma (zamiast ostrzeżenia),
wiek przez filter_input z filtrem FILTER_VALIDATE_INT i zakresem 1-120: wynik to liczba całkowita albo false dla „abc”, „36.5” czy „500”. Ten sam zakres ma pole w HTML-u, ale tamto sprawdzenie można ominąć - to w PHP decyduje.
Typowe błędy
$_POST['name'] bez ?? '' przy żądaniu bez tego pola daje ostrzeżenie „Undefined array key”.
Krok 43: Skrypt 3: czy wprowadzono imię, nazwisko i wiek
ok. 1 min
Polecenie z arkusza
Skrypt 3 działa tylko wtedy, gdy imię, nazwisko i wiek zostały wprowadzone w formularzu.
Warunek na wartościach po oczyszczeniu: niepuste imię i nazwisko oraz wiek, który przeszedł filtr (false = zła wartość, null = brak pola). Porównania !== są ścisłe - nie ma pułapki, w której empty('0') uznaje zero za brak wartości.
Krok 44: Skrypt 3: zapytanie 3 przygotowane z danymi z formularza
Kryteria:
R.5.7 Skrypt 3 sprawdza czy dane zostały wysłane z formularza, jeśli tak, to wysyła do bazy danych zmodyfikowane zapytanie 3, z wstawionymi danymi z formularza oraz wyświetla tekst „Dane uczestnika <imię> <nazwisko> zostały dodane”, gdzie pola <> są wysłane z formularza (sposób alternatywny z atrybutem required, wstawienie rekordu sprawdzić w phpMyAdmin)
ok. 3 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie 3, zmodyfikowane tak, że dane wysłane z formularza są wstawione do zapytania.
Zapytanie 3 jako zapytanie przygotowane: w miejscu wartości stoją parametry nazwane :name, :surname, :age, a dane idą osobno w execute([...]). Baza najpierw dostaje treść zapytania, potem same wartości - nazwisko Nowak'); DROP TABLE uczestnicy; -- zostanie zapisane jako dziwne nazwisko, a nie wykonane jako polecenie (SQL injection jest niemożliwe). Parametry nazwane czyta się łatwiej niż ? - widać, która wartość trafia w które miejsce.
Klucz R.5.7 sprawdza, że do bazy idzie zapytanie 3 z danymi z formularza - przygotowane spełnia to tak samo jak sklejone. Podgląd: stan po wysłaniu formularza z ilustracji 5 (Magdalena Janowska, 35) - wiersz jest już w bazie.
Typowe błędy
Sklejanie "... VALUES ('$name', ...)" także w PDO jest podatne na SQL injection - zabezpiecza dopiero oddzielenie danych od zapytania.
Krok 45: Skrypt 3: komunikat i przekierowanie po zapisie (PRG)
Kryteria:
R.5.7 Skrypt 3 sprawdza czy dane zostały wysłane z formularza, jeśli tak, to wysyła do bazy danych zmodyfikowane zapytanie 3, z wstawionymi danymi z formularza oraz wyświetla tekst „Dane uczestnika <imię> <nazwisko> zostały dodane”, gdzie pola <> są wysłane z formularza (sposób alternatywny z atrybutem required, wstawienie rekordu sprawdzić w phpMyAdmin)
ok. 4 min
Polecenie z arkusza
Wyświetla paragraf o treści „Dane uczestnika <imię><nazwisko> zostały dodane”, gdzie pola <> oznaczają wartości wysłane z formularza.
Po udanym INSERT strona nie wypisuje komunikatu od razu, tylko stosuje wzorzec PRG (Post/Redirect/Get):
komunikat z imieniem i nazwiskiem trafia do sesji ($_SESSION['message'], sesję otwiera session_start() na początku pliku),
header('Location: index.php', true, 303) każe przeglądarce otworzyć stronę od nowa zwykłym GET-em,
przy tym wejściu $message odbiera komunikat z sesji i od razu go usuwa (unset) - pokaże się dokładnie raz, w paragrafie pod formularzem. Odbiór stoi przed obsługą formularza: w żądaniu POST sesja jest jeszcze pusta, a nowy komunikat czeka na następne wejście.
Po co: bez przekierowania F5 po zapisie pyta „wysłać formularz ponownie?” i jedno kliknięcie dodaje Magdalenę Janowską drugi raz. Przekierowanie stoi za blokiem try - po wszystkich operacjach na bazie - i musi być przed jakimkolwiek HTML-em, bo nagłówek wysyła się przed treścią. W szablonie e($message), bo imię pochodzi od użytkownika.
Podgląd: strona po przekierowaniu, z komunikatem „Dane uczestnika Magdalena Janowska zostały dodane” (R.5.7). Pola formularza są puste - to też skutek PRG, strona załadowała się od nowa.
Typowe błędy
header() po wypisaniu choćby spacji przed <?php nie zadziała („headers already sent”). Plik zaczyna się od <?php w pierwszej linii, bez pustej linii przed nim.
R.5.8 Skrypt 3 w przeciwnym do R.5.7 przypadku wyświetla tekst „Wprowadź wszystkie dane”
ok. 1 min
Polecenie z arkusza
W przeciwnym wypadku wyświetla paragraf „Wprowadź wszystkie dane”.
Gdy w sesji nie ma komunikatu o zapisie, ?? podstawia „Wprowadź wszystkie dane” (R.5.8). Tak jest przy formularzu z pustym albo złym polem (brak przekierowania) i przy pierwszym wejściu na stronę - dlatego na ilustracji 2 ten napis widać od razu. Podgląd wraca do pierwszego wejścia.
R.5.2 Ostatnią operacją na bazie jest jej zamknięcie
ok. 1 min
Polecenie z arkusza
Na końcu zamykane jest połączenie z bazą.
W PDO nie ma metody close() - połączenie zamyka się, gdy znika ostatnie odwołanie do obiektu, czyli po $db = null; (R.5.2). Stoi na końcu bloku try, po wszystkich zapytaniach i przed przekierowaniem - ostatnia operacja na bazie. Szablon niżej korzysta już tylko z gotowych tablic, połączenia nie potrzebuje.
Typowe błędy
$db->close() nie istnieje w PDO - to błąd „Call to undefined method”.
Krok 48 · po tym etapie masz 39 z 39 kryteriów (pkt)
Krok 48: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 2 min
Obie wersje dają komplet 39 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ
display: flex, szerokości w sekcjach
display: grid, szerokości w grid-template-columns
formularz
<br> po każdym elemencie
siatka w CSS, HTML bez <br>
baza
funkcje mysqli_* z tabeli 1
PDO, wyjątki, config.php
INSERT
dane sklejone z zapytaniem
zapytanie przygotowane
wypisywanie
echo w pętli while
szablon foreach … endforeach i e()
po zapisie
komunikat od razu
PRG: przekierowanie i komunikat z sesji
Na egzaminie: wersja podstawowa jest krótsza i używa tylko funkcji z tabeli 1 arkusza - mniej miejsc na pomyłkę, gdy brakuje czasu. Wersję nowoczesną pisz, jeśli znasz PDO na pamięć: arkusz nie podaje jego funkcji, a literówka w PDO::ATTR_ERRMODE to błąd, którego nikt nie podpowie. Gotowa aplikacja dostałaby jeszcze token CSRF w formularzu (ochrona przed wysłaniem formularza z cudzej strony) - klucz tego nie wymaga.
Krok 49 · po tym etapie masz 39 z 39 kryteriów (pkt)
Krok 49: Plik przeglądarka.txt i nagranie płyty
ok. 5 min
Polecenie z arkusza
Po zakończeniu pracy utwórz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Nagraj płytę z rezultatami pracy.
W przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana przez http://localhost/…. W folderze z numerem zdającego: 1.jpg … 8.jpg, import.png, index.php, config.php (bez niego strona się nie połączy), kw1.jpg … kw4.jpg, kwerendy.txt, przeglądarka.txt, styl.css. Płytę sprawdź po nagraniu i opisz 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.