Przejdź do głównej treści
INF.03-01-25.06-SGCzerwiec 2025 · 45 kroków · ok. 99 min · 41 kryteriów z klucza

Rozwiązanie krok po kroku: Ranking gier komputerowych (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza: portal z rankingiem gier, baza gry, strona gry.php w trzech kolumnach i cztery skrypty PHP. Obie wersje spełniają wszystkie 41 kryteriów klucza - różni je sposób pisania kodu, nie wynik.

Czym ta wersja różni się od podstawowej:

  • układ na siatce CSS (display: grid) pisany „od małego ekranu”: domyślnie sekcje stoją jedna pod drugą, a zapytanie @media (width > 800px) układa je w trzy kolumny 20% / 60% / 20% - bez float i clear,
  • kolory z arkusza w zmiennych CSS (--color-primary: Indigo), więc każdy kolor jest wpisany raz,
  • PHP na PDO z wyjątkami i trybem pobierania tablic asocjacyjnych, dane połączenia w osobnym pliku config.php,
  • logika PHP na górze pliku, HTML na dole: najpierw wszystkie zapytania i zamknięcie połączenia, a w HTML tylko pętle foreach wypisujące gotowe dane,
  • bezpieczeństwo od pierwszej linijki: zapytania przygotowane z parametrami nazwanymi, filter_var przy wejściu, htmlspecialchars przy wyjściu (krótka funkcja e()), wzorzec PRG po dodaniu gry.

Kroki nadal idą zdanie po zdaniu za treścią arkusza. Podgląd strony PHP w każdym kroku to wynik prawdziwego uruchomienia kodu z tego kroku w PHP 8.2 na bazie z arkusza (po zapytaniu 4 i z wpisanym 6 w stopce - jak na ilustracji 2). Zdjęcia, baza i rekord.txt pochodzą z prawdziwego archiwum pliki1.

Jak liczą się punkty. Klucz ma 41 kryteriów (R.1.1 - R.5.10), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 31 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-7 · po tym etapie masz 6 z 41 kryteriów (pkt)

    Krok 1: Baza danych gry

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych gry, czynność udokumentowana w pliku import.png

    ok. 2 min
    Polecenie z arkusza

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

    W phpMyAdmin: Nowa, nazwa gry, metoda porównywania utf8_unicode_ci (albo utf8_polish_ci). Ten sam efekt da jedno zapytanie w zakładce SQL:

    CREATE DATABASE gry CHARACTER SET utf8 COLLATE utf8_unicode_ci;
    

    Importowana tabela gry ma zestaw i porównywanie zapisane we własnej definicji (utf8, utf8_unicode_ci), więc ogonki w opisach gier przetrwają niezależnie od ustawienia bazy - to dziedziczą tylko tabele zakładane później bez własnego ustawienia.

    Typowe błędy

    Skrypty łączą się z bazą po nazwie - Gry albo gra odetnie także punkty za połączenie (R.5.1).

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

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych gry, czynność udokumentowana w pliku import.png

    ok. 2 min
    Polecenie z arkusza

    Do bazy zaimportuj tabele z pliku baza.sql z rozpakowanego archiwum.

    Zaznacz bazę gry w lewym panelu, potem Import → Wybierz plik → baza.sql → Wykonaj. Powstaje tabela gry z ilustracji 1: id (klucz z autonumeracją), nazwa, opis, punkty, cena, zdjecie - 8 wierszy. W kolumnie zdjecie jest sama nazwa pliku, a pliki graficzne leżą w archiwum - stąd zalecenie, by trzymać je obok gry.php.

    Typowe błędy

    Import bez zaznaczonej bazy gry ląduje w innej bazie albo kończy się błędem „nie wybrano bazy danych”.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych gry, czynność udokumentowana w pliku import.png

    ok. 2 min
    Polecenie z arkusza

    Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie PNG pod nazwą import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań.

    Print Screen (cały ekran z paskiem zadań), wklej do edytora grafiki, zapisz jako import.png. Na zrzucie ma być widać komunikat o udanym imporcie albo tabelę gry. W tym arkuszu zrzut importu jest w PNG, a zrzuty kwerend w JPEG.

    Typowe błędy

    Wykadrowany zrzut (samo okno, bez paska zadań) nie jest oceniany - klucz mówi to wprost.

  4. Krok 4: Zapytanie 1: id, nazwa i zdjęcie

    Kryteria: ,

    R.1.2 Zapisany plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania

    R.1.3 Wykonane zapytanie 1 wybierające jedynie id, nazwę i zdjęcie z tabeli gry. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id, nazwa, zdjecie FROM gry; oraz wynik działania udokumentowany zrzutem, na którym jest widocznych minimum 8 rekordów, jedynie Pola id, nazwa, zdjecie

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie id, nazwę i zdjęcie z tabeli gry.

    • SELECT id, nazwa, zdjecie - dokładnie trzy kolumny, bez *,
    • FROM gry - tabela.

    Wynik: 8 wierszy. Każde zapytanie zapisuj w kwerendy.txt z komentarzem -- nad nim (to też poprawny SQL, więc całość da się wkleić do phpMyAdmin jednym ruchem). Zrzut: kw1.jpg. To zapytanie wyśle skrypt 2.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT id, nazwa, zdjecie FROM gry;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: gra o id 1 ze skróconym opisem

    Kryteria:

    R.1.4 Wykonane zapytanie 2 wybierające jedynie nazwę, pierwsze 100 znaków opisu, punkty oraz cenę z tabeli gry dla wiersza o id = 1. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id=1; oraz wynik działania udokumentowany zrzutem, na którym jest widoczny dokładnie jeden rekord, jedynie pola nazwa, opis, punkty, cena. Zastosowana jest funkcja obcinająca opis do pierwszych 100 znaków

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie nazwę, pierwsze 100 znaków opisu, punkty oraz cenę z tabeli gry dla wiersza o id równym 1.

    • LEFT(opis, 100) AS opis - funkcja z tabeli 2 arkusza obcina opis do 100 znaków, a alias AS opis nadaje kolumnie wyniku czytelną nazwę. Bez aliasu kolumna nazywałaby się dosłownie LEFT(opis, 100) - a pod tą nazwą trzeba by ją potem odczytać w PHP,
    • WHERE id = 1 - jeden wiersz po kluczu głównym.

    Zapytanie zapisane w kilku liniach (klauzula w linii) czyta się szybciej niż jeden długi wiersz - dla bazy to bez różnicy. Wynik: Czarodziejka, 45 punktów, cena 100. Zrzut: kw2.jpg.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT id, nazwa, zdjecie FROM gry;
    
    -- Zapytanie 2
    SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cena
    FROM gry
    WHERE id = 1;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: pięć gier z najwyższą punktacją

    Kryteria:

    R.1.5 Wykonane zapytanie 3 wybierające jedynie pierwsze pięć wierszy o najwyższej punktacji zawierające jedynie nazwę i punkty z tabeli gry. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5; oraz wynik działania udokumentowany zrzutem, na którym widocznych jest dokładnie 5 rekordów, jedynie pola nazwa i punkty, punkty posortowane malejąco

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pola nazwa i punkty z pięciu pierwszych wierszy o najwyższej punktacji z tabeli gry.

    • ORDER BY punkty DESC - od najwyższej punktacji,
    • LIMIT 5 - pięć pierwszych wierszy po sortowaniu.

    Wynik: Mag 357, Księżniczka Persji 293, Kalipso 125, Wyprawa Na Księżyc 103, Nieznany Ląd 96. Zrzut: kw3.jpg.

    Typowe błędy

    LIMIT 5 bez ORDER BY daje pięć dowolnych wierszy, a nie pięć najlepszych.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT id, nazwa, zdjecie FROM gry;
    
    -- Zapytanie 2
    SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cena
    FROM gry
    WHERE id = 1;
    
    -- Zapytanie 3
    SELECT nazwa, punkty
    FROM gry
    ORDER BY punkty DESC
    LIMIT 5;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: nowa gra z pliku rekord.txt

    Kryteria:

    R.1.6 Wykonane zapytanie 4 wstawiające wiersz do tabeli gry o danych zawartych w pliku rekord.txt. Sprawdzając można pominąć zgodność pola opis. Klucz główny nadawany automatycznie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO `gry`(`nazwa`, `opis`, `punkty`, `cena`, `zdjecie`) VALUES ('Zamczysko','...','200','50','zamczysko.jpg'); (możliwe różne warianty zapytania) oraz wynik działania udokumentowany zrzutem, na którym jest widoczny komunikat o poprawnym wykonaniu zapytania lub tabela z wstawionym wierszem

    ok. 4 min
    Polecenie z arkusza

    Zapytanie 4: wstawiające do tabeli gry wiersz o danych zawartych w pliku rekord.txt (dane należy skopiować z pliku do zapytania). Klucz główny nadawany automatycznie.

    Lista kolumn bez id - autonumeracja nada wartość 9. Napisy w apostrofach, punkty i cena jako liczby (w rekord.txt jest „Cena: 50 zł”, do bazy trafia samo 50). Wypisanie kolumn z nazwy zamiast INSERT INTO gry VALUES (...) chroni przed pomyłką w kolejności i działa nawet wtedy, gdy ktoś później doda kolumnę do tabeli. Zrzut kw4.jpg: komunikat o wstawieniu wiersza albo tabela z nowym rekordem.

    Typowe błędy

    rekord.txt jest zapisany w kodowaniu Windows-1250 - w edytorze ustawionym na UTF-8 zamiast „ó” i „ż” zobaczysz krzaki. Otwórz go Notatnikiem albo przełącz kodowanie.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT id, nazwa, zdjecie FROM gry;
    
    -- Zapytanie 2
    SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cena
    FROM gry
    WHERE id = 1;
    
    -- Zapytanie 3
    SELECT nazwa, punkty
    FROM gry
    ORDER BY punkty DESC
    LIMIT 5;
    
    -- Zapytanie 4
    INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie)
    VALUES ('Zamczysko', 'Na odludziu stoi opuszczone zamczysko, w którym znajduje się skarb strzeżony przez złowrogie demony i duchy', 200, 50, 'zamczysko.jpg');
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

    Kroki 8-19 · po tym etapie masz 18 z 41 kryteriów (pkt)

    Krok 8: Przeskalowanie grafiki zamczysko.jpg

    Kryteria:

    R.2.1 Plik zamczysko.jpg przeskalowany zachowując proporcje do szerokości 550 px odpowiadająca temu wysokość wynosi 309 px (± 5 px)

    ok. 3 min
    Polecenie z arkusza

    Plik zamczysko.jpg, wypakowany z archiwum, należy przeskalować zachowując proporcje do szerokości 550 px.

    Oryginał ma 640 × 360 px. W GIMP-ie Obraz → Skaluj obraz, szerokość 550 px przy zamkniętym łańcuchu proporcji - wysokość przeliczy się na 309 px (360 · 550 / 640). Klucz sprawdza 309 ±5 px. Skalowanie w samym CSS (width: 550px przy obrazie) nie zmienia pliku i punktu nie daje.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Plik gry.php: HTML5, język i kodowanie

    Kryteria:

    R.2.2 W pliku gry.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków

    ok. 2 min
    Polecenie z arkusza

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

    Trzy zdania arkusza dotyczą tych samych czterech linii, więc idą razem: <!DOCTYPE html> (HTML5), lang="pl" w <html> (czytnik ekranu czyta polską wymową, przeglądarka dobiera słownik) i <meta charset="UTF-8"> jako pierwszy element <head> - przeglądarka musi znać kodowanie, zanim przeczyta tytuł z polskimi literami. Plik zapisz w UTF-8 bez BOM. Rozszerzenie .php, bo dojdą skrypty.

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

    Kryteria:

    R.2.3 Nadany tytuł strony: Gry komputerowe

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Gry komputerowe”.

    <title>Gry komputerowe</title> - tekst karty przeglądarki (R.2.3). Obok <meta name="viewport">: arkusz każe przełączać układ przy 800 px, a bez tej linii telefon udaje ekran szeroki na ok. 980 px i zapytanie @media na nim nie zadziała. Klucz viewportu nie ocenia, ale bez niego wersja na wąski ekran działa tylko w zwężonym oknie komputera.

    gry.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>Gry komputerowe</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  11. Krok 11: 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"> - znacznik pusty, w HTML5 bez ukośnika />. Ścieżka względna, bo styl.css leży obok gry.php.

    gry.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>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  12. Krok 12: Bloki semantyczne

    Kryteria:

    R.2.4 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML 5: header, section + section + section, footer

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 3.

    Klucz R.2.4 wymienia znaczniki wprost: <header>, trzy <section>, <footer>. Ten zestaw jest narzucony, więc nie ma tu miejsca na <main> czy <aside> - choć sekcja prawa z formularzem pasowałaby do <aside>, kryterium liczy trzy <section>. Identyfikatory left, middle, right (po angielsku, konsekwentnie w całym projekcie) posłużą do stylów.

    W tej wersji wszystkie bloki są bezpośrednimi dziećmi <body> - dzięki temu siatka CSS ustawiona na <body> rozłoży je bez żadnego dodatkowego div.

    Typowe błędy

    Pięć <div> wygląda tak samo, ale R.2.4 sprawdza znaczniki w kodzie źródłowym.

    gry.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>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
        </header>
        <section id="left">
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  13. Krok 13: Nagłówek strony

    Kryteria:

    R.2.5 W bloku nagłówkowym zapisany nagłówek <h1>: "Ranking gier komputerowych", w sekcji lewej przynajmniej jeden <h3> z: "Top 5 gier w tym miesiącu" lub "Nasz sklep", lub "Stronę wykonał", w sekcji prawej <h3>: "Dodaj nową grę" oraz zapisany na stronie przynajmniej jeden paragraf

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści „Ranking gier komputerowych”.

    <h1> - jedyny nagłówek pierwszego stopnia na stronie. Dalej są <h3> i <h2> dokładnie tak, jak każe arkusz.

    gry.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>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  14. Krok 14: Sekcja lewa: nagłówek i lista

    Kryteria:

    R.2.6 W sekcji lewej umieszczona jedna lista punktowana, zastosowane znaczniki <ul>, <li> (wygenerowana skryptem lub w HTML)

    ok. 1 min
    Polecenie z arkusza

    Zawartość sekcji lewej: nagłówek trzeciego stopnia o treści: „Top 5 gier w tym miesiącu”. Lista punktowana (nieuporządkowana) wypełniona przez skrypt 1.

    <h3> i pusta lista <ul> - elementy <li> wypisze skrypt 1. Lista nieuporządkowana, bo arkusz mówi „punktowana”; <ol> byłaby numerowana.

    gry.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>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  15. Krok 15: Sekcja lewa: sklep i odnośnik

    Kryteria:

    R.3.3 Odnośnik o treści „Tu kupisz gry” inicjuje otwarcie strony http://sklep.gry.pl ( lub https://)

    ok. 1 min
    Polecenie z arkusza

    Nagłówek trzeciego stopnia o treści: „Nasz sklep”. Odnośnik o treści „Tu kupisz gry” prowadzący do adresu http://sklep.gry.pl.

    Adres w href w całości, z http:// - bez protokołu przeglądarka szukałaby pliku sklep.gry.pl obok gry.php. Odnośnik prowadzi poza serwis, ale arkusz nie każe otwierać go w nowej karcie, więc bez target.

    gry.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>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  16. Krok 16: Sekcja lewa: autor strony

    Kryteria:

    R.2.5 W bloku nagłówkowym zapisany nagłówek <h1>: "Ranking gier komputerowych", w sekcji lewej przynajmniej jeden <h3> z: "Top 5 gier w tym miesiącu" lub "Nasz sklep", lub "Stronę wykonał", w sekcji prawej <h3>: "Dodaj nową grę" oraz zapisany na stronie przynajmniej jeden paragraf

    ok. 1 min
    Polecenie z arkusza

    Nagłówek trzeciego stopnia o treści: „Stronę wykonał”. Paragraf z numerem zdającego.

    <h3> i paragraf <p>. Tu stoi nazwisko autora rozwiązania - na egzaminie wpisujesz w to miejsce swój numer zdającego (PESEL), tak jak na ilustracji 2. Klucz R.2.5 wymaga na stronie co najmniej jednego paragrafu i <h3> w sekcjach.

    gry.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>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  17. Krok 17: Sekcja prawa: nagłówek i formularz

    Kryteria:

    R.3.4 Przynajmniej jeden formularz wysyła dane do tego samego pliku metodą POST (poprawne również bez atrybutu action)

    ok. 1 min
    Polecenie z arkusza

    Zawartość sekcji środkowej: efekt działania skryptu 2. Zawartość sekcji prawej: nagłówek trzeciego stopnia o treści: „Dodaj nową grę”. Formularz wysyłający dane do tego samego pliku metodą bezpieczną.

    Sekcja środkowa zostaje pusta - wypełni ją skrypt 2. W prawej: <h3> i <form method="post">. POST wysyła dane w treści żądania, a nie w adresie, więc nie zostają w historii przeglądarki ani w zakładkach. Brak action oznacza „ten sam adres” - to dokładnie „do tego samego pliku”.

    gry.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>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
            </form>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  18. Krok 18: Formularz: cztery pola z etykietami i przycisk

    Kryteria: ,

    R.2.7 W sekcji prawej umieszczony formularz z czterema polami edycyjnymi i przyciskiem "DODAJ". Zastosowany znacznik <form> oraz przynajmniej jedna etykieta <label>

    R.3.5 Przycisk z przynajmniej jednego formularza wysyła dane do skryptu (input z typem submit lub button)

    ok. 3 min
    Polecenie z arkusza

    Formularz zawiera cztery pola edycyjne podpisane etykietami: nazwa, opis, cena, zdjęcie oraz przycisk DODAJ, rozmieszczone zgodnie z ilustracją 2.

    Każde pole ma <label for> wskazujące jego id - kliknięcie w napis ustawia kursor w polu. Atrybuty pól niosą wiedzę o danych:

    • maxlength="20" i required przy nazwie - kolumna nazwa ma varchar(20), a skrypt 4 bez nazwy i tak nic nie zapisze,
    • type="number" z step="0.01" przy cenie - przeglądarka nie przepuści liter, a na telefonie pokaże klawiaturę numeryczną.

    To podpowiedzi dla użytkownika, nie zabezpieczenie: każdy może wysłać żądanie z pominięciem formularza, dlatego PHP i tak sprawdzi dane. Między polami nie ma <br> - ułożenie „etykieta nad polem” z ilustracji 2 zrobi CSS (display: block), bo to sprawa wyglądu, nie treści.

    Typowe błędy

    Atrybut name jest kluczem w $_POST - pole bez name nie zostanie wysłane, nawet jeśli ma id.

    gry.php PHP zmienione: 9 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  19. Krok 19: Stopka: formularz z polem i przyciskiem „Pokaż opis”

    Kryteria:

    R.2.8 W stopce umieszczony formularz z jednym polem edycyjnym i przyciskiem "Pokaż opis". Zastosowany znacznik <form>

    ok. 2 min
    Polecenie z arkusza

    Zawartość stopki: formularz wysyłający dane do tego samego pliku metodą bezpieczną. Formularz zawiera pole edycyjne i przycisk o treści „Pokaż opis”. Efekt działania skryptu 3.

    Drugi formularz, też method="post". Na ilustracji pole nie ma podpisu, więc zamiast widocznej etykiety jest aria-label - czytnik ekranu przeczyta „Identyfikator gry”, a wygląd się nie zmienia. type="number" z min="1", bo identyfikatory gier to dodatnie liczby całkowite. Wynik skryptu 3 pojawi się pod formularzem.

    gry.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="number" name="game_id" min="1" aria-label="Identyfikator gry" required>
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  20. Etap 3: Styl CSS

    Kroki 20-31 · po tym etapie masz 29 z 41 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.

    Plik styl.css zaczyna się od własnych właściwości (zmiennych CSS) w :root. Indigo pojawi się w pięciu regułach - wpisany raz w --color-primary, w regułach jest używany jako var(--color-primary). Zmiana koloru całej strony to wtedy jedna linia. W inspektorze przeglądarki egzaminator i tak widzi obliczony kolor (Indigo = rgb(75, 0, 130)).

    Nazwy zmiennych opisują rolę koloru (główny, punkty), a nie sam kolor - --indigo: Indigo niczego by nie ułatwiło.

    styl.css CSS nowy plik
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    Podgląd strony po tym kroku (gry.php)
  21. Krok 21: Krój i kolor czcionki dla wszystkich selektorów

    Kryteria:

    R.4.1 Ustawione domyślne formatowanie wszystkich selektorów (dla *): krój czcionki Garamond, biały kolor czcionki

    ok. 1 min
    Polecenie z arkusza

    Domyślne formatowanie wszystkich selektorów: krój czcionki Garamond, biały kolor czcionki.

    Selektor uniwersalny * (wymagany przez R.4.1). Po Garamond stoi rodzina zastępcza serif: gdy na komputerze nie ma Garamonda, przeglądarka weźmie inny krój szeryfowy zamiast domyślnego. Biały tekst chwilowo znika na białym tle - tła dojdą za moment.

    styl.css CSS zmienione: 5 linii
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    
    * {
        font-family: Garamond, serif;
        color: var(--color-text);
    }
    Podgląd strony po tym kroku (gry.php)
  22. Krok 22: Blok nagłówkowy

    Kryteria:

    R.4.6 Ustawione wyrównanie tekstu do środka dla bloku nagłówkowego

    ok. 1 min
    Polecenie z arkusza

    Dla bloku nagłówkowego: kolor tła Indigo, wyrównanie tekstu do środka, marginesy wewnętrzne 2 px.

    Kolor ze zmiennej, text-align: center, a margines wewnętrzny to padding.

    styl.css CSS zmienione: 6 linii
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    
    * {
        font-family: Garamond, serif;
        color: var(--color-text);
    }
    
    header {
        background-color: var(--color-primary);
        text-align: center;
        padding: 2px;
    }
    Podgląd strony po tym kroku (gry.php)
  23. Krok 23: Sekcje lewa i prawa

    Kryteria:

    R.4.2 Ustawiony kolor tła Indigo dla bloków: bloku nagłówkowego, sekcji lewej, prawej i stopki oraz czarny kolor tła dla sekcji środkowej

    ok. 1 min
    Polecenie z arkusza

    Dla sekcji lewej i prawej: kolor tła Indigo, wysokość 600 px.

    Wspólna reguła dla dwóch identyfikatorów; każdy selektor w osobnej linii czyta się łatwiej, gdy lista rośnie.

    styl.css CSS zmienione: 6 linii
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    
    * {
        font-family: Garamond, serif;
        color: var(--color-text);
    }
    
    header {
        background-color: var(--color-primary);
        text-align: center;
        padding: 2px;
    }
    
    #left,
    #right {
        background-color: var(--color-primary);
        height: 600px;
    }
    Podgląd strony po tym kroku (gry.php)
  24. Krok 24: Sekcja środkowa

    Kryteria:

    R.4.8 Ustawione dla sekcji środkowej zawsze widoczne paski przewijania (overflow: scroll;)

    ok. 1 min
    Polecenie z arkusza

    Dla sekcji środkowej: czarny kolor tła, wysokość 600 px, zawsze widoczne paski przewijania.

    „Zawsze widoczne” to overflow: scroll (R.4.8) - auto pokazywałby paski dopiero przy przepełnieniu.

    Typowe błędy

    overflow: auto przy pustej sekcji nie pokaże pasków - R.4.8 wymaga scroll.

    styl.css CSS zmienione: 6 linii
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    
    * {
        font-family: Garamond, serif;
        color: var(--color-text);
    }
    
    header {
        background-color: var(--color-primary);
        text-align: center;
        padding: 2px;
    }
    
    #left,
    #right {
        background-color: var(--color-primary);
        height: 600px;
    }
    
    #middle {
        background-color: var(--color-middle);
        height: 600px;
        overflow: scroll;
    }
    Podgląd strony po tym kroku (gry.php)
  25. Krok 25: Szerokie ekrany: siatka 20% / 60% / 20%

    Kryteria:

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 3 w arkuszu dla ekranów o szerokości większej niż 800 px. Blok środkowy może być pusty. (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)

    ok. 4 min
    Polecenie z arkusza

    Dla wszystkich rodzajów ekranu o szerokości większej niż 800 px szerokość sekcji lewej i prawej wynosi 20%, środkowej 60% (są wyświetlane obok siebie).

    <body> staje się siatką (display: grid), a jego dzieci - kolejne bloki - jej elementami. Na szerokim ekranie siatka ma trzy kolumny: grid-template-columns: 20% 60% 20%. Elementy wpadają w komórki w kolejności z HTML; nagłówek i stopka dostają grid-column: 1 / -1, czyli „od pierwszej do ostatniej linii siatki” - zajmują cały wiersz. Trzy sekcje same lądują obok siebie w drugim wierszu, a stopka pod nimi - bez float i bez clear.

    Warunek to zapis z poziomu 4 specyfikacji zapytań medialnych: (width > 800px) znaczy dokładnie „większej niż 800 px”, tak jak w arkuszu. Starszy zapis (min-width: 801px) robi to samo, ale wymaga przeliczenia granicy w głowie. Składnię zakresów rozumieją wszystkie współczesne przeglądarki (Chrome i Edge od 2022, Firefox od 2018, Safari od 2023).

    Typowe błędy

    Procenty w grid-template-columns liczą się od szerokości siatki. Dodanie gap rozepchnęłoby kolumny ponad 100% i prawa sekcja wyjechałaby poza ekran - przy procentach bez odstępów, przy fr (np. 1fr 3fr 1fr) odstęp byłby bezpieczny.

    styl.css CSS zmienione: 15 linii
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    
    * {
        font-family: Garamond, serif;
        color: var(--color-text);
    }
    
    header {
        background-color: var(--color-primary);
        text-align: center;
        padding: 2px;
    }
    
    #left,
    #right {
        background-color: var(--color-primary);
        height: 600px;
    }
    
    #middle {
        background-color: var(--color-middle);
        height: 600px;
        overflow: scroll;
    }
    
    body {
        display: grid;
    }
    
    @media (width > 800px) {
        body {
            grid-template-columns: 20% 60% 20%;
        }
    
        header,
        footer {
            grid-column: 1 / -1;
        }
    }
    Podgląd strony po tym kroku (gry.php)
  26. Krok 26: Wąskie ekrany: sekcje jedna pod drugą

    Kryteria:

    R.4.5 Sekcje lewa, środkowa i prawa są wyświetlane obok siebie jeśli ekran jest szerszy niż 800px - odpowiednio szerokości 20%, 60%, 20%, a w przeciwnym wypadku są wyświetlane jedna pod drugą (zastosowany np. zapis @media all and (max-width: 800px) - sprawdzić w kodzie i poprzez zmianę szerokości okna przeglądarki)

    ok. 2 min
    Polecenie z arkusza

    Dla wszystkich rodzajów ekranu o pozostałej szerokości sekcje lewa, środkowa i prawa są wyświetlane jedna pod drugą.

    To podejście „od małego ekranu” (mobile first): reguła bez warunku opisuje wąski ekran - siatka z jedną kolumną na całą szerokość, więc wszystkie bloki stoją jeden pod drugim. Zapytanie @media (width > 800px) z poprzedniego kroku tylko rozszerza ten układ dla szerszych ekranów. Nic nie trzeba odwoływać (float: none, width: 100%), jak przy podejściu odwrotnym.

    Klucz R.4.5 podaje przykład @media all and (max-width: 800px) - to „np.”, ocenia się działanie: zwęź okno do 800 px i mniej, a sekcje ustawią się jedna pod drugą.

    styl.css CSS zmienione: 1 linia
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    
    * {
        font-family: Garamond, serif;
        color: var(--color-text);
    }
    
    header {
        background-color: var(--color-primary);
        text-align: center;
        padding: 2px;
    }
    
    #left,
    #right {
        background-color: var(--color-primary);
        height: 600px;
    }
    
    #middle {
        background-color: var(--color-middle);
        height: 600px;
        overflow: scroll;
    }
    
    body {
        display: grid;
        grid-template-columns: 100%;
    }
    
    @media (width > 800px) {
        body {
            grid-template-columns: 20% 60% 20%;
        }
    
        header,
        footer {
            grid-column: 1 / -1;
        }
    }
    Podgląd strony po tym kroku (gry.php)
  27. Krok 27: Stopka

    Kryteria: ,

    R.4.2 Ustawiony kolor tła Indigo dla bloków: bloku nagłówkowego, sekcji lewej, prawej i stopki oraz czarny kolor tła dla sekcji środkowej

    R.4.4 Ustawiona wysokość sekcji lewej, środkowej i prawej 600px oraz stopki 150px

    ok. 1 min
    Polecenie z arkusza

    Dla stopki: kolor tła Indigo, wysokość 150 px.

    Tło i wysokość. clear: both nie jest potrzebne - w siatce nic nie pływa, stopka ma po prostu własny wiersz.

    Teraz wszystkie bloki mają tła i wysokości: Indigo w nagłówku, sekcjach bocznych i stopce, czerń w środku (R.4.2), 600 px sekcji i 150 px stopki (R.4.4). Każdy kolor pochodzi z jednej z dwóch zmiennych - w inspektorze (Obliczone) widać rgb(75, 0, 130) albo rgb(0, 0, 0).

    styl.css CSS zmienione: 5 linii
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    
    * {
        font-family: Garamond, serif;
        color: var(--color-text);
    }
    
    header {
        background-color: var(--color-primary);
        text-align: center;
        padding: 2px;
    }
    
    #left,
    #right {
        background-color: var(--color-primary);
        height: 600px;
    }
    
    #middle {
        background-color: var(--color-middle);
        height: 600px;
        overflow: scroll;
    }
    
    body {
        display: grid;
        grid-template-columns: 100%;
    }
    
    @media (width > 800px) {
        body {
            grid-template-columns: 20% 60% 20%;
        }
    
        header,
        footer {
            grid-column: 1 / -1;
        }
    }
    
    footer {
        background-color: var(--color-primary);
        height: 150px;
    }
    Podgląd strony po tym kroku (gry.php)
  28. Krok 28: Oznaczenie liczby punktów

    Kryteria:

    R.4.9 Ustawiony styl jedynie dla oznaczenia liczby punktów zgodnie z ilustracją 4: kolor tła Tomato, zaokrąglenie rogów 50%, marginesy wewnętrzne (padding) 5px (np. klasą, zastosowanie id jest błędem)

    ok. 1 min
    Polecenie z arkusza

    Dla oznaczenia liczby punktów widocznych na ilustracji 4: kolor tła Tomato, zaokrąglenie rogów 50%, marginesy wewnętrzne 5 px.

    Klasa .points - takich elementów będzie pięć, a identyfikator musi być unikalny (klucz R.4.9: id to błąd). border-radius: 50% robi z tła kółko jak na ilustracji 4.

    styl.css CSS zmienione: 6 linii
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    
    * {
        font-family: Garamond, serif;
        color: var(--color-text);
    }
    
    header {
        background-color: var(--color-primary);
        text-align: center;
        padding: 2px;
    }
    
    #left,
    #right {
        background-color: var(--color-primary);
        height: 600px;
    }
    
    #middle {
        background-color: var(--color-middle);
        height: 600px;
        overflow: scroll;
    }
    
    body {
        display: grid;
        grid-template-columns: 100%;
    }
    
    @media (width > 800px) {
        body {
            grid-template-columns: 20% 60% 20%;
        }
    
        header,
        footer {
            grid-column: 1 / -1;
        }
    }
    
    footer {
        background-color: var(--color-primary);
        height: 150px;
    }
    
    .points {
        background-color: var(--color-points);
        border-radius: 50%;
        padding: 5px;
    }
    Podgląd strony po tym kroku (gry.php)
  29. Krok 29: Element listy

    ok. 1 min
    Polecenie z arkusza

    Dla elementu listy: marginesy wewnętrzne 5 px.

    padding: 5px w każdym <li> - kółka z punktami nie nachodzą na siebie.

    styl.css CSS zmienione: 4 linie
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    
    * {
        font-family: Garamond, serif;
        color: var(--color-text);
    }
    
    header {
        background-color: var(--color-primary);
        text-align: center;
        padding: 2px;
    }
    
    #left,
    #right {
        background-color: var(--color-primary);
        height: 600px;
    }
    
    #middle {
        background-color: var(--color-middle);
        height: 600px;
        overflow: scroll;
    }
    
    body {
        display: grid;
        grid-template-columns: 100%;
    }
    
    @media (width > 800px) {
        body {
            grid-template-columns: 20% 60% 20%;
        }
    
        header,
        footer {
            grid-column: 1 / -1;
        }
    }
    
    footer {
        background-color: var(--color-primary);
        height: 150px;
    }
    
    .points {
        background-color: var(--color-points);
        border-radius: 50%;
        padding: 5px;
    }
    
    li {
        padding: 5px;
    }
    Podgląd strony po tym kroku (gry.php)
  30. Krok 30: Bloki gier w sekcji środkowej

    Kryteria:

    R.4.10 Ustawiony styl dla bloku gry: bloki są umieszczone jeden obok drugiego, wyrównanie tekstu do środka, marginesy wewnętrzne (padding) 3px (np. klasą, selektorem typu, potomka, zastosowanie id jest błędem)

    ok. 2 min
    Polecenie z arkusza

    Dla bloków gier znajdujących się w sekcji środkowej: bloki są umieszczone jeden obok drugiego, wyrównanie tekstu do środka, marginesy wewnętrzne 3 px.

    Tu zostaje display: inline-block, choć wersja „siatkowa” prosiłaby się o display: flex; flex-wrap: wrap na sekcji środkowej. Powód: kryterium R.4.10 ocenia styl samego bloku gry („ustawiony styl dla bloku gry: bloki są umieszczone jeden obok drugiego”), a przy flexie właściwość układająca bloki stałaby w regule rodzica. Efekt byłby ten sam, ale reguła .game nie miałaby czym się wykazać. Punkt z klucza wygrywa z elegancją.

    styl.css CSS zmienione: 6 linii
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    
    * {
        font-family: Garamond, serif;
        color: var(--color-text);
    }
    
    header {
        background-color: var(--color-primary);
        text-align: center;
        padding: 2px;
    }
    
    #left,
    #right {
        background-color: var(--color-primary);
        height: 600px;
    }
    
    #middle {
        background-color: var(--color-middle);
        height: 600px;
        overflow: scroll;
    }
    
    body {
        display: grid;
        grid-template-columns: 100%;
    }
    
    @media (width > 800px) {
        body {
            grid-template-columns: 20% 60% 20%;
        }
    
        header,
        footer {
            grid-column: 1 / -1;
        }
    }
    
    footer {
        background-color: var(--color-primary);
        height: 150px;
    }
    
    .points {
        background-color: var(--color-points);
        border-radius: 50%;
        padding: 5px;
    }
    
    li {
        padding: 5px;
    }
    
    .game {
        display: inline-block;
        text-align: center;
        padding: 3px;
    }
    Podgląd strony po tym kroku (gry.php)
  31. Krok 31: Pola edycyjne i etykiety

    Kryteria: ,

    R.4.3 Ustawiony kolor czcionki Indigo dla pola edycyjnego

    R.4.7 Ustawione marginesy wewnętrzne (padding) dla bloku nagłówkowego 2px, dla elementu listy 5px oraz marginesy zewnętrzne (margin) dla pola edycyjnego 10px

    ok. 2 min
    Polecenie z arkusza

    Dla pola edycyjnego: marginesy zewnętrzne 10 px, kolor czcionki Indigo.

    input z margin (zewnętrzny) i kolorem ze zmiennej - bez tego wpisywany tekst byłby biały na białym polu. Druga reguła, label { display: block; }, stawia etykietę w osobnej linii nad polem, jak na ilustracji 2 - to zastępuje znaczniki <br>, których nie ma w HTML.

    styl.css CSS zmienione: 9 linii
    :root {
        --color-primary: Indigo;
        --color-middle: black;
        --color-points: Tomato;
        --color-text: white;
    }
    
    * {
        font-family: Garamond, serif;
        color: var(--color-text);
    }
    
    header {
        background-color: var(--color-primary);
        text-align: center;
        padding: 2px;
    }
    
    #left,
    #right {
        background-color: var(--color-primary);
        height: 600px;
    }
    
    #middle {
        background-color: var(--color-middle);
        height: 600px;
        overflow: scroll;
    }
    
    body {
        display: grid;
        grid-template-columns: 100%;
    }
    
    @media (width > 800px) {
        body {
            grid-template-columns: 20% 60% 20%;
        }
    
        header,
        footer {
            grid-column: 1 / -1;
        }
    }
    
    footer {
        background-color: var(--color-primary);
        height: 150px;
    }
    
    .points {
        background-color: var(--color-points);
        border-radius: 50%;
        padding: 5px;
    }
    
    li {
        padding: 5px;
    }
    
    .game {
        display: inline-block;
        text-align: center;
        padding: 3px;
    }
    
    input {
        margin: 10px;
        color: var(--color-primary);
    }
    
    label {
        display: block;
    }
    Podgląd strony po tym kroku (gry.php)
  32. Etap 4: Skrypty PHP

    Kroki 32-42 · po tym etapie masz 41 z 41 kryteriów (pkt)

    Krok 32: Dane połączenia w pliku config.php

    ok. 3 min
    Polecenie z arkusza

    Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie gry.

    Adres serwera, nazwa bazy, użytkownik i hasło trafiają do osobnego pliku config.php, który tylko zwraca tablicę (return [...]). Powody:

    • dane logowania są w jednym miejscu - zmiana hasła nie wymaga szukania po plikach stron,
    • plik konfiguracji można wyłączyć z repozytorium kodu, a stronę udostępnić bez hasła do bazy.

    dsn (nazwa źródła danych) to napis PDO: sterownik mysql, serwer localhost, baza gry i od razu charset=utf8mb4 - kodowanie połączenia ustawione przy łączeniu, więc polskie znaki z bazy przychodzą poprawnie. Arkusz pozwala na dodatkowe pliki („ewentualnie inne przygotowane pliki”), więc config.php trafia na płytę razem z resztą.

    config.php PHP nowy plik
    <?php
    // Dane połączenia z bazą - w jednym miejscu, poza plikiem strony
    return [
        'dsn' => 'mysql:host=localhost;dbname=gry;charset=utf8mb4',
        'user' => 'root',
        'password' => '',
    ];

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

  33. Krok 33: Połączenie PDO z obsługą błędu

    Kryteria: ,

    R.5.1 Skrypt zawiera działające połączenie z serwerem bazy danych i wybór bazy gry

    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. 4 min
    Polecenie z arkusza

    Skrypty napisane w języku PHP. Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie gry.

    Na samej górze gry.php - przed <!DOCTYPE html>, bo za chwilę dojdzie tu przekierowanie, a nagłówki HTTP wysyła się przed treścią strony. require 'config.php' wczytuje tablicę z danymi, a new PDO(...) otwiera połączenie z dwiema opcjami:

    • PDO::ERRMODE_EXCEPTION - błąd SQL rzuca wyjątek, zamiast po cichu zwrócić false,
    • PDO::FETCH_ASSOC - wiersze przychodzą jako tablice z nazwami kolumn ($game['nazwa'] zamiast $row[0]), więc kod nie zależy od kolejności kolumn w SELECT.

    Blok try/catch (PDOException ...) łapie nieudane połączenie i pokazuje ogólny komunikat. Szczegóły błędu ($exception->getMessage()) zawierają nazwę serwera i użytkownika - nie pokazuje się ich odwiedzającym, tylko zapisuje w dzienniku serwera.

    Klucz dopuszcza PDO wprost („Kryteria ... sprawdzane tylko w przypadku użycia bibliotek MySQLi lub PDO”). Podgląd od tego kroku to wynik prawdziwego uruchomienia strony w PHP 8.2.

    Typowe błędy

    PDO nie ma w tabeli 1 arkusza - nazwy opcji trzeba znać na pamięć. Literówka w PDO::ATTR_ERRMODE to błąd krytyczny i biała strona; kto nie jest pewny składni, pisze funkcjami mysqli_* z tabeli 1 (wersja podstawowa).

    gry.php PHP zmienione: 13 linii
    <?php
    $config = require 'config.php';
    
    try {
        $pdo = new PDO($config['dsn'], $config['user'], $config['password'], [
            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ę.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="number" name="game_id" min="1" aria-label="Identyfikator gry" required>
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  34. Krok 34: Funkcja e() do bezpiecznego wypisywania

    ok. 2 min
    Polecenie z arkusza

    Zwrócone wiersze są wyświetlane w elementach listy z sekcji lewej według wzoru: <nazwa> <punkty>, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych.

    Zanim cokolwiek z bazy trafi na stronę, powstaje krótka funkcja e() - skrót od escape. Do bazy każdy może dopisać grę formularzem, więc nazwa <script>…</script> wypisana wprost wykonałaby się w przeglądarce każdego gościa (atak XSS). htmlspecialchars zamienia <, >, & i - dzięki ENT_QUOTES - oba cudzysłowy na encje, więc tekst zawsze wyświetla się dosłownie, także wewnątrz atrybutu alt="...".

    Typy w nagłówku funkcji (string|int|float|null, wynik : string) dokumentują, co funkcja przyjmuje - z bazy przychodzą napisy, liczby albo NULL.

    gry.php PHP zmienione: 5 linii
    <?php
    $config = require 'config.php';
    
    try {
        $pdo = new PDO($config['dsn'], $config['user'], $config['password'], [
            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ę.');
    }
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="number" name="game_id" min="1" aria-label="Identyfikator gry" required>
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>

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

  35. Krok 35: Skrypt 1: zapytanie 3 do bazy

    Kryteria:

    R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 3 lub skrypt 2 wysyła zapytanie 1, lub skrypt 3 wysyła zapytanie 2 zmodyfikowane w ten sposób, że wybierany jest wiersz o id podanym w polu edycyjnym

    ok. 2 min
    Polecenie z arkusza

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

    W tej wersji każdy skrypt ma dwie części: pobranie danych na górze pliku i wypisanie w HTML. $pdo->query(...) wysyła zapytanie 3, a fetchAll() od razu odbiera wszystkie pięć wierszy do tablicy $topGames.

    Zwykłe query (bez prepare) jest tu na miejscu: w zapytaniu nie ma ani jednej wartości od użytkownika, więc nie ma czego wstrzyknąć.

    gry.php PHP zmienione: 3 linie
    <?php
    $config = require 'config.php';
    
    try {
        $pdo = new PDO($config['dsn'], $config['user'], $config['password'], [
            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ę.');
    }
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    // Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)
    $topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="number" name="game_id" min="1" aria-label="Identyfikator gry" required>
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>

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

  36. Krok 36: Skrypt 1: elementy listy z nazwą i punktami

    Kryteria: ,

    R.5.6 Skrypt 1 wyświetla nazwę i punkty zwrócone zapytaniem jako elementy listy

    R.2.9 Liczba punktów z sekcji lewej zapisana w dodatkowym elemencie, np. znacznik <span> (w skrypcie lub HTML)

    ok. 3 min
    Polecenie z arkusza

    Zwrócone wiersze są wyświetlane w elementach listy z sekcji lewej według wzoru: <nazwa> <punkty>, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych. Liczba punktów jest dodatkowo formatowana stylem, którego efekt jest widoczny na ilustracji 4.

    W HTML zostaje sam szablon: pętla foreach ($topGames as $game): ... endforeach; - składnia alternatywna PHP, pomyślana właśnie do przeplatania z HTML (dwukropek i endforeach zamiast nawiasów klamrowych, które łatwo zgubić między znacznikami). <?= ... ?> to skrót od <?php echo ... ?>.

    Liczba punktów jest w osobnym <span class="points"> (R.2.9) - na niego czeka reguła .points. Atrybut HTML w zwykłych cudzysłowach, bo kod PHP nie składa już napisów z HTML-em.

    gry.php PHP zmienione: 3 linie
    <?php
    $config = require 'config.php';
    
    try {
        $pdo = new PDO($config['dsn'], $config['user'], $config['password'], [
            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ę.');
    }
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    // Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)
    $topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php foreach ($topGames as $game): ?>
                <li><?= e($game['nazwa']) ?> <span class="points"><?= e($game['punkty']) ?></span></li>
    <?php endforeach; ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="number" name="game_id" min="1" aria-label="Identyfikator gry" required>
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  37. Krok 37: Skrypt 2: zapytanie 1 i obrazy gier

    Kryteria: ,

    R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 3 lub skrypt 2 wysyła zapytanie 1, lub skrypt 3 wysyła zapytanie 2 zmodyfikowane w ten sposób, że wybierany jest wiersz o id podanym w polu edycyjnym

    R.2.10 W sekcji środkowej umieszczony przynajmniej jeden obraz z tekstem alternatywnym i atrybutem title (w skrypcie lub HTML)

    ok. 4 min
    Polecenie z arkusza

    Skrypt 2: wysyła do bazy danych zapytanie 1. Zwrócone zapytaniem wiersze są wyświetlone w bloku, który składa się z: obrazu, którego źródłem jest pole zdjecie, tekstem alternatywnym jest pole nazwa, a podpowiedzią (dymek widoczny na ilustracji 2 dla pierwszej grafiki) jest pole id.

    Na górze: zapytanie 1 do tablicy $games. W sekcji środkowej: pętla, która dla każdej gry tworzy blok <article class="game"> - article, bo każdy blok to samodzielna całość (obraz i podpis gry), którą dałoby się przenieść w inne miejsce. W obrazie src = zdjecie, alt = nazwa, title = id - ten ostatni to „dymek” z ilustracji 2 (R.2.10).

    Wartości trafiają do atrybutów przez e(): apostrof albo cudzysłów w nazwie gry nie rozerwie atrybutu alt.

    Typowe błędy

    Zamiana alt z title kosztuje R.2.10 i R.5.7 - arkusz mówi wprost, które pole gdzie.

    gry.php PHP zmienione: 8 linii
    <?php
    $config = require 'config.php';
    
    try {
        $pdo = new PDO($config['dsn'], $config['user'], $config['password'], [
            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ę.');
    }
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    // Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)
    $topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();
    
    // Skrypt 2: wszystkie gry do galerii (zapytanie 1)
    $games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php foreach ($topGames as $game): ?>
                <li><?= e($game['nazwa']) ?> <span class="points"><?= e($game['punkty']) ?></span></li>
    <?php endforeach; ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php foreach ($games as $game): ?>
            <article class="game">
                <img src="<?= e($game['zdjecie']) ?>" alt="<?= e($game['nazwa']) ?>" title="<?= e($game['id']) ?>">
            </article>
    <?php endforeach; ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="number" name="game_id" min="1" aria-label="Identyfikator gry" required>
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  38. Krok 38: Skrypt 2: paragraf z nazwą

    Kryteria:

    R.5.7 Skrypt 2 wyświetla obrazy oraz ich nazwy zwrócone zapytaniem

    ok. 1 min
    Polecenie z arkusza

    Paragrafu z nazwą zdjęcia.

    <p> z nazwą pod obrazem, w tym samym bloku. Sekcja środkowa wygląda jak na ilustracji 2: dziewięć bloków (z Zamczyskiem dodanym zapytaniem 4) i paski przewijania.

    gry.php PHP zmienione: 1 linia
    <?php
    $config = require 'config.php';
    
    try {
        $pdo = new PDO($config['dsn'], $config['user'], $config['password'], [
            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ę.');
    }
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    // Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)
    $topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();
    
    // Skrypt 2: wszystkie gry do galerii (zapytanie 1)
    $games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php foreach ($topGames as $game): ?>
                <li><?= e($game['nazwa']) ?> <span class="points"><?= e($game['punkty']) ?></span></li>
    <?php endforeach; ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php foreach ($games as $game): ?>
            <article class="game">
                <img src="<?= e($game['zdjecie']) ?>" alt="<?= e($game['nazwa']) ?>" title="<?= e($game['id']) ?>">
                <p><?= e($game['nazwa']) ?></p>
            </article>
    <?php endforeach; ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="number" name="game_id" min="1" aria-label="Identyfikator gry" required>
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  39. Krok 39: Skrypt 3: id z formularza i zapytanie przygotowane

    Kryteria: ,

    R.5.4 W przynajmniej jednym skrypcie związanym z formularzem działania następują tylko wtedy, gdy wypełnione pole nazwa (skrypt 4) lub id (skrypt 3)

    R.5.5 Skrypt zawiera instrukcję pobierającą dane z tablicy globalnej odpowiadającej zastosowanej metodzie formularza ($_POST, $_GET, $_REQUEST)

    ok. 4 min
    Polecenie z arkusza

    Skrypt 3 związany z formularzem w stopce: jeżeli wpisano id do pola edycyjnego, skrypt wysyła do bazy danych zapytanie 2 zmodyfikowane tak, że wybierany jest wiersz o id podanym w polu edycyjnym.

    Dwie warstwy ochrony:

    1. Walidacja wejścia. filter_var(..., FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]) zwraca liczbę całkowitą albo false - dla pustego pola, liter, 1 OR 1=1 i zera. Warunek $gameId !== false to „jeżeli wpisano id” z arkusza (R.5.4); operator ?? podstawia pusty napis, gdy formularz nie został wysłany, więc nie trzeba osobnego isset. Dane czytane są z $_POST (R.5.5) - filter_input(INPUT_POST, ...) też by zadziałał, ale nie zawiera $_POST, a tego dotyczy kryterium.
    2. Zapytanie przygotowane. W treści zapytania stoi parametr nazwany :id, a wartość idzie osobno w execute(['id' => $gameId]). Baza zna strukturę zapytania, zanim zobaczy dane, więc wpisany tekst nigdy nie stanie się częścią SQL.

    fetch() zwraca jeden wiersz albo false, gdy gry o takim id nie ma. Pole w stopce zachowuje wpisaną wartość (value), jak na ilustracji 2.

    Typowe błędy

    Porównanie if ($gameId) zamiast !== false też odrzuci zero, ale przy filter_var zawsze porównuj ściśle z false - przy innych filtrach poprawna wartość 0 zostałaby wzięta za błąd.

    gry.php PHP zmienione: 11 linii
    <?php
    $config = require 'config.php';
    
    try {
        $pdo = new PDO($config['dsn'], $config['user'], $config['password'], [
            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ę.');
    }
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    // Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)
    $topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();
    
    // Skrypt 2: wszystkie gry do galerii (zapytanie 1)
    $games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();
    
    // Skrypt 3: opis gry o id z formularza w stopce (zapytanie 2)
    $selectedGame = false;
    $gameId = filter_var($_POST['game_id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    if ($gameId !== false) {
        $statement = $pdo->prepare('SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cena FROM gry WHERE id = :id');
        $statement->execute(['id' => $gameId]);
        $selectedGame = $statement->fetch();
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php foreach ($topGames as $game): ?>
                <li><?= e($game['nazwa']) ?> <span class="points"><?= e($game['punkty']) ?></span></li>
    <?php endforeach; ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php foreach ($games as $game): ?>
            <article class="game">
                <img src="<?= e($game['zdjecie']) ?>" alt="<?= e($game['nazwa']) ?>" title="<?= e($game['id']) ?>">
                <p><?= e($game['nazwa']) ?></p>
            </article>
    <?php endforeach; ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="number" name="game_id" min="1" aria-label="Identyfikator gry" required
                       value="<?= $gameId !== false ? e($gameId) : '' ?>">
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  40. Krok 40: Skrypt 3: nagłówek i opis pod formularzem

    Kryteria:

    R.5.8 Skrypt 3 wyświetla napis: „<nazwa>, <punkty> punktów, <cena> zł", gdzie nawiasy <> oznaczają wartość pobraną z bazy danych oraz wyświetla opis

    ok. 3 min
    Polecenie z arkusza

    Zwrócony zapytaniem wiersz jest wyświetlany pod formularzem według wzoru: w nagłówku drugiego stopnia: <nazwa>, <punkty> punktów, <cena> zł. Opis w paragrafie.

    Pod formularzem if ($selectedGame): - nagłówek <h2> według wzoru z arkusza i paragraf z opisem skróconym do 100 znaków (kolumna wyniku nazywa się opis dzięki aliasowi AS opis). Gałąź elseif obsługuje id spoza bazy krótkim komunikatem - zamiast pustego miejsca albo ostrzeżenia PHP o odczycie z false. Klucz tego nie ocenia, ale egzaminator wpisujący np. 99 zobaczy sensowną odpowiedź.

    Podgląd pokazuje stan z ilustracji 2: w polu 6, pod formularzem „Mag, 357 punktów, 150 zł”.

    gry.php PHP zmienione: 6 linii
    <?php
    $config = require 'config.php';
    
    try {
        $pdo = new PDO($config['dsn'], $config['user'], $config['password'], [
            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ę.');
    }
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    // Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)
    $topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();
    
    // Skrypt 2: wszystkie gry do galerii (zapytanie 1)
    $games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();
    
    // Skrypt 3: opis gry o id z formularza w stopce (zapytanie 2)
    $selectedGame = false;
    $gameId = filter_var($_POST['game_id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    if ($gameId !== false) {
        $statement = $pdo->prepare('SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cena FROM gry WHERE id = :id');
        $statement->execute(['id' => $gameId]);
        $selectedGame = $statement->fetch();
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php foreach ($topGames as $game): ?>
                <li><?= e($game['nazwa']) ?> <span class="points"><?= e($game['punkty']) ?></span></li>
    <?php endforeach; ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php foreach ($games as $game): ?>
            <article class="game">
                <img src="<?= e($game['zdjecie']) ?>" alt="<?= e($game['nazwa']) ?>" title="<?= e($game['id']) ?>">
                <p><?= e($game['nazwa']) ?></p>
            </article>
    <?php endforeach; ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="number" name="game_id" min="1" aria-label="Identyfikator gry" required
                       value="<?= $gameId !== false ? e($gameId) : '' ?>">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php if ($selectedGame): ?>
            <h2><?= e($selectedGame['nazwa']) ?>, <?= e($selectedGame['punkty']) ?> punktów, <?= e($selectedGame['cena']) ?> zł</h2>
            <p><?= e($selectedGame['opis']) ?></p>
    <?php elseif ($gameId !== false): ?>
            <p>Nie ma gry o identyfikatorze <?= e($gameId) ?>.</p>
    <?php endif; ?>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  41. Krok 41: Skrypt 4: wstawienie gry z zerem punktów i przekierowanie

    Kryteria:

    R.5.9 Skrypt 4 wstawia do bazy danych rekord z danymi pobranymi z formularza, liczba punktów =0 (sprawdzić w phpMyAdmin)

    ok. 5 min
    Polecenie z arkusza

    Skrypt 4 związany z formularzem z sekcji prawej: jeżeli wypełniono pole nazwa, skrypt wysyła do bazy zmodyfikowane zapytanie 4 w ten sposób, że wstawione są wartości pobrane z formularza, a liczba punktów wynosi 0.

    Skrypt 4 stoi przed odczytami, żeby nowa gra była już w bazie, gdy skrypty 1 i 2 pobierają listę i galerię.

    • Wejście: trim usuwa spacje z brzegów, warunek $name !== '' to „jeżeli wypełniono pole nazwa”, a mb_strlen($name) <= 20 pilnuje długości kolumny varchar(20) (mb_, bo „ż” to dla zwykłego strlen dwa bajty). Cena przechodzi przez FILTER_VALIDATE_FLOAT; gdy jej nie ma albo jest błędna, do bazy idzie NULL (kolumna to dopuszcza), czyli „brak ceny”. Pusty napis w kolumnie liczbowej MariaDB w XAMPP po cichu zamienia na 0 (gra „za darmo”), a serwer w trybie ścisłym odrzuciłby całe zapytanie.
    • Zapytanie przygotowane z parametrami nazwanymi :name, :description, :price, :image; zero punktów jest stałą w treści zapytania, bo nie pochodzi od użytkownika.
    • PRG (Post/Redirect/Get): po zapisie header('Location: gry.php') i exit. Przeglądarka pobiera stronę od nowa zwykłym GET-em, więc odświeżenie (F5) nie wyśle formularza drugi raz i nie doda tej samej gry podwójnie. exit kończy skrypt - PHP sam zamyka wtedy połączenie.

    Podgląd się nie zmienia: skrypt działa dopiero po kliknięciu DODAJ, a efekt sprawdza się w phpMyAdmin (R.5.9) i na liście gier.

    Typowe błędy

    header() musi stać przed jakimkolwiek wypisanym znakiem - nawet przed spacją albo pustą linią przed <?php. Dlatego cała logika jest na górze pliku, nad <!DOCTYPE html>.

    gry.php PHP zmienione: 17 linii
    <?php
    $config = require 'config.php';
    
    try {
        $pdo = new PDO($config['dsn'], $config['user'], $config['password'], [
            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ę.');
    }
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    // Skrypt 4: nowa gra z formularza w sekcji prawej (zapytanie 4) - przed odczytami
    $name = trim($_POST['name'] ?? '');
    if ($name !== '' && mb_strlen($name) <= 20) {
        $price = filter_var($_POST['price'] ?? '', FILTER_VALIDATE_FLOAT);
        $statement = $pdo->prepare(
            'INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie) VALUES (:name, :description, 0, :price, :image)'
        );
        $statement->execute([
            'name' => $name,
            'description' => trim($_POST['description'] ?? ''),
            'price' => $price === false ? null : $price,
            'image' => trim($_POST['image'] ?? ''),
        ]);
        header('Location: gry.php');
        exit;
    }
    
    // Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)
    $topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();
    
    // Skrypt 2: wszystkie gry do galerii (zapytanie 1)
    $games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();
    
    // Skrypt 3: opis gry o id z formularza w stopce (zapytanie 2)
    $selectedGame = false;
    $gameId = filter_var($_POST['game_id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    if ($gameId !== false) {
        $statement = $pdo->prepare('SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cena FROM gry WHERE id = :id');
        $statement->execute(['id' => $gameId]);
        $selectedGame = $statement->fetch();
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php foreach ($topGames as $game): ?>
                <li><?= e($game['nazwa']) ?> <span class="points"><?= e($game['punkty']) ?></span></li>
    <?php endforeach; ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php foreach ($games as $game): ?>
            <article class="game">
                <img src="<?= e($game['zdjecie']) ?>" alt="<?= e($game['nazwa']) ?>" title="<?= e($game['id']) ?>">
                <p><?= e($game['nazwa']) ?></p>
            </article>
    <?php endforeach; ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="number" name="game_id" min="1" aria-label="Identyfikator gry" required
                       value="<?= $gameId !== false ? e($gameId) : '' ?>">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php if ($selectedGame): ?>
            <h2><?= e($selectedGame['nazwa']) ?>, <?= e($selectedGame['punkty']) ?> punktów, <?= e($selectedGame['cena']) ?> zł</h2>
            <p><?= e($selectedGame['opis']) ?></p>
    <?php elseif ($gameId !== false): ?>
            <p>Nie ma gry o identyfikatorze <?= e($gameId) ?>.</p>
    <?php endif; ?>
        </footer>
    </body>
    </html>

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

  42. Krok 42: Zamknięcie połączenia z bazą

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

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

    W PDO połączenie zamyka się, usuwając obiekt: $pdo = null;. Ponieważ wszystkie zapytania są na górze pliku, zamknięcie stoi zaraz po ostatnim zapytaniu, jeszcze przed HTML - połączenie z bazą nie wisi otwarte, gdy serwer wysyła stronę. To wciąż ostatnia operacja na bazie (R.5.2).

    Szablon niżej korzysta już tylko z tablic $topGames, $games i $selectedGame, które zostają w pamięci po zamknięciu połączenia.

    Typowe błędy

    $pdo = null działa tylko wtedy, gdy nic innego nie trzyma referencji do połączenia. Niezamknięte $statement też ją trzyma - przy jednym zapytaniu nie ma to znaczenia, ale w dłuższym kodzie warto zerować także obiekty zapytań.

    gry.php PHP zmienione: 3 linie
    <?php
    $config = require 'config.php';
    
    try {
        $pdo = new PDO($config['dsn'], $config['user'], $config['password'], [
            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ę.');
    }
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
    }
    
    // Skrypt 4: nowa gra z formularza w sekcji prawej (zapytanie 4) - przed odczytami
    $name = trim($_POST['name'] ?? '');
    if ($name !== '' && mb_strlen($name) <= 20) {
        $price = filter_var($_POST['price'] ?? '', FILTER_VALIDATE_FLOAT);
        $statement = $pdo->prepare(
            'INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie) VALUES (:name, :description, 0, :price, :image)'
        );
        $statement->execute([
            'name' => $name,
            'description' => trim($_POST['description'] ?? ''),
            'price' => $price === false ? null : $price,
            'image' => trim($_POST['image'] ?? ''),
        ]);
        header('Location: gry.php');
        exit;
    }
    
    // Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)
    $topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();
    
    // Skrypt 2: wszystkie gry do galerii (zapytanie 1)
    $games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();
    
    // Skrypt 3: opis gry o id z formularza w stopce (zapytanie 2)
    $selectedGame = false;
    $gameId = filter_var($_POST['game_id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    if ($gameId !== false) {
        $statement = $pdo->prepare('SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cena FROM gry WHERE id = :id');
        $statement->execute(['id' => $gameId]);
        $selectedGame = $statement->fetch();
    }
    
    // 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>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php foreach ($topGames as $game): ?>
                <li><?= e($game['nazwa']) ?> <span class="points"><?= e($game['punkty']) ?></span></li>
    <?php endforeach; ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php foreach ($games as $game): ?>
            <article class="game">
                <img src="<?= e($game['zdjecie']) ?>" alt="<?= e($game['nazwa']) ?>" title="<?= e($game['id']) ?>">
                <p><?= e($game['nazwa']) ?></p>
            </article>
    <?php endforeach; ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label>
                <input type="text" id="name" name="name" maxlength="20" required>
                <label for="description">opis</label>
                <input type="text" id="description" name="description">
                <label for="price">cena</label>
                <input type="number" id="price" name="price" min="0" step="0.01">
                <label for="image">zdjęcie</label>
                <input type="text" id="image" name="image">
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="number" name="game_id" min="1" aria-label="Identyfikator gry" required
                       value="<?= $gameId !== false ? e($gameId) : '' ?>">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php if ($selectedGame): ?>
            <h2><?= e($selectedGame['nazwa']) ?>, <?= e($selectedGame['punkty']) ?> punktów, <?= e($selectedGame['cena']) ?> zł</h2>
            <p><?= e($selectedGame['opis']) ?></p>
    <?php elseif ($gameId !== false): ?>
            <p>Nie ma gry o identyfikatorze <?= e($gameId) ?>.</p>
    <?php endif; ?>
        </footer>
    </body>
    </html>

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

  43. Etap 5: Porównanie z wersją podstawową

    Kroki 43-44 · po tym etapie masz 41 z 41 kryteriów (pkt)

    Krok 43: 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, skrypt połączenia z bazą.

    Obie wersje dają te same punkty. Różnice:

    wersja podstawowata wersja
    układfloat + clear, @media (max-width: 800px) odwołuje układgrid, mobile first, @media (width > 800px) dokłada kolumny
    kolorywpisane w każdej regulezmienne w :root
    bazamysqli_* z tabeli 1PDO, wyjątki, FETCH_ASSOC
    dane w zapytaniachsklejane z napisemparametry nazwane :id, :name
    wypisywanieecho w pętli whilelogika na górze, szablon z foreach/endforeach i e()
    po dodaniu grystrona z formularzemprzekierowanie (PRG)

    Czego tu jeszcze nie ma, a w prawdziwym serwisie by było: token CSRF w formularzach (ukryte pole z losową wartością z sesji, sprawdzane przy zapisie - blokuje wysłanie formularza z cudzej strony) i ograniczenie dostępu do dodawania gier. Klucz tego nie wymaga.

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

  44. Krok 44: 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 jeszcze opanowane: funkcje mysqli_* są podpowiedziane w tabeli 1 arkusza, a każdy fragment PHP stoi tam, gdzie widać jego efekt.
    • Ta wersja, gdy PDO i siatka CSS są pisane z pamięci. Kodu jest podobnie dużo, a błąd łatwiej znaleźć: wyjątek PDO wskazuje złe zapytanie, zamiast zostawiać pustą listę.

    Mieszanie też jest dopuszczalne: siatka CSS z tej wersji i mysqli_* z podstawowej dadzą pełne punkty. Najważniejsze, żeby pisać to, co się umie bez sprawdzania składni - 150 minut nie zostawia czasu na szukanie literówki w PDO::ATTR_ERRMODE.

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

  45. Etap 6: Oddanie pracy

    Krok 45 · po tym etapie masz 41 z 41 kryteriów (pkt)

    Krok 45: Plik przeglądarka.txt i nagranie płyty

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Nagraj płytę z rezultatami pracy.

    W przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana przez http://localhost/….

    Na płycie, w folderze z numerem zdającego: Czarodziejka.jpg, gry.php, import.png, kalipso.jpg, katedra.jpg, krokodyl.jpg, kw1.jpg - kw4.jpg, kwerendy.txt, Mag.jpg, NieznanyLad.jpg, Persja.jpg, przeglądarka.txt, styl.css, WyprawaNaKsiezyc.jpg, zamczysko.jpg oraz config.php - bez niego strona się nie połączy z bazą. Po nagraniu sprawdź odczyt płyty.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.