INF.03-04-26.01-SGStyczeń 2026 · 50 kroków · ok. 101 min · 40 kryteriów z klucza
Rozwiązanie krok po kroku: Portal dla maturzystów (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza: portal dla maturzystów, baza matura, dwie strony (index.php z listą uczniów i wynik.php z wynikami wybranego ucznia) z tymi samymi czterema kafelkami statystyk. Obie wersje spełniają wszystkie 40 kryteriów klucza - różni je sposób pisania kodu, nie wynik.
Czym ta wersja różni się od podstawowej:
siatka CSS z nazwanymi obszarami (grid-template-areas) na <body>: układ z ilustracji 3 zapisany w CSS jak mały rysunek, bez float i clear; kafelki w kontenerze flex, kolory w zmiennych CSS,
wspólne statystyki w osobnym pliku: Skrypt 1 w statystyki.php, który obie strony dołączają jedną linią require; połączenie z bazą i funkcja escape() stoją na początku każdej strony, bo klucz szuka połączenia z nazwą bazy w kodzie strony,
PHP na PDO z wyjątkami, aliasy w zapytaniach (AS najstarszy), jedna funkcja dla najlepszego i najgorszego wyniku z białą listą kierunku sortowania,
bezpieczne adresy: http_build_query koduje imię i nazwisko w odnośniku, filter_var sprawdza identyfikator w wynik.php, a zapytanie 5 jest przygotowane - wartość z adresu nigdy nie staje się częścią SQL.
Kroki nadal idą zdanie po zdaniu za treścią arkusza. Podgląd stron w każdym kroku z PHP to wynik prawdziwego uruchomienia kodu z tego kroku w PHP 8.2 na bazie z arkusza; wynik.php pokazuje ucznia z ilustracji 4, Aleksandra Kosińskiego (id 44). Grafiki i baza pochodzą z prawdziwego archiwum pliki3-2.
Jak liczą się punkty. Klucz ma 40 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; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.
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-8 · po tym etapie masz 7 z 40 kryteriów (pkt)
Krok 1: Baza danych matura
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie matura, z zestawem polskich znaków (np. utf8_polish_ci)
Archiwum pliki3-2.7z ma hasło Z@Rok!Matur@ (dwie małpy, wykrzyknik, wielkość liter ma znaczenie). W phpMyAdmin: Nowa, nazwa matura, porównywanie utf8_polish_ci, albo jedno polecenie w zakładce SQL:
CREATE DATABASE matura CHARACTER SET utf8 COLLATE utf8_polish_ci;
Tabele z egzamin.sql mają porównywanie zapisane we własnej definicji (COLLATE=utf8_polish_ci), więc sortują po polsku niezależnie od ustawienia bazy - ustawienie bazy dziedziczyłyby dopiero tabele zakładane później.
Typowe błędy
Skrypty łączą się z bazą po nazwie - literówka w matura zabiera także punkt za połączenie (R.5.1).
Krok 2: Import tabel z pliku egzamin.sql
ok. 2 min
Polecenie z arkusza
Z rozpakowanego archiwum zaimportuj tabele z pliku egzamin.sql do utworzonej bazy
Zaznacz bazę matura, potem Import → egzamin.sql → Wykonaj. Powstają trzy tabele z ilustracji 1: maturzysta (imię, nazwisko, szkoła), arkusz (symbol, rok, przedmiot, sesja) i wynik - tabela łącząca ucznia (maturzysta_id) z arkuszem (symbol) i liczbą punktów. Jeden uczeń ma wiele wyników, jeden arkusz pisało wielu uczniów.
Krok 3: Zrzut ekranu import.jpg
Kryteria:
R.1.1 Wykonany import tabel do bazy danych matura - plik import w formacie JPEG
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie JPEG 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
Print Screen (cały ekran z paskiem zadań), wklej do edytora grafiki i zapisz jako import.jpg. Uwaga na formaty: import w JPEG, zrzuty zapytań w PNG - klucz R.1.1 sprawdza format importu.
Typowe błędy
Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany (uwaga w kluczu przy R.1).
Krok 4: Zapytanie 1: uczniowie szkoły T3 według nazwiska
Kryteria: ,
R.1.2 W pliku kwerendy.txt zapisane co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Wykonane zapytanie 1 wybierające jedynie id, imię i nazwisko maturzystów ze szkoły „T3”, posortowane rosnąco według nazwiska. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id, imie, nazwisko FROM maturzysta WHERE szkola = "T3" ORDER BY nazwisko; (opcjonalnie ASC) oraz na zrzucie widocznych jest dokładnie 13 rekordów.
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie id, imię i nazwisko maturzystów ze szkoły „T3”, posortowane rosnąco według nazwiska
SELECT id, imie, nazwisko - trzy pola, bez szkoły,
WHERE szkola = 'T3' - kod szkoły to tekst, więc w apostrofach,
ORDER BY nazwisko - rosnąco, bo ASC jest domyślne.
Wynik: 13 wierszy, od Grzegorza Anaka (29) - ta sama kolejność co na ilustracji 2. Zapytania zapisuj w kwerendy.txt z komentarzem -- nad każdym, zrzut jako kw1.png.
R.1.4 Wykonane zapytanie 2 wybierające jedynie nazwy przedmiotów maturalnych z tabeli arkusz. Nazwy przedmiotów nie mogą się powtarzać. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT DISTINCT przedmiot FROM arkusz; oraz na zrzucie widoczne są dokładnie 4 rekordy: biologia, matematyka, fizyka, geografia
ok. 1 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie nazwy przedmiotów maturalnych z tabeli arkusz. Nazwy przedmiotów nie mogą się powtarzać
DISTINCT zaraz po SELECT usuwa z wyniku powtarzające się wiersze. Tabela arkusz ma 18 arkuszy, a różnych przedmiotów są cztery: biologia, matematyka, fizyka, geografia (R.1.4). Zrzut: kw2.png.
Typowe błędy
Bez DISTINCT zrzut pokazuje 18 wierszy z powtórzeniami - klucz wymaga dokładnie 4.
R.1.5 Wykonane zapytanie 3 wybierające jedynie minimalny i maksymalny rok arkusza. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT MIN(rok), MAX(rok) FROM arkusz; oraz na zrzucie widoczne są dokładnie 2 wartości: 2000 i 2006
ok. 2 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie rok dla najstarszego i najmłodszego arkusza maturalnego
Funkcje agregujące MIN i MAX zwracają najmniejszy i największy rok z całej kolumny: jeden wiersz, 2000 i 2006 (R.1.5). AliasyAS najstarszy i AS najmlodszy nadają kolumnom wyniku czytelne nazwy - bez nich kolumny nazywałyby się dosłownie MIN(rok) i MAX(rok), a pod tymi nazwami trzeba by je potem odczytać w PHP. Zrzut: kw3.png.
kwerendy.txtTekstzmienione: 4 linie
-- Zapytanie 1SELECT id, imie, nazwiskoFROM maturzystaWHERE szkola = 'T3'ORDER BY nazwisko;-- Zapytanie 2SELECT DISTINCT przedmiotFROM arkusz;-- Zapytanie 3SELECT MIN(rok) AS najstarszy, MAX(rok) AS najmlodszyFROM arkusz;
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: uczeń z najlepszą średnią
Kryteria:
R.1.6 Wykonane zapytanie 4 wybierające jedynie id maturzysty oraz średnią ze wszystkich punktów otrzymanych przez tego maturzystę, alias „Wynik”; posortowane malejąco według obliczonej średniej, zapytanie powinno wyświetlić tylko 1 rekord. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT maturzysta_id, AVG(punkty) AS "Wynik" FROM wynik GROUP BY maturzysta_id ORDER BY Wynik DESC LIMIT 1; (AS nieobowiązkowe) oraz na zrzucie widoczne są dane 19, 99.0000 (dowolne zaokrąglenie)
ok. 3 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie id maturzysty oraz średnią ze wszystkich punktów otrzymanych przez tego maturzystę. Średnia punktów ma nazwę kolumny (alias) „Wynik”. Dane należy posortować malejąco według obliczonej średniej i wyświetlić tylko 1 rekord. Zapytanie jest grupujące i wybiera maturzystę z najlepszym średnim wynikiem punktów
AVG(punkty) AS Wynik - średnia z aliasem dokładnie z arkusza, z wielkiej litery,
GROUP BY maturzysta_id - jedna grupa, czyli jedna średnia, na ucznia,
ORDER BY Wynik DESC - sortowanie po aliasie, od największej średniej,
LIMIT 1 - zostaje pierwszy wiersz.
Kolejność klauzul jest stała: SELECT … FROM … GROUP BY … ORDER BY … LIMIT. Wynik: 19, 99.0000 (R.1.6) - MariaDB pokazuje średnią z czterema miejscami po przecinku. Zrzut: kw4.png.
Typowe błędy
WHERE nie widzi wyników grupowania - warunek na średnią (np. „średnia powyżej 50”) pisze się w HAVING, nie w WHERE.
kwerendy.txtTekstzmienione: 7 linii
-- Zapytanie 1SELECT id, imie, nazwiskoFROM maturzystaWHERE szkola = 'T3'ORDER BY nazwisko;-- Zapytanie 2SELECT DISTINCT przedmiotFROM arkusz;-- Zapytanie 3SELECT MIN(rok) AS najstarszy, MAX(rok) AS najmlodszyFROM arkusz;-- Zapytanie 4SELECT maturzysta_id, AVG(punkty) AS WynikFROM wynikGROUP BY maturzysta_idORDER BY Wynik DESCLIMIT 1;
Podgląd strony po tym kroku
Krok 8: Zapytanie 5: wyniki ucznia 31 ze złączeniem tabel
Kryteria:
R.1.7 Wykonane zapytanie 5 wybierające jedynie rok, sesję i przedmiot z tabeli arkusz oraz odpowiadające temu punkty z tabeli wynik dla id maturzysty 31. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT rok, sesja, przedmiot, punkty FROM arkusz JOIN wynik USING(symbol) WHERE maturzysta_id = 31; (możliwe również INNER JOIN, porównanie kluczy po WHERE lub po ON) oraz na zrzucie widoczne są dokładnie 3 rekordy: 2000 lato biologia 25, 2000 lato matematyka 25, 2001 lato biologia 29
ok. 3 min
Polecenie z arkusza
Zapytanie 5: wybierające jedynie rok, sesję i przedmiot z tabeli arkusz oraz odpowiadające temu punkty z tabeli wynik dla id maturzysty 31. Należy posłużyć się relacją
FROM arkusz AS a JOIN wynik AS w - aliasy tabel: krótkie a i w zamiast pełnych nazw w całym zapytaniu,
ON w.symbol = a.symbol - relacja: wiersz wyniku łączy się z arkuszem o tym samym symbolu,
a.rok, w.punkty - przy każdej kolumnie widać, z której tabeli pochodzi; przy złączeniach to oszczędza zgadywania,
WHERE w.maturzysta_id = 31 - wyniki jednego ucznia.
Wynik: 2000 lato biologia 25, 2000 lato matematyka 25, 2001 lato biologia 29 (R.1.7). Klucz przyjmuje też JOIN … USING (symbol) i warunek złączenia w WHERE. Zrzut: kw5.png.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT id, imie, nazwiskoFROM maturzystaWHERE szkola = 'T3'ORDER BY nazwisko;-- Zapytanie 2SELECT DISTINCT przedmiotFROM arkusz;-- Zapytanie 3SELECT MIN(rok) AS najstarszy, MAX(rok) AS najmlodszyFROM arkusz;-- Zapytanie 4SELECT maturzysta_id, AVG(punkty) AS WynikFROM wynikGROUP BY maturzysta_idORDER BY Wynik DESCLIMIT 1;-- Zapytanie 5SELECT a.rok, a.sesja, a.przedmiot, w.punktyFROM arkusz AS aJOIN wynik AS w ON w.symbol = a.symbolWHERE w.maturzysta_id = 31;
Podgląd strony po tym kroku
Etap 2: Strona HTML
Kroki 9-19 · po tym etapie masz 14 z 40 kryteriów (pkt)
Krok 9: Plik index.php w HTML5
ok. 2 min
Polecenie z arkusza
Składa się ze stron index.php i wynik.php. Cechy wspólne dla obu stron: zapisane w języku HTML5
<!DOCTYPE html> i szkielet strony głównej. Druga strona, wynik.php, powstanie jako kopia gotowej pierwszej: obie mają ten sam nagłówek, blok boczny, kafelki i stopkę, a różnią się tylko pierwszą sekcją.
Jawnie zastosowany właściwy standard kodowania polskich znaków
<meta charset="UTF-8"> jako pierwszy element <head>. Połączenie z bazą też dostanie charset=utf8mb4 - strona, plik i baza muszą mówić tym samym kodowaniem, inaczej „ń” rozsypie się w drodze.
R.2.3 W sekcji head znajduje się jawnie podany standard kodowania polskich znaków i tytuł strony „Matura”
ok. 1 min
Polecenie z arkusza
Tytuł strony „Matura”
<title> to tekst karty przeglądarki (z kodowaniem - R.2.3). <meta name="viewport"> każe telefonowi pokazać stronę w prawdziwej szerokości zamiast udawać ekran komputera. Arkusz tego nie wymaga.
R.2.4 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML5: header, aside, section, section, footer (dopuszcza się zastosowanie bloków pomocniczych)
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
Klucz R.2.4 wymienia znaczniki: header, aside, dwa section, footer. Wszystkie są bezpośrednimi dziećmi <body> - siatka ustawiona na <body> rozmieści je według ilustracji 3 bez żadnego dodatkowego bloku.
Sekcje dostają identyfikatory opisujące zawartość, a nie położenie: content (lista uczniów albo wyniki ucznia) i statistics (kafelki). Gdyby układ się zmienił i statystyki trafiły nad listę, nazwy nadal byłyby prawdziwe - top/bottom już nie.
Zawartość bloku bocznego trzy grafiki: ma.jpg, poniżej tu.jpg, poniżej ra.jpg, każda z tekstem alternatywnym „Matura”.
Trzy obrazy w kolejności z arkusza - czytane z góry na dół dają słowo „Matura”, i właśnie to słowo jest w alt każdego z nich. Jedna pod drugą ustawi je CSS.
Krok 17: Druga sekcja: cztery kafelki z tą samą klasą
Kryteria:
R.2.7 W drugim bloku sekcji umieszczone cztery bloki z zawartością. Bloki mają przypisaną tą samą klasę
ok. 2 min
Polecenie z arkusza
Zawartość drugiego bloku sekcji: cztery bloki, w każdym nagłówek czwartego stopnia o treści, kolejno „Przedmioty”, „Lata”, „Najlepszy wynik”, „Najgorszy wynik”. Wszystkie bloki mają przypisaną tę samą klasę
Kafelki to bloki pomocnicze <div> (klucz je dopuszcza) z tą samą klasą block (R.2.7) - klasa, w przeciwieństwie do identyfikatora, może być przypisana wielu elementom. W każdym <h4>; wyniki dopisze Skrypt 1.
R.2.5 W bloku nagłówkowym znajduje się <h1>: „System informacji dla maturzystów”, w drugim bloku sekcji <h4>: „Przedmioty”, „Lata”, " Najlepszy wynik”, „Najgorszy wynik” oraz w stopce <p> z numerem zdającego
ok. 1 min
Polecenie z arkusza
Zawartość stopki: paragraf o treści „Stronę wykonał: ”, dalej wstawiony numer zdającego
Akapit w <footer>. Tu stoi imię i nazwisko autora rozwiązania - na egzaminie wpisujesz swój numer zdającego (PESEL). Są już elementy z R.2.5: <h1>, cztery <h4> i <p> w stopce.
Kroki 20-30 · po tym etapie masz 27 z 40 kryteriów (pkt)
Krok 20: Zmienne CSS z kolorami arkusza
ok. 2 min
Polecenie z arkusza
Styl CSS zdefiniowany jest w całości w zewnętrznym pliku o nazwie styl.css W tabeli 2 zawarto pomoc do niektórych właściwości CSS.
Cztery kolory z arkusza są zapisane w kodzie szesnastkowym - łatwo o literówkę, gdy trzeba je wpisać kilka razy. Dlatego trafiają raz do zmiennych CSS w :root, z nazwami mówiącymi, do czego służą: tło strony, paski nagłówka i stopki, kafelek, podświetlenie odnośnika. W regułach będą jako var(--color-bar).
R.4.1 dla selektora body: krój czcionki cursive, sans-serif (dozwolona tylko taka kolejność) , kolor tła #4682B4
ok. 1 min
Polecenie z arkusza
Dla selektora ciała strony: krój czcionki cursive, w przypadku braku sans-serif, kolor tła #4682B4
Selektor znacznika body, jak każe uwaga arkusza (R.4.1). cursive to nie konkretny krój, tylko rodzina krojów odręcznych - przeglądarka wybierze jeden z zainstalowanych (w Windows zwykle Comic Sans MS, jak na ilustracjach). Klucz dopuszcza tylko kolejność cursive, sans-serif.
Dla bloku bocznego: szerokość 20%, wysokość 500 px
Wysokość 500 px to dokładnie dwie sekcje po 250 px - blok boczny sięgnie od nagłówka do stopki. Szerokość 20% wyznaczy siatka w kroku układu; klucz R.4.4 dopuszcza ją w grid-template-columns.
R.4.7 paski przewijania: dla obu bloków sekcji - overflow: auto;
ok. 1 min
Polecenie z arkusza
Wspólne dla bloków sekcji: szerokość 80%, wysokość 250 px, paski przewijania tylko w przypadku przepełnienia
overflow: auto pokaże pasek dopiero wtedy, gdy treść nie zmieści się w 250 px (R.4.7) - lista 13 uczniów się nie mieści, więc pierwsza sekcja dostanie pasek, jak na ilustracji 2. scroll pokazywałby paski zawsze. Szerokość 80% - w siatce.
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)
R.4.4 wysokość: blok boczny - 500px, oba bloki sekcji - 250px szerokość: blok boczny - 20%, oba bloki sekcji - 80% (width lub grid-template-columns lub flex) , oraz selektor img - 150px
ok. 4 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
<body> staje się siatką o dwóch kolumnach 20% i 80% (R.4.4). grid-template-areas rysuje układ z ilustracji 3 słowami - każdy napis w cudzysłowie to jeden wiersz siatki:
"header header" - nagłówek przez obie kolumny,
"aside content" i "aside statistics" - blok boczny w lewej kolumnie przez dwa wiersze, sekcje jedna nad drugą po prawej,
"footer footer" - stopka przez całą szerokość.
grid-area przypisuje każdy blok do obszaru o tej nazwie. Kolejność bloków w HTML przestaje decydować o ich miejscu - i nie trzeba float, clear ani liczenia, która sekcja „spadnie” niżej (R.3.1).
Typowe błędy
Każdy wiersz w grid-template-areas musi mieć tyle nazw, ile jest kolumn, a obszar musi być prostokątem - inaczej przeglądarka zignoruje całą właściwość i bloki ustawią się jeden pod drugim.
R.4.6 marginesy wewnętrzne (padding) : blok nagłówkowy, stopka - 3px, dla obrazu - 2px
R.2.6 W bloku bocznym umieszczone 3 grafiki ma.jpg , tu.jpg , ra.jpg , każdy z tekstem alternatywnym „Matura”, grafiki są ułożone jedna pod drugą (w HTML lub CSS)
ok. 2 min
Polecenie z arkusza
Dla selektora obrazu: szerokość 150 px, marginesy wewnętrzne 2 px
Selektor znacznika img (uwaga arkusza): szerokość 150 px - wysokość przeskaluje się proporcjonalnie z 200 × 200 do 150 × 150 - i padding: 2px (R.4.6). Kryterium wymiarów R.4.4 obejmuje też tę szerokość obrazu.
display: block daje każdemu obrazowi własną linię, więc „Ma”, „tu”, „ra” stoją jedna pod drugą na każdym ekranie (R.2.6: „w HTML lub CSS”). Bez tego na szerokim monitorze dwa obrazy po 154 px zmieściłyby się obok siebie w kolumnie 20%.
R.4.3 kolor czcionki: blok nagłówkowy, stopka, selektor a - biały
R.4.8 tekst bez podkreślenia: dla odnośnika - text-decoration: none;
ok. 1 min
Polecenie z arkusza
Dla selektora odnośnika: biały kolor czcionki, tekst bez podkreślenia
text-decoration: none usuwa podkreślenie (R.4.8). Z białym kolorem nagłówka i stopki jest komplet R.4.3. Odnośników jeszcze nie ma - wypisze je Skrypt 2.
R.4.10 gdy kursor znajdzie się na odnośniku jego kolor tła zmienia się na #12538A
ok. 1 min
Polecenie z arkusza
Gdy kursor znajdzie się na odnośniku jego kolor tła zmienia się na #12538A (jak na ilustracji 2)
Pseudoklasa :hover (R.4.10). Ta sama reguła dla :focus-visible: kto przechodzi po liście klawiszem Tab, widzi podświetlenie na tym samym uczniu, na którym jest fokus. Odnośnik jest elementem liniowym, więc tło dostaje tylko jego tekst - jak „44. Aleksander Kosiński” na ilustracji 2.
Krok 30: Kafelki: kolory, wymiary, cień, obok siebie
Kryteria: ,
R.4.9 dla klasy przypisanej do bloków z drugiego bloku sekcji: kolor tła - #2B6BA0, kolor czcionki - biały, szerokość i wysokość - 150px, marginesy zewnętrzne - 10px, wewnętrzne - 5px, zaokrąglenie rogów - 10px, cień bloku box shadow: 3px 3px 5px black; (wystarczy 5 cech z 8)
R.3.5 Bloki z sekcji drugiej są ułożone obok siebie w poziomie
ok. 3 min
Polecenie z arkusza
Dla klasy przypisanej do bloków z drugiego bloku sekcji: kolor tła #2B6BA0, biały kolor czcionki, szerokość i wysokość 150 px, marginesy zewnętrzne 10 px, wewnętrzne 5 px, zaokrąglenie rogów 10 px, cień bloku o przesunięciu 3 px w obu osiach, rozmyciu 5 px i kolorze czarnym, bloki powinny być ustawione obok siebie
Reguła .block ma osiem cech z arkusza (R.4.9): kolory, wymiary, marginesy, zaokrąglenie i cień 3px 3px 5px black - przesunięcie w poziomie, w pionie, rozmycie, kolor.
„Obok siebie” ustawia rodzic: sekcja #statistics jako kontener flex stawia kafelki w rzędzie (R.3.5), flex-wrap: wrap przenosi je do następnego rzędu na wąskim ekranie, a align-items: flex-start nie pozwala im się rozciągać na wysokość rzędu. Klucz sprawdza ułożenie obok siebie na stronie, a nie właściwość w regule klasy - w wersji podstawowej to samo robi float: left w .block.
Kroki 31-47 · po tym etapie masz 40 z 40 kryteriów (pkt)
Krok 31: Połączenie PDO na początku strony i zamknięcie
Kryteria:
R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy matura oraz ostatnią operacją na bazie jest jej zamknięcie
ok. 4 min
Polecenie z arkusza
Łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie matura. Na końcu zamykane jest połączenie z serwerem
Kod PHP zaczyna plik index.php, przed <!DOCTYPE html>: najpierw cała praca z bazą, potem szablon, który tylko wypisuje gotowe dane. Połączenie z nazwą bazy stoi w pliku strony, bo tam klucz każe go szukać, gdy witryna nie uruchomi się z powodu błędu PHP (R.5.1). wynik.php powstanie później jako kopia tej strony, więc dostanie to samo połączenie.
new PDO(...) z napisem połączenia (DSN: serwer localhost, baza matura, kodowanie utf8mb4), użytkownikiem root bez hasła i dwiema opcjami: ERRMODE_EXCEPTION (błąd SQL rzuca wyjątek) i FETCH_ASSOC (wiersze z nazwami kolumn). try/catch zamienia nieudane połączenie na ogólny komunikat z kodem 500 - bez nazwy serwera i użytkownika, które są w treści wyjątku.
Pod połączeniem od razu $pdo = null; - zamknięcie połączenia. Zapytania będą dopisywane nad tą linią, więc zamknięcie zostanie ostatnią operacją na bazie (R.5.1). Od tego kroku podgląd to wynik prawdziwego uruchomienia w PHP 8.2.
Typowe błędy
Zapytanie dopisane pod linią $pdo = null; kończy się błędem „Call to a member function query() on null” - wszystkie zapytania idą nad zamknięciem.
index.phpPHPzmienione: 15 linii
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"> <h3>Wybierz ucznia z listy:</h3> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> </div> <divclass="block"> <h4>Lata</h4> </div> <divclass="block"> <h4>Najlepszy wynik</h4> </div> <divclass="block"> <h4>Najgorszy wynik</h4> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 32: Funkcja escape() na początku strony
ok. 2 min
Polecenie z arkusza
Należy stosować znaczące nazewnictwo zmiennych i funkcji w języku polskim lub angielskim
Pod połączeniem stoi funkcja escape(): htmlspecialchars z ENT_QUOTES zamienia <, >, & i oba cudzysłowy na encje. Każdy tekst z bazy albo z adresu przechodzi przez nią przed wypisaniem - imię z apostrofem nie rozerwie atrybutu, a <script> w nazwisku pokaże się jako tekst (ochrona przed XSS).
Nazwa jest pełnym słowem, a nie popularnym w frameworkach skrótem e(), bo arkusz wymaga nazw mówiących, do czego służą. Typy w nagłówku (string|int|float|null, wynik : string) dokumentują, co funkcja przyjmuje.
index.phpPHPzmienione: 6 linii
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"> <h3>Wybierz ucznia z listy:</h3> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> </div> <divclass="block"> <h4>Lata</h4> </div> <divclass="block"> <h4>Najlepszy wynik</h4> </div> <divclass="block"> <h4>Najgorszy wynik</h4> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
R.5.2 Przynajmniej jedno zapytanie zostało wysłane do bazy
ok. 3 min
Polecenie z arkusza
Skrypt 1 w bloku „Przedmioty”: wysyła do bazy danych zapytanie 2
Statystyki są takie same na obu stronach, więc Skrypt 1 ma własny plik: statystyki.php, dołączony przez require pod funkcją escape() (korzysta z $pdo). Arkusz przypisuje do plików tylko Skrypt 2 i Skrypt 3, a klucz ocenia Skrypt 1 po efekcie w kafelkach - połączenie z nazwą bazy zostaje w pliku strony. require wkleja i wykonuje kod pliku w tym miejscu, a gdy pliku brakuje, zatrzymuje stronę błędem (include szedłby dalej bez danych). Zapytanie 2 przez $pdo->query(...) - bez parametrów od użytkownika, więc bez prepare (R.5.2).
fetchAll(PDO::FETCH_COLUMN) zwraca samą pierwszą kolumnę jako zwykłą listę napisów: ['biologia', 'matematyka', 'fizyka', 'geografia'] - bez tablic w tablicy, bo z każdego wiersza potrzebna jest tylko nazwa przedmiotu.
index.phpPHPzmienione: 2 linie
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"> <h3>Wybierz ucznia z listy:</h3> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> </div> <divclass="block"> <h4>Lata</h4> </div> <divclass="block"> <h4>Najlepszy wynik</h4> </div> <divclass="block"> <h4>Najgorszy wynik</h4> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
statystyki.phpPHPnowy plik
<?php// Skrypt 1: statystyki do czterech kafelków (wspólne dla index.php i wynik.php)$subjects = $pdo->query('SELECT DISTINCT przedmiot FROM arkusz')->fetchAll(PDO::FETCH_COLUMN);
Krok 34: Skrypt 1, Przedmioty: nazwy oddzielone spacją
Kryteria:
R.5.3 Skrypt 1 wyświetla w pierwszym bloku dane zwrócone zapytaniem 2
ok. 2 min
Polecenie z arkusza
Skrypt 1 w bloku „Przedmioty”: wyświetla dane zwrócone zapytaniem oddzielone spacją
implode(' ', $subjects) skleja listę w jeden napis, wstawiając spację między elementami (R.5.3) - bez zbędnej spacji na końcu, którą zostawia pętla z echo "$row[0] ". Wynik w akapicie: „biologia matematyka fizyka geografia”; kafelek ma 150 px szerokości, więc tekst łamie się jak na ilustracji 2. <?= ... ?> to skrót od <?php echo ... ?>.
index.phpPHPzmienione: 1 linia
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"> <h3>Wybierz ucznia z listy:</h3> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> <p><?= escape(implode(' ', $subjects)) ?></p> </div> <divclass="block"> <h4>Lata</h4> </div> <divclass="block"> <h4>Najlepszy wynik</h4> </div> <divclass="block"> <h4>Najgorszy wynik</h4> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 35: Skrypt 1, Lata: zapytanie 3
ok. 2 min
Polecenie z arkusza
Skrypt 1 w bloku „Lata”: wysyła do bazy danych zapytanie 3
Zapytanie z MIN i MAX zawsze zwraca jeden wiersz, więc wystarczy fetch() - bez pętli. Dzięki aliasom z kwerendy.txt lata są w $years['najstarszy'] i $years['najmlodszy'], a nie pod kluczem MIN(rok).
statystyki.phpPHPzmienione: 2 linie
<?php// Skrypt 1: statystyki do czterech kafelków (wspólne dla index.php i wynik.php)$subjects = $pdo->query('SELECT DISTINCT przedmiot FROM arkusz')->fetchAll(PDO::FETCH_COLUMN);$years = $pdo->query('SELECT MIN(rok) AS najstarszy, MAX(rok) AS najmlodszy FROM arkusz')->fetch();
R.5.4 Skrypt 1 wyświetla w drugim bloku napis "<rok1> - <rok2>" dla lat zwróconych zapytaniem 3
ok. 1 min
Polecenie z arkusza
Skrypt 1 w bloku „Lata”: wyświetla dane zwrócone zapytaniem oddzielone spacjami i myślnikiem
Dwa wypisania i między nimi „spacja, myślnik, spacja” zapisane wprost w HTML: „2000 - 2006” (R.5.4). Liczby z bazy też przechodzą przez escape() - funkcja przyjmuje i tekst, i liczby, więc nie trzeba pamiętać, które pole jakiego jest typu.
index.phpPHPzmienione: 1 linia
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"> <h3>Wybierz ucznia z listy:</h3> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> <p><?= escape(implode(' ', $subjects)) ?></p> </div> <divclass="block"> <h4>Lata</h4> <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p> </div> <divclass="block"> <h4>Najlepszy wynik</h4> </div> <divclass="block"> <h4>Najgorszy wynik</h4> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 37: Skrypt 1, Najlepszy wynik: zapytanie 4 w funkcji
ok. 3 min
Polecenie z arkusza
Skrypt 1 w bloku „Najlepszy wynik”: wysyła do bazy danych zapytanie 4
Zapytanie 4 i jego zmodyfikowana wersja różnią się jednym słowem, więc od razu trafiają do funkcjiaverageResult(): wysyła zapytanie, bierze jeden wiersz i oddaje średnią jako gotowy tekst. $pdo przychodzi w parametrze - funkcja w PHP nie widzi zmiennych spoza siebie.
number_format((float) $row['Wynik'], 2, ',', ' ') zaokrągla do dwóch miejsc i pisze polski przecinek: „99,00” zamiast surowego „99.0000”. PDO oddaje wynik AVG jako tekst, stąd rzutowanie (float).
statystyki.phpPHPzmienione: 9 linii
<?php// Skrypt 1: statystyki do czterech kafelków (wspólne dla index.php i wynik.php)$subjects = $pdo->query('SELECT DISTINCT przedmiot FROM arkusz')->fetchAll(PDO::FETCH_COLUMN);$years = $pdo->query('SELECT MIN(rok) AS najstarszy, MAX(rok) AS najmlodszy FROM arkusz')->fetch();functionaverageResult(PDO $pdo): string{$query = 'SELECT maturzysta_id, AVG(punkty) AS Wynik FROM wynik GROUP BY maturzysta_id ORDER BY Wynik DESC LIMIT 1';$row = $pdo->query($query)->fetch();returnnumber_format((float) $row['Wynik'], 2, ',', ' ');}$best = averageResult($pdo);
Krok 38: Skrypt 1, Najlepszy wynik: średnia ze znakiem %
Kryteria:
R.5.5 Skrypt 1 wyświetla w trzecim bloku najlepszy wynik z symbolem % zwrócony zapytaniem 4
ok. 1 min
Polecenie z arkusza
Skrypt 1 w bloku „Najlepszy wynik”: wyświetla daną zwróconą zapytaniem, za wynikiem wstawiony znak %
Znak procentu stoi w HTML zaraz za wypisaną wartością: „99,00%” (R.5.5). Ilustracja 2 pokazuje „99.0000%” - surowy format MariaDB. Klucz ocenia, że w kafelku jest najlepszy wynik ze znakiem %, a przy zapytaniu 4 sam pisze o „dowolnym zaokrągleniu”.
index.phpPHPzmienione: 1 linia
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"> <h3>Wybierz ucznia z listy:</h3> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> <p><?= escape(implode(' ', $subjects)) ?></p> </div> <divclass="block"> <h4>Lata</h4> <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p> </div> <divclass="block"> <h4>Najlepszy wynik</h4> <p><?= escape($best) ?>%</p> </div> <divclass="block"> <h4>Najgorszy wynik</h4> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 39: Skrypt 1, Najgorszy wynik: kierunek sortowania w parametrze
ok. 3 min
Polecenie z arkusza
Skrypt 1 w bloku „Najgorszy wynik”: wysyła do bazy danych zmodyfikowane zapytanie 4 (należy zmienić sposób sortowania na rosnący)
Funkcja dostaje drugi parametr: kierunek sortowania. averageResult($pdo, 'ASC') wysyła zapytanie 4 z sortowaniem rosnącym - najniższa średnia stoi wtedy na początku, a LIMIT 1 zostawia właśnie ją.
Słowa ASC/DESC są częścią składni SQL, a nie wartością, więc nie da się ich przekazać parametrem zapytania przygotowanego (:direction byłoby błędem). Jedyna bezpieczna droga to biała lista: zmienna może mieć tylko jedną z dwóch wartości wpisanych w kod, cokolwiek innego zamienia się na DESC. Ten sam wzorzec obowiązuje przy wyborze kolumny sortowania z formularza.
statystyki.phpPHPzmienione: 5 linii
<?php// Skrypt 1: statystyki do czterech kafelków (wspólne dla index.php i wynik.php)$subjects = $pdo->query('SELECT DISTINCT przedmiot FROM arkusz')->fetchAll(PDO::FETCH_COLUMN);$years = $pdo->query('SELECT MIN(rok) AS najstarszy, MAX(rok) AS najmlodszy FROM arkusz')->fetch();functionaverageResult(PDO $pdo, string $direction): string{$direction = $direction === 'ASC' ? 'ASC' : 'DESC'; // biała lista: kierunku sortowania nie da się przekazać parametrem$query = "SELECT maturzysta_id, AVG(punkty) AS Wynik FROM wynik GROUP BY maturzysta_id ORDER BY Wynik $direction LIMIT 1";$row = $pdo->query($query)->fetch();returnnumber_format((float) $row['Wynik'], 2, ',', ' ');}$best = averageResult($pdo, 'DESC');$worst = averageResult($pdo, 'ASC');
Krok 40: Skrypt 1, Najgorszy wynik: średnia ze znakiem %
Kryteria:
R.5.6 Skrypt 1 wyświetla w czwartym bloku najgorszy wynik z symbolem % zwrócony zmodyfikowanym zapytaniem 4 (sortowanie rosnące)
ok. 1 min
Polecenie z arkusza
Skrypt 1 w bloku „Najgorszy wynik”: wyświetla daną zwróconą zapytaniem, za wynikiem wstawiony znak %
To samo dla najniższej średniej: „9,29%” (R.5.6). Komplet kafelków - wartości jak na ilustracji 2, tylko zaokrąglone do dwóch miejsc.
index.phpPHPzmienione: 1 linia
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"> <h3>Wybierz ucznia z listy:</h3> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> <p><?= escape(implode(' ', $subjects)) ?></p> </div> <divclass="block"> <h4>Lata</h4> <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p> </div> <divclass="block"> <h4>Najlepszy wynik</h4> <p><?= escape($best) ?>%</p> </div> <divclass="block"> <h4>Najgorszy wynik</h4> <p><?= escape($worst) ?>%</p> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 41: Skrypt 2: zapytanie 1
ok. 1 min
Polecenie z arkusza
Skrypt 2, w pliku index.php: wysyła do bazy zapytanie 1
Skrypt 2 należy tylko do strony głównej, więc stoi w index.php, nad zamknięciem połączenia. Zapytanie w cudzysłowach, bo w środku są apostrofy wokół T3. fetchAll() - wszystkie 13 wierszy do tablicy $students.
index.phpPHPzmienione: 3 linie
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Skrypt 2: uczniowie szkoły T3 (zapytanie 1)$students = $pdo->query("SELECT id, imie, nazwisko FROM maturzysta WHERE szkola = 'T3' ORDER BY nazwisko")->fetchAll();// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"> <h3>Wybierz ucznia z listy:</h3> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> <p><?= escape(implode(' ', $subjects)) ?></p> </div> <divclass="block"> <h4>Lata</h4> <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p> </div> <divclass="block"> <h4>Najlepszy wynik</h4> <p><?= escape($best) ?>%</p> </div> <divclass="block"> <h4>Najgorszy wynik</h4> <p><?= escape($worst) ?>%</p> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 42: Skrypt 2: odnośnik z treścią „id. imię nazwisko”
Kryteria:
R.5.7 Skrypt 2 na stronie głównej generuje odnośniki na podstawie wierszy zwróconych zapytaniem 1. Wyświetlone jest tyle odnośników ile zwróconych wierszy
ok. 3 min
Polecenie z arkusza
Skrypt 2 dla każdego zwróconego wiersza definiuje odnośnik, którego treścią jest „<id>. <imie><nazwisko>”, gdzie pola są zwrócone z bazy (jak na ilustracji 2)
Pętla foreach ... endforeach wypisuje jeden odnośnik na ucznia: identyfikator, kropka, spacja, imię, spacja, nazwisko (R.5.7) - 13 odnośników, od „29. Grzegorz Anak”.
Lista odnośników to lista: każdy uczeń w <li> wewnątrz <ul class="students">. Czytnik ekranu zapowie „lista, 13 elementów”, a każdy odnośnik stoi w osobnym wierszu bez <br>. Reguła .students w CSS zdejmuje kropki i wcięcie listy, żeby wyglądała jak na ilustracji 2. Adres dojdzie w następnym kroku.
index.phpPHPzmienione: 5 linii
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Skrypt 2: uczniowie szkoły T3 (zapytanie 1)$students = $pdo->query("SELECT id, imie, nazwisko FROM maturzysta WHERE szkola = 'T3' ORDER BY nazwisko")->fetchAll();// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"> <h3>Wybierz ucznia z listy:</h3> <ulclass="students"><?phpforeach ($studentsas$student): ?> <li><ahref="wynik.php"><?= escape($student['id']) ?>. <?= escape($student['imie']) ?><?= escape($student['nazwisko']) ?></a></li><?phpendforeach; ?> </ul> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> <p><?= escape(implode(' ', $subjects)) ?></p> </div> <divclass="block"> <h4>Lata</h4> <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p> </div> <divclass="block"> <h4>Najlepszy wynik</h4> <p><?= escape($best) ?>%</p> </div> <divclass="block"> <h4>Najgorszy wynik</h4> <p><?= escape($worst) ?>%</p> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 43: Skrypt 2: adres wynik.php z parametrami GET
Kryteria: ,
R.3.3 Utworzony przynajmniej jeden odnośnik z treścią: numer id, imię i nazwisko (w HTML lub skrypcie) . Po wybraniu odnośnika otwiera się strona wynik.php
R.3.4 W adresie przynajmniej jednego odnośnika przesyłane są dane: id, imię, nazwisko w formacie np. ?id=...&imie=...&nazwisko=… (w HTML lub skrypcie) . Dane są zgodne z treścią odnośnika
ok. 3 min
Polecenie z arkusza
Adres odnośnika: strona wynik.php i przesłane metodą GET parametry id, imię i nazwisko zwrócone z bazy
http_build_query buduje część adresu po ? z tablicy: id=44&imie=Aleksander&nazwisko=Kosi%C5%84ski (R.3.3, R.3.4). Sam dobiera separator & i koduje wartości - „ń” zamienia na %C5%84, a spacja albo & w nazwisku nie rozbiłyby parametrów. Nazwy kluczy tablicy (id, imie, nazwisko) to nazwy parametrów, pod którymi wynik.php odczyta dane z $_GET.
Cały adres przechodzi jeszcze przez escape(): w atrybucie HTML & zapisuje się jako & - przeglądarka przy kliknięciu zamieni je z powrotem na &.
Typowe błędy
Adres sklejony ręcznie ("wynik.php?id=$id&imie=$imie") działa dla „Aleksander”, ale nazwisko „Nowak&Syn” rozbiłoby parametry na dwa. http_build_query albo urlencode przy każdej wartości.
index.phpPHPzmienione: 1 linia
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Skrypt 2: uczniowie szkoły T3 (zapytanie 1)$students = $pdo->query("SELECT id, imie, nazwisko FROM maturzysta WHERE szkola = 'T3' ORDER BY nazwisko")->fetchAll();// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"> <h3>Wybierz ucznia z listy:</h3> <ulclass="students"><?phpforeach ($studentsas$student): ?> <li><ahref="wynik.php?<?= escape(http_build_query(['id' => $student['id'], 'imie' => $student['imie'], 'nazwisko' => $student['nazwisko']])) ?>"><?= escape($student['id']) ?>. <?= escape($student['imie']) ?><?= escape($student['nazwisko']) ?></a></li><?phpendforeach; ?> </ul> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> <p><?= escape(implode(' ', $subjects)) ?></p> </div> <divclass="block"> <h4>Lata</h4> <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p> </div> <divclass="block"> <h4>Najlepszy wynik</h4> <p><?= escape($best) ?>%</p> </div> <divclass="block"> <h4>Najgorszy wynik</h4> <p><?= escape($worst) ?>%</p> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 44: Strona wynik.php jako kopia index.php
Kryteria:
R.2.1 Witryna internetowa zapisana w plikach index.php i wynik.php
ok. 2 min
Polecenie z arkusza
Składa się ze stron index.php i wynik.php. Zawartość pierwszego bloku sekcji dla strony wynik.php: efekt działania skryptu 3
Zapisz jakowynik.php i usuń z kopii wszystko, co należy tylko do strony głównej: Skrypt 2 z góry pliku i listę uczniów z sekcji content (R.2.1). Zostaje to, co wspólne: połączenie, funkcja escape(), require 'statystyki.php', zamknięcie połączenia, nagłówek, blok boczny, kafelki i stopka - poprawka w statystyki.php od razu działa na obu stronach.
Teraz kliknięcie ucznia na index.php otwiera tę stronę, na razie z pustą pierwszą sekcją.
wynik.phpPHPnowy plik
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> <p><?= escape(implode(' ', $subjects)) ?></p> </div> <divclass="block"> <h4>Lata</h4> <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p> </div> <divclass="block"> <h4>Najlepszy wynik</h4> <p><?= escape($best) ?>%</p> </div> <divclass="block"> <h4>Najgorszy wynik</h4> <p><?= escape($worst) ?>%</p> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wynik.php)
Krok 45: Skrypt 3: imię i nazwisko z adresu w nagłówku
Kryteria:
R.5.8 Skrypt 3 na podstronie stosuje metodę $_GET, aby pobrać parametry przesłane do strony
ok. 4 min
Polecenie z arkusza
Skrypt 3, w pliku wynik.php: w nagłówku drugiego stopnia wyświetla imię i nazwisko, które zostało przesłane do strony metodą GET
Parametry z adresu czekają w $_GET pod nazwami z http_build_query (R.5.8). Każdy odczyt ma ?? '', więc wejście na wynik.php bez parametrów nie kończy się ostrzeżeniem „Undefined array key” jak w wersji z egzaminu.
Identyfikator sprawdza filter_var(..., FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]): liczba całkowita albo false (pusty, „abc”, „44 OR 1=1”). Przy false zamiast nagłówka jest „Nie wybrano ucznia” i odnośnik do listy.
Imię i nazwisko - zgodnie z arkuszem - pochodzą z adresu i idą do <h2> przez escape(). Ktoś może wpisać w adres cokolwiek, ale wyświetli się to jako zwykły tekst.
Podgląd: strona po kliknięciu „44. Aleksander Kosiński”, nagłówek jak na ilustracji 4.
Typowe błędy
Arkusz każe wziąć imię i nazwisko z adresu, więc adres ?id=44&imie=Jan&nazwisko=Kowalski pokaże „Jan Kowalski” nad wynikami Kosińskiego. W prawdziwym serwisie imię brałoby się z bazy po identyfikatorze (tak robi krok dobrych praktyk wersji podstawowej) - na egzaminie trzymaj się arkusza.
wynik.phpPHPzmienione: 10 linii
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Skrypt 3: wyniki ucznia o id z adresu (zapytanie 5)$studentId = filter_var($_GET['id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);$fullName = trim(($_GET['imie'] ?? '') . ' ' . ($_GET['nazwisko'] ?? ''));// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"><?phpif ($studentId !== false): ?> <h2><?= escape($fullName) ?></h2><?phpelse: ?> <h2>Nie wybrano ucznia</h2> <p><ahref="index.php">Wróć do listy uczniów</a></p><?phpendif; ?> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> <p><?= escape(implode(' ', $subjects)) ?></p> </div> <divclass="block"> <h4>Lata</h4> <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p> </div> <divclass="block"> <h4>Najlepszy wynik</h4> <p><?= escape($best) ?>%</p> </div> <divclass="block"> <h4>Najgorszy wynik</h4> <p><?= escape($worst) ?>%</p> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wynik.php)
Krok 46: Skrypt 3: zapytanie 5 przygotowane
ok. 4 min
Polecenie z arkusza
Wysyła do bazy danych zmodyfikowane zapytanie 5, tak że wybierane są dane dla ucznia o id przesłanym do strony metodą GET
Zapytanie 5 z kwerendy.txt, w którym zamiast 31 stoi parametr nazwany :id, a wartość idzie osobno w execute(['id' => $studentId]). Baza zna strukturę zapytania, zanim zobaczy dane - adres wynik.php?id=44 OR 1=1 nie zamieni warunku w „zawsze prawdę” (a i tak zatrzymał go już filter_var).
Zapytanie w kodzie PHP jest rozpisane w kilku liniach jak w kwerendy.txt - napis w apostrofach może mieć znaki nowej linii, a baza ich nie zauważa. $results = [] przed warunkiem: gdy identyfikator jest zły, szablon dostanie pustą listę zamiast niezdefiniowanej zmiennej. $statement = null zwalnia obiekt zapytania, który trzyma połączenie.
wynik.phpPHPzmienione: 12 linii
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Skrypt 3: wyniki ucznia o id z adresu (zapytanie 5)$studentId = filter_var($_GET['id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);$fullName = trim(($_GET['imie'] ?? '') . ' ' . ($_GET['nazwisko'] ?? ''));$results = [];if ($studentId !== false) {$statement = $pdo->prepare('SELECT a.rok, a.sesja, a.przedmiot, w.punkty FROM arkusz AS a JOIN wynik AS w ON w.symbol = a.symbol WHERE w.maturzysta_id = :id' );$statement->execute(['id' => $studentId]);$results = $statement->fetchAll();$statement = null;}// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"><?phpif ($studentId !== false): ?> <h2><?= escape($fullName) ?></h2><?phpelse: ?> <h2>Nie wybrano ucznia</h2> <p><ahref="index.php">Wróć do listy uczniów</a></p><?phpendif; ?> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> <p><?= escape(implode(' ', $subjects)) ?></p> </div> <divclass="block"> <h4>Lata</h4> <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p> </div> <divclass="block"> <h4>Najlepszy wynik</h4> <p><?= escape($best) ?>%</p> </div> <divclass="block"> <h4>Najgorszy wynik</h4> <p><?= escape($worst) ?>%</p> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 47: Skrypt 3: rok i sesja, przedmiot i punkty
Kryteria: ,
R.5.9 Skrypt 3 wyświetla dane zwrócone zmodyfikowanym zapytaniem 5, dotyczące ucznia, którego id przesłano do strony: rok i sesję oraz przedmiot i punkty. Skrypt wyświetla wszystkie zawrócone zapytaniem rekordy
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
Dla każdego wiersza zwróconego z bazy wyświetla: w nagłówku trzeciego stopnia rok i sesję oddzielone spacją, w paragrafie nazwę przedmiotu i punkty oddzielone dwukropkiem i spacją
Pętla po $results: na każdy wiersz <h3> z rokiem i sesją oraz <p> z przedmiotem, dwukropkiem i punktami (R.5.9). Podgląd zaczyna się jak ilustracja 4 - „2000 lato”, „biologia: 25”, „2000 lato”, „matematyka: 17” - a po przewinięciu sekcji jest reszta z 10 wyników ucznia.
Pusta lista dostaje komunikat „Brak wyników matur tego ucznia.” - Oliwier Danek (38) jest na liście szkoły T3, ale nie ma w bazie żadnego wyniku, i w wersji z egzaminu widzi tylko sam nagłówek. Nazwy zmiennych mówią, co przechowują: $pdo, $subjects, $years, $best, $worst, $students, $studentId, $fullName, $results, $row (R.5.10).
wynik.phpPHPzmienione: 7 linii
<?php// Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumntry {$pdo = new PDO('mysql:host=localhost;dbname=matura;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}// Bezpieczne wypisanie tekstu w HTML (znaki < > & " ' jako encje)functionescape(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}require'statystyki.php';// Skrypt 3: wyniki ucznia o id z adresu (zapytanie 5)$studentId = filter_var($_GET['id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);$fullName = trim(($_GET['imie'] ?? '') . ' ' . ($_GET['nazwisko'] ?? ''));$results = [];if ($studentId !== false) {$statement = $pdo->prepare('SELECT a.rok, a.sesja, a.przedmiot, w.punkty FROM arkusz AS a JOIN wynik AS w ON w.symbol = a.symbol WHERE w.maturzysta_id = :id' );$statement->execute(['id' => $studentId]);$results = $statement->fetchAll();$statement = null;}// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Matura</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>System informacji dla maturzystów</h1> </header> <aside> <imgsrc="ma.jpg"alt="Matura"> <imgsrc="tu.jpg"alt="Matura"> <imgsrc="ra.jpg"alt="Matura"> </aside> <sectionid="content"><?phpif ($studentId !== false): ?> <h2><?= escape($fullName) ?></h2><?phpforeach ($resultsas$row): ?> <h3><?= escape($row['rok']) ?><?= escape($row['sesja']) ?></h3> <p><?= escape($row['przedmiot']) ?>: <?= escape($row['punkty']) ?></p><?phpendforeach; ?><?phpif ($results === []): ?> <p>Brak wyników matur tego ucznia.</p><?phpendif; ?><?phpelse: ?> <h2>Nie wybrano ucznia</h2> <p><ahref="index.php">Wróć do listy uczniów</a></p><?phpendif; ?> </section> <sectionid="statistics"> <divclass="block"> <h4>Przedmioty</h4> <p><?= escape(implode(' ', $subjects)) ?></p> </div> <divclass="block"> <h4>Lata</h4> <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p> </div> <divclass="block"> <h4>Najlepszy wynik</h4> <p><?= escape($best) ?>%</p> </div> <divclass="block"> <h4>Najgorszy wynik</h4> <p><?= escape($worst) ?>%</p> </div> </section> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wynik.php)
Etap 5: Porównanie z wersją podstawową
Kroki 48-49 · po tym etapie masz 40 z 40 kryteriów (pkt)
Krok 48: Co jest inaczej i dlaczego
ok. 3 min
Polecenie z arkusza
Ocenie będzie podlegać 5 rezultatów: operacje na bazie danych, zawartość witryny internetowej, działanie witryny internetowej, styl CSS witryny internetowej oraz skrypt połączenia z bazą.
Obie wersje dają te same punkty. Różnice:
wersja podstawowa
ta wersja
układ
float: left + clear: both
grid-template-areas na <body>
kafelki obok siebie
float: left w .block
display: flex w sekcji #statistics
wspólny kod
Skrypt 1 skopiowany do obu stron
Skrypt 1 w statystyki.php dołączanym przez require, połączenie w każdej stronie
baza
mysqli_* z tabeli 1
PDO, wyjątki, FETCH_ASSOC, FETCH_COLUMN
najlepszy i najgorszy
dwa prawie identyczne bloki
jedna funkcja, kierunek z białej listy
adres odnośnika
sklejony ręcznie
http_build_query + escape()
id z adresu
wstawiony do SQL
filter_var i parametr :id
brak danych
ostrzeżenia PHP, pusty blok
komunikaty „Nie wybrano ucznia”, „Brak wyników”
Poza egzaminem połączenie z danymi logowania trzyma się zwykle w osobnym pliku (np. baza.php dołączanym przez require), żeby hasło nie trafiało do repozytorium razem z kodem - tu stoi w każdej stronie, bo klucz szuka połączenia z nazwą bazy w kodzie strony. Czego tu jeszcze nie ma: imię i nazwisko nadal pochodzą z adresu, bo tego wymaga arkusz. W prawdziwym serwisie adres niósłby tylko identyfikator, a dane ucznia - zapytanie do bazy.
Czas przeznaczony na wykonanie zadania wynosi 150 minut.
Wersja podstawowa, gdy liczy się czas albo PDO nie jest opanowane: funkcje mysqli_* są w tabeli 1 arkusza, a każdy fragment PHP stoi tam, gdzie widać jego efekt.
Ta wersja, gdy PDO, require i siatka są pisane z pamięci. Dodatkowy plik statystyki.php trzeba nagrać na płytę - bez niego obie strony się zatrzymają.
Mieszanie też daje pełne punkty: grid-template-areas z tej wersji i mysqli_* z podstawowej.
Krok 50 · po tym etapie masz 40 z 40 kryteriów (pkt)
Krok 50: 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. Umieść go w folderze z numerem zdającego. Nagraj płytę z rezultatami pracy.
W przeglądarka.txt nazwa przeglądarki, w której strony były sprawdzane przez http://localhost/….
W folderze z numerem zdającego: import.jpg, index.php, wynik.php, statystyki.php, kw1.png - kw5.png, kwerendy.txt, ma.jpg, tu.jpg, ra.jpg, przeglądarka.txt, styl.css. Po nagraniu sprawdź odczyt płyty.
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.