Przejdź do głównej treści
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.

  1. 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).

  2. 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.

  3. 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).

  4. 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.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT id, imie, nazwisko
    FROM maturzysta
    WHERE szkola = 'T3'
    ORDER BY nazwisko;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: przedmioty bez powtórzeń

    Kryteria:

    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.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT id, imie, nazwisko
    FROM maturzysta
    WHERE szkola = 'T3'
    ORDER BY nazwisko;
    
    -- Zapytanie 2
    SELECT DISTINCT przedmiot
    FROM arkusz;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: najstarszy i najmłodszy rok

    Kryteria:

    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). Aliasy AS 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.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT id, imie, nazwisko
    FROM maturzysta
    WHERE szkola = 'T3'
    ORDER BY nazwisko;
    
    -- Zapytanie 2
    SELECT DISTINCT przedmiot
    FROM arkusz;
    
    -- Zapytanie 3
    SELECT MIN(rok) AS najstarszy, MAX(rok) AS najmlodszy
    FROM arkusz;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. 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.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT id, imie, nazwisko
    FROM maturzysta
    WHERE szkola = 'T3'
    ORDER BY nazwisko;
    
    -- Zapytanie 2
    SELECT DISTINCT przedmiot
    FROM arkusz;
    
    -- Zapytanie 3
    SELECT MIN(rok) AS najstarszy, MAX(rok) AS najmlodszy
    FROM arkusz;
    
    -- Zapytanie 4
    SELECT maturzysta_id, AVG(punkty) AS Wynik
    FROM wynik
    GROUP BY maturzysta_id
    ORDER BY Wynik DESC
    LIMIT 1;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. 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.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT id, imie, nazwisko
    FROM maturzysta
    WHERE szkola = 'T3'
    ORDER BY nazwisko;
    
    -- Zapytanie 2
    SELECT DISTINCT przedmiot
    FROM arkusz;
    
    -- Zapytanie 3
    SELECT MIN(rok) AS najstarszy, MAX(rok) AS najmlodszy
    FROM arkusz;
    
    -- Zapytanie 4
    SELECT maturzysta_id, AVG(punkty) AS Wynik
    FROM wynik
    GROUP BY maturzysta_id
    ORDER BY Wynik DESC
    LIMIT 1;
    
    -- Zapytanie 5
    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 = 31;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. 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ą.

    index.php PHP nowy plik
    <!DOCTYPE html>
    <html>
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  10. Krok 10: Język strony

    Kryteria:

    R.2.2 W pliku znajduje się deklaracja HTML5 <!DOCTYPE HTML> i deklaracja języka zawartości witryny: polski np. <html lang="pl">

    ok. 1 min
    Polecenie z arkusza

    Zadeklarowany polski język zawartości witryny

    lang="pl" w <html> - czytnik ekranu przeczyta „Kosiński” polską wymową. Z <!DOCTYPE html> to jedno kryterium (R.2.2).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  11. Krok 11: Kodowanie 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>. Połączenie z bazą też dostanie charset=utf8mb4 - strona, plik i baza muszą mówić tym samym kodowaniem, inaczej „ń” rozsypie się w drodze.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  12. Krok 12: Tytuł strony i viewport

    Kryteria:

    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.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  13. 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"> - w HTML5 bez ukośnika />. Całe formatowanie pochodzi z tego pliku (R.3.2).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  14. Krok 14: Bloki semantyczne z nazwami sekcji

    Kryteria:

    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.

    index.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
        </header>
        <aside>
        </aside>
        <section id="content">
        </section>
        <section id="statistics">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Nagłówek strony

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści „System informacji dla maturzystów”

    <h1> - jedyny nagłówek pierwszego stopnia. Dalej będą <h2> z nazwiskiem ucznia, <h3> i <h4> - hierarchia nagłówków odpowiada ważności treści.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
        </aside>
        <section id="content">
        </section>
        <section id="statistics">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Blok boczny: trzy grafiki

    ok. 1 min
    Polecenie z arkusza

    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.

    index.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
        </section>
        <section id="statistics">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  17. 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.

    index.php PHP zmienione: 12 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
            </div>
            <div class="block">
                <h4>Lata</h4>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
            </div>
            <div class="block">
                <h4>Najgorszy wynik</h4>
            </div>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Stopka

    Kryteria:

    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.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
            </div>
            <div class="block">
                <h4>Lata</h4>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
            </div>
            <div class="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)
  19. Krok 19: Pierwsza sekcja strony głównej

    Kryteria:

    R.2.8 Na stronie głównej umieszczony <h3>: „Wybierz ucznia z listy:”

    ok. 1 min
    Polecenie z arkusza

    Zawartość pierwszego bloku sekcji dla strony index.php: Nagłówek trzeciego stopnia o treści: „Wybierz ucznia z listy:”

    <h3> na początku sekcji content (R.2.8). Pod nim Skrypt 2 wypisze listę uczniów.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
            <h3>Wybierz ucznia z listy:</h3>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
            </div>
            <div class="block">
                <h4>Lata</h4>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
            </div>
            <div class="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)
  20. Etap 3: Styl CSS

    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).

    styl.css CSS nowy plik
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    Podgląd strony po tym kroku (index.php)
  21. Krok 21: Ciało strony

    Kryteria:

    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.

    styl.css CSS zmienione: 5 linii
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    
    body {
        font-family: cursive, sans-serif;
        background-color: var(--color-page);
    }
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Nagłówek i stopka

    Kryteria:

    R.4.2 kolor tła: blok nagłówkowy i stopka - #0F0F64

    ok. 1 min
    Polecenie z arkusza

    Wspólne dla bloków nagłówkowego i stopki: kolor tła #0F0F64, biały kolor czcionki, wyrównanie tekstu do środka, marginesy wewnętrzne 3 px

    Jedna reguła dla dwóch selektorów, kolor tła ze zmiennej (R.4.2).

    styl.css CSS zmienione: 8 linii
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    
    body {
        font-family: cursive, sans-serif;
        background-color: var(--color-page);
    }
    
    header,
    footer {
        background-color: var(--color-bar);
        color: white;
        text-align: center;
        padding: 3px;
    }
    Podgląd strony po tym kroku (index.php)
  23. Krok 23: Blok boczny: wysokość

    ok. 1 min
    Polecenie z arkusza

    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.

    styl.css CSS zmienione: 4 linie
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    
    body {
        font-family: cursive, sans-serif;
        background-color: var(--color-page);
    }
    
    header,
    footer {
        background-color: var(--color-bar);
        color: white;
        text-align: center;
        padding: 3px;
    }
    
    aside {
        height: 500px;
    }
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Sekcje: wysokość i paski przewijania

    Kryteria:

    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.

    styl.css CSS zmienione: 5 linii
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    
    body {
        font-family: cursive, sans-serif;
        background-color: var(--color-page);
    }
    
    header,
    footer {
        background-color: var(--color-bar);
        color: white;
        text-align: center;
        padding: 3px;
    }
    
    aside {
        height: 500px;
    }
    
    section {
        height: 250px;
        overflow: auto;
    }
    Podgląd strony po tym kroku (index.php)
  25. Krok 25: Układ: siatka z nazwanymi obszarami

    Kryteria: ,

    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.

    styl.css CSS zmienione: 30 linii
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    
    body {
        font-family: cursive, sans-serif;
        background-color: var(--color-page);
    }
    
    header,
    footer {
        background-color: var(--color-bar);
        color: white;
        text-align: center;
        padding: 3px;
    }
    
    aside {
        height: 500px;
    }
    
    section {
        height: 250px;
        overflow: auto;
    }
    
    body {
        display: grid;
        grid-template-columns: 20% 80%;
        grid-template-areas:
            "header header"
            "aside content"
            "aside statistics"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        grid-area: aside;
    }
    
    #content {
        grid-area: content;
    }
    
    #statistics {
        grid-area: statistics;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (index.php)
  26. Krok 26: Obrazy jeden pod drugim

    Kryteria: ,

    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%.

    styl.css CSS zmienione: 6 linii
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    
    body {
        font-family: cursive, sans-serif;
        background-color: var(--color-page);
    }
    
    header,
    footer {
        background-color: var(--color-bar);
        color: white;
        text-align: center;
        padding: 3px;
    }
    
    aside {
        height: 500px;
    }
    
    section {
        height: 250px;
        overflow: auto;
    }
    
    body {
        display: grid;
        grid-template-columns: 20% 80%;
        grid-template-areas:
            "header header"
            "aside content"
            "aside statistics"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        grid-area: aside;
    }
    
    #content {
        grid-area: content;
    }
    
    #statistics {
        grid-area: statistics;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 150px;
        padding: 2px;
        display: block;
    }
    Podgląd strony po tym kroku (index.php)
  27. Krok 27: Nagłówki kafelków wyśrodkowane

    Kryteria:

    R.4.5 wyrównanie tekstu do środka: blok nagłówkowy, stopka, selektor h4

    ok. 1 min
    Polecenie z arkusza

    Dla selektora nagłówka czwartego stopnia: wyrównanie tekstu do środka

    Selektor znacznika h4. Z nagłówkiem i stopką to komplet R.4.5.

    styl.css CSS zmienione: 4 linie
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    
    body {
        font-family: cursive, sans-serif;
        background-color: var(--color-page);
    }
    
    header,
    footer {
        background-color: var(--color-bar);
        color: white;
        text-align: center;
        padding: 3px;
    }
    
    aside {
        height: 500px;
    }
    
    section {
        height: 250px;
        overflow: auto;
    }
    
    body {
        display: grid;
        grid-template-columns: 20% 80%;
        grid-template-areas:
            "header header"
            "aside content"
            "aside statistics"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        grid-area: aside;
    }
    
    #content {
        grid-area: content;
    }
    
    #statistics {
        grid-area: statistics;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 150px;
        padding: 2px;
        display: block;
    }
    
    h4 {
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  28. Krok 28: Odnośniki

    Kryteria: ,

    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.

    styl.css CSS zmienione: 5 linii
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    
    body {
        font-family: cursive, sans-serif;
        background-color: var(--color-page);
    }
    
    header,
    footer {
        background-color: var(--color-bar);
        color: white;
        text-align: center;
        padding: 3px;
    }
    
    aside {
        height: 500px;
    }
    
    section {
        height: 250px;
        overflow: auto;
    }
    
    body {
        display: grid;
        grid-template-columns: 20% 80%;
        grid-template-areas:
            "header header"
            "aside content"
            "aside statistics"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        grid-area: aside;
    }
    
    #content {
        grid-area: content;
    }
    
    #statistics {
        grid-area: statistics;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 150px;
        padding: 2px;
        display: block;
    }
    
    h4 {
        text-align: center;
    }
    
    a {
        color: white;
        text-decoration: none;
    }
    Podgląd strony po tym kroku (index.php)
  29. Krok 29: Odnośnik pod kursorem i z klawiatury

    Kryteria:

    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.

    styl.css CSS zmienione: 5 linii
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    
    body {
        font-family: cursive, sans-serif;
        background-color: var(--color-page);
    }
    
    header,
    footer {
        background-color: var(--color-bar);
        color: white;
        text-align: center;
        padding: 3px;
    }
    
    aside {
        height: 500px;
    }
    
    section {
        height: 250px;
        overflow: auto;
    }
    
    body {
        display: grid;
        grid-template-columns: 20% 80%;
        grid-template-areas:
            "header header"
            "aside content"
            "aside statistics"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        grid-area: aside;
    }
    
    #content {
        grid-area: content;
    }
    
    #statistics {
        grid-area: statistics;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 150px;
        padding: 2px;
        display: block;
    }
    
    h4 {
        text-align: center;
    }
    
    a {
        color: white;
        text-decoration: none;
    }
    
    a:hover,
    a:focus-visible {
        background-color: var(--color-hover);
    }
    Podgląd strony po tym kroku (index.php)
  30. 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.

    styl.css CSS zmienione: 17 linii
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    
    body {
        font-family: cursive, sans-serif;
        background-color: var(--color-page);
    }
    
    header,
    footer {
        background-color: var(--color-bar);
        color: white;
        text-align: center;
        padding: 3px;
    }
    
    aside {
        height: 500px;
    }
    
    section {
        height: 250px;
        overflow: auto;
    }
    
    body {
        display: grid;
        grid-template-columns: 20% 80%;
        grid-template-areas:
            "header header"
            "aside content"
            "aside statistics"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        grid-area: aside;
    }
    
    #content {
        grid-area: content;
    }
    
    #statistics {
        grid-area: statistics;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 150px;
        padding: 2px;
        display: block;
    }
    
    h4 {
        text-align: center;
    }
    
    a {
        color: white;
        text-decoration: none;
    }
    
    a:hover,
    a:focus-visible {
        background-color: var(--color-hover);
    }
    
    #statistics {
        display: flex;
        flex-wrap: wrap;
        align-items: flex-start;
    }
    
    .block {
        background-color: var(--color-tile);
        color: white;
        width: 150px;
        height: 150px;
        margin: 10px;
        padding: 5px;
        border-radius: 10px;
        box-shadow: 3px 3px 5px black;
    }
    Podgląd strony po tym kroku (index.php)
  31. Etap 4: Skrypty PHP

    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.php PHP zmienione: 15 linii
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
            <h3>Wybierz ucznia z listy:</h3>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
            </div>
            <div class="block">
                <h4>Lata</h4>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
            </div>
            <div class="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)
  32. 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.php PHP zmienione: 6 linii
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    // Wszystkie dane pobrane - zamknięcie połączenia
    $pdo = null;
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
            <h3>Wybierz ucznia z listy:</h3>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
            </div>
            <div class="block">
                <h4>Lata</h4>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
            </div>
            <div class="block">
                <h4>Najgorszy wynik</h4>
            </div>
        </section>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

    Podgląd strony bez zmian - taki jak po kroku 31.

  33. Krok 33: Skrypt 1, Przedmioty: zapytanie 2

    Kryteria:

    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.php PHP zmienione: 2 linie
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    require 'statystyki.php';
    
    // Wszystkie dane pobrane - zamknięcie połączenia
    $pdo = null;
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
            <h3>Wybierz ucznia z listy:</h3>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
            </div>
            <div class="block">
                <h4>Lata</h4>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
            </div>
            <div class="block">
                <h4>Najgorszy wynik</h4>
            </div>
        </section>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    statystyki.php PHP nowy 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);

    Podgląd strony bez zmian - taki jak po kroku 31.

  34. 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.php PHP zmienione: 1 linia
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    require 'statystyki.php';
    
    // Wszystkie dane pobrane - zamknięcie połączenia
    $pdo = null;
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
            <h3>Wybierz ucznia z listy:</h3>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
                <p><?= escape(implode(' ', $subjects)) ?></p>
            </div>
            <div class="block">
                <h4>Lata</h4>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
            </div>
            <div class="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)
  35. 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.php PHP zmienione: 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();

    Podgląd strony bez zmian - taki jak po kroku 34.

  36. Krok 36: Skrypt 1, Lata: dwa lata z myślnikiem

    Kryteria:

    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.php PHP zmienione: 1 linia
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    require 'statystyki.php';
    
    // Wszystkie dane pobrane - zamknięcie połączenia
    $pdo = null;
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
            <h3>Wybierz ucznia z listy:</h3>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
                <p><?= escape(implode(' ', $subjects)) ?></p>
            </div>
            <div class="block">
                <h4>Lata</h4>
                <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
            </div>
            <div class="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)
  37. 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 funkcji averageResult(): 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.php PHP zmienione: 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();
    
    function averageResult(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();
        return number_format((float) $row['Wynik'], 2, ',', ' ');
    }
    
    $best = averageResult($pdo);

    Podgląd strony bez zmian - taki jak po kroku 36.

  38. 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.php PHP zmienione: 1 linia
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    require 'statystyki.php';
    
    // Wszystkie dane pobrane - zamknięcie połączenia
    $pdo = null;
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
            <h3>Wybierz ucznia z listy:</h3>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
                <p><?= escape(implode(' ', $subjects)) ?></p>
            </div>
            <div class="block">
                <h4>Lata</h4>
                <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
                <p><?= escape($best) ?>%</p>
            </div>
            <div class="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)
  39. 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.php PHP zmienione: 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();
    
    function averageResult(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();
        return number_format((float) $row['Wynik'], 2, ',', ' ');
    }
    
    $best = averageResult($pdo, 'DESC');
    $worst = averageResult($pdo, 'ASC');

    Podgląd strony bez zmian - taki jak po kroku 38.

  40. 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.php PHP zmienione: 1 linia
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    require 'statystyki.php';
    
    // Wszystkie dane pobrane - zamknięcie połączenia
    $pdo = null;
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
            <h3>Wybierz ucznia z listy:</h3>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
                <p><?= escape(implode(' ', $subjects)) ?></p>
            </div>
            <div class="block">
                <h4>Lata</h4>
                <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
                <p><?= escape($best) ?>%</p>
            </div>
            <div class="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)
  41. 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.php PHP zmienione: 3 linie
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((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>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
            <h3>Wybierz ucznia z listy:</h3>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
                <p><?= escape(implode(' ', $subjects)) ?></p>
            </div>
            <div class="block">
                <h4>Lata</h4>
                <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
                <p><?= escape($best) ?>%</p>
            </div>
            <div class="block">
                <h4>Najgorszy wynik</h4>
                <p><?= escape($worst) ?>%</p>
            </div>
        </section>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

    Podgląd strony bez zmian - taki jak po kroku 40.

  42. 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.php PHP zmienione: 5 linii
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((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>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
            <h3>Wybierz ucznia z listy:</h3>
            <ul class="students">
    <?php foreach ($students as $student): ?>
                <li><a href="wynik.php"><?= escape($student['id']) ?>. <?= escape($student['imie']) ?> <?= escape($student['nazwisko']) ?></a></li>
    <?php endforeach; ?>
            </ul>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
                <p><?= escape(implode(' ', $subjects)) ?></p>
            </div>
            <div class="block">
                <h4>Lata</h4>
                <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
                <p><?= escape($best) ?>%</p>
            </div>
            <div class="block">
                <h4>Najgorszy wynik</h4>
                <p><?= escape($worst) ?>%</p>
            </div>
        </section>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    styl.css CSS zmienione: 5 linii
    :root {
        --color-page: #4682B4;
        --color-bar: #0F0F64;
        --color-tile: #2B6BA0;
        --color-hover: #12538A;
    }
    
    body {
        font-family: cursive, sans-serif;
        background-color: var(--color-page);
    }
    
    header,
    footer {
        background-color: var(--color-bar);
        color: white;
        text-align: center;
        padding: 3px;
    }
    
    aside {
        height: 500px;
    }
    
    section {
        height: 250px;
        overflow: auto;
    }
    
    body {
        display: grid;
        grid-template-columns: 20% 80%;
        grid-template-areas:
            "header header"
            "aside content"
            "aside statistics"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        grid-area: aside;
    }
    
    #content {
        grid-area: content;
    }
    
    #statistics {
        grid-area: statistics;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 150px;
        padding: 2px;
        display: block;
    }
    
    h4 {
        text-align: center;
    }
    
    a {
        color: white;
        text-decoration: none;
    }
    
    a:hover,
    a:focus-visible {
        background-color: var(--color-hover);
    }
    
    #statistics {
        display: flex;
        flex-wrap: wrap;
        align-items: flex-start;
    }
    
    .block {
        background-color: var(--color-tile);
        color: white;
        width: 150px;
        height: 150px;
        margin: 10px;
        padding: 5px;
        border-radius: 10px;
        box-shadow: 3px 3px 5px black;
    }
    
    .students {
        list-style: none;
        padding-left: 0;
    }
    Podgląd strony po tym kroku (index.php)
  43. 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 &amp; - 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.php PHP zmienione: 1 linia
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((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>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
            <h3>Wybierz ucznia z listy:</h3>
            <ul class="students">
    <?php foreach ($students as $student): ?>
                <li><a href="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>
    <?php endforeach; ?>
            </ul>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
                <p><?= escape(implode(' ', $subjects)) ?></p>
            </div>
            <div class="block">
                <h4>Lata</h4>
                <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
                <p><?= escape($best) ?>%</p>
            </div>
            <div class="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)
  44. 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 jako wynik.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.php PHP nowy plik
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    require 'statystyki.php';
    
    // Wszystkie dane pobrane - zamknięcie połączenia
    $pdo = null;
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
                <p><?= escape(implode(' ', $subjects)) ?></p>
            </div>
            <div class="block">
                <h4>Lata</h4>
                <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
                <p><?= escape($best) ?>%</p>
            </div>
            <div class="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)
  45. 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.php PHP zmienione: 10 linii
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((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>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
    <?php if ($studentId !== false): ?>
            <h2><?= escape($fullName) ?></h2>
    <?php else: ?>
            <h2>Nie wybrano ucznia</h2>
            <p><a href="index.php">Wróć do listy uczniów</a></p>
    <?php endif; ?>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
                <p><?= escape(implode(' ', $subjects)) ?></p>
            </div>
            <div class="block">
                <h4>Lata</h4>
                <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
                <p><?= escape($best) ?>%</p>
            </div>
            <div class="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)
  46. 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.php PHP zmienione: 12 linii
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((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>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
    <?php if ($studentId !== false): ?>
            <h2><?= escape($fullName) ?></h2>
    <?php else: ?>
            <h2>Nie wybrano ucznia</h2>
            <p><a href="index.php">Wróć do listy uczniów</a></p>
    <?php endif; ?>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
                <p><?= escape(implode(' ', $subjects)) ?></p>
            </div>
            <div class="block">
                <h4>Lata</h4>
                <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
                <p><?= escape($best) ?>%</p>
            </div>
            <div class="block">
                <h4>Najgorszy wynik</h4>
                <p><?= escape($worst) ?>%</p>
            </div>
        </section>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

    Podgląd strony bez zmian - taki jak po kroku 45.

  47. 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.php PHP zmienione: 7 linii
    <?php
    // Połączenie z bazą: błędy jako wyjątki, wiersze jako tablice z nazwami kolumn
    try {
        $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)
    function escape(string|int|float|null $value): string
    {
        return htmlspecialchars((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>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Matura</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>System informacji dla maturzystów</h1>
        </header>
        <aside>
            <img src="ma.jpg" alt="Matura">
            <img src="tu.jpg" alt="Matura">
            <img src="ra.jpg" alt="Matura">
        </aside>
        <section id="content">
    <?php if ($studentId !== false): ?>
            <h2><?= escape($fullName) ?></h2>
    <?php foreach ($results as $row): ?>
            <h3><?= escape($row['rok']) ?> <?= escape($row['sesja']) ?></h3>
            <p><?= escape($row['przedmiot']) ?>: <?= escape($row['punkty']) ?></p>
    <?php endforeach; ?>
    <?php if ($results === []): ?>
            <p>Brak wyników matur tego ucznia.</p>
    <?php endif; ?>
    <?php else: ?>
            <h2>Nie wybrano ucznia</h2>
            <p><a href="index.php">Wróć do listy uczniów</a></p>
    <?php endif; ?>
        </section>
        <section id="statistics">
            <div class="block">
                <h4>Przedmioty</h4>
                <p><?= escape(implode(' ', $subjects)) ?></p>
            </div>
            <div class="block">
                <h4>Lata</h4>
                <p><?= escape($years['najstarszy']) ?> - <?= escape($years['najmlodszy']) ?></p>
            </div>
            <div class="block">
                <h4>Najlepszy wynik</h4>
                <p><?= escape($best) ?>%</p>
            </div>
            <div class="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)
  48. 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 podstawowata wersja
    układfloat: left + clear: bothgrid-template-areas na <body>
    kafelki obok siebiefloat: left w .blockdisplay: flex w sekcji #statistics
    wspólny kodSkrypt 1 skopiowany do obu stronSkrypt 1 w statystyki.php dołączanym przez require, połączenie w każdej stronie
    bazamysqli_* z tabeli 1PDO, wyjątki, FETCH_ASSOC, FETCH_COLUMN
    najlepszy i najgorszydwa prawie identyczne blokijedna funkcja, kierunek z białej listy
    adres odnośnikasklejony ręczniehttp_build_query + escape()
    id z adresuwstawiony do SQLfilter_var i parametr :id
    brak danychostrzeżenia PHP, pusty blokkomunikaty „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.

    Podgląd strony bez zmian - taki jak po kroku 47.

  49. Krok 49: Którą wersję wybrać na egzaminie

    ok. 2 min
    Polecenie z arkusza

    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.

    Podgląd strony bez zmian - taki jak po kroku 47.

  50. Etap 6: Oddanie pracy

    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.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

    Podgląd strony bez zmian - taki jak po kroku 47.

Podgląd

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: . 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.