Przejdź do głównej treści
INF.03-02-26.06-SGCzerwiec 2026 · 49 kroków · ok. 91 min · 41 kryteriów z klucza

Rozwiązanie krok po kroku: Witryna Łowcy wodospadów (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza wodospady, pięć zapytań, zdjęcie przeskalowane do 133 px, strona index.php z odnośnikami kontynentów, tabelą wodospadów, formularzem i galerią, style w styl.css - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • blok główny na siatce CSS (grid-template-columns: 30% 70%) zamiast display: flex, kolory i zaokrąglenia z arkusza w zmiennych CSS, tabela z częściami thead i tbody,
  • PHP na PDO z logiką na górze pliku: wszystkie zapytania wykonują się przed HTML-em, a szablon tylko wypisuje dane przez funkcję e(),
  • dane z zewnątrz są sprawdzane: identyfikator kontynentu z adresu przechodzi przez filter_var z wartością domyślną 6, a pola formularza przez filter_input - adres ?id=6 OR 1=1 niczego nie zmienia, a puste pole nie wywraca strony,
  • zapis przez zapytanie przygotowane z obsługą błędu klucza obcego (wodospad spoza bazy daje komunikat, nie błąd PHP) i PRG: po zapisie strona przekierowuje sama na siebie, więc odświeżenie nie dopisze osiągnięcia drugi raz.

Wersja podstawowa (zakładka obok) pisze kod najprostszymi funkcjami z tabeli 1 arkusza i dopiero na końcu go poprawia; tu kod jest od razu taki, jak w prawdziwym projekcie. Na końcu jest krótkie porównanie obu wersji i podpowiedź, którą wybrać na egzaminie.

Baza i zdjęcia pochodzą z prawdziwego archiwum pliki2 (załącznik na stronie arkusza, hasło w treści zadania), a podgląd każdego kroku z PHP to wynik prawdziwego uruchomienia strony na serwerze z tą bazą; tabela pokazuje Europę, jak na ilustracji 2.

Jak liczą się punkty. Klucz ma 41 kryteriów (R.1.1 - R.5.9), 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.

  1. Etap 1: Baza danych

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

    Krok 1: Baza danych wodospady

    ok. 2 min
    Polecenie z arkusza

    Utwórz bazę danych o nazwie wodospady, z zestawem znaków utf8_unicode_ci.

    Rozpakuj pliki2.7z z pulpitu (hasło z treści zadania) i załóż folder z numerem zdającego. W phpMyAdmin: Nowa, nazwa wodospady, porównywanie utf8_unicode_ci. W SQL:

    CREATE DATABASE wodospady CHARACTER SET utf8 COLLATE utf8_unicode_ci;
    

    Arkusz nazywa to „zestawem znaków”, ale utf8_unicode_ci to metoda porównywania - zestawem jest utf8.

    Typowe błędy

    Literówka w nazwie bazy zabiera też punkty za skrypty - połączenie podaje nazwę bazy (R.5.1).

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

    ok. 2 min
    Polecenie z arkusza

    Do utworzonej bazy zaimportuj tabele z pliku baza2.sql.

    Zaznacz bazę wodospady, Import → baza2.sql → Wykonaj. Powstaje pięć tabel z ilustracji 1, w tym pusta osiagniecia - tabela pośrednicząca relacji m:n między turystami a obiektami.

  3. Krok 3: Zrzut ekranu import.jpg

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych wodospady - plik import w formacie JPEG

    ok. 2 min
    Polecenie z arkusza

    Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie JPEG pod nazwą import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań. Na zrzucie powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel.

    Cały ekran, zapis jako import.jpg - import w JPEG, zrzuty zapytań w PNG.

    Typowe błędy

    Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany.

  4. Krok 4: Zapytanie 1: wszystkie kontynenty

    Kryteria: ,

    R.1.2 W pliku kwerendy.txt zapisane co najmniej jedno zapytanie SQL, wynikające z treści zadania

    R.1.3 Wykonane zapytanie 1 wybierające identyfikatory i nazwy wszystkich kontynentów. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT idKontynent, nazwa FROM kontynenty; (lub * po SELECT ) oraz na zrzucie widoczne są dane dla identyfikatorów 1 ÷ 7

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 1: wybierające identyfikatory i nazwy wszystkich kontynentów.

    Dwie kolumny bez warunku: siedem kontynentów, identyfikatory 1-7 (R.1.3). Zapytanie do kwerendy.txt, zrzut kw1.png.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT idKontynent, nazwa
    FROM kontynenty;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: wodospady w Polsce

    Kryteria:

    R.1.4 Wykonane zapytanie 2 wybierające jedynie pola nazwa, wartoscCechy i plik z tabeli obiekty dla wodospadów z Polski. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, wartoscCechy, plik FROM obiekty WHERE idRodzaj = 10 AND panstwo = "Polska";(wymagane sprawdzenie, że jest to wodospad) oraz na zrzucie widoczne są dokładnie 4 rekordy dla: Kamieńczyk, Mała Siklawa, Wielka Siklawa, Wilczki

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pola nazwa, wartoscCechy i plik z tabeli obiekty dla wodospadów z Polski (w warunku można zastosować pole idRodzaj = 10).

    Dwa warunki naraz (AND): rodzaj 10 to wodospad, państwo zawęża do Polski. Każdy warunek w osobnej linii - łatwo dopisać trzeci albo zakomentować jeden. Wynik: Kamieńczyk, Mała Siklawa, Wielka Siklawa, Wilczki (R.1.4), zrzut kw2.png.

    Typowe błędy

    Sam warunek idRodzaj = 10 zwraca 12 wodospadów z całego świata - klucz wymaga obu warunków.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT idKontynent, nazwa
    FROM kontynenty;
    
    -- Zapytanie 2
    SELECT nazwa, wartoscCechy, plik
    FROM obiekty
    WHERE idRodzaj = 10
      AND panstwo = 'Polska';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: wodospady Europy

    Kryteria:

    R.1.5 Wykonane zapytanie 3 wybierające jedynie pola idObiekt, panstwo, nazwa i wartoscCechy z tabeli obiekty dla pól idRodzaj = 10 i idKontynent = 6. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT idObiekt, panstwo, nazwa, wartoscCechy FROM obiekty WHERE idRodzaj = 10 AND idKontynent = 6; oraz na zrzucie widocznych jest dokładnie 8 rekordów o id: 42, 43, 44, 45, 47, 48, 53, 54

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pola idObiekt, panstwo, nazwa i wartoscCechy z tabeli obiekty dla pól idRodzaj = 10 i idKontynent = 6.

    Te same dwa warunki, zamiast państwa kontynent 6 (Europa). Wynik: osiem wierszy o identyfikatorach 42, 43, 44, 45, 47, 48, 53, 54 (R.1.5), zrzut kw3.png. Na stronie w miejscu 6 stanie identyfikator kontynentu z adresu.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT idKontynent, nazwa
    FROM kontynenty;
    
    -- Zapytanie 2
    SELECT nazwa, wartoscCechy, plik
    FROM obiekty
    WHERE idRodzaj = 10
      AND panstwo = 'Polska';
    
    -- Zapytanie 3
    SELECT idObiekt, panstwo, nazwa, wartoscCechy
    FROM obiekty
    WHERE idRodzaj = 10
      AND idKontynent = 6;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: osiągnięcie turysty

    Kryteria:

    R.1.6 Wykonane zapytanie 4 wstawiające do tabeli osiagniecia rekord o wartościach: idObiekt = 34, idTurysta = 2. Klucz podstawowy nadawany automatycznie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO osiagniecia(`idObiekt`, `idTurysta`) VALUES (34, 2); (klucz główny pominięty lub NULL) oraz na zrzucie widoczny jest komunikat o poprawnie dodanym rekordzie lub tabela z dodanym rekordem

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: wstawiające do tabeli osiagniecia rekord o wartościach: idObiekt = 34, idTurysta = 2. Klucz podstawowy nadawany automatycznie.

    Lista kolumn podana jawnie, bez klucza idOsiagniecie - baza nada go sama (AUTO_INCREMENT), tu 1. Jawna lista kolumn sprawia też, że zapytanie przeżyje dopisanie do tabeli nowej kolumny. Zrzut z komunikatem albo tabelą: kw4.png (R.1.6).

    Typowe błędy

    Wpisanie klucza ręcznie przeczy wymaganiu „klucz nadawany automatycznie”.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT idKontynent, nazwa
    FROM kontynenty;
    
    -- Zapytanie 2
    SELECT nazwa, wartoscCechy, plik
    FROM obiekty
    WHERE idRodzaj = 10
      AND panstwo = 'Polska';
    
    -- Zapytanie 3
    SELECT idObiekt, panstwo, nazwa, wartoscCechy
    FROM obiekty
    WHERE idRodzaj = 10
      AND idKontynent = 6;
    
    -- Zapytanie 4
    INSERT INTO osiagniecia (idObiekt, idTurysta)
    VALUES (34, 2);
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Krok 8: Zapytanie 5: turyści

    Kryteria:

    R.1.7 Wykonane zapytanie 5 wybierające identyfikatory i wszystkie pola nick turystów. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT idTurysta, nick FROM turysci; oraz na zrzucie widocznych jest dokładnie 13 rekordów

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 5: wybierające pola idTurysta i nick z tabeli turysci.

    Dwie kolumny, 13 turystów (R.1.7), zrzut kw5.png. Identyfikator trafi do value opcji listy, nick będzie jej treścią.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT idKontynent, nazwa
    FROM kontynenty;
    
    -- Zapytanie 2
    SELECT nazwa, wartoscCechy, plik
    FROM obiekty
    WHERE idRodzaj = 10
      AND panstwo = 'Polska';
    
    -- Zapytanie 3
    SELECT idObiekt, panstwo, nazwa, wartoscCechy
    FROM obiekty
    WHERE idRodzaj = 10
      AND idKontynent = 6;
    
    -- Zapytanie 4
    INSERT INTO osiagniecia (idObiekt, idTurysta)
    VALUES (34, 2);
    
    -- Zapytanie 5
    SELECT idTurysta, nick
    FROM turysci;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Etap 2: Grafika i strona HTML

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

    Krok 9: Grafika siklawica.jpg: szerokość 133 px

    Kryteria:

    R.2.1 Grafika siklawica.jpg ma szerokość 133 px i wysokość 200 px (± 5 px)

    ok. 3 min
    Polecenie z arkusza

    Grafikę należy przeskalować z zachowaniem proporcji do szerokości 133 px.

    Oryginał ma 1000 × 1500 px. Obraz → Skaluj obraz, szerokość 133 px przy zamkniętym łańcuchu - wysokość wychodzi 200 px (R.2.1: 133 × 200 ±5). Eksport pod tą samą nazwą.

    Typowe błędy

    Otwarty łańcuch proporcji zostawia wysokość 1500 px - zdjęcie jest zniekształcone.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 9
  10. Krok 10: Plik index.php w HTML5

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Składa się ze strony o nazwie index.php. Zapisana w języku HTML5.

    Plik index.php w folderze zdającego. <!DOCTYPE html> włącza tryb standardów HTML5, dalej szkielet: <html>, <head> i <body>. Rozszerzenie .php, bo na początku pliku stanie kod PHP.

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

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Zadeklarowany polski język zawartości witryny.

    lang="pl" w <html> - czytnik ekranu czyta polską wymową, przeglądarka dobiera słownik i dzielenie wyrazów.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  12. Krok 12: Kodowanie polskich znaków

    Kryteria:

    R.2.3 W sekcji head znajduje się jawnie podany standard kodowania polskich znaków i tytuł strony "Wodospady"

    ok. 1 min
    Polecenie z arkusza

    Jawnie zastosowany właściwy standard kodowania polskich znaków.

    <meta charset="UTF-8"> jako pierwszy element <head>. Ten sam standard ustawi połączenie z bazą (charset=utf8mb4 w PDO), więc polskie litery przejdą bez zmian całą drogę: baza → PHP → przeglądarka.

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

    Kryteria:

    R.2.3 W sekcji head znajduje się jawnie podany standard kodowania polskich znaków i tytuł strony "Wodospady"

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony „Wodospady”.

    <title>Wodospady</title> - napis na karcie przeglądarki. Obok <meta name="viewport">: bez niego telefon pokazałby stronę pomniejszoną jak na monitorze. Arkusz tego nie wymaga, a w każdym współczesnym szablonie ta linia jest.

    index.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>Wodospady</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  14. Krok 14: Podpięcie arkusza stylów

    Kryteria:

    R.3.2 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.css formatowanie CSS pochodzi jedynie z tego arkusza

    ok. 1 min
    Polecenie z arkusza

    Arkusz stylów w pliku o nazwie styl.css prawidłowo połączony z kodem strony.

    <link rel="stylesheet" href="styl.css"> - nazwa dokładnie z arkusza. Cały wygląd będzie w tym pliku, w HTML nie ma ani jednego atrybutu style.

    Typowe błędy

    Zła nazwa w href to brak stylów, a wtedy cały rezultat R.4 przepada.

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

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: nagłówkowy, główny, artykułu, stopki. Blok główny zawiera blok boczny i blok sekcji. Podział zrealizowany za pomocą semantycznych znaczników języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków był zgodny z ilustracją 3.

    header, main z aside i section, potem article i footer (R.2.4). Artykuł i stopka stoją poza main - na ilustracji 3 zajmują całą szerokość pod blokiem głównym.

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

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego: nagłówek drugiego stopnia o treści „Łowcy wodospadów”.

    <h2>, nie <h1> - stopień narzuca arkusz.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
            </section>
        </main>
        <article>
        </article>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  17. Krok 17: Tabela: wiersz nagłówkowy w thead

    Kryteria: ,

    R.2.6 W bloku sekcji znajduje się tabela o czterech kolumnach (znaczniki <table>, <tr>, <td>; w skrypcie lub HTML )

    R.2.7 Pierwszy wiersz tabeli zawiera cztery komórki nagłówkowe (znaczniki <tr>, <th> )

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku sekcji: tabela o czterech kolumnach. Pierwszy wiersz jest wierszem nagłówkowym z komórkami: „Identyfikator”, „Państwo”, „Nazwa wodospadu”, „Wysokość”. Tabela jest wypełniona skryptem 2.

    Wiersz z czterema komórkami <th> (R.2.6, R.2.7) w części <thead>, a miejsce na dane w <tbody>. Podział mówi przeglądarce i czytnikom ekranu, który wiersz opisuje kolumny; przy wydruku długiej tabeli thead powtarza się na każdej stronie. Wiersze z danymi wstawi szablon PHP do tbody.

    index.php PHP zmienione: 12 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
            </section>
        </main>
        <article>
        </article>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Nagłówek nad formularzem

    ok. 1 min
    Polecenie z arkusza

    Nagłówek czwartego stopnia o treści „Wpisz osiągnięcie do bazy”.

    <h4> pod tabelą, w bloku sekcji.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
            </section>
        </main>
        <article>
        </article>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  19. Krok 19: Formularz wysyłany metodą POST

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Formularz wysyłający dane metodą bezpieczną do tego samego pliku. Formularz zawiera pola rozmieszczone zgodnie z ilustracją 2.

    method="post" - dane w treści żądania. Bez atrybutu action formularz wysyła dane pod bieżący adres razem z ?id=…, więc wybrany kontynent zostaje (R.3.4).

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                </form>
            </section>
        </main>
        <article>
        </article>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  20. Krok 20: Etykieta i pole identyfikatora

    ok. 2 min
    Polecenie z arkusza

    Etykieta o treści: „identyfikator wodospadu”. Pole edycyjne typu numerycznego połączone z etykietą.

    <label for="waterfall"> łączy się z polem po id, type="number" przyjmuje tylko liczby (R.3.6). Dodatkowo min="1" i required: przeglądarka nie wyśle pustego pola ani zera i od razu pokaże podpowiedź. To wygoda dla użytkownika, nie zabezpieczenie - żądanie można wysłać z pominięciem przeglądarki, więc PHP sprawdzi wartość jeszcze raz.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                </form>
            </section>
        </main>
        <article>
        </article>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  21. Krok 21: Etykieta i lista turystów

    ok. 1 min
    Polecenie z arkusza

    Etykieta o treści: „turysta”. Lista rozwijana połączona z etykietą, której elementy są wypełnione skryptem 3.

    for etykiety równe id listy. Opcje wypisze szablon PHP z tabeli turysci.

    index.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                </form>
            </section>
        </main>
        <article>
        </article>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Przycisk „Wpisz”

    Kryteria: ,

    R.2.8 W bloku sekcji znajduje się formularz z 2 etykietami, polem edycyjnym, listą rozwijaną (może być pusta) , i przyciskiem "Wpisz". Co najmniej jedno pole formularza ma powiązaną etykietę (label z for i id lub zagnieżdżenie kontrolki w label)

    R.3.5 Przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu )

    ok. 1 min
    Polecenie z arkusza

    Przycisk o treści „Wpisz” wysyłający dane do skryptu 4.

    <button type="submit"> wysyła formularz (R.2.8, R.3.5). Elementy formularza są liniowe, więc stoją w jednym wierszu jak na ilustracji 2.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
        </article>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  23. Krok 23: Nagłówek artykułu

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku artykułu: nagłówek trzeciego stopnia o treści „Wodospady w Polsce”.

    <h3> w <article>.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
        </article>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Galeria czterech wodospadów

    Kryteria:

    R.2.9 W bloku artykułu znajdują się grafiki kamienczyk.jpg , siklawica.jpg , siklawa.jpg , wilczki.jpg z tekstem alternatywnym "wodospad"

    ok. 2 min
    Polecenie z arkusza

    Obrazy o nazwach kamienczyk.jpg, siklawica.jpg, siklawa.jpg, wilczki.jpg z tekstem alternatywnym „wodospad”.

    Cztery <img> w kolejności z arkusza, każdy z alt="wodospad" (R.2.9). Zdjęcia nie pochodzą z bazy - arkusz każe wpisać je w HTML-u.

    index.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>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  25. Krok 25: Stopka

    Kryteria:

    R.2.5 W bloku nagłówkowym znajduje się <h2>: "Łowcy wodospadów", w bloku sekcji <h4>: "Wpisz osiągnięcie do bazy", w bloku artykułu <h3>: "Wodospady w Polsce" oraz <p> z numerem zdającego w stopce

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku stopki: paragraf o treści „Autor: ”, dalej wstawiony numer zdającego.

    Paragraf w <footer> (R.2.5 - razem z nagłówkami h2, h4, h3). Tu stoi nazwisko autora rozwiązania - na egzaminie wpisujesz swój numer zdającego (PESEL).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  26. Etap 3: Styl CSS

    Kroki 26-35 · po tym etapie masz 30 z 41 kryteriów (pkt)

    Krok 26: Zmienne CSS z wartościami 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 zmiennych w :root: kolory z arkusza i zaokrąglenie 20px, które dotyczy trzech bloków. W regułach występują jako var(--radius) - zmiana zaokrąglenia całej strony to jedna linia.

    styl.css CSS nowy plik
    :root {
        --frame-bg: SkyBlue;
        --article-bg: AliceBlue;
        --link-bg: DodgerBlue;
        --link-hover: RoyalBlue;
        --radius: 20px;
    }
    Podgląd strony po tym kroku (index.php)
  27. Krok 27: Krój i wyrównanie dla wszystkich selektorów

    Kryteria:

    R.4.1 domyślne formatowanie selektorów (*): krój czcionki Garamond - jako pierwszy, serif - jako ostatni, wyrównanie tekstu do środka

    ok. 1 min
    Polecenie z arkusza

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

    Selektor *: najpierw Garamond, na końcu rodzina zastępcza serif (R.4.1). text-align z selektora * dostaje każdy element osobno, nie tylko dziedziczy od rodzica.

    styl.css CSS zmienione: 5 linii
    :root {
        --frame-bg: SkyBlue;
        --article-bg: AliceBlue;
        --link-bg: DodgerBlue;
        --link-hover: RoyalBlue;
        --radius: 20px;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  28. Krok 28: Nagłówek i stopka

    ok. 2 min
    Polecenie z arkusza

    Dla bloku nagłówkowego i stopki: kolor tła SkyBlue, marginesy wewnętrzne 8 px, zewnętrzne 10 px, zaokrąglenie rogów 20 px.

    Te same marginesy i zaokrąglenie ma też blok artykułu (następne zdanie arkusza), więc wspólna część trzech bloków jest w jednej regule header, footer, article, a różni je tylko kolor tła. Zmiana odstępu w całej stronie to wtedy jedna linia, a nie trzy.

    styl.css CSS zmienione: 13 linii
    :root {
        --frame-bg: SkyBlue;
        --article-bg: AliceBlue;
        --link-bg: DodgerBlue;
        --link-hover: RoyalBlue;
        --radius: 20px;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer,
    article {
        margin: 10px;
        padding: 8px;
        border-radius: var(--radius);
    }
    
    header,
    footer {
        background-color: var(--frame-bg);
    }
    Podgląd strony po tym kroku (index.php)
  29. Krok 29: Blok główny: siatka 30% i 70%

    Kryteria: ,

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 3 w arkuszu egzaminacyjnym (prawidłowo zastosowane właściwości CSS decydujące o układzie bloków na stronie, np. float i clear albo display flex albo grid albo position; żaden blok nie zachodzi na inne bloki - sprawdzić w inspektorze)

    R.4.4 wysokość: blok sekcji - 300px, blok artykułu - 280px szerokość: blok boczny - 30%, blok sekcji - 70% (width lub grid-template-columns lub flex ), selektor table - 90%, odnośnik - 65%

    ok. 2 min
    Polecenie z arkusza

    Dla bloku bocznego: szerokość 30%.

    Blok główny staje się siatką o dwóch kolumnach: 30% na blok boczny i 70% na sekcję - obie szerokości z arkusza w jednej linii grid-template-columns (klucz R.4.4 przyjmuje je w siatce). Dzieci siatki same trafiają do kolejnych kolumn, a artykuł i stopka poza main zostają pod spodem - bez float i clear (R.3.1).

    W wersji podstawowej to samo robi display: flex z szerokościami w regułach bloków. Siatka trzyma cały podział w jednym miejscu.

    Typowe błędy

    width: 30% na bloku bocznym wewnątrz siatki liczyłby się od szerokości kolumny, nie bloku głównego.

    styl.css CSS zmienione: 5 linii
    :root {
        --frame-bg: SkyBlue;
        --article-bg: AliceBlue;
        --link-bg: DodgerBlue;
        --link-hover: RoyalBlue;
        --radius: 20px;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer,
    article {
        margin: 10px;
        padding: 8px;
        border-radius: var(--radius);
    }
    
    header,
    footer {
        background-color: var(--frame-bg);
    }
    
    main {
        display: grid;
        grid-template-columns: 30% 70%;
    }
    Podgląd strony po tym kroku (index.php)
  30. Krok 30: Blok sekcji: wysokość i paski przewijania

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Dla bloku sekcji: szerokość 70%, wysokość 300 px, paski przewijania jedynie w przypadku przepełnienia bloku.

    Szerokość 70% to druga kolumna siatki. overflow: auto - pasek tylko przy przepełnieniu (R.4.5).

    styl.css CSS zmienione: 5 linii
    :root {
        --frame-bg: SkyBlue;
        --article-bg: AliceBlue;
        --link-bg: DodgerBlue;
        --link-hover: RoyalBlue;
        --radius: 20px;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer,
    article {
        margin: 10px;
        padding: 8px;
        border-radius: var(--radius);
    }
    
    header,
    footer {
        background-color: var(--frame-bg);
    }
    
    main {
        display: grid;
        grid-template-columns: 30% 70%;
    }
    
    section {
        height: 300px;
        overflow: auto;
    }
    Podgląd strony po tym kroku (index.php)
  31. Krok 31: Blok artykułu

    Kryteria:

    R.4.7 zaokrąglenie rogów (border-radius) : blok nagłówkowy, stopki, artykułu - 20px

    ok. 1 min
    Polecenie z arkusza

    Dla bloku artykułu: kolor tła AliceBlue, wysokość 280 px, marginesy wewnętrzne 8 px, zewnętrzne 10 px, zaokrąglenie rogów 20 px.

    Marginesy i zaokrąglenie artykuł ma już ze wspólnej reguły; tu tylko wysokość i własne tło (R.4.7 - zaokrąglenie trzech bloków).

    styl.css CSS zmienione: 5 linii
    :root {
        --frame-bg: SkyBlue;
        --article-bg: AliceBlue;
        --link-bg: DodgerBlue;
        --link-hover: RoyalBlue;
        --radius: 20px;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer,
    article {
        margin: 10px;
        padding: 8px;
        border-radius: var(--radius);
    }
    
    header,
    footer {
        background-color: var(--frame-bg);
    }
    
    main {
        display: grid;
        grid-template-columns: 30% 70%;
    }
    
    section {
        height: 300px;
        overflow: auto;
    }
    
    article {
        height: 280px;
        background-color: var(--article-bg);
    }
    Podgląd strony po tym kroku (index.php)
  32. Krok 32: Obrazy obok siebie od lewej

    Kryteria:

    R.4.3 dla selektora img marginesy zewnętrzne (margin) - 4px

    ok. 2 min
    Polecenie z arkusza

    Dla selektora obrazu: marginesy zewnętrzne 4 px, umieszczone obok siebie od lewej strony.

    Tu zostaje float: left. Obrazy już stoją obok siebie, ale text-align: center z selektora * ustawia je na środku artykułu. Arkusz każe formatować obraz wyłącznie selektorem img (uwaga pod listą stylów), więc nie można przestawić wyrównania w artykule ani zrobić z niego siatki - a jedyną właściwością samego obrazu, która dosuwa go do lewej krawędzi, jest float. Artykuł ma stałą wysokość, więc pływające obrazy nie wystają poza niego (R.4.3).

    styl.css CSS zmienione: 5 linii
    :root {
        --frame-bg: SkyBlue;
        --article-bg: AliceBlue;
        --link-bg: DodgerBlue;
        --link-hover: RoyalBlue;
        --radius: 20px;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer,
    article {
        margin: 10px;
        padding: 8px;
        border-radius: var(--radius);
    }
    
    header,
    footer {
        background-color: var(--frame-bg);
    }
    
    main {
        display: grid;
        grid-template-columns: 30% 70%;
    }
    
    section {
        height: 300px;
        overflow: auto;
    }
    
    article {
        height: 280px;
        background-color: var(--article-bg);
    }
    
    img {
        float: left;
        margin: 4px;
    }
    Podgląd strony po tym kroku (index.php)
  33. Krok 33: Tabela

    Kryteria:

    R.4.8 obramowanie: tabela - 1px dotted black

    ok. 1 min
    Polecenie z arkusza

    Dla selektora tabeli: szerokość 90%, obramowanie kropkowane o szerokości 1 px i kolorze czarnym.

    border: 1px dotted black (R.4.8). Dodatkowo margin: 0 auto - wolne 10% szerokości dzieli się po równo na boki i tabela stoi na środku sekcji, jak na ilustracji 2 (text-align nie przesuwa tabeli, tylko tekst).

    styl.css CSS zmienione: 6 linii
    :root {
        --frame-bg: SkyBlue;
        --article-bg: AliceBlue;
        --link-bg: DodgerBlue;
        --link-hover: RoyalBlue;
        --radius: 20px;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer,
    article {
        margin: 10px;
        padding: 8px;
        border-radius: var(--radius);
    }
    
    header,
    footer {
        background-color: var(--frame-bg);
    }
    
    main {
        display: grid;
        grid-template-columns: 30% 70%;
    }
    
    section {
        height: 300px;
        overflow: auto;
    }
    
    article {
        height: 280px;
        background-color: var(--article-bg);
    }
    
    img {
        float: left;
        margin: 4px;
    }
    
    table {
        width: 90%;
        margin: 0 auto;
        border: 1px dotted black;
    }
    Podgląd strony po tym kroku (index.php)
  34. Krok 34: Odnośniki jako niebieskie paski

    Kryteria: , , ,

    R.4.2 kolor tła: blok nagłówkowy, stopka - SkyBlue, blok artykułu - AliceBlue, selektor a - DodgerBlue, kolor czcionki: odnośnik - biały

    R.4.4 wysokość: blok sekcji - 300px, blok artykułu - 280px szerokość: blok boczny - 30%, blok sekcji - 70% (width lub grid-template-columns lub flex ), selektor table - 90%, odnośnik - 65%

    R.4.6 marginesy wewnętrzne (padding ): blok nagłówkowy, stopki, artykułu - 8px marginesy zewnętrzne (margin ): blok nagłówkowy, stopki, artykułu, odnośnik - 10px

    R.4.9 styl odnośnika: blokowy sposób wyświetlania (display: block) , rozmiar czcionki - 130%, bez podkreślenia (text-decoration: none)

    ok. 2 min
    Polecenie z arkusza

    Dla selektora odnośnika: blokowy sposób wyświetlania, kolor tła DodgerBlue, biały kolor czcionki, szerokość 65%, rozmiar czcionki 130%, marginesy zewnętrzne 10 px, bez podkreślenia.

    display: block (tabela 2 arkusza) - dopiero blok przyjmuje szerokość 65% i stoi w osobnym wierszu. Właściwości w kolejności: układ, wymiary i odstępy, tekst, kolory (R.4.2, R.4.4, R.4.6, R.4.9). Odnośniki wypisze szablon PHP.

    styl.css CSS zmienione: 10 linii
    :root {
        --frame-bg: SkyBlue;
        --article-bg: AliceBlue;
        --link-bg: DodgerBlue;
        --link-hover: RoyalBlue;
        --radius: 20px;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer,
    article {
        margin: 10px;
        padding: 8px;
        border-radius: var(--radius);
    }
    
    header,
    footer {
        background-color: var(--frame-bg);
    }
    
    main {
        display: grid;
        grid-template-columns: 30% 70%;
    }
    
    section {
        height: 300px;
        overflow: auto;
    }
    
    article {
        height: 280px;
        background-color: var(--article-bg);
    }
    
    img {
        float: left;
        margin: 4px;
    }
    
    table {
        width: 90%;
        margin: 0 auto;
        border: 1px dotted black;
    }
    
    a {
        display: block;
        width: 65%;
        margin: 10px;
        font-size: 130%;
        text-decoration: none;
        background-color: var(--link-bg);
        color: white;
    }
    Podgląd strony po tym kroku (index.php)
  35. Krok 35: Odnośnik pod kursorem

    Kryteria:

    R.4.10 gdy kursor znajdzie się na odnośniku, jego kolor tła zmienia się na RoyalBlue

    ok. 1 min
    Polecenie z arkusza

    Gdy kursor znajdzie się na odnośniku, jego kolor tła zmienia się na RoyalBlue.

    :hover (R.4.10).

    styl.css CSS zmienione: 4 linie
    :root {
        --frame-bg: SkyBlue;
        --article-bg: AliceBlue;
        --link-bg: DodgerBlue;
        --link-hover: RoyalBlue;
        --radius: 20px;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer,
    article {
        margin: 10px;
        padding: 8px;
        border-radius: var(--radius);
    }
    
    header,
    footer {
        background-color: var(--frame-bg);
    }
    
    main {
        display: grid;
        grid-template-columns: 30% 70%;
    }
    
    section {
        height: 300px;
        overflow: auto;
    }
    
    article {
        height: 280px;
        background-color: var(--article-bg);
    }
    
    img {
        float: left;
        margin: 4px;
    }
    
    table {
        width: 90%;
        margin: 0 auto;
        border: 1px dotted black;
    }
    
    a {
        display: block;
        width: 65%;
        margin: 10px;
        font-size: 130%;
        text-decoration: none;
        background-color: var(--link-bg);
        color: white;
    }
    
    a:hover {
        background-color: var(--link-hover);
    }
    Podgląd strony po tym kroku (index.php)
  36. Etap 4: Skrypty PHP

    Kroki 36-47 · po tym etapie masz 41 z 41 kryteriów (pkt)

    Krok 36: Połączenie z bazą przez PDO i funkcja e()

    Kryteria: ,

    R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy wodospady oraz ostatnią operacją na bazie jest jej zamknięcie

    R.5.2 Zastosowana przynajmniej jedna zmienna oraz nazewnictwo jest w języku polskim lub angielskim i odzwierciedla cel zastosowania

    ok. 3 min
    Polecenie z arkusza

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

    Kod PHP stoi na samym początku pliku: najpierw wszystkie operacje na bazie, potem szablon, który tylko wypisuje gotowe dane. Klucz R.5 przyjmuje PDO tak samo jak MySQLi (R.5.1, R.5.2).

    • e() - każdy tekst z bazy przejdzie przez htmlspecialchars z ENT_QUOTES, więc nick z < czy cudzysłowem nie rozbije listy ani nie wykona się jako skrypt (XSS); typ ?string przyjmuje też null z pustych kolumn,
    • new PDO(...) - połączenie i wybór bazy wodospady: pierwszy argument to DSN, czyli adres bazy dla PDO w jednym napisie (mysql:host=localhost;dbname=wodospady;charset=utf8mb4, z kodowaniem połączenia utf8mb4), dalej użytkownik root i puste hasło,
    • ERRMODE_EXCEPTION (błąd SQL = wyjątek) i FETCH_ASSOC (wiersze z nazwami kolumn),
    • try … catch - przy awarii bazy ogólny komunikat i kod 500 zamiast treści błędu z nazwą serwera.

    Od tego kroku podgląd to prawdziwy wynik strony z serwera.

    Typowe błędy

    Klucz sprawdza połączenie i wybór bazy wodospady w kodzie źródłowym strony (R.5.1), więc DSN z nazwą bazy stoi w index.php, a nie w dołączanym pliku.

    index.php PHP zmienione: 16 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  37. Krok 37: Skrypt 1: zapytanie 1

    Kryteria:

    R.5.3 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1 lub Skrypt 3 zawiera instrukcję wysyłającą do bazy danych zapytanie 5

    ok. 1 min
    Polecenie z arkusza

    Skrypt 1 wysyła do bazy danych zapytanie 1.

    Zapytanie bez danych z zewnątrz - query, a fetchAll() odbiera siedem kontynentów do tablicy $continents (R.5.3).

    index.php PHP zmienione: 2 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $continents = $db->query('SELECT idKontynent, nazwa FROM kontynenty')->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  38. Krok 38: Skrypt 1: odnośniki kontynentów z parametrem id

    Kryteria: ,

    R.3.3 Przynajmniej jeden odnośnik z nazwą kontynentu prowadzi do strony index.php (w skrypcie lub HTML )

    R.5.4 Skrypt 1 dla wszystkich zwróconych rekordów tworzy odnośnik do pliku index.php , w którym identyfikator jest przesyłany metodą GET, a nazwa jest treścią odnośnika. Po kliknięciu w odnośnik zmienia się zawartość tabeli na odpowiadającą wybranemu kontynentowi

    ok. 3 min
    Polecenie z arkusza

    Dla każdego zwróconego zapytaniem rekordu wyświetla odnośnik, którego treścią jest nazwa kontynentu. Odnośnik prowadzi do tej samej strony i przesyła identyfikator kontynentu jako parametr metody GET. Na ilustracji 4 jest przedstawiony adres URL po wybraniu odnośnika Europa.

    W bloku bocznym pętla w składni szablonu (foreach (...): … endforeach;): odnośnik zostaje zwykłym HTML-em, a wartości wstawia <?= ... ?>. Parametr GET doklejony po znaku zapytania - index.php?id=6 jak na ilustracji 4 (tabela 4 arkusza). Atrybuty są w HTML-u, więc cudzysłowy nie wymagają ukośników jak w napisie dla echo (R.3.3, R.5.4).

    index.php PHP zmienione: 3 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $continents = $db->query('SELECT idKontynent, nazwa FROM kontynenty')->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
    <?php foreach ($continents as $continent): ?>
                <a href="index.php?id=<?= e($continent['idKontynent']) ?>"><?= e($continent['nazwa']) ?></a>
    <?php endforeach; ?>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  39. Krok 39: Skrypt 2: identyfikator kontynentu z adresu

    ok. 3 min
    Polecenie z arkusza

    Skrypt 2 pobiera wartość przesłaną metodą GET, a w przypadku braku - wartością 6. Wartość ta jest identyfikatorem kontynentu.

    Jedno wyrażenie robi trzy rzeczy:

    • $_GET['id'] ?? 6 - wartość z adresu, a gdy parametru nie ma - 6 (Europa), bez ostrzeżenia „Undefined array key”,
    • filter_var(…, FILTER_VALIDATE_INT) przepuszcza tylko liczbę całkowitą, min_range 1 odrzuca zero i liczby ujemne,
    • opcja default podaje 6 także wtedy, gdy wartość jest zła (?id=abc, ?id=6 OR 1=1).

    W $continentId jest więc zawsze poprawna liczba. Tablica $_GET stoi w kodzie wprost - klucz R.5.5 sprawdza, że identyfikator jest pobierany metodą GET.

    index.php PHP zmienione: 4 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $continents = $db->query('SELECT idKontynent, nazwa FROM kontynenty')->fetchAll();
    
        $continentId = filter_var($_GET['id'] ?? 6, FILTER_VALIDATE_INT, [
            'options' => ['default' => 6, 'min_range' => 1],
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
    <?php foreach ($continents as $continent): ?>
                <a href="index.php?id=<?= e($continent['idKontynent']) ?>"><?= e($continent['nazwa']) ?></a>
    <?php endforeach; ?>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  40. Krok 40: Skrypt 2: zapytanie 3 dla wybranego kontynentu

    Kryteria:

    R.5.5 Skrypt 2 zawiera instrukcję wysyłającą do bazy zapytanie 3, zmodyfikowane tak, że identyfikator kontynentu jest pobrany metodą GET, gdy $_GET istnieje

    ok. 2 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 3, zmodyfikowane tak, że sprawdzany jest identyfikator kontynentu, patrz poprzedni punkt.

    Zapytanie 3 jako zapytanie przygotowane: w miejscu 6 parametr :continentId, wartość osobno w execute (R.5.5). Nawet gdyby walidacja coś przepuściła, wartość nie stanie się częścią polecenia SQL.

    index.php PHP zmienione: 7 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $continents = $db->query('SELECT idKontynent, nazwa FROM kontynenty')->fetchAll();
    
        $continentId = filter_var($_GET['id'] ?? 6, FILTER_VALIDATE_INT, [
            'options' => ['default' => 6, 'min_range' => 1],
        ]);
        $waterfallsQuery = $db->prepare(
            'SELECT idObiekt, panstwo, nazwa, wartoscCechy
             FROM obiekty
             WHERE idRodzaj = 10 AND idKontynent = :continentId'
        );
        $waterfallsQuery->execute(['continentId' => $continentId]);
        $waterfalls = $waterfallsQuery->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
    <?php foreach ($continents as $continent): ?>
                <a href="index.php?id=<?= e($continent['idKontynent']) ?>"><?= e($continent['nazwa']) ?></a>
    <?php endforeach; ?>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  41. Krok 41: Skrypt 2: wiersze tabeli

    Kryteria:

    R.5.6 Skrypt 2: wszystkie rekordy zwrócone zapytaniem 3 wyświetla jako kolejne wiersze tabeli, pola są wstawione w odpowiednie komórki

    ok. 3 min
    Polecenie z arkusza

    Dla każdego zwróconego zapytaniem rekordu wstawia wiersz tabeli, wypełniając kolejne komórki zwróconymi polami (np. jeśli wybrano odnośnik Europa - tabela zawiera dane wodospadów z Europy).

    W tbody jeden <tr> na wodospad, cztery komórki po nazwach kolumn (R.5.6). Podgląd: osiem wodospadów Europy, jak na ilustracji 2. Po kliknięciu „Ameryka Północna” (?id=3) zostaje jeden wiersz (Yosemite), „Antarktyda” (?id=5) zostawia sam nagłówek - tam w bazie nie ma wodospadów.

    index.php PHP zmienione: 8 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $continents = $db->query('SELECT idKontynent, nazwa FROM kontynenty')->fetchAll();
    
        $continentId = filter_var($_GET['id'] ?? 6, FILTER_VALIDATE_INT, [
            'options' => ['default' => 6, 'min_range' => 1],
        ]);
        $waterfallsQuery = $db->prepare(
            'SELECT idObiekt, panstwo, nazwa, wartoscCechy
             FROM obiekty
             WHERE idRodzaj = 10 AND idKontynent = :continentId'
        );
        $waterfallsQuery->execute(['continentId' => $continentId]);
        $waterfalls = $waterfallsQuery->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
    <?php foreach ($continents as $continent): ?>
                <a href="index.php?id=<?= e($continent['idKontynent']) ?>"><?= e($continent['nazwa']) ?></a>
    <?php endforeach; ?>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($waterfalls as $waterfall): ?>
                        <tr>
                            <td><?= e($waterfall['idObiekt']) ?></td>
                            <td><?= e($waterfall['panstwo']) ?></td>
                            <td><?= e($waterfall['nazwa']) ?></td>
                            <td><?= e($waterfall['wartoscCechy']) ?></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  42. Krok 42: Skrypt 3: zapytanie 5

    Kryteria:

    R.5.3 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1 lub Skrypt 3 zawiera instrukcję wysyłającą do bazy danych zapytanie 5

    ok. 1 min
    Polecenie z arkusza

    Skrypt 3 wysyła do bazy danych zapytanie 5.

    PDO::FETCH_KEY_PAIR zamienia wynik dwóch kolumn w tablicę „klucz → wartość”: $tourists[1] to „Ewa”. Dla listy rozwijanej to dokładnie para value → treść opcji (R.5.3).

    index.php PHP zmienione: 2 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $continents = $db->query('SELECT idKontynent, nazwa FROM kontynenty')->fetchAll();
    
        $continentId = filter_var($_GET['id'] ?? 6, FILTER_VALIDATE_INT, [
            'options' => ['default' => 6, 'min_range' => 1],
        ]);
        $waterfallsQuery = $db->prepare(
            'SELECT idObiekt, panstwo, nazwa, wartoscCechy
             FROM obiekty
             WHERE idRodzaj = 10 AND idKontynent = :continentId'
        );
        $waterfallsQuery->execute(['continentId' => $continentId]);
        $waterfalls = $waterfallsQuery->fetchAll();
    
        $tourists = $db->query('SELECT idTurysta, nick FROM turysci')->fetchAll(PDO::FETCH_KEY_PAIR);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
    <?php foreach ($continents as $continent): ?>
                <a href="index.php?id=<?= e($continent['idKontynent']) ?>"><?= e($continent['nazwa']) ?></a>
    <?php endforeach; ?>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($waterfalls as $waterfall): ?>
                        <tr>
                            <td><?= e($waterfall['idObiekt']) ?></td>
                            <td><?= e($waterfall['panstwo']) ?></td>
                            <td><?= e($waterfall['nazwa']) ?></td>
                            <td><?= e($waterfall['wartoscCechy']) ?></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  43. Krok 43: Skrypt 3: opcje z identyfikatorem w value

    Kryteria: ,

    R.3.6 Pole edycyjne jest typu number. Lista rozwijana zawiera co najmniej jedno imię (<option> w skrypcie lub HTML )

    R.5.7 Skrypt 3: wszystkie zwrócone nicki są wyświetlane w liście rozwijanej jako opcje, a identyfikatory przypisane są do atrybutu value

    ok. 2 min
    Polecenie z arkusza

    Kolejne rekordy są opcjami listy rozwijanej tak, że atrybut wartości to pole idTurysta, a treść to nick.

    foreach ($tourists as $touristId => $nick) - klucz tablicy trafia do value, wartość jest treścią opcji (R.3.6, R.5.7). Formularz wyśle identyfikator, bo tabela osiagniecia przechowuje idTurysta, nie nick. 13 turystów, pierwsza Ewa.

    index.php PHP zmienione: 3 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $continents = $db->query('SELECT idKontynent, nazwa FROM kontynenty')->fetchAll();
    
        $continentId = filter_var($_GET['id'] ?? 6, FILTER_VALIDATE_INT, [
            'options' => ['default' => 6, 'min_range' => 1],
        ]);
        $waterfallsQuery = $db->prepare(
            'SELECT idObiekt, panstwo, nazwa, wartoscCechy
             FROM obiekty
             WHERE idRodzaj = 10 AND idKontynent = :continentId'
        );
        $waterfallsQuery->execute(['continentId' => $continentId]);
        $waterfalls = $waterfallsQuery->fetchAll();
    
        $tourists = $db->query('SELECT idTurysta, nick FROM turysci')->fetchAll(PDO::FETCH_KEY_PAIR);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
    <?php foreach ($continents as $continent): ?>
                <a href="index.php?id=<?= e($continent['idKontynent']) ?>"><?= e($continent['nazwa']) ?></a>
    <?php endforeach; ?>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($waterfalls as $waterfall): ?>
                        <tr>
                            <td><?= e($waterfall['idObiekt']) ?></td>
                            <td><?= e($waterfall['panstwo']) ?></td>
                            <td><?= e($waterfall['nazwa']) ?></td>
                            <td><?= e($waterfall['wartoscCechy']) ?></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
    <?php foreach ($tourists as $touristId => $nick): ?>
                        <option value="<?= e($touristId) ?>"><?= e($nick) ?></option>
    <?php endforeach; ?>
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  44. Krok 44: Skrypt 4: tylko po wysłaniu formularza

    Kryteria:

    R.5.8 Skrypt 4 działa tylko wtedy, gdy dane zostały wysłane z formularza (wystarczy sprawdzenie jednej kontrolki )

    ok. 3 min
    Polecenie z arkusza

    Skrypt 4 działa tylko w przypadku, gdy z formularza zostały przesłane dane.

    Pola formularza czyta filter_input(INPUT_POST, …) z filtrem liczby całkowitej: wynik to liczba, false dla złej wartości (puste pole, „abc”) albo null, gdy formularza w ogóle nie wysłano. Warunek if ($waterfallId && $touristId) jest prawdziwy tylko wtedy, gdy przyszły dwie poprawne liczby - przy zwykłym wejściu na stronę i przy pustym polu zapis się nie wykona (R.5.8).

    filter_input czyta dane prosto z żądania, nie z tablicy $_POST, której wartości można w kodzie przypadkiem nadpisać. Przy identyfikatorze kontynentu zostało $_GET, bo tam klucz szuka go w kodzie.

    index.php PHP zmienione: 5 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $continents = $db->query('SELECT idKontynent, nazwa FROM kontynenty')->fetchAll();
    
        $continentId = filter_var($_GET['id'] ?? 6, FILTER_VALIDATE_INT, [
            'options' => ['default' => 6, 'min_range' => 1],
        ]);
        $waterfallsQuery = $db->prepare(
            'SELECT idObiekt, panstwo, nazwa, wartoscCechy
             FROM obiekty
             WHERE idRodzaj = 10 AND idKontynent = :continentId'
        );
        $waterfallsQuery->execute(['continentId' => $continentId]);
        $waterfalls = $waterfallsQuery->fetchAll();
    
        $tourists = $db->query('SELECT idTurysta, nick FROM turysci')->fetchAll(PDO::FETCH_KEY_PAIR);
    
        $waterfallId = filter_input(INPUT_POST, 'waterfall', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        $touristId = filter_input(INPUT_POST, 'tourist', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($waterfallId && $touristId) {
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
    <?php foreach ($continents as $continent): ?>
                <a href="index.php?id=<?= e($continent['idKontynent']) ?>"><?= e($continent['nazwa']) ?></a>
    <?php endforeach; ?>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($waterfalls as $waterfall): ?>
                        <tr>
                            <td><?= e($waterfall['idObiekt']) ?></td>
                            <td><?= e($waterfall['panstwo']) ?></td>
                            <td><?= e($waterfall['nazwa']) ?></td>
                            <td><?= e($waterfall['wartoscCechy']) ?></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
    <?php foreach ($tourists as $touristId => $nick): ?>
                        <option value="<?= e($touristId) ?>"><?= e($nick) ?></option>
    <?php endforeach; ?>
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  45. Krok 45: Skrypt 4: zapis osiągnięcia z formularza

    Kryteria:

    R.5.9 Skrypt 4 wysyła do bazy danych zapytanie 4, dane pobrane z formularza są wstawiane do bazy jako identyfikatory: obiektu i turysty (sprawdzić w phpMyAdmin dla dwóch wstawień, należy podawać tylko id z tabeli )

    ok. 3 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 4, zmodyfikowane w ten sposób, że do bazy wstawione są wartości pobrane z formularza (zakłada się że użytkownik wpisuje tylko identyfikatory wyświetlone w tabeli i nie trzeba sprawdzać poprawności identyfikatora).

    Zapytanie 4 przygotowane, z parametrami nazwanymi w miejscu 34 i 2 (R.5.9). Podgląd: stan po wysłaniu wodospadu 42 z turystką Ewą - strona wygląda tak samo, nowy rekord widać w phpMyAdmin w tabeli osiagniecia.

    index.php PHP zmienione: 2 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $continents = $db->query('SELECT idKontynent, nazwa FROM kontynenty')->fetchAll();
    
        $continentId = filter_var($_GET['id'] ?? 6, FILTER_VALIDATE_INT, [
            'options' => ['default' => 6, 'min_range' => 1],
        ]);
        $waterfallsQuery = $db->prepare(
            'SELECT idObiekt, panstwo, nazwa, wartoscCechy
             FROM obiekty
             WHERE idRodzaj = 10 AND idKontynent = :continentId'
        );
        $waterfallsQuery->execute(['continentId' => $continentId]);
        $waterfalls = $waterfallsQuery->fetchAll();
    
        $tourists = $db->query('SELECT idTurysta, nick FROM turysci')->fetchAll(PDO::FETCH_KEY_PAIR);
    
        $waterfallId = filter_input(INPUT_POST, 'waterfall', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        $touristId = filter_input(INPUT_POST, 'tourist', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($waterfallId && $touristId) {
            $insert = $db->prepare('INSERT INTO osiagniecia (idObiekt, idTurysta) VALUES (:waterfallId, :touristId)');
            $insert->execute(['waterfallId' => $waterfallId, 'touristId' => $touristId]);
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
    <?php foreach ($continents as $continent): ?>
                <a href="index.php?id=<?= e($continent['idKontynent']) ?>"><?= e($continent['nazwa']) ?></a>
    <?php endforeach; ?>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($waterfalls as $waterfall): ?>
                        <tr>
                            <td><?= e($waterfall['idObiekt']) ?></td>
                            <td><?= e($waterfall['panstwo']) ?></td>
                            <td><?= e($waterfall['nazwa']) ?></td>
                            <td><?= e($waterfall['wartoscCechy']) ?></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
    <?php foreach ($tourists as $touristId => $nick): ?>
                        <option value="<?= e($touristId) ?>"><?= e($nick) ?></option>
    <?php endforeach; ?>
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  46. Krok 46: Skrypt 4: komunikat, błąd klucza obcego i przekierowanie

    ok. 5 min
    Polecenie z arkusza

    Skrypt 4 wysyła do bazy danych zapytanie 4, zmodyfikowane w ten sposób, że do bazy wstawione są wartości pobrane z formularza.

    Arkusz zwalnia ze sprawdzania identyfikatora, ale tabela osiagniecia ma klucz obcy: wodospad 999, którego nie ma w bazie, baza odrzuca. Bez obsługi strona kończyłaby się błędem „Integrity constraint violation”. Dlatego:

    • try/catch wokół zapisu łapie wyjątek; kod 23000 to naruszenie więzów (klucz obcy) - wtedy komunikat dla użytkownika, a każdy inny błąd idzie dalej (throw) do ogólnego catch,
    • po udanym zapisie wzorzec PRG (Post/Redirect/Get): komunikat trafia do sesji, header('Location: …', true, 303) każe przeglądarce otworzyć stronę od nowa GET-em - z tym samym ?id=, więc wybrany kontynent zostaje. Bez przekierowania F5 wysłałoby formularz drugi raz i dopisało drugie, identyczne osiągnięcie,
    • przy następnym wejściu $message odbiera komunikat z sesji i od razu go usuwa - pokaże się raz, w paragrafie pod formularzem, przez e().

    Przekierowanie stoi po bloku try, przed HTML-em - nagłówek wysyła się przed treścią. Podgląd: próba zapisu wodospadu 999 z komunikatem.

    Typowe błędy

    header() po wypisaniu czegokolwiek (nawet pustej linii przed <?php) kończy się ostrzeżeniem „headers already sent” i przekierowanie nie działa.

    index.php PHP zmienione: 22 linie
    <?php
    session_start();
    $redirect = false;
    
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $message = $_SESSION['message'] ?? '';
        unset($_SESSION['message']);
    
        $continents = $db->query('SELECT idKontynent, nazwa FROM kontynenty')->fetchAll();
    
        $continentId = filter_var($_GET['id'] ?? 6, FILTER_VALIDATE_INT, [
            'options' => ['default' => 6, 'min_range' => 1],
        ]);
        $waterfallsQuery = $db->prepare(
            'SELECT idObiekt, panstwo, nazwa, wartoscCechy
             FROM obiekty
             WHERE idRodzaj = 10 AND idKontynent = :continentId'
        );
        $waterfallsQuery->execute(['continentId' => $continentId]);
        $waterfalls = $waterfallsQuery->fetchAll();
    
        $tourists = $db->query('SELECT idTurysta, nick FROM turysci')->fetchAll(PDO::FETCH_KEY_PAIR);
    
        $waterfallId = filter_input(INPUT_POST, 'waterfall', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        $touristId = filter_input(INPUT_POST, 'tourist', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($waterfallId && $touristId) {
            $insert = $db->prepare('INSERT INTO osiagniecia (idObiekt, idTurysta) VALUES (:waterfallId, :touristId)');
            try {
                $insert->execute(['waterfallId' => $waterfallId, 'touristId' => $touristId]);
                $_SESSION['message'] = 'Osiągnięcie zapisane.';
                $redirect = true;
            } catch (PDOException $error) {
                if ($error->getCode() !== '23000') {
                    throw $error;
                }
                $message = "Nie ma wodospadu o identyfikatorze $waterfallId.";
            }
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    if ($redirect) {
        header("Location: index.php?id=$continentId", true, 303);
        exit;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
    <?php foreach ($continents as $continent): ?>
                <a href="index.php?id=<?= e($continent['idKontynent']) ?>"><?= e($continent['nazwa']) ?></a>
    <?php endforeach; ?>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($waterfalls as $waterfall): ?>
                        <tr>
                            <td><?= e($waterfall['idObiekt']) ?></td>
                            <td><?= e($waterfall['panstwo']) ?></td>
                            <td><?= e($waterfall['nazwa']) ?></td>
                            <td><?= e($waterfall['wartoscCechy']) ?></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
    <?php foreach ($tourists as $touristId => $nick): ?>
                        <option value="<?= e($touristId) ?>"><?= e($nick) ?></option>
    <?php endforeach; ?>
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
                <p><?= e($message) ?></p>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  47. Krok 47: Zamknięcie połączenia z bazą

    Kryteria:

    R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy wodospady oraz ostatnią operacją na bazie jest jej zamknięcie

    ok. 2 min
    Polecenie z arkusza

    Na końcu należy zamknąć połączenie z bazą.

    W PDO połączenie zamyka się, gdy znika ostatnie odwołanie do obiektu. Zapytania przygotowane ($waterfallsQuery, $insert) też je trzymają, więc najpierw one, potem $db = null (R.5.1). Przypisanie null do zmiennej, która nie powstała (gdy formularza nie wysłano, $insert nie istnieje), jest bezpieczne.

    Podgląd: strona po udanym zapisie i przekierowaniu - komunikat „Osiągnięcie zapisane.” pod formularzem.

    Typowe błędy

    $db->close() nie istnieje w PDO - to błąd „Call to undefined method”.

    index.php PHP zmienione: 4 linie
    <?php
    session_start();
    $redirect = false;
    
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=wodospady;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $message = $_SESSION['message'] ?? '';
        unset($_SESSION['message']);
    
        $continents = $db->query('SELECT idKontynent, nazwa FROM kontynenty')->fetchAll();
    
        $continentId = filter_var($_GET['id'] ?? 6, FILTER_VALIDATE_INT, [
            'options' => ['default' => 6, 'min_range' => 1],
        ]);
        $waterfallsQuery = $db->prepare(
            'SELECT idObiekt, panstwo, nazwa, wartoscCechy
             FROM obiekty
             WHERE idRodzaj = 10 AND idKontynent = :continentId'
        );
        $waterfallsQuery->execute(['continentId' => $continentId]);
        $waterfalls = $waterfallsQuery->fetchAll();
    
        $tourists = $db->query('SELECT idTurysta, nick FROM turysci')->fetchAll(PDO::FETCH_KEY_PAIR);
    
        $waterfallId = filter_input(INPUT_POST, 'waterfall', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        $touristId = filter_input(INPUT_POST, 'tourist', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($waterfallId && $touristId) {
            $insert = $db->prepare('INSERT INTO osiagniecia (idObiekt, idTurysta) VALUES (:waterfallId, :touristId)');
            try {
                $insert->execute(['waterfallId' => $waterfallId, 'touristId' => $touristId]);
                $_SESSION['message'] = 'Osiągnięcie zapisane.';
                $redirect = true;
            } catch (PDOException $error) {
                if ($error->getCode() !== '23000') {
                    throw $error;
                }
                $message = "Nie ma wodospadu o identyfikatorze $waterfallId.";
            }
        }
    
        $waterfallsQuery = null;
        $insert = null;
        $db = null;
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    if ($redirect) {
        header("Location: index.php?id=$continentId", true, 303);
        exit;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wodospady</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2>Łowcy wodospadów</h2>
        </header>
        <main>
            <aside>
    <?php foreach ($continents as $continent): ?>
                <a href="index.php?id=<?= e($continent['idKontynent']) ?>"><?= e($continent['nazwa']) ?></a>
    <?php endforeach; ?>
            </aside>
            <section>
                <table>
                    <thead>
                        <tr>
                            <th>Identyfikator</th>
                            <th>Państwo</th>
                            <th>Nazwa wodospadu</th>
                            <th>Wysokość</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($waterfalls as $waterfall): ?>
                        <tr>
                            <td><?= e($waterfall['idObiekt']) ?></td>
                            <td><?= e($waterfall['panstwo']) ?></td>
                            <td><?= e($waterfall['nazwa']) ?></td>
                            <td><?= e($waterfall['wartoscCechy']) ?></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
                <h4>Wpisz osiągnięcie do bazy</h4>
                <form method="post">
                    <label for="waterfall">identyfikator wodospadu</label>
                    <input type="number" id="waterfall" name="waterfall" min="1" required>
                    <label for="tourist">turysta</label>
                    <select id="tourist" name="tourist">
    <?php foreach ($tourists as $touristId => $nick): ?>
                        <option value="<?= e($touristId) ?>"><?= e($nick) ?></option>
    <?php endforeach; ?>
                    </select>
                    <button type="submit">Wpisz</button>
                </form>
                <p><?= e($message) ?></p>
            </section>
        </main>
        <article>
            <h3>Wodospady w Polsce</h3>
            <img src="kamienczyk.jpg" alt="wodospad">
            <img src="siklawica.jpg" alt="wodospad">
            <img src="siklawa.jpg" alt="wodospad">
            <img src="wilczki.jpg" alt="wodospad">
        </article>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  48. Etap 5: Porównanie z wersją podstawową

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

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

    ok. 2 min
    Polecenie z arkusza

    Czas przeznaczony na wykonanie zadania wynosi 150 minut.

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

    wersja podstawowawersja nowoczesna
    blok głównydisplay: flexgrid-template-columns: 30% 70%
    tabelawiersze w <table>thead i tbody
    bazafunkcje mysqli_* z tabeli 1PDO, wyjątki, połączenie w pliku strony
    ?id= z adresuwklejony do SQL (?id=6 OR 1=1 pokazywał całą bazę)filter_var z wartością domyślną + parametr
    formularzisset i sklejony INSERTfilter_input, zapytanie przygotowane, try/catch
    po zapisieta sama strona (F5 dopisuje drugi raz)PRG z komunikatem w sesji

    float został przy obrazach - arkusz pozwala formatować je tylko selektorem img.

    Na egzaminie: wersja podstawowa używa tylko funkcji z tabeli 1 arkusza i jest krótsza. Wersję nowoczesną pisz, jeśli znasz PDO i filtry na pamięć - arkusz nie podaje ich składni. Poza egzaminem dane logowania do bazy trzyma się zwykle w osobnym pliku (np. config.php), żeby hasło nie trafiało do repozytorium razem z kodem - tu stoją w index.php, bo klucz sprawdza połączenie w kodzie strony. Gotowa aplikacja dostałaby jeszcze token CSRF w formularzu (ukryte pole z losową wartością z sesji, sprawdzane przy zapisie - blokuje wysłanie formularza z cudzej strony); klucz tego nie wymaga.

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

  49. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

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

    W folderze z numerem zdającego: import.jpg, index.php, kamienczyk.jpg, kw1.png … kw5.png, kwerendy.txt, przeglądarka.txt, siklawa.jpg, siklawica.jpg, styl.css, wilczki.jpg. Płytę sprawdź po nagraniu i opisz numerem zdającego.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

Podgląd

To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.

Autor rozwiązania: . Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.