Przejdź do głównej treści
INF.03-01-26.01-SGStyczeń 2026 · 27 kroków · ok. 65 min · 34 kryteria z klucza

Rozwiązanie krok po kroku: Portal konfiguracji samochodów (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza samochody z czterema zapytaniami i strona index.php z tabelą aut, tabelą konfiguracji ze scalonymi komórkami i sekcją kontaktu - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • siatka CSS w bloku głównym: trzy kolumny po 33,3% w grid-template-columns (klucz R.4.4 przyjmuje je wprost) zamiast float: left i clear: both,
  • PHP na PDO: oba zapytania na górze pliku, połączenie zamknięte, zanim zacznie się HTML, a tabele wypisywane pętlą foreach w szablonie,
  • bezpieczeństwo: htmlspecialchars na wszystkim, co trafia z bazy na stronę, try/catch zamiast białej strony przy awarii bazy, jawna zamiana cen z napisów na liczby,
  • funkcja carRows() zamiast dwóch kopii kodu wypisującego wylosowane auto,
  • zapytania z aliasami i złączeniem JOIN ... ON.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafiki pochodzą z prawdziwego archiwum pliki1, zapytania zostały wykonane na tej bazie w MariaDB z XAMPP-a, a podgląd każdej strony PHP to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2. Zapytanie 4 losuje, więc w podglądzie stoją dwa auta z ilustracji 2 (BM beta za 94000 i za 77000).

Jak liczą się punkty. Klucz ma 34 kryteria (R.1.1 - R.5.8), każde za 1 punkt. Próg zdania to 75%, czyli 26 punktów. Przy każdym kroku jest napisane, które kryteria spełnia.

  1. Etap 1: Baza danych

    Kroki 1-7 · po tym etapie masz 6 z 34 kryteriów (pkt)

    Krok 1: Baza danych samochody

    ok. 2 min
    Polecenie z arkusza

    Utwórz bazę danych o nazwie samochody, z zestawem polskich znaków (np. utf8_polish_ci).

    Archiwum pliki1.7z rozpakuj hasłem S@mocHody^ (z małpą zamiast „a” i daszkiem na końcu): baza.sql i trzy grafiki. W phpMyAdmin Nowa → samochody, porównywanie utf8_polish_ci, albo:

    CREATE DATABASE samochody CHARACTER SET utf8 COLLATE utf8_polish_ci;
    
    Typowe błędy

    Nazwa bazy musi być dokładnie samochody - skrypt łączy się z bazą po nazwie (R.5.1).

  2. Krok 2: Import tabel z pliku baza.sql

    ok. 2 min
    Polecenie z arkusza

    Z rozpakowanego archiwum zaimportuj tabele z pliku baza.sql do utworzonej bazy.

    Import → baza.sql. Dwie tabele: pojazdy (62 auta) i kolory (8 kolorów z dopłatą - metaliki kosztują 1000 zł więcej); pojazdy.kolor to klucz obcy wskazujący kolory.id.

  3. Krok 3: Zrzut ekranu import.jpg

    Kryteria:

    R.1.1 Wykonany import tabeli do bazy danych samochody - plik import.jpg

    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.

    Cały ekran z paskiem zadań. Uwaga na formaty: import w JPEG (import.jpg), zrzuty zapytań w PNG.

    Typowe błędy

    Wykadrowany zrzut albo zrzut importu zapisany jako PNG to strata R.1.1.

  4. Krok 4: Zapytanie 1: 15 najdroższych aut marki BM

    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 pierwsze 15 rekordów, jedynie pola: marka, model, cena dla pojazdów, których marka to BM posortowane malejąco według ceny. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT marka, model, cena FROM pojazdy WHERE marka = "BM" ORDER BY cena DESC LIMIT 15; oraz na zrzucie jest dokładnie 15 rekordów zawierających jedynie pola marka, model, cena. Ceny od 94000 do 84000

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające pierwsze 15 rekordów, jedynie pola: marka, model, cena dla pojazdów, których marka to BM posortowane malejąco według ceny.

    Klauzule zawsze w kolejności SELECT → FROM → WHERE → ORDER BY → LIMIT. LIMIT 15 działa po sortowaniu, więc daje 15 najdroższych. Wynik: ceny od 94000 do 84000. Zapytania zapisuj w kwerendy.txt, zrzut jako kw1.png.

    Typowe błędy

    LIMIT przed ORDER BY to błąd składni, a bez DESC wychodzi 15 najtańszych.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT marka, model, cena
    FROM pojazdy
    WHERE marka = 'BM'
    ORDER BY cena DESC
    LIMIT 15;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: średnia i najwyższa cena modelu meta

    Kryteria:

    R.1.4 Wykonane zapytanie 2 liczące średnią cenę pojazdów oraz wybierające najdroższy pojazd dla modelu „meta”. Nazwy kolumn (aliasy) : „Średnia cena”, „Maksymalna cena”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT AVG(cena) AS "Średnia cena", MAX(cena) AS "Maksymalna cena" FROM pojazdy WHERE model = "meta"; (AS nieobowiązkowe, w aliasie dopuszczalne literówki) oraz na zrzucie widoczne są wartości: 92071.4286 oraz 98000 (dowolne zaokrąglenie)

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: liczące średnią cenę pojazdów oraz wybierające najdroższy pojazd dla modelu „meta”. Nazwy kolumn (aliasy) odpowiednio: „Średnia cena”, „Maksymalna cena”.

    AVG i MAX liczone z wierszy modelu „meta”. Aliasy ze spacją i polską literą w odwrotnych apostrofach - w MySQL i MariaDB to znak dla nazw (cudzysłów z przykładu w kluczu też zadziała). Wynik: jeden wiersz - 92071.4286 i 98000. Zrzut: kw2.png.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT marka, model, cena
    FROM pojazdy
    WHERE marka = 'BM'
    ORDER BY cena DESC
    LIMIT 15;
    
    -- Zapytanie 2
    SELECT AVG(cena) AS `Średnia cena`,
           MAX(cena) AS `Maksymalna cena`
    FROM pojazdy
    WHERE model = 'meta';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: auta modelu alfa z kolorem i dopłatą

    Kryteria:

    R.1.5 Wykonane zapytanie 3 wybierające z tabeli pojazdy jedynie pola: marka, model, cena oraz odpowiadające im z tabeli kolory pola: nazwa, doplata dla modelu „alfa”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT marka, model, cena, nazwa, doplata FROM pojazdy INNER JOIN kolory ON kolor = kolory.id WHERE model = "alfa"; (możliwe też samo JOIN lub porównanie kluczy po WHERE) oraz na zrzucie widocznych jest dokładnie 6 wierszy, nazwy kolorów: zielony, czarny, czarny, zielony metalik, czerwony metalik, czarny metalik

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające z tabeli pojazdy jedynie pola: marka, model, cena oraz odpowiadające im z tabeli kolory pola: nazwa, doplata dla modelu „alfa”. Należy posłużyć się relacją.

    Aliasy p (pojazdy) i k (kolory): przy każdej kolumnie widać, z której tabeli pochodzi, a ON k.id = p.kolor pokazuje parę kluczy relacji. Wynik: 6 wierszy z kolorami zielony, czarny, czarny, zielony metalik, czerwony metalik, czarny metalik (bez ORDER BY kolejność wierszy zależy od serwera). Zrzut: kw3.png. To zapytanie wyśle skrypt 1.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT marka, model, cena
    FROM pojazdy
    WHERE marka = 'BM'
    ORDER BY cena DESC
    LIMIT 15;
    
    -- Zapytanie 2
    SELECT AVG(cena) AS `Średnia cena`,
           MAX(cena) AS `Maksymalna cena`
    FROM pojazdy
    WHERE model = 'meta';
    
    -- Zapytanie 3
    SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata
    FROM pojazdy AS p
    JOIN kolory AS k ON k.id = p.kolor
    WHERE p.model = 'alfa';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: dwa losowe auta

    Kryteria:

    R.1.6 Wykonane zapytanie 4 losujące dwa dowolne rekordy z tabeli pojazdy i wybierające jedynie markę, model i cenę. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT marka, model, cena FROM pojazdy ORDER BY RAND() LIMIT 2; oraz na zrzucie są dokładnie 2 losowe wiersze z polami marka, model, cena

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: losujące dwa dowolne rekordy z tabeli pojazdy i wybierające jedynie markę, model i cenę.

    ORDER BY RAND() sortuje wiersze według liczby losowej, LIMIT 2 zostawia dwa pierwsze - przy każdym uruchomieniu inne. Na 62 wierszach to nic nie kosztuje; przy milionach wierszy serwer musiałby wylosować liczbę dla każdego z nich i całość posortować, więc duże serwisy losują inaczej. Zrzut kw4.png: dokładnie 2 wiersze.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT marka, model, cena
    FROM pojazdy
    WHERE marka = 'BM'
    ORDER BY cena DESC
    LIMIT 15;
    
    -- Zapytanie 2
    SELECT AVG(cena) AS `Średnia cena`,
           MAX(cena) AS `Maksymalna cena`
    FROM pojazdy
    WHERE model = 'meta';
    
    -- Zapytanie 3
    SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata
    FROM pojazdy AS p
    JOIN kolory AS k ON k.id = p.kolor
    WHERE p.model = 'alfa';
    
    -- Zapytanie 4
    SELECT marka, model, cena
    FROM pojazdy
    ORDER BY RAND()
    LIMIT 2;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Strona HTML

    Kroki 8-12 · po tym etapie masz 14 z 34 kryteriów (pkt)

    Krok 8: Plik index.php: HTML5, język, kodowanie

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Składa się ze strony o nazwie index.php. Zapisana w języku HTML5. Zadeklarowany polski język zawartości witryny. Jawnie zastosowany właściwy standard kodowania polskich znaków.

    Grafiki a1.jpg, a2.jpg, a3.png z archiwum skopiuj do folderu zdającego. Plik index.php: <!DOCTYPE html>, <html lang="pl"> (R.2.1) i <meta charset="UTF-8"> jako pierwszy element <head>. Rozszerzenie .php, bo strona będzie pobierać dane z bazy - otwierasz ją przez http://localhost/....

    index.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  9. Krok 9: Tytuł strony i arkusz stylów

    Kryteria: ,

    R.2.2 W sekcji head znajduje się jawnie podany standard kodowania polskich znaków i tytuł strony "Konfigurator samochodów”

    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

    Tytuł strony „Konfigurator samochodów”. Arkusz stylów w pliku o nazwie styl.css prawidłowo połączony z kodem strony.

    <title> (z kodowaniem to R.2.2), <link> do styl.css - całe formatowanie z tego pliku (R.3.2) - i viewport, żeby telefon nie pomniejszał strony.

    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>Konfigurator samochodów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  10. Krok 10: Bloki semantyczne, nagłówek i nawigacja

    Kryteria:

    R.2.3 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML5: header, nav, main->section+section+section, footer (dopuszcza się zastosowanie bloków pomocniczych)

    ok. 3 min
    Polecenie z arkusza

    Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 3. Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści „Serwis konfiguracji samochodów”. Zawartość bloku nawigacyjnego: trzy nagłówki drugiego stopnia o treści: „Samochody”, „Konfigurator”, „Kontakt”. Zawartość bloku głównego: blok sekcji lewej, środkowej i prawej.

    Struktura z klucza R.2.3: header, nav, main z trzema section, footer. W nagłówku <h1>, w nawigacji trzy <h2> - na stronie staną obok siebie po stylach.

    Sekcje dostają identyfikatory od treści, nie od położenia: cars (tabela aut), configurator, contact. Nazwy left/right przestałyby pasować, gdyby na telefonie sekcje stanęły jedna pod drugą; nazwy od treści pasują zawsze. Część stylów dotyczy „jedynie” jednej sekcji, więc CSS musi je rozróżniać.

    index.php PHP zmienione: 18 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Konfigurator samochodów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Serwis konfiguracji samochodów</h1>
        </header>
        <nav>
            <h2>Samochody</h2>
            <h2>Konfigurator</h2>
            <h2>Kontakt</h2>
        </nav>
        <main>
            <section id="cars">
            </section>
            <section id="configurator">
            </section>
            <section id="contact">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  11. Krok 11: Tabele sekcji lewej i środkowej

    Kryteria: ,

    R.2.7 Tabela w bloku sekcji środkowej ma w pierwszym wierszu scalone kolumny 1 i 2; w co najmniej jednym wierszu z obrazem są scalone wszystkie kolumny. W co najmniej jednej komórce z ceną są scalone dwa wiersze

    R.2.8 Pierwszy wiersz tabeli z bloku sekcji środkowej zawiera komórki nagłówkowe „Konfiguracja”, „Cena”, zastosowane znaczniki <tr> i <th>

    ok. 3 min
    Polecenie z arkusza

    Zawartość bloku sekcji lewej: tabela z czterema kolumnami, wypełniona skryptem 1. Zawartość bloku sekcji środkowej: Tabela z trzema kolumnami i siedmioma wierszami, niektóre komórki są scalone zgodnie z ilustracją 2. W pierwszym wierszu tabeli komórki nagłówkowe: „Konfiguracja”, „Cena”. W drugim wierszu tabeli obraz a1.jpg z tekstem alternatywnym „Konfiguracja 1”. W piątym wierszu tabeli obraz a2.jpg z tekstem alternatywnym „Konfiguracja 2”.

    • w sekcji aut pusta tabela - wiersze wypisze skrypt 1,
    • w sekcji konfiguratora tabela o trzech kolumnach: pierwszy wiersz to komórki nagłówkowe <th>, z których „Konfiguracja” zajmuje dwie kolumny - colspan="2" (R.2.8),
    • wiersze z obrazami mają jedną komórkę na całą szerokość: colspan="3" (R.2.7).

    Tabela ma na razie trzy wiersze; wiersze 3-4 i 6-7 dopisze skrypt 2 - przed i po obrazie a2.jpg.

    index.php PHP zmienione: 14 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Konfigurator samochodów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Serwis konfiguracji samochodów</h1>
        </header>
        <nav>
            <h2>Samochody</h2>
            <h2>Konfigurator</h2>
            <h2>Kontakt</h2>
        </nav>
        <main>
            <section id="cars">
                <table>
                </table>
            </section>
            <section id="configurator">
                <table>
                    <tr>
                        <th colspan="2">Konfiguracja</th>
                        <th>Cena</th>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a1.jpg" alt="Konfiguracja 1"></td>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a2.jpg" alt="Konfiguracja 2"></td>
                    </tr>
                </table>
            </section>
            <section id="contact">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  12. Krok 12: Sekcja prawa i stopka

    Kryteria: ,

    R.2.4 W bloku nagłówkowym znajduje się <h1>: „Serwis konfiguracji samochodów”, w bloku nawigacyjnym <h2>: „Samochody”, „Konfigurator” i „Kontakt”, w bloku sekcji prawej <h3> „111 222 444” oraz w stopce <p> z numerem zdającego

    R.2.5 Na stronie umieszczono grafiki z tekstem alternatywnym: a1.jpg "Konfiguracja 1", a2.jpg "Konfiguracja 2" oraz a3.png "Samochód"

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku sekcji prawej: Nagłówek trzeciego stopnia o treści „111 222 444”. Obraz a3.png z tekstem alternatywnym „Samochód”. Zawartość stopki: paragraf o treści: „Stronę wykonał: ”, dalej wstawiony numer zdającego.

    <h3> z telefonem i kabriolet - komplet trzech obrazów z tekstem alternatywnym (R.2.5). W stopce akapit: na egzaminie po „Stronę wykonał:” wpisujesz swój numer zdającego (PESEL), tu stoi autor rozwiązania. Komplet nagłówków i stopki to R.2.4.

    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>Konfigurator samochodów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Serwis konfiguracji samochodów</h1>
        </header>
        <nav>
            <h2>Samochody</h2>
            <h2>Konfigurator</h2>
            <h2>Kontakt</h2>
        </nav>
        <main>
            <section id="cars">
                <table>
                </table>
            </section>
            <section id="configurator">
                <table>
                    <tr>
                        <th colspan="2">Konfiguracja</th>
                        <th>Cena</th>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a1.jpg" alt="Konfiguracja 1"></td>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a2.jpg" alt="Konfiguracja 2"></td>
                    </tr>
                </table>
            </section>
            <section id="contact">
                <h3>111 222 444</h3>
                <img src="a3.png" alt="Samochód">
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  13. Etap 3: Styl CSS

    Kroki 13-18 · po tym etapie masz 25 z 34 kryteriów (pkt)

    Krok 13: Plik styl.css: wyrównanie i krój czcionki

    Kryteria:

    R.4.1 domyślne formatowanie selektorów (*): wyrównanie tekstu do środka, krój czcionki - Helvetica, sans-serif (dozwolona tylko taka kolejność)

    ok. 1 min
    Polecenie z arkusza

    Domyślnie dla wszystkich selektorów: wyrównanie tekstu do środka, krój czcionki Helvetica, w przypadku braku, sans-serif.

    Selektor *; klucz R.4.1 dopuszcza tylko kolejność „Helvetica, sans-serif” - Helvetica, a gdy jej brak (na Windows zwykle), dowolny krój bezszeryfowy.

    styl.css CSS nowy plik
    * {
        text-align: center;
        font-family: Helvetica, sans-serif;
    }
    Podgląd strony po tym kroku (index.php)
  14. Krok 14: Nagłówek, nawigacja i stopka

    Kryteria:

    R.4.3 kolor czcionki: blok nagłówkowy, nawigacyjny, stopka - biały

    ok. 2 min
    Polecenie z arkusza

    Wspólne dla bloków nagłówkowego, nawigacyjnego i stopki: biały kolor czcionki, marginesy wewnętrzne 2 px. Dodatkowo dla bloków nagłówkowego i stopki: kolor tła DarkGreen oraz dla bloku nawigacyjnego kolor tła ForestGreen.

    Wspólna reguła trzech bloków i dwie reguły kolorów tła - każdy selektor w osobnej linii, więc dopisanie czwartego bloku to jedna linia. Biały tekst (R.4.3), marginesy wewnętrzne 2 px.

    styl.css CSS zmienione: 16 linii
    * {
        text-align: center;
        font-family: Helvetica, sans-serif;
    }
    
    header,
    nav,
    footer {
        padding: 2px;
        color: white;
    }
    
    header,
    footer {
        background-color: DarkGreen;
    }
    
    nav {
        background-color: ForestGreen;
    }
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Sekcje obok siebie: siatka w bloku głównym

    Kryteria: , , ,

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 3 w arkuszu egzaminacyjnym (prawidłowo zastosowano 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.2 kolor tła: blok nagłówkowy, stopka - DarkGreen, blok nawigacyjny - ForestGreen, bloki wszystkich sekcji - LightGreen

    R.4.5 paski przewijania: wszystkie bloki sekcji - auto (overflow)

    R.4.6 marginesy wewnętrzne (padding) : blok nagłówkowy, nawigacyjny, stopka - 2px margines wewnętrzny górny (padding-top) : wszystkie bloki sekcji - 10px

    ok. 3 min
    Polecenie z arkusza

    Dla wszystkich bloków sekcji: kolor tła LightGreen, szerokość 33.3%, wysokość 550 px, jedynie górny margines wewnętrzny 10 px, paski przewijania widoczne jedynie w przypadku przepełnienia bloku.

    Blok główny staje się siatką o trzech kolumnach: grid-template-columns: repeat(3, 33.3%) to skrót od 33.3% 33.3% 33.3%, a klucz R.4.4 przyjmuje szerokość sekcji zapisaną właśnie tak („width lub grid-template-columns lub flex”). Sekcje wpadają do kolumn po kolei - bez float w sekcjach i bez clear w stopce, bo siatka nie wyjmuje elementów z przepływu: stopka i tak stoi pod blokiem głównym (R.3.1).

    Reguła section: wysokość 550 px, padding-top - jedynie górny margines wewnętrzny (poziomy padding w sekcji nie rozepchnąłby siatki, ale zmieniłby szerokość treści), overflow: auto - pasek tylko w sekcji, której treść się nie mieści (środkowa z dwoma zdjęciami, ilustracja 2), i tło LightGreen. Kolory tła trzech rodzajów bloków to R.4.2, przewijanie - R.4.5, marginesy wewnętrzne - R.4.6.

    styl.css CSS zmienione: 12 linii
    * {
        text-align: center;
        font-family: Helvetica, sans-serif;
    }
    
    header,
    nav,
    footer {
        padding: 2px;
        color: white;
    }
    
    header,
    footer {
        background-color: DarkGreen;
    }
    
    nav {
        background-color: ForestGreen;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(3, 33.3%);
    }
    
    section {
        height: 550px;
        padding-top: 10px;
        overflow: auto;
        background-color: LightGreen;
    }
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Nagłówki drugiego stopnia obok siebie

    Kryteria:

    R.4.8 styl dla nagłówka drugiego stopnia: właściwość display: inline-block

    ok. 1 min
    Polecenie z arkusza

    Dla selektora nagłówka drugiego stopnia: szerokość 33%, sposób wyświetlania elementu liniowo-blokowy.

    Tu nie ma wyboru techniki: klucz R.4.8 ocenia wprost display: inline-block na selektorze h2, więc siatka na <nav> dałaby ten sam wygląd, ale nie ten punkt. Nagłówek liniowo-blokowy stoi w linii jak tekst, a przyjmuje szerokość - trzy po 33% mieszczą się obok siebie.

    styl.css CSS zmienione: 5 linii
    * {
        text-align: center;
        font-family: Helvetica, sans-serif;
    }
    
    header,
    nav,
    footer {
        padding: 2px;
        color: white;
    }
    
    header,
    footer {
        background-color: DarkGreen;
    }
    
    nav {
        background-color: ForestGreen;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(3, 33.3%);
    }
    
    section {
        height: 550px;
        padding-top: 10px;
        overflow: auto;
        background-color: LightGreen;
    }
    
    h2 {
        display: inline-block;
        width: 33%;
    }
    Podgląd strony po tym kroku (index.php)
  17. Krok 17: Tabele: obramowanie, szerokość, połączone linie

    Kryteria: ,

    R.4.4 wysokość: wszystkie bloki sekcji - 550px szerokość: wszystkie bloki sekcji - 33.3% (width lub grid-template-columns lub flex) , selektor table - 90%, selektor h2 - 33%

    R.4.7 obramowanie: dla tabeli, selektorów td i th - 1px solid DarkGreen obramowanie połączone (collapse) - tabela

    ok. 2 min
    Polecenie z arkusza

    Wspólne dla selektora tabeli, komórki tabeli i komórki nagłówkowej: obramowanie linią ciągłą o szerokości 1 px i kolorze DarkGreen. Dodatkowo dla selektora tabeli: szerokość 90%, linie obramowania połączone.

    Selektory znaczników table, td, th (uwaga arkusza). border-collapse: collapse łączy ramki sąsiednich komórek w jedną linię (R.4.7). Szerokości: sekcje 33,3%, wysokość 550 px, tabele 90% i nagłówki h2 33% - komplet R.4.4.

    styl.css CSS zmienione: 11 linii
    * {
        text-align: center;
        font-family: Helvetica, sans-serif;
    }
    
    header,
    nav,
    footer {
        padding: 2px;
        color: white;
    }
    
    header,
    footer {
        background-color: DarkGreen;
    }
    
    nav {
        background-color: ForestGreen;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(3, 33.3%);
    }
    
    section {
        height: 550px;
        padding-top: 10px;
        overflow: auto;
        background-color: LightGreen;
    }
    
    h2 {
        display: inline-block;
        width: 33%;
    }
    
    table,
    td,
    th {
        border: 1px solid DarkGreen;
    }
    
    table {
        width: 90%;
        border-collapse: collapse;
    }
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Obrazy konfiguratora i wiersz aut pod kursorem

    Kryteria: ,

    R.4.9 styl jedynie dla obrazów z bloku sekcji środkowej: szerokość - 95%, zaokrąglenie rogów - 8px (border-radius)

    R.4.10 styl jedynie dla wierszy tabeli z bloku lewego: gdy kursor znajdzie się na wierszu jego kolor tła zmienia się na ForestGreen. Styl nie działa na tabeli z bloku sekcji środkowej

    ok. 2 min
    Polecenie z arkusza

    Jedynie dla obrazów z bloku środkowego: szerokość 95%, zaokrąglenie rogów 8 px. Jedynie dla wierszy tabeli z bloku lewego: gdy kursor znajdzie się na wierszu tabeli jego kolor tła zmienia się na ForestGreen (jak na ilustracji 2).

    Selektory potomka ograniczone do jednej sekcji: #configurator img nie rusza kabrioletu w sekcji kontaktu (R.4.9), a #cars tr:hover nie podświetla tabeli konfiguratora (R.4.10 sprawdza też to). 95% liczy się od komórki tabeli, więc zdjęcia mieszczą się w kolumnie.

    styl.css CSS zmienione: 9 linii
    * {
        text-align: center;
        font-family: Helvetica, sans-serif;
    }
    
    header,
    nav,
    footer {
        padding: 2px;
        color: white;
    }
    
    header,
    footer {
        background-color: DarkGreen;
    }
    
    nav {
        background-color: ForestGreen;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(3, 33.3%);
    }
    
    section {
        height: 550px;
        padding-top: 10px;
        overflow: auto;
        background-color: LightGreen;
    }
    
    h2 {
        display: inline-block;
        width: 33%;
    }
    
    table,
    td,
    th {
        border: 1px solid DarkGreen;
    }
    
    table {
        width: 90%;
        border-collapse: collapse;
    }
    
    #configurator img {
        width: 95%;
        border-radius: 8px;
    }
    
    #cars tr:hover {
        background-color: ForestGreen;
    }
    Podgląd strony po tym kroku (index.php)
  19. Etap 4: Skrypty PHP

    Kroki 19-25 · po tym etapie masz 34 z 34 kryteriów (pkt)

    Krok 19: Połączenie PDO na górze pliku

    Kryteria: , ,

    R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy samochody

    R.5.2 Ostatnią operacją na bazie jest jej zamknięcie

    R.5.8 W skryptach zastosowana przynajmniej jedna zmienna oraz nazwy wszystkich zmiennych są w języku polskim lub angielskim i odzwierciedlają cel zastosowania

    ok. 4 min
    Polecenie z arkusza

    Napisane w języku PHP. Należy stosować znaczące nazewnictwo zmiennych i funkcji w języku polskim lub angielskim. Łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie samochody.

    Cała praca z bazą stoi przed <!DOCTYPE html>: najpierw dane, potem HTML, który je tylko wypisuje. Połączenie przez PDO (klucz R.5 przyjmuje MySQLi i PDO):

    • napis DSN 'mysql:host=localhost;dbname=samochody;charset=utf8mb4' - serwer, baza i kodowanie; 'root', '' - użytkownik i puste hasło (R.5.1),
    • ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze jako tablice z nazwami kolumn,
    • $pdo = null zamyka połączenie (PDO nie ma osobnej funkcji „zamknij”) - zapytania trafią między te dwie linie, więc zamknięcie zostanie ostatnią operacją na bazie (R.5.2),
    • try / catch (PDOException $exception) - gdy serwer bazy nie odpowiada, szczegóły idą do dziennika (error_log), a strona dostaje ogólny komunikat w $dbError,
    • funkcja e() to krótka nazwa htmlspecialchars(..., ENT_QUOTES); będzie przy każdej wartości z bazy.

    Zmienne $cars, $randomCars, $dbError mają wartości początkowe, żeby szablon działał także po błędzie. Nazwy po angielsku, mówią, co przechowują (R.5.8). Od tego kroku podgląd to wynik prawdziwego uruchomienia.

    index.php PHP zmienione: 21 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $cars = [];
    $randomCars = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Konfigurator samochodów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Serwis konfiguracji samochodów</h1>
        </header>
        <nav>
            <h2>Samochody</h2>
            <h2>Konfigurator</h2>
            <h2>Kontakt</h2>
        </nav>
        <main>
            <section id="cars">
                <table>
                </table>
            </section>
            <section id="configurator">
                <table>
                    <tr>
                        <th colspan="2">Konfiguracja</th>
                        <th>Cena</th>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a1.jpg" alt="Konfiguracja 1"></td>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a2.jpg" alt="Konfiguracja 2"></td>
                    </tr>
                </table>
            </section>
            <section id="contact">
                <h3>111 222 444</h3>
                <img src="a3.png" alt="Samochód">
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  20. Krok 20: Skrypt 1: zapytanie 3

    Kryteria:

    R.5.3 Skrypty zawierają instrukcję wysyłającą do bazy danych zapytanie 3 lub zapytanie 4

    ok. 2 min
    Polecenie z arkusza

    Skrypt 1: Wysyła do bazy danych zapytanie 3.

    $pdo->query(...) wysyła zapytanie 3, a ->fetchAll() od razu odbiera wszystkie 6 wierszy do tablicy $cars (R.5.3). Napis PHP w cudzysłowach, bo w środku jest apostrof wokół alfa. query wystarcza, bo w zapytaniu nie ma danych od użytkownika - gdyby model wybierał użytkownik z listy, byłoby prepare i execute z parametrem.

    index.php PHP zmienione: 6 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $cars = [];
    $randomCars = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cars = $pdo->query(
            "SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata
             FROM pojazdy AS p
             JOIN kolory AS k ON k.id = p.kolor
             WHERE p.model = 'alfa'"
        )->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Konfigurator samochodów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Serwis konfiguracji samochodów</h1>
        </header>
        <nav>
            <h2>Samochody</h2>
            <h2>Konfigurator</h2>
            <h2>Kontakt</h2>
        </nav>
        <main>
            <section id="cars">
                <table>
                </table>
            </section>
            <section id="configurator">
                <table>
                    <tr>
                        <th colspan="2">Konfiguracja</th>
                        <th>Cena</th>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a1.jpg" alt="Konfiguracja 1"></td>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a2.jpg" alt="Konfiguracja 2"></td>
                    </tr>
                </table>
            </section>
            <section id="contact">
                <h3>111 222 444</h3>
                <img src="a3.png" alt="Samochód">
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  21. Krok 21: Skrypt 1: wiersz tabeli dla każdego auta

    Kryteria: ,

    R.2.6 W bloku sekcji lewej umieszczono tabelę z czterema kolumnami oraz w bloku sekcji środkowej - z trzema kolumnami (w skrypcie lub HTML) , zastosowane znaczniki <table>, <tr>, <td>

    R.5.5 Skrypt 1 wykorzystuje pętlę, której liczba powtórzeń równa jest liczbie rekordów zwróconych zapytaniem

    ok. 3 min
    Polecenie z arkusza

    W wierszach tabeli wyświetlane są kolejne rekordy zgodnie z ilustracją 2.

    W tabeli sekcji aut pętla foreach ($cars as $car): ... endforeach; w składni alternatywnej - wykona się tyle razy, ile wierszy oddało zapytanie, czyli 6 (R.5.5). Każdy obrót to <tr> z komórkami: marka, model i nazwa koloru po nazwach kolumn ($car['nazwa']), a każda wartość przez e() (R.2.6). <?= ... ?> to skrót od <?php echo ... ?>.

    Kolejność wierszy wybiera serwer (zapytanie nie ma ORDER BY); MariaDB z XAMPP-a oddaje je w kolejności identyfikatorów aut, więc „zielony metalik” stoi trzeci, a nie czwarty jak na ilustracji 2 - klucz kolejności nie ocenia. Najedź na wiersz: podświetla się na ForestGreen.

    index.php PHP zmienione: 7 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $cars = [];
    $randomCars = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cars = $pdo->query(
            "SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata
             FROM pojazdy AS p
             JOIN kolory AS k ON k.id = p.kolor
             WHERE p.model = 'alfa'"
        )->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Konfigurator samochodów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Serwis konfiguracji samochodów</h1>
        </header>
        <nav>
            <h2>Samochody</h2>
            <h2>Konfigurator</h2>
            <h2>Kontakt</h2>
        </nav>
        <main>
            <section id="cars">
                <table>
                    <?php foreach ($cars as $car): ?>
                        <tr>
                            <td><?= e($car['marka']) ?></td>
                            <td><?= e($car['model']) ?></td>
                            <td><?= e($car['nazwa']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </table>
            </section>
            <section id="configurator">
                <table>
                    <tr>
                        <th colspan="2">Konfiguracja</th>
                        <th>Cena</th>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a1.jpg" alt="Konfiguracja 1"></td>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a2.jpg" alt="Konfiguracja 2"></td>
                    </tr>
                </table>
            </section>
            <section id="contact">
                <h3>111 222 444</h3>
                <img src="a3.png" alt="Samochód">
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Skrypt 1: cena całkowita w czwartej kolumnie

    Kryteria: ,

    R.5.4 Skrypt 1 oblicza dla każdego rekordu cenę całkowitą jako sumę ceny i dopłaty za kolor zwróconych zapytaniem

    R.5.6 Skrypt 1 wyświetla w wierszach tabeli markę, model, nazwę koloru zwrócone zapytaniem i obliczoną cenę całkowitą

    ok. 3 min
    Polecenie z arkusza

    W czwartej kolumnie tabeli wyświetlana jest cena całkowita, która jest sumą ceny z tabeli pojazdy oraz dopłaty z tabeli kolory.

    Czwarta komórka: (float) $car['cena'] + (float) $car['doplata'] (R.5.4, R.5.6). PDO oddaje liczby z bazy jako napisy ("72000", "1000.00"); + w PHP i tak zamieniłby je na liczby, ale jawne (float) mówi czytającemu, co się dzieje, i nie zależy od tego, czy napis „wygląda na liczbę”. Wynik 73000.0 PHP wypisuje jako 73000 - zielony metalik za 72000 kosztuje razem 73000, jak na ilustracji 2.

    Wynik liczbowy nie potrzebuje e(): liczba nie zawiera znaków HTML.

    Typowe błędy

    Kropka zamiast plusa ($car['cena'] . $car['doplata']) skleja napisy w „720001000.00” - w PHP kropka łączy tekst.

    index.php PHP zmienione: 1 linia
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $cars = [];
    $randomCars = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cars = $pdo->query(
            "SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata
             FROM pojazdy AS p
             JOIN kolory AS k ON k.id = p.kolor
             WHERE p.model = 'alfa'"
        )->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Konfigurator samochodów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Serwis konfiguracji samochodów</h1>
        </header>
        <nav>
            <h2>Samochody</h2>
            <h2>Konfigurator</h2>
            <h2>Kontakt</h2>
        </nav>
        <main>
            <section id="cars">
                <table>
                    <?php foreach ($cars as $car): ?>
                        <tr>
                            <td><?= e($car['marka']) ?></td>
                            <td><?= e($car['model']) ?></td>
                            <td><?= e($car['nazwa']) ?></td>
                            <td><?= (float) $car['cena'] + (float) $car['doplata'] ?></td>
                        </tr>
                    <?php endforeach; ?>
                </table>
            </section>
            <section id="configurator">
                <table>
                    <tr>
                        <th colspan="2">Konfiguracja</th>
                        <th>Cena</th>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a1.jpg" alt="Konfiguracja 1"></td>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a2.jpg" alt="Konfiguracja 2"></td>
                    </tr>
                </table>
            </section>
            <section id="contact">
                <h3>111 222 444</h3>
                <img src="a3.png" alt="Samochód">
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  23. Krok 23: Skrypt 2: zapytanie 4 i pierwsze auto w wierszach 3-4

    Kryteria:

    R.5.7 Skrypt 2 wyświetla dwa zwrócone z bazy rekordy: markę, model i cenę

    ok. 4 min
    Polecenie z arkusza

    Skrypt 2: Wysyła do bazy danych zapytanie 4. Wyświetla pierwszy rekord w tabeli w wierszach 3 i 4.

    • w bloku danych drugie zapytanie: fetchAll() oddaje dwa wylosowane auta do $randomCars, a $pdo = null nadal stoi na końcu,
    • funkcja carRows(array $car) zwraca dwa wiersze tabeli dla jednego auta: „Marka” z marką i ceną scaloną na dwa wiersze (rowspan="2"), potem „Model” z modelem - już bez trzeciej komórki, bo jej miejsce zajmuje cena z wiersza wyżej (R.2.7). Każda wartość przez e(). Typ array i string w nagłówku mówią, co funkcja przyjmuje i co zwraca,
    • pod wierszem z a1.jpg: if (isset($randomCars[0])) - pierwsze auto, jeśli jest (po awarii bazy tablica jest pusta i wiersze się nie pojawią).

    W podglądzie pierwsze auto z ilustracji 2 - BM beta za 94000 (w kopii uruchomieniowej losowanie zastąpione tymi dwoma autami).

    Typowe błędy

    Trzecia komórka także w wierszu z modelem rozjeżdża tabelę - przy rowspan="2" drugi wiersz ma o jedną komórkę mniej.

    index.php PHP zmienione: 10 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $cars = [];
    $randomCars = [];
    $dbError = '';
    
    function carRows(array $car): string
    {
        return '<tr><td>Marka</td><td>' . e($car['marka']) . '</td><td rowspan="2">' . e($car['cena']) . '</td></tr>'
            . '<tr><td>Model</td><td>' . e($car['model']) . '</td></tr>';
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cars = $pdo->query(
            "SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata
             FROM pojazdy AS p
             JOIN kolory AS k ON k.id = p.kolor
             WHERE p.model = 'alfa'"
        )->fetchAll();
        $randomCars = $pdo->query('SELECT marka, model, cena FROM pojazdy ORDER BY RAND() LIMIT 2')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Konfigurator samochodów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Serwis konfiguracji samochodów</h1>
        </header>
        <nav>
            <h2>Samochody</h2>
            <h2>Konfigurator</h2>
            <h2>Kontakt</h2>
        </nav>
        <main>
            <section id="cars">
                <table>
                    <?php foreach ($cars as $car): ?>
                        <tr>
                            <td><?= e($car['marka']) ?></td>
                            <td><?= e($car['model']) ?></td>
                            <td><?= e($car['nazwa']) ?></td>
                            <td><?= (float) $car['cena'] + (float) $car['doplata'] ?></td>
                        </tr>
                    <?php endforeach; ?>
                </table>
            </section>
            <section id="configurator">
                <table>
                    <tr>
                        <th colspan="2">Konfiguracja</th>
                        <th>Cena</th>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a1.jpg" alt="Konfiguracja 1"></td>
                    </tr>
                    <?php if (isset($randomCars[0])): ?>
                        <?= carRows($randomCars[0]) ?>
                    <?php endif; ?>
                    <tr>
                        <td colspan="3"><img src="a2.jpg" alt="Konfiguracja 2"></td>
                    </tr>
                </table>
            </section>
            <section id="contact">
                <h3>111 222 444</h3>
                <img src="a3.png" alt="Samochód">
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Skrypt 2: drugie auto w wierszach 6-7

    ok. 2 min
    Polecenie z arkusza

    Drugi rekord w wierszach 6 i 7.

    Pod wierszem z a2.jpg ta sama funkcja dla $randomCars[1]. Jeden kod wypisuje oba auta - zmiana wyglądu wiersza (np. „zł” przy cenie) to jedna linia w funkcji. Tabela ma komplet siedmiu wierszy, jak na ilustracji 2. Odśwież stronę na egzaminie - auta się zmieniają.

    index.php PHP zmienione: 3 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $cars = [];
    $randomCars = [];
    $dbError = '';
    
    function carRows(array $car): string
    {
        return '<tr><td>Marka</td><td>' . e($car['marka']) . '</td><td rowspan="2">' . e($car['cena']) . '</td></tr>'
            . '<tr><td>Model</td><td>' . e($car['model']) . '</td></tr>';
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cars = $pdo->query(
            "SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata
             FROM pojazdy AS p
             JOIN kolory AS k ON k.id = p.kolor
             WHERE p.model = 'alfa'"
        )->fetchAll();
        $randomCars = $pdo->query('SELECT marka, model, cena FROM pojazdy ORDER BY RAND() LIMIT 2')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Konfigurator samochodów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Serwis konfiguracji samochodów</h1>
        </header>
        <nav>
            <h2>Samochody</h2>
            <h2>Konfigurator</h2>
            <h2>Kontakt</h2>
        </nav>
        <main>
            <section id="cars">
                <table>
                    <?php foreach ($cars as $car): ?>
                        <tr>
                            <td><?= e($car['marka']) ?></td>
                            <td><?= e($car['model']) ?></td>
                            <td><?= e($car['nazwa']) ?></td>
                            <td><?= (float) $car['cena'] + (float) $car['doplata'] ?></td>
                        </tr>
                    <?php endforeach; ?>
                </table>
            </section>
            <section id="configurator">
                <table>
                    <tr>
                        <th colspan="2">Konfiguracja</th>
                        <th>Cena</th>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a1.jpg" alt="Konfiguracja 1"></td>
                    </tr>
                    <?php if (isset($randomCars[0])): ?>
                        <?= carRows($randomCars[0]) ?>
                    <?php endif; ?>
                    <tr>
                        <td colspan="3"><img src="a2.jpg" alt="Konfiguracja 2"></td>
                    </tr>
                    <?php if (isset($randomCars[1])): ?>
                        <?= carRows($randomCars[1]) ?>
                    <?php endif; ?>
                </table>
            </section>
            <section id="contact">
                <h3>111 222 444</h3>
                <img src="a3.png" alt="Samochód">
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  25. Krok 25: Komunikat, gdy baza nie odpowiada

    ok. 3 min
    Polecenie z arkusza

    Na końcu zamykane jest połączenie z serwerem.

    W tabeli aut wiersz z komunikatem z $dbError - tylko gdy catch coś do niego wpisał. Sprawdzone uruchomieniem z błędną nazwą bazy: strona się wyświetla, w sekcji aut stoi „Nie udało się pobrać danych o samochodach…”, tabela konfiguratora ma same obrazy, a w treści nie ma ani nazwy bazy, ani kodu błędu SQL. Bez try/catch PHP przerwałby stronę w połowie komunikatem „Fatal error: Uncaught PDOException” z pełną ścieżką pliku.

    W normalnym działaniu połączenie jest zamykane ($pdo = null) zaraz po dwóch zapytaniach, przed pierwszą linią HTML - ostatnią operacją na bazie, jak chce arkusz. Podgląd: strona po prawdziwym uruchomieniu, bez komunikatu.

    index.php PHP zmienione: 5 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $cars = [];
    $randomCars = [];
    $dbError = '';
    
    function carRows(array $car): string
    {
        return '<tr><td>Marka</td><td>' . e($car['marka']) . '</td><td rowspan="2">' . e($car['cena']) . '</td></tr>'
            . '<tr><td>Model</td><td>' . e($car['model']) . '</td></tr>';
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=samochody;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cars = $pdo->query(
            "SELECT p.marka, p.model, p.cena, k.nazwa, k.doplata
             FROM pojazdy AS p
             JOIN kolory AS k ON k.id = p.kolor
             WHERE p.model = 'alfa'"
        )->fetchAll();
        $randomCars = $pdo->query('SELECT marka, model, cena FROM pojazdy ORDER BY RAND() LIMIT 2')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o samochodach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Konfigurator samochodów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Serwis konfiguracji samochodów</h1>
        </header>
        <nav>
            <h2>Samochody</h2>
            <h2>Konfigurator</h2>
            <h2>Kontakt</h2>
        </nav>
        <main>
            <section id="cars">
                <table>
                    <?php foreach ($cars as $car): ?>
                        <tr>
                            <td><?= e($car['marka']) ?></td>
                            <td><?= e($car['model']) ?></td>
                            <td><?= e($car['nazwa']) ?></td>
                            <td><?= (float) $car['cena'] + (float) $car['doplata'] ?></td>
                        </tr>
                    <?php endforeach; ?>
                    <?php if ($dbError !== ''): ?>
                        <tr>
                            <td><?= e($dbError) ?></td>
                        </tr>
                    <?php endif; ?>
                </table>
            </section>
            <section id="configurator">
                <table>
                    <tr>
                        <th colspan="2">Konfiguracja</th>
                        <th>Cena</th>
                    </tr>
                    <tr>
                        <td colspan="3"><img src="a1.jpg" alt="Konfiguracja 1"></td>
                    </tr>
                    <?php if (isset($randomCars[0])): ?>
                        <?= carRows($randomCars[0]) ?>
                    <?php endif; ?>
                    <tr>
                        <td colspan="3"><img src="a2.jpg" alt="Konfiguracja 2"></td>
                    </tr>
                    <?php if (isset($randomCars[1])): ?>
                        <?= carRows($randomCars[1]) ?>
                    <?php endif; ?>
                </table>
            </section>
            <section id="contact">
                <h3>111 222 444</h3>
                <img src="a3.png" alt="Samochód">
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  26. Etap 5: Porównanie z wersją podstawową

    Krok 26 · po tym etapie masz 34 z 34 kryteriów (pkt)

    Krok 26: Co jest inaczej i którą wersję wybrać na egzaminie

    ok. 2 min

    Obie wersje dają komplet 34 punktów. Różnice:

    wersja podstawowawersja nowoczesna
    układ sekcjifloat: left + clear: both w stopcedisplay: grid i repeat(3, 33.3%) w bloku głównym
    bazamysqli_connect, mysqli_fetch_row, $row[3]PDO z wyjątkami, fetchAll, $car['nazwa']
    gdzie PHProzproszone po HTML-u, połączenie zamykane po stopcedane na górze pliku, połączenie zamknięte przed HTML-em
    pętlawhile + echo całego wiersza w napisieforeach w szablonie, HTML poza napisami
    wylosowane autadwie kopie echo (w dobrych praktykach funkcja)funkcja carRows() od początku
    dane na stroniewprost (w dobrych praktykach htmlspecialchars)e() przy każdej wartości
    awaria bazybiała strona z błędemkomunikat w tabeli, szczegóły w error_log

    Na egzaminie wersja podstawowa trzyma się dosłownie funkcji z tabeli 1 arkusza i jest szybsza do napisania. Nowoczesna oddziela pobieranie danych od wyglądu - przy większej stronie łatwiej ją zmieniać i testować. Czego tu świadomie nie ma: pliku config.php z danymi logowania do bazy (arkusz każe szukać połączenia w index.php) i zapytań przygotowanych - w tej stronie żadna wartość nie przychodzi od użytkownika.

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

  27. Etap 6: Oddanie pracy

    Krok 27 · po tym etapie masz 34 z 34 kryteriów (pkt)

    Krok 27: 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 folderze z numerem zdającego, powinny znajdować się pliki: a1.jpg, a2.jpg, a3.png, import.jpg, index.php, kw1.png, kw2.png, kw3.png, kw4.png, kwerendy.txt, przeglądarka.txt, styl.css, ewentualnie inne przygotowane pliki.

    W przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana przez http://localhost/.... Sprawdź listę plików z arkusza (import w JPEG, reszta zrzutów w PNG), nagraj płytę i sprawdź jej odczyt.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.