Przejdź do głównej treści
INF.03-03-24.06-SGCzerwiec 2024 · 46 kroków · ok. 88 min · 39 kryteriów z klucza

Rozwiązanie krok po kroku: Poziomy rzek Dolnego Śląska (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza rzeki z czterema zapytaniami, mapa z przezroczystym tłem i strona poziomRzek.php, na której formularz z trzema polami opcji filtruje tabelę stanów wody, a pod informacjami stoją średnie stany z każdego dnia - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • siatka CSS na całej stronie: kolumny 70% 30% dla bloków lewego i prawego zapisane w grid-template-columns (klucz R.4.5 przyjmuje je wprost), a oba banery w jednym pasie siatki, każdy z szerokością 50% przyklejony do swojej krawędzi - zamiast float: left i clear: both,
  • kolory w zmiennych CSS, tabela z <thead> i <tbody>, podświetlenie tylko wierszy z danymi,
  • PHP na PDO nad HTML-em: oba zapytania wykonane i połączenie zamknięte, zanim zacznie się strona; szablon tylko wypisuje dane,
  • filtr z białej listy: do zapytania doklejany jest wyłącznie gotowy warunek ze stałej FILTERS, nigdy tekst z formularza, a data idzie jako parametr zapytania przygotowanego,
  • formularz pamięta wybór (checked) i od pierwszego wejścia pokazuje wszystkie wodowskazy, średnie z dwoma miejscami po przecinku, e() przy każdej wartości z bazy, komunikat przy awarii bazy.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafiki pochodzą z prawdziwego archiwum pliki3, zapytania zostały wykonane na tej bazie w MariaDB z XAMPP-a, a podgląd strony od etapu PHP to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2 - po wybraniu „Wszystkie” i kliknięciu „Pokaż” (obraz 2), a w krokach z warunkami po wybraniu pozostałych pól (obrazy 3 i 4).

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

  1. Etap 1: Baza danych

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

    Krok 1: Baza danych rzeki

    ok. 2 min
    Polecenie z arkusza

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

    Archiwum pliki3.zip rozpakuj hasłem St@ny%RzeK (małpa, procent, wielkość liter ma znaczenie) do folderu z numerem zdającego. W phpMyAdmin Nowa → rzeki, porównywanie utf8_unicode_ci, albo zapytaniem:

    CREATE DATABASE rzeki CHARACTER SET utf8 COLLATE utf8_unicode_ci;
    
    Typowe błędy

    Nazwa bazy musi być dokładnie rzeki - skrypty łączą się z nią po nazwie (R.5.1).

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

    ok. 2 min
    Polecenie z arkusza

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

    Import → baza.sql. Dwie tabele z obrazu 1: wodowskazy (17 punktów pomiarowych ze stanem ostrzegawczym i alarmowym) i pomiary (34 pomiary z 5 i 6 maja 2022). Klucz obcy wodowskazy_id wskazuje wodowskaz - jeden wodowskaz, wiele pomiarów, czyli relacja 1..n.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonano import tabel do bazy danych rzeki czynność udokumentowano plikiem o nazwie import w formacie PNG

    ok. 2 min
    Polecenie z arkusza

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

    Cały ekran z paskiem zadań, zapis jako import.png. Uwaga na formaty: import w PNG, zrzuty zapytań w JPEG (kw1.jpg - kw4.jpg).

    Typowe błędy

    Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany (uwaga w kluczu przy R.1).

  4. Krok 4: Zapytanie 1: wodowskazy i stan alarmowy

    Kryteria: ,

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

    R.1.3 Wykonano zapytanie 1 wybierające jedynie pola nazwa, rzeka, stanAlarmowy z tabeli wodowskazy . W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, rzeka, stanAlarmowy FROM wodowskazy; oraz wynik działania udokumentowano zrzutem, na którym jest widocznych dokładnie 17 wierszy, jedynie pola nazwa, rzeka, stanAlarmowy

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pola nazwa, rzeka, stanAlarmowy z tabeli wodowskazy

    Trzy pola z jednej tabeli, bez warunku - 17 wierszy, jak w kluczu. Każda klauzula w osobnej linii: przy dłuższych zapytaniach od razu widać, co jest listą pól, a co źródłem danych. Zapytania trafiają do kwerendy.txt, zrzut do kw1.jpg.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT nazwa, rzeka, stanAlarmowy
    FROM wodowskazy;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: stany wody z 5 maja 2022

    Kryteria:

    R.1.4 Wykonano zapytanie 2 wybierające jedynie pola nazwa, rzeka, stanOstrzegawczy, stanAlarmowy z tabeli wodowskazy oraz odpowiadające im pole stanWody z tabeli pomiary dla daty 2022-05-05. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, rzeka, stanOstrzegawczy, stanAlarmowy, stanWody FROM wodowskazy JOIN pomiary ON wodowskazy.id = wodowskazy_id WHERE dataPomiaru='2022-05-05'; (możliwe również INNER JOIN bez JOIN i porównanie kluczy po WHERE) oraz wynik działania udokumentowano zrzutem, na którym jest widocznych dokładnie 17 wierszy, jedynie pola nazwa, rzeka, stanOstrzegawczy, stanAlarmowy, stanWody

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pola nazwa, rzeka, stanOstrzegawczy, stanAlarmowy z tabeli wodowskazy oraz odpowiadające im pole stanWody z tabeli pomiary dla daty pomiaru 2022-05-05. Należy posłużyć się relacją

    • aliasy tabel w (wodowskazy) i p (pomiary) - przy każdej kolumnie widać, z której tabeli pochodzi; obie tabele mają kolumnę id, więc bez przedrostka byłaby niejednoznaczna,
    • JOIN ... ON p.wodowskazy_id = w.id - relacja z obrazu 1: klucz obcy pomiaru wskazuje klucz główny wodowskazu,
    • WHERE p.dataPomiaru = '2022-05-05' - z dwóch pomiarów każdego wodowskazu zostaje ten z 5 maja.

    Wynik: 17 wierszy (bez warunku na datę byłoby 34). To jest tabela z obrazu 2. Zrzut: kw2.jpg.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT nazwa, rzeka, stanAlarmowy
    FROM wodowskazy;
    
    -- Zapytanie 2
    SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
    FROM wodowskazy AS w
    JOIN pomiary AS p ON p.wodowskazy_id = w.id
    WHERE p.dataPomiaru = '2022-05-05';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: wodowskazy ponad stanem ostrzegawczym

    Kryteria:

    R.1.5 Wykonano zapytanie 3 wybierające jedynie pola nazwa, rzeka, stanOstrzegawczy, stanAlarmowy z tabeli wodowskazy oraz odpowiadające im pole stanWody z tabeli pomiary dla daty 2022-05-05 oraz takie, dla których stanWody jest wyższy niż stanOstrzegawczy. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, rzeka, stanOstrzegawczy, stanAlarmowy, stanWody FROM wodowskazy JOIN pomiary ON wodowskazy.id = wodowskazy_id WHERE dataPomiaru='2022-05-05'AND stanWody > stanOstrzegawczy; (możliwe również INNER JOIN lub bez JOIN i porównanie kluczy po WHERE) oraz wynik działania udokumentowano zrzutem, na którym jest widocznych dokładnie 5 wierszy, jedynie pola nazwa, rzeka, stanOstrzegawczy, stanAlarmowy, stanWody, dla nazw: Jarnołtów, Łazany, Jelenia Góra, Nowogrodziec, Bardo

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pola nazwa, rzeka, stanOstrzegawczy, stanAlarmowy z tabeli wodowskazy oraz odpowiadające im pole stanWody z tabeli pomiary dla daty pomiaru 2022-05-05 oraz takie, dla których stanWody jest wyższy niż stanOstrzegawczy. Należy posłużyć się relacją

    Zapytanie 2 z drugim warunkiem: AND p.stanWody > w.stanOstrzegawczy porównuje dwie kolumny tego samego wiersza - stan z pomiaru i próg z wodowskazu, dlatego oba z przedrostkami. „Wyższy niż” to ostra nierówność. Wynik: 5 wierszy - Jarnołtów, Łazany, Jelenia Góra, Nowogrodziec, Bardo, jak w kluczu i na obrazie 3. Zrzut: kw3.jpg.

    kwerendy.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT nazwa, rzeka, stanAlarmowy
    FROM wodowskazy;
    
    -- Zapytanie 2
    SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
    FROM wodowskazy AS w
    JOIN pomiary AS p ON p.wodowskazy_id = w.id
    WHERE p.dataPomiaru = '2022-05-05';
    
    -- Zapytanie 3
    SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
    FROM wodowskazy AS w
    JOIN pomiary AS p ON p.wodowskazy_id = w.id
    WHERE p.dataPomiaru = '2022-05-05'
      AND p.stanWody > w.stanOstrzegawczy;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: średni stan wody w każdym dniu

    Kryteria:

    R.1.6 Wykonano zapytanie 4 wybierające jedynie datę pomiaru oraz liczące średnie stany wody z tabeli pomiary grupując je według daty pomiaru. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT dataPomiaru, AVG(stanWody) FROM pomiary GROUP BY dataPomiaru; oraz wynik działania udokumentowano zrzutem, na którym są widoczne dane: 2022-05-05 167.6471 oraz 2022-05-06 179.5882; z dowolną dokładnością

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie datę pomiaru oraz liczące średnie stany wody z tabeli pomiary grupując je według daty pomiaru

    GROUP BY dataPomiaru tworzy jedną grupę na dzień, AVG(stanWody) liczy średnią w grupie, a alias sredni_stan nadaje kolumnie wyniku nazwę zamiast AVG(stanWody) - po tej nazwie PHP odczyta wartość. Wynik: 2022-05-05 167.6471 i 2022-05-06 179.5882, jak w kluczu. Zrzut: kw4.jpg.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT nazwa, rzeka, stanAlarmowy
    FROM wodowskazy;
    
    -- Zapytanie 2
    SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
    FROM wodowskazy AS w
    JOIN pomiary AS p ON p.wodowskazy_id = w.id
    WHERE p.dataPomiaru = '2022-05-05';
    
    -- Zapytanie 3
    SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
    FROM wodowskazy AS w
    JOIN pomiary AS p ON p.wodowskazy_id = w.id
    WHERE p.dataPomiaru = '2022-05-05'
      AND p.stanWody > w.stanOstrzegawczy;
    
    -- Zapytanie 4
    SELECT dataPomiaru, AVG(stanWody) AS sredni_stan
    FROM pomiary
    GROUP BY dataPomiaru;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

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

    Krok 8: obraz1.png: przezroczyste tło

    ok. 3 min
    Polecenie z arkusza

    Dla grafiki obraz1.png, wypakowanej z archiwum, należy nadać przezroczyste tło oraz wypełnić obszar województwa dolnośląskiego kolorem niebieskim o dowolnym odcieniu. Województwo dolnośląskie znajduje się w lewym dolnym rogu mapy – patrz obraz 2.

    Mapa ma 100 × 100 pikseli i białe tło bez przezroczystości. W GIMP-ie Warstwa → Przezroczystość → Dodaj kanał alfa, potem Zaznaczanie spójnego obszaru (różdżka) w biel poza mapą (z Shift w każdym rogu, jeśli obszary się nie łączą) i Delete. Różdżka zatrzymuje się na granicy kraju, więc wnętrza województw zostają białe. Podgląd: mapa na ciemnym tle po tym kroku.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: obraz1.png: województwo dolnośląskie na niebiesko

    Kryteria:

    R.2.1 Grafika obraz1.png ma tło przezroczyste, województwo dolnośląskie jest wypełnione kolorem niebieskim (dowolny odcień)

    ok. 2 min
    Polecenie z arkusza

    Dla grafiki obraz1.png, wypakowanej z archiwum, należy nadać przezroczyste tło oraz wypełnić obszar województwa dolnośląskiego kolorem niebieskim o dowolnym odcieniu. Województwo dolnośląskie znajduje się w lewym dolnym rogu mapy – patrz obraz 2.

    Niebieski kolor pierwszego planu, Wypełnienie kubełkiem i kliknięcie we wnętrze województwa w lewym dolnym rogu (nad nim leży lubuskie - łatwo się pomylić). Eksport: Plik → Wyeksportuj jako obraz1.png - PNG zachowuje przezroczystość.

    Typowe błędy

    Eksport do JPEG gubi przezroczystość - tło znowu staje się białe i R.2.1 przepada.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 9
  10. Krok 10: Plik poziomRzek.php: HTML5, język, kodowanie

    Kryteria:

    R.2.2 Witrynę internetową zapisano w pliku poziomRzek.php oraz zapisano jawnie standard kodowania polskich znaków i deklarację HTML5 <!DOCTYPE HTML> i zadeklarowano język witryny polski np.<HTML lang="pl">. Strona zawiera sekcje head i body

    ok. 2 min
    Polecenie z arkusza

    Składa się ze strony o nazwie poziomRzek.php zapisanej w języku HTML5. Ustawiony język zawartości strony na polski. Jawnie zastosowany właściwy standard kodowania polskich znaków

    Plik poziomRzek.php (wielka litera „R” w środku - na serwerze wielkość liter w nazwach plików ma znaczenie): <!DOCTYPE html>, <html lang="pl">, <meta charset="UTF-8"> jako pierwszy element <head> i sekcja <body> (R.2.2).

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

    Kryteria:

    R.2.3 Nadano tytuł stronie: "Poziomy rzek"

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Poziomy rzek”

    <title> w <head> (R.2.3), obok viewport, żeby telefon nie pomniejszał strony.

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

    Kryteria:

    R.3.5 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.css , formatowanie CSS pochodzi jedynie z tego arkusza Uwaga: kryterium to nie jest spełnione również, gdy zdający źle przypisał co najmniej jeden selektor do bloku (np. kropka zamiast #)

    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 styl.css, nie style.css. R.3.5 przepada też wtedy, gdy któryś selektor jest źle zapisany, np. kropka zamiast # przy identyfikatorze.

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

    Kryteria:

    R.2.4 Układ strony zdefiniowano dzieląc ją na: dwa bloki banera, blok menu, lewy, prawy oraz stopkę. Zastosowano semantyczne znaczniki sekcji HTML5 zgodnie z ich znaczeniem, w tym do części nagłówkowej - header, stopki - footer, dopuszcza się zagnieżdżenie bloków div w znacznikach semantycznych

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: na górze dwa bloki banera, poniżej menu, poniżej bloki: lewy i prawy, na dole blok stopki. Podział zrealizowany wyłącznie za pomocą semantycznych znaczników sekcji języka HTML5 tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z obrazem 2

    Dwa banery to dwa <header> (klucz R.2.4 wymienia header i footer wprost), rozróżnione identyfikatorami map i title. Menu z formularzem filtra to <nav>, blok lewy z tabelą - <main> (główna treść), blok prawy z informacjami - <aside>, stopka - <footer>. Kolejność w kodzie odpowiada kolejności czytania na obrazie 2.

    poziomRzek.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>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
        </header>
        <header id="title">
        </header>
        <nav>
        </nav>
        <main>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  14. Krok 14: Pierwszy baner: mapa

    ok. 1 min
    Polecenie z arkusza

    Zawartość pierwszego banera: obraz1.png z tekstem alternatywnym o treści: „Mapa Polski”

    Mapa przygotowana w etapie grafiki. Dzięki przezroczystemu tłu na granatowym banerze widać tylko kształt Polski.

    poziomRzek.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>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
        </header>
        <nav>
        </nav>
        <main>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  15. Krok 15: Drugi baner: nagłówek pierwszego stopnia

    ok. 1 min
    Polecenie z arkusza

    Zawartość drugiego banera: nagłówek pierwszego stopnia o treści „Rzeki w województwie dolnośląskim”

    <h1> w drugim banerze.

    poziomRzek.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>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
        </nav>
        <main>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  16. Krok 16: Menu: formularz wysyłany metodą POST

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Zawartość menu: Formularz wysyłający dane do skryptu na tej samej stronie, metodą bezpieczną, zawierający:

    method="post" - „metoda bezpieczna” w rozumieniu arkusza: wybór jedzie w treści żądania, nie w adresie strony. W specyfikacji HTTP „bezpieczne” są co prawda metody, które niczego nie zmieniają na serwerze (GET i HEAD), ale arkusz używa tego słowa w innym sensie, a klucz R.3.3 wymaga POST. Bez atrybutu action formularz wysyła dane do bieżącego pliku (klucz to dopuszcza).

    poziomRzek.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>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
            </form>
        </nav>
        <main>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  17. Krok 17: Trzy pola opcji z etykietami w jednej grupie

    ok. 3 min
    Polecenie z arkusza

    Obok siebie 3 pola opcji (radio) zgrupowane, tak aby było możliwe jednoczesne wybranie tylko jednego pola. Etykiety przy polach opcji: „Wszystkie”, „Ponad stan ostrzegawczy”, „Ponad stan alarmowy” (pola opcji będą formatowane jedną klasą CSS)

    Grupę tworzy wspólne name="option" - przeglądarka pozwala zaznaczyć jedno pole, a do skryptu trafia option z wartością wybranego (all, warning, alarm). Wartości są krótkimi kluczami po angielsku, nie etykietami: skrypt sprawdzi je na liście dozwolonych. Każda <label for> wskazuje id swojego pola - kliknięcie napisu zaznacza pole, a czytnik ekranu odczytuje etykietę przy polu. Wspólna klasa option-label dla stylów.

    poziomRzek.php PHP zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
            </form>
        </nav>
        <main>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  18. Krok 18: Przycisk „Pokaż”

    Kryteria: ,

    R.2.9 W bloku menu umieszczono formularz z trzema polami opcji i przyciskiem. Pola opcji są podpisane etykietami: "Wszystkie", "Ponad stan ostrzegawczy", "Ponad stan alarmowy", zastosowano znaczniki <label> (dopuszczalne zastosowanie <span> lub <div>)

    R.3.4 W jednym momencie można zaznaczyć tylko jedno pole opcji oraz przycisk wysyła dane do skryptu (typ submit lub button bez typu)

    ok. 1 min
    Polecenie z arkusza

    Przycisk o treści „Pokaż”, wysyłający dane do skryptu

    <button type="submit"> - typ zapisany jawnie, choć to wartość domyślna przycisku w formularzu: kto czyta kod, nie musi tego pamiętać. Formularz ma komplet: trzy pola z etykietami i przycisk (R.2.9), jednocześnie zaznaczone może być tylko jedno pole (R.3.4).

    poziomRzek.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>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  19. Krok 19: Blok lewy: nagłówek

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku lewego: Nagłówek trzeciego stopnia o treści: „Stany na dzień 2022-05-05”

    <h3> - stopień narzuca arkusz.

    poziomRzek.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>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  20. Krok 20: Tabela: wiersz komórek nagłówkowych

    Kryteria:

    R.2.7 Pierwszy wiersz tabeli zawiera komórki nagłówkowe o treści: „Wodomierz”, „Rzeka”, „Ostrzegawczy”, „Alarmowy”, „Aktualny”, zastosowano znaczniki <th>

    ok. 2 min
    Polecenie z arkusza

    Tabela zawierająca dane: Pierwszy wiersz wypełniony komórkami nagłówkowymi o treści: „Wodomierz”, „Rzeka”, „Ostrzegawczy”, „Alarmowy”, „Aktualny”

    Wiersz nagłówkowy w <thead>, a miejsce na dane w <tbody> - przeglądarka i czytnik ekranu wiedzą, które wiersze opisują kolumny, a CSS może podświetlać tylko wiersze z danymi. scope="col" mówi, że komórka <th> jest nagłówkiem kolumny (R.2.7). Pięć kolumn odpowiada pięciu polom zapytania 2.

    poziomRzek.php PHP zmienione: 13 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  21. Krok 21: Blok prawy: nagłówek „Informacje”

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku prawego: Nagłówek trzeciego stopnia o treści: „Informacje”

    <h3> na początku bloku prawego.

    poziomRzek.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>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  22. Krok 22: Lista punktowana z trzema informacjami

    Kryteria:

    R.2.8 W bloku prawym umieszczono jedną listę punktowaną o trzech elementach, zastosowano znaczniki <ul>, <li>

    ok. 1 min
    Polecenie z arkusza

    Lista punktowana (nieuporządkowana) zawierająca elementy: „Brak ostrzeżeń o burzach z gradem”, „Smog w mieście Wrocław”, „Silny wiatr w Karkonoszach”

    <ul> z trzema <li> (R.2.8).

    poziomRzek.php PHP zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  23. Krok 23: Nagłówek „Średnie stany wód”

    ok. 1 min
    Polecenie z arkusza

    Nagłówek trzeciego stopnia o treści: „Średnie stany wód”

    Drugi <h3> w bloku prawym. Pod nim staną akapity ze średnimi (Skrypt 2).

    poziomRzek.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>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  24. Krok 24: Odnośnik „Dowiedz się więcej”

    Kryteria:

    R.3.2 Odnośnik o treści "Dowiedz się więcej" inicjuje otwarcie strony "https://komunikaty.pl"

    ok. 1 min
    Polecenie z arkusza

    Odnośnik do adresu „https://komunikaty.pl” o treści „Dowiedz się więcej”

    Adres zewnętrzny z https:// - bez tego przeglądarka szukałaby pliku komunikaty.pl w folderze strony (R.3.2). Między nagłówkiem a odnośnikiem staną średnie.

    poziomRzek.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>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
            <a href="https://komunikaty.pl">Dowiedz się więcej</a>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  25. Krok 25: Obraz rzeki

    Kryteria:

    R.2.10 W pierwszym banerze umieszczono obraz mapy z tekstem alternatywnym: „Mapa Polski” oraz w bloku prawym obraz rzeki i tekście alternatywnym „rzeka”

    ok. 1 min
    Polecenie z arkusza

    Obraz o nazwie obraz2.jpg i tekście alternatywnym „rzeka”

    Zdjęcie pod odnośnikiem. Oba obrazy mają teksty alternatywne z arkusza (R.2.10).

    poziomRzek.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>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
            <a href="https://komunikaty.pl">Dowiedz się więcej</a>
            <img src="obraz2.jpg" alt="rzeka">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  26. Krok 26: Stopka z autorem

    Kryteria:

    R.2.5 W drugim banerze zapisano nagłówek h1: "Rzeki w województwie dolnośląskim", w bloku lewym h3: "Stany na dzień 2022-05-05", w bloku prawym h3: "Informacje" i "Średnie stany wód" oraz w stopce paragraf z numerem zdającego

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: akapit (paragraf) o treści: „Stronę wykonał: ”, dalej wstawiony numer zdającego

    Na egzaminie po „Stronę wykonał:” wpisujesz swój numer zdającego (PESEL); tu stoi autor rozwiązania, Bartosz Bryniarski. Strona ma już wszystkie nagłówki i akapit stopki z klucza (R.2.5).

    poziomRzek.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>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
            <a href="https://komunikaty.pl">Dowiedz się więcej</a>
            <img src="obraz2.jpg" alt="rzeka">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  27. Etap 3: Styl CSS

    Kroki 27-36 · po tym etapie masz 30 z 39 kryteriów (pkt)

    Krok 27: Plik styl.css: zmienne i krój Georgia

    Kryteria:

    R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (dla *): krój czcionki Georgia

    ok. 2 min
    Polecenie z arkusza

    Domyślne formatowanie wszystkich selektorów: krój czcionki Georgia

    Granat z arkusza występuje w czterech blokach i w ramce tabeli, Azure w dwóch, biały tekst w trzech - zapisane raz jako zmienne CSS na :root, a w regułach odczytywane przez var(--nazwa). Selektor * z Georgią i zapasowym krojem szeryfowym, gdyby Georgii nie było w systemie (R.4.1).

    styl.css CSS nowy plik
    :root {
        --dark-bg: Navy;
        --light-bg: Azure;
        --light-text: white;
    }
    
    * {
        font-family: Georgia, serif;
    }
    Podgląd strony po tym kroku (poziomRzek.php)
  28. Krok 28: Układ bloków na siatce

    Kryteria:

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu (prawidłowo zastosowano właściwości CSS decydujące o układzie strony, np. float i clear albo display flex albo grid)

    ok. 3 min
    Polecenie z arkusza

    Podział strony na bloki: na górze dwa bloki banera, poniżej menu, poniżej bloki: lewy i prawy, na dole blok stopki. Podział zrealizowany wyłącznie za pomocą semantycznych znaczników sekcji języka HTML5 tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z obrazem 2. Dodatkowo blok lewy ma szerokość 70% a blok prawy szerokość 30%

    <body> staje się siatką o dwóch kolumnach 70% 30% - to szerokości bloku lewego i prawego z arkusza (klucz R.4.5 przyjmuje grid-template-columns). grid-template-areas rysuje obraz 2 słowami: pas banerów, menu, blok lewy obok prawego, stopka; każdy blok trafia w swoje pole przez grid-area (R.3.1).

    Banery mają po 50%, a kolumny to 70 i 30 - dlatego oba header dostają jedno wspólne pole banner na całą szerokość, a połowę strony każdy ustawi sobie w następnym kroku. Menu i stopka zajmują pełną szerokość bez clear, bo w siatce nic nie „pływa”. Na razie oba banery leżą w tym samym polu jeden na drugim - mapę zasłania nagłówek.

    styl.css CSS zmienione: 30 linii
    :root {
        --dark-bg: Navy;
        --light-bg: Azure;
        --light-text: white;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 30%;
        grid-template-areas:
            "banner banner"
            "menu   menu"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: banner;
    }
    
    nav {
        grid-area: menu;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (poziomRzek.php)
  29. Krok 29: Oba banery: kolory, wyśrodkowanie, wymiary

    Kryteria:

    R.4.6 Ustawiono wyrównanie tekstu do środka dla obu banerów

    ok. 3 min
    Polecenie z arkusza

    Wspólne dla obu bloków banera: kolor tła Navy, biały kolor czcionki, wyrównanie tekstu do środka, wysokość 100 px, szerokość 50%

    Wspólne cechy w jednej regule dla znacznika header. width: 50% liczy się od pola siatki - a pole banner ma całą szerokość strony, więc każdy baner dostaje dokładnie połowę. justify-self przesuwa baner w poziomie wewnątrz pola: mapę (#map) do lewej krawędzi, tytuł (#title) do prawej. Dwa elementy w jednym polu siatki to w pełni poprawna technika - tu leżą obok siebie, bo każdy zajmuje inną połowę.

    Mapa ma 100 × 100 pikseli, więc wypełnia wysokość banera, a text-align: center stawia ją na środku lewej połowy (R.4.6).

    Typowe błędy

    width: 50% przy banerach umieszczonych w osobnych kolumnach 70% i 30% dałoby 35% i 15% strony - procent elementu siatki liczy się od jego pola, nie od całej strony.

    styl.css CSS zmienione: 13 linii
    :root {
        --dark-bg: Navy;
        --light-bg: Azure;
        --light-text: white;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 30%;
        grid-template-areas:
            "banner banner"
            "menu   menu"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: banner;
        width: 50%;
        height: 100px;
        background-color: var(--dark-bg);
        color: var(--light-text);
        text-align: center;
    }
    
    #map {
        justify-self: start;
    }
    
    #title {
        justify-self: end;
    }
    
    nav {
        grid-area: menu;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (poziomRzek.php)
  30. Krok 30: Menu i teksty przy polach opcji

    ok. 2 min
    Polecenie z arkusza

    Dla bloku menu: kolor tła Navy, marginesy wewnętrzne 20 px. Dla klasy formatującej teksty przy polach opcji: kolor czcionki Silver, marginesy zewnętrzne 50 px

    Granatowy pasek z padding: 20px i srebrne etykiety z marginesem 50 px. Etykiety są elementami liniowymi, więc margines działa tylko w poziomie i rozsuwa pola opcji, jak na obrazie 2. Formularz zostaje w zwykłym przepływie tekstu, celowo bez display: flex: w kontenerze flex etykiety stałyby się blokami i margines 50 px rozepchnąłby menu także w pionie.

    styl.css CSS zmienione: 7 linii
    :root {
        --dark-bg: Navy;
        --light-bg: Azure;
        --light-text: white;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 30%;
        grid-template-areas:
            "banner banner"
            "menu   menu"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: banner;
        width: 50%;
        height: 100px;
        background-color: var(--dark-bg);
        color: var(--light-text);
        text-align: center;
    }
    
    #map {
        justify-self: start;
    }
    
    #title {
        justify-self: end;
    }
    
    nav {
        grid-area: menu;
        background-color: var(--dark-bg);
        padding: 20px;
    }
    
    .option-label {
        color: Silver;
        margin: 50px;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (poziomRzek.php)
  31. Krok 31: Tekst pola opcji pod kursorem i przy klawiaturze

    Kryteria:

    R.4.9 W momencie, gdy kursor myszy znajdzie się na tekście przy polu opcji, tekst zmienia kolor na biały i zostaje podkreślony

    ok. 2 min
    Polecenie z arkusza

    W momencie, gdy kursor myszy znajdzie się na tekście pola opcji ma on kolor biały i podkreślenie

    .option-label:hover spełnia polecenie (R.4.9). Drugi selektor, input:focus-visible + .option-label, daje to samo wyróżnienie osobie, która przechodzi po polach klawiszem Tab i strzałkami - + wskazuje etykietę stojącą tuż za polem z fokusem. Arkusz tego nie wymaga, a nie zmienia wyglądu przy myszy.

    styl.css CSS zmienione: 6 linii
    :root {
        --dark-bg: Navy;
        --light-bg: Azure;
        --light-text: white;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 30%;
        grid-template-areas:
            "banner banner"
            "menu   menu"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: banner;
        width: 50%;
        height: 100px;
        background-color: var(--dark-bg);
        color: var(--light-text);
        text-align: center;
    }
    
    #map {
        justify-self: start;
    }
    
    #title {
        justify-self: end;
    }
    
    nav {
        grid-area: menu;
        background-color: var(--dark-bg);
        padding: 20px;
    }
    
    .option-label {
        color: Silver;
        margin: 50px;
    }
    
    .option-label:hover,
    input:focus-visible + .option-label {
        color: white;
        text-decoration: underline;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (poziomRzek.php)
  32. Krok 32: Blok lewy i prawy: tło i wysokość

    Kryteria:

    R.4.4 Ustawiono wysokość obu banerów na 100px, bloków lewego i prawego na 550px

    ok. 1 min
    Polecenie z arkusza

    Wspólne dla bloku lewego i prawego: kolor tła Azure, wysokość 550 px

    Wspólna reguła dla main, aside. Szerokości 70% i 30% mają już z kolumn siatki - width tutaj liczyłby się od kolumny i zwęził blok. Wysokości: banery 100 px, bloki 550 px (R.4.4).

    styl.css CSS zmienione: 6 linii
    :root {
        --dark-bg: Navy;
        --light-bg: Azure;
        --light-text: white;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 30%;
        grid-template-areas:
            "banner banner"
            "menu   menu"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: banner;
        width: 50%;
        height: 100px;
        background-color: var(--dark-bg);
        color: var(--light-text);
        text-align: center;
    }
    
    #map {
        justify-self: start;
    }
    
    #title {
        justify-self: end;
    }
    
    nav {
        grid-area: menu;
        background-color: var(--dark-bg);
        padding: 20px;
    }
    
    .option-label {
        color: Silver;
        margin: 50px;
    }
    
    .option-label:hover,
    input:focus-visible + .option-label {
        color: white;
        text-decoration: underline;
    }
    
    main,
    aside {
        background-color: var(--light-bg);
        height: 550px;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (poziomRzek.php)
  33. Krok 33: Stopka: kolory

    Kryteria: ,

    R.4.2 Ustawiono kolor tła Navy dla obu banerów, menu i stopki, Azure dla bloków lewego i prawego

    R.4.3 Ustawiono biały kolor czcionki dla obu bloków banera i stopki, kolor Silver dla klasy formatującej teksty przy polach opcji

    ok. 1 min
    Polecenie z arkusza

    Dla stopki: kolor tła Navy, biały kolor czcionki

    Te same zmienne co banery. Granatowe tło mają oba banery, menu i stopka, Azure - bloki lewy i prawy (R.4.2); biały tekst banery i stopka, srebrny - etykiety pól opcji (R.4.3).

    styl.css CSS zmienione: 2 linie
    :root {
        --dark-bg: Navy;
        --light-bg: Azure;
        --light-text: white;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 30%;
        grid-template-areas:
            "banner banner"
            "menu   menu"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: banner;
        width: 50%;
        height: 100px;
        background-color: var(--dark-bg);
        color: var(--light-text);
        text-align: center;
    }
    
    #map {
        justify-self: start;
    }
    
    #title {
        justify-self: end;
    }
    
    nav {
        grid-area: menu;
        background-color: var(--dark-bg);
        padding: 20px;
    }
    
    .option-label {
        color: Silver;
        margin: 50px;
    }
    
    .option-label:hover,
    input:focus-visible + .option-label {
        color: white;
        text-decoration: underline;
    }
    
    main,
    aside {
        background-color: var(--light-bg);
        height: 550px;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
        background-color: var(--dark-bg);
        color: var(--light-text);
    }
    Podgląd strony po tym kroku (poziomRzek.php)
  34. Krok 34: Tabela: ramka, wyśrodkowanie, szerokość

    Kryteria: ,

    R.4.7 Ustawiono marginesy wewnętrzne (padding) dla menu 20px oraz marginesy zewnętrzne (margin) dla klasy formatującej teksty przy polach opcji 50px, dla tabeli - auto

    R.4.8 Ustawiono dla tabeli: obramowanie 1px solid Navy

    ok. 2 min
    Polecenie z arkusza

    Dla selektora tabeli: obramowanie linią ciągłą o szerokości 1 px i kolorze Navy, marginesy zewnętrzne automatycznie wyliczane przez przeglądarkę, szerokość 70%

    Selektor to sam znacznik table - tak każe uwaga arkusza, a klucz nie uzna klasy (R.4.5). Ramka 1px solid w kolorze ze zmiennej obejmuje całą tabelę, nie komórki (R.4.8). margin: auto dzieli wolne miejsce po równo, więc tabela szeroka na 70% bloku lewego staje na środku. Marginesy menu, etykiet i tabeli są komplet (R.4.7).

    styl.css CSS zmienione: 6 linii
    :root {
        --dark-bg: Navy;
        --light-bg: Azure;
        --light-text: white;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 30%;
        grid-template-areas:
            "banner banner"
            "menu   menu"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: banner;
        width: 50%;
        height: 100px;
        background-color: var(--dark-bg);
        color: var(--light-text);
        text-align: center;
    }
    
    #map {
        justify-self: start;
    }
    
    #title {
        justify-self: end;
    }
    
    nav {
        grid-area: menu;
        background-color: var(--dark-bg);
        padding: 20px;
    }
    
    .option-label {
        color: Silver;
        margin: 50px;
    }
    
    .option-label:hover,
    input:focus-visible + .option-label {
        color: white;
        text-decoration: underline;
    }
    
    main,
    aside {
        background-color: var(--light-bg);
        height: 550px;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
        background-color: var(--dark-bg);
        color: var(--light-text);
    }
    
    table {
        border: 1px solid var(--dark-bg);
        margin: auto;
        width: 70%;
    }
    Podgląd strony po tym kroku (poziomRzek.php)
  35. Krok 35: Wiersz tabeli pod kursorem

    Kryteria:

    R.4.10 W momencie, gdy kursor myszy znajdzie się na wierszu tabeli, jego kolor tła zmienia się na SkyBlue

    ok. 1 min
    Polecenie z arkusza

    W momencie, gdy kursor myszy znajdzie się na wierszu tabeli, jego kolor tła zmienia się na SkyBlue

    tbody tr:hover - podświetla się wiersz z danymi pod kursorem (na obrazie 2 Zgorzelec), a wiersz nagłówkowy w <thead> zostaje bez zmian (R.4.10). Wiersze pojawią się po dopisaniu skryptu.

    styl.css CSS zmienione: 4 linie
    :root {
        --dark-bg: Navy;
        --light-bg: Azure;
        --light-text: white;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 30%;
        grid-template-areas:
            "banner banner"
            "menu   menu"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: banner;
        width: 50%;
        height: 100px;
        background-color: var(--dark-bg);
        color: var(--light-text);
        text-align: center;
    }
    
    #map {
        justify-self: start;
    }
    
    #title {
        justify-self: end;
    }
    
    nav {
        grid-area: menu;
        background-color: var(--dark-bg);
        padding: 20px;
    }
    
    .option-label {
        color: Silver;
        margin: 50px;
    }
    
    .option-label:hover,
    input:focus-visible + .option-label {
        color: white;
        text-decoration: underline;
    }
    
    main,
    aside {
        background-color: var(--light-bg);
        height: 550px;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
        background-color: var(--dark-bg);
        color: var(--light-text);
    }
    
    table {
        border: 1px solid var(--dark-bg);
        margin: auto;
        width: 70%;
    }
    
    tbody tr:hover {
        background-color: SkyBlue;
    }
    Podgląd strony po tym kroku (poziomRzek.php)
  36. Krok 36: Obraz w prawym bloku: szerokość

    Kryteria:

    R.4.5 Ustawiono szerokość dla obu banerów na 50%, bloku lewego 70%, bloku prawego 30% (width lub grid-template-columns lub flex), selektora table 70%, obrazu w prawym bloku 90%

    ok. 1 min
    Polecenie z arkusza

    Dla obrazu w prawym bloku: szerokość 90%

    Selektor potomka aside img dotyczy tylko zdjęcia rzeki - mapa w banerze zostaje 100 × 100. Szerokości: banery 50%, bloki 70% i 30% (kolumny siatki), tabela 70%, obraz 90% (R.4.5).

    styl.css CSS zmienione: 4 linie
    :root {
        --dark-bg: Navy;
        --light-bg: Azure;
        --light-text: white;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 30%;
        grid-template-areas:
            "banner banner"
            "menu   menu"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: banner;
        width: 50%;
        height: 100px;
        background-color: var(--dark-bg);
        color: var(--light-text);
        text-align: center;
    }
    
    #map {
        justify-self: start;
    }
    
    #title {
        justify-self: end;
    }
    
    nav {
        grid-area: menu;
        background-color: var(--dark-bg);
        padding: 20px;
    }
    
    .option-label {
        color: Silver;
        margin: 50px;
    }
    
    .option-label:hover,
    input:focus-visible + .option-label {
        color: white;
        text-decoration: underline;
    }
    
    main,
    aside {
        background-color: var(--light-bg);
        height: 550px;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
        background-color: var(--dark-bg);
        color: var(--light-text);
    }
    
    table {
        border: 1px solid var(--dark-bg);
        margin: auto;
        width: 70%;
    }
    
    tbody tr:hover {
        background-color: SkyBlue;
    }
    
    aside img {
        width: 90%;
    }
    Podgląd strony po tym kroku (poziomRzek.php)
  37. Etap 4: Skrypty PHP

    Kroki 37-44 · po tym etapie masz 39 z 39 kryteriów (pkt)

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

    Kryteria:

    R.5.1 Skrypt zawiera instrukcje połączenia z serwerem bazy danych i wyboru bazy rzeki

    ok. 4 min
    Polecenie z arkusza

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

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

    • DSN mysql:host=localhost;dbname=rzeki;charset=utf8mb4 - serwer, baza rzeki i kodowanie połączenia, użytkownik root, puste hasło (R.5.1),
    • ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze z nazwami kolumn,
    • $pdo = null zamyka połączenie; oba skrypty trafią przed tę linię,
    • try / catch - przy awarii bazy szczegóły idą do dziennika serwera (error_log), a strona dostaje ogólny komunikat,
    • e() - htmlspecialchars(..., ENT_QUOTES) pod krótką nazwą, do każdej wartości z bazy wstawianej w HTML.

    Zmienne mają wartości początkowe, żeby szablon działał także po błędzie. Od tego kroku podgląd to wynik prawdziwego uruchomienia po wybraniu „Wszystkie” i kliknięciu „Pokaż”.

    poziomRzek.php PHP zmienione: 21 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $gauges = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=rzeki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać stanów wody. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
            <a href="https://komunikaty.pl">Dowiedz się więcej</a>
            <img src="obraz2.jpg" alt="rzeka">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  38. Krok 38: Skrypt formularza: zapytanie 2 z parametrem daty

    Kryteria: ,

    R.5.3 Skrypt skojarzony z formularzem zawiera instrukcje wysyłające do bazy danych zapytanie 2 lub skrypt 2: zapytanie 4

    R.5.8 Zastosowano znaczące nazewnictwo dla wszystkich zmiennych lub funkcji w języku polskim lub angielskim. Nazwy odzwierciedlają cel zastosowania

    ok. 3 min
    Polecenie z arkusza

    Skrypt skojarzony z formularzem, po wysłaniu danych z formularza, wyświetla dane w tabeli w zależności od wybranego pola opcji: Wszystkie dane wodomierzy są uzyskane za pomocą zapytania 2 (patrz obraz 2)

    Zapytanie 2 jako zapytanie przygotowane: zamiast daty w tekście stoi znacznik :date, a wartość podaje execute() ze stałej MEASUREMENT_DATE - dane nigdy nie są doklejane do treści SQL, więc nie mogą jej zmienić (R.5.3). fetchAll() zbiera 17 wodowskazów do tablicy $gauges.

    $statement = null zwalnia wynik: PDO zamyka połączenie dopiero wtedy, gdy nie trzyma go już żadne zapytanie, więc bez tej linii $pdo = null nie byłoby naprawdę ostatnią operacją. Nazwy po angielsku mówią, co przechowują - $gauges to wodowskazy (R.5.8).

    poziomRzek.php PHP zmienione: 13 linii
    <?php
    const MEASUREMENT_DATE = '2022-05-05';
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $gauges = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=rzeki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt formularza: zapytanie 2
        $statement = $pdo->prepare(
            'SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
             FROM wodowskazy AS w
             JOIN pomiary AS p ON p.wodowskazy_id = w.id
             WHERE p.dataPomiaru = :date'
        );
        $statement->execute(['date' => MEASUREMENT_DATE]);
        $gauges = $statement->fetchAll();
        $statement = null;
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać stanów wody. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
            <a href="https://komunikaty.pl">Dowiedz się więcej</a>
            <img src="obraz2.jpg" alt="rzeka">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  39. Krok 39: Skrypt formularza: wiersze tabeli w szablonie

    Kryteria: ,

    R.2.6 W bloku lewym umieszczono tabelę o 5 kolumnach, zastosowano znaczniki <table>, <tr>, <td> (tabelę wygenerowano skryptem lub w HTML)

    R.5.4 Skrypt formularza wyświetla w tabeli wyniki działania zapytania zgodnie z obrazem 2 arkusza egzaminacyjnego

    ok. 3 min
    Polecenie z arkusza

    Pozostałe wiersze wypełnione przez skrypt skojarzony z formularzem

    W <tbody> pętla foreach ($gauges as $gauge): w składni alternatywnej (endforeach) - HTML wiersza zostaje zwykłym HTML-em, a wartości wchodzą przez <?= e(...) ?>, po nazwach kolumn. Podgląd: 17 wierszy jak w bloku lewym obrazu 2 (R.5.4), tabela z table, tr, td (R.2.6).

    poziomRzek.php PHP zmienione: 9 linii
    <?php
    const MEASUREMENT_DATE = '2022-05-05';
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $gauges = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=rzeki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt formularza: zapytanie 2
        $statement = $pdo->prepare(
            'SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
             FROM wodowskazy AS w
             JOIN pomiary AS p ON p.wodowskazy_id = w.id
             WHERE p.dataPomiaru = :date'
        );
        $statement->execute(['date' => MEASUREMENT_DATE]);
        $gauges = $statement->fetchAll();
        $statement = null;
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać stanów wody. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($gauges as $gauge): ?>
                        <tr>
                            <td><?= e($gauge['nazwa']) ?></td>
                            <td><?= e($gauge['rzeka']) ?></td>
                            <td><?= e($gauge['stanOstrzegawczy']) ?></td>
                            <td><?= e($gauge['stanAlarmowy']) ?></td>
                            <td><?= e($gauge['stanWody']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
            <a href="https://komunikaty.pl">Dowiedz się więcej</a>
            <img src="obraz2.jpg" alt="rzeka">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  40. Krok 40: Wybór pola opcji: ponad stan ostrzegawczy

    Kryteria:

    R.5.5 Gdy w formularzu wybrano "Ponad stan ostrzegawczy" i zatwierdzono, są wyświetlone tylko te rekordy, dla których stan aktualny jest wyższy niż ostrzegawczy

    ok. 4 min
    Polecenie z arkusza

    Dane wodomierzy z przekroczonym stanem ostrzegawczym są uzyskane za pomocą zapytania 3 lub alternatywnie za pomocą zapytania 2 i odpowiednio przygotowanego warunku (patrz obraz 3)

    Arkusz pozwala dokleić warunek do zapytania 2. Porównania kolumn nie da się przekazać parametrem (parametr to zawsze wartość, nie fragment SQL), więc dopiski stoją w stałej FILTERS - białej liście: klucz to wartość pola opcji, wartość to gotowy kawałek zapytania.

    • filter_input(INPUT_POST, 'option') czyta wybór; zwraca null, gdy formularza nie wysłano,
    • is_string i array_key_exists - wszystko spoza listy (brak wyboru, x' OR 1=1, tablica zamiast napisu) zamienia się w all,
    • do SQL trafia wyłącznie tekst ze stałej, nigdy tekst od użytkownika.

    Dzięki domyślnemu all tabela jest pełna od pierwszego wejścia - arkusz każe wypełniać ją „po wysłaniu danych”, ale pustej tabeli nie wymaga, a egzaminator sprawdza ją po wysłaniu formularza. Podgląd po wybraniu „Ponad stan ostrzegawczy”: 5 wierszy, jak na obrazie 3 (R.5.5). Sprawdzone też x' OR 1=1 - 17 wierszy, zapytanie bez zmian.

    poziomRzek.php PHP zmienione: 10 linii
    <?php
    const MEASUREMENT_DATE = '2022-05-05';
    const FILTERS = [
        'all' => '',
        'warning' => ' AND p.stanWody > w.stanOstrzegawczy',
    ];
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $option = filter_input(INPUT_POST, 'option');
    if (!is_string($option) || !array_key_exists($option, FILTERS)) {
        $option = 'all';
    }
    
    $gauges = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=rzeki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt formularza: zapytanie 2
        $statement = $pdo->prepare(
            'SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
             FROM wodowskazy AS w
             JOIN pomiary AS p ON p.wodowskazy_id = w.id
             WHERE p.dataPomiaru = :date' . FILTERS[$option]
        );
        $statement->execute(['date' => MEASUREMENT_DATE]);
        $gauges = $statement->fetchAll();
        $statement = null;
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać stanów wody. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($gauges as $gauge): ?>
                        <tr>
                            <td><?= e($gauge['nazwa']) ?></td>
                            <td><?= e($gauge['rzeka']) ?></td>
                            <td><?= e($gauge['stanOstrzegawczy']) ?></td>
                            <td><?= e($gauge['stanAlarmowy']) ?></td>
                            <td><?= e($gauge['stanWody']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
            <a href="https://komunikaty.pl">Dowiedz się więcej</a>
            <img src="obraz2.jpg" alt="rzeka">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  41. Krok 41: Wybór pola opcji: ponad stan alarmowy

    Kryteria:

    R.5.6 Gdy w formularzu wybrano "Ponad stan alarmowy" i zatwierdzono, są wyświetlone tylko te rekordy, dla których stan aktualny jest wyższy niż alarmowy

    ok. 1 min
    Polecenie z arkusza

    Dane wodomierzy z przekroczonym stanem alarmowym są uzyskane za pomocą zmodyfikowanego zapytania 3 lub alternatywnie za pomocą zapytania 2 i odpowiednio przygotowanego warunku (patrz obraz 4)

    Trzecia opcja to jedna linia w FILTERS - kod wyboru się nie zmienia. Podgląd po wybraniu „Ponad stan alarmowy”: tylko Łazany (300 przy stanie alarmowym 240), jak na obrazie 4 (R.5.6).

    poziomRzek.php PHP zmienione: 1 linia
    <?php
    const MEASUREMENT_DATE = '2022-05-05';
    const FILTERS = [
        'all' => '',
        'warning' => ' AND p.stanWody > w.stanOstrzegawczy',
        'alarm' => ' AND p.stanWody > w.stanAlarmowy',
    ];
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $option = filter_input(INPUT_POST, 'option');
    if (!is_string($option) || !array_key_exists($option, FILTERS)) {
        $option = 'all';
    }
    
    $gauges = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=rzeki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt formularza: zapytanie 2
        $statement = $pdo->prepare(
            'SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
             FROM wodowskazy AS w
             JOIN pomiary AS p ON p.wodowskazy_id = w.id
             WHERE p.dataPomiaru = :date' . FILTERS[$option]
        );
        $statement->execute(['date' => MEASUREMENT_DATE]);
        $gauges = $statement->fetchAll();
        $statement = null;
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać stanów wody. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all">
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning">
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm">
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($gauges as $gauge): ?>
                        <tr>
                            <td><?= e($gauge['nazwa']) ?></td>
                            <td><?= e($gauge['rzeka']) ?></td>
                            <td><?= e($gauge['stanOstrzegawczy']) ?></td>
                            <td><?= e($gauge['stanAlarmowy']) ?></td>
                            <td><?= e($gauge['stanWody']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
            <a href="https://komunikaty.pl">Dowiedz się więcej</a>
            <img src="obraz2.jpg" alt="rzeka">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  42. Krok 42: Wybrane pole opcji zostaje zaznaczone

    ok. 2 min
    Polecenie z arkusza

    Obok siebie 3 pola opcji (radio) zgrupowane, tak aby było możliwe jednoczesne wybranie tylko jednego pola. Etykiety przy polach opcji: „Wszystkie”, „Ponad stan ostrzegawczy”, „Ponad stan alarmowy” (pola opcji będą formatowane jedną klasą CSS)

    Na obrazach 3 i 4 po wysłaniu formularza zaznaczone jest wybrane pole. Każde pole dostaje krótki warunek: gdy $option równa się jego wartości, PHP dopisuje atrybut checked. $option jest ustalone na górze pliku, przed formularzem, więc nic tu nie trzeba przenosić. Przy pierwszym wejściu zaznaczone jest „Wszystkie” - zgodnie z tym, co pokazuje tabela. Arkusz tego nie wymaga, ale bez tego nie widać, który filtr działa.

    poziomRzek.php PHP zmienione: 3 linie
    <?php
    const MEASUREMENT_DATE = '2022-05-05';
    const FILTERS = [
        'all' => '',
        'warning' => ' AND p.stanWody > w.stanOstrzegawczy',
        'alarm' => ' AND p.stanWody > w.stanAlarmowy',
    ];
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $option = filter_input(INPUT_POST, 'option');
    if (!is_string($option) || !array_key_exists($option, FILTERS)) {
        $option = 'all';
    }
    
    $gauges = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=rzeki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt formularza: zapytanie 2
        $statement = $pdo->prepare(
            'SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
             FROM wodowskazy AS w
             JOIN pomiary AS p ON p.wodowskazy_id = w.id
             WHERE p.dataPomiaru = :date' . FILTERS[$option]
        );
        $statement->execute(['date' => MEASUREMENT_DATE]);
        $gauges = $statement->fetchAll();
        $statement = null;
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać stanów wody. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all"<?= $option === 'all' ? ' checked' : '' ?>>
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning"<?= $option === 'warning' ? ' checked' : '' ?>>
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm"<?= $option === 'alarm' ? ' checked' : '' ?>>
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($gauges as $gauge): ?>
                        <tr>
                            <td><?= e($gauge['nazwa']) ?></td>
                            <td><?= e($gauge['rzeka']) ?></td>
                            <td><?= e($gauge['stanOstrzegawczy']) ?></td>
                            <td><?= e($gauge['stanAlarmowy']) ?></td>
                            <td><?= e($gauge['stanWody']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
            <a href="https://komunikaty.pl">Dowiedz się więcej</a>
            <img src="obraz2.jpg" alt="rzeka">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  43. Krok 43: Skrypt 2: średnie stany w akapitach

    Kryteria: ,

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

    R.5.7 W skrypcie 2 jest wyświetlany każdy zwrócony zapytaniem wiersz o treści: „<data>: <średnia>”, gdzie dane zapisane w <> są pobierane z bazy danych

    ok. 3 min
    Polecenie z arkusza

    Skrypt 2: Wysyła do bazy danych zapytanie 4. Każdy zwrócony zapytaniem wiersz jest wyświetlany w paragrafie o treści: „<data>: <średnia>”, gdzie dane zapisane w <> są pobierane z bazy danych. Na końcu jest zamykane połączenie z serwerem.

    Zapytanie 4 nie ma danych od użytkownika, więc wystarcza query(); wynik od razu trafia do $averages, a zapytanie nie zostaje w żadnej zmiennej. W szablonie pod nagłówkiem pętla wypisuje akapit „data: średnia” dla każdego dnia (R.5.7).

    Średnia przychodzi z bazy jako napis 167.6471 - number_format(..., 2, ',', '') zaokrągla ją do dwóch miejsc z polskim przecinkiem: „167,65”. Wartość nadal pochodzi z bazy (klucz w R.1.6 przyjmuje średnią „z dowolną dokładnością”). Połączenie zamyka $pdo = null zaraz po obu zapytaniach - ostatnia operacja na bazie (R.5.2).

    poziomRzek.php PHP zmienione: 11 linii
    <?php
    const MEASUREMENT_DATE = '2022-05-05';
    const FILTERS = [
        'all' => '',
        'warning' => ' AND p.stanWody > w.stanOstrzegawczy',
        'alarm' => ' AND p.stanWody > w.stanAlarmowy',
    ];
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $option = filter_input(INPUT_POST, 'option');
    if (!is_string($option) || !array_key_exists($option, FILTERS)) {
        $option = 'all';
    }
    
    $gauges = [];
    $averages = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=rzeki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt formularza: zapytanie 2
        $statement = $pdo->prepare(
            'SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
             FROM wodowskazy AS w
             JOIN pomiary AS p ON p.wodowskazy_id = w.id
             WHERE p.dataPomiaru = :date' . FILTERS[$option]
        );
        $statement->execute(['date' => MEASUREMENT_DATE]);
        $gauges = $statement->fetchAll();
        $statement = null;
    
        // Skrypt 2: zapytanie 4
        $averages = $pdo->query(
            'SELECT dataPomiaru, AVG(stanWody) AS sredni_stan
             FROM pomiary
             GROUP BY dataPomiaru'
        )->fetchAll();
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać stanów wody. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all"<?= $option === 'all' ? ' checked' : '' ?>>
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning"<?= $option === 'warning' ? ' checked' : '' ?>>
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm"<?= $option === 'alarm' ? ' checked' : '' ?>>
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($gauges as $gauge): ?>
                        <tr>
                            <td><?= e($gauge['nazwa']) ?></td>
                            <td><?= e($gauge['rzeka']) ?></td>
                            <td><?= e($gauge['stanOstrzegawczy']) ?></td>
                            <td><?= e($gauge['stanAlarmowy']) ?></td>
                            <td><?= e($gauge['stanWody']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
            <?php foreach ($averages as $average): ?>
                <p><?= e($average['dataPomiaru']) ?>: <?= number_format((float) $average['sredni_stan'], 2, ',', '') ?></p>
            <?php endforeach; ?>
            <a href="https://komunikaty.pl">Dowiedz się więcej</a>
            <img src="obraz2.jpg" alt="rzeka">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  44. Krok 44: Komunikat, gdy baza nie odpowiada

    ok. 2 min
    Polecenie z arkusza

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

    Pod nagłówkiem bloku lewego akapit z $dbError - tylko gdy catch coś do niego wpisał. Sprawdzone uruchomieniem z błędną nazwą bazy: strona się wyświetla, w bloku lewym stoi „Nie udało się pobrać stanów wody…”, tabela ma sam nagłówek, a w treści nie ma nazwy bazy ani kodu błędu SQL. W normalnym działaniu nic się tu nie pokazuje.

    poziomRzek.php PHP zmienione: 3 linie
    <?php
    const MEASUREMENT_DATE = '2022-05-05';
    const FILTERS = [
        'all' => '',
        'warning' => ' AND p.stanWody > w.stanOstrzegawczy',
        'alarm' => ' AND p.stanWody > w.stanAlarmowy',
    ];
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $option = filter_input(INPUT_POST, 'option');
    if (!is_string($option) || !array_key_exists($option, FILTERS)) {
        $option = 'all';
    }
    
    $gauges = [];
    $averages = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=rzeki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt formularza: zapytanie 2
        $statement = $pdo->prepare(
            'SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWody
             FROM wodowskazy AS w
             JOIN pomiary AS p ON p.wodowskazy_id = w.id
             WHERE p.dataPomiaru = :date' . FILTERS[$option]
        );
        $statement->execute(['date' => MEASUREMENT_DATE]);
        $gauges = $statement->fetchAll();
        $statement = null;
    
        // Skrypt 2: zapytanie 4
        $averages = $pdo->query(
            'SELECT dataPomiaru, AVG(stanWody) AS sredni_stan
             FROM pomiary
             GROUP BY dataPomiaru'
        )->fetchAll();
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać stanów wody. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Poziomy rzek</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="map">
            <img src="obraz1.png" alt="Mapa Polski">
        </header>
        <header id="title">
            <h1>Rzeki w województwie dolnośląskim</h1>
        </header>
        <nav>
            <form method="post">
                <input type="radio" id="all" name="option" value="all"<?= $option === 'all' ? ' checked' : '' ?>>
                <label for="all" class="option-label">Wszystkie</label>
                <input type="radio" id="warning" name="option" value="warning"<?= $option === 'warning' ? ' checked' : '' ?>>
                <label for="warning" class="option-label">Ponad stan ostrzegawczy</label>
                <input type="radio" id="alarm" name="option" value="alarm"<?= $option === 'alarm' ? ' checked' : '' ?>>
                <label for="alarm" class="option-label">Ponad stan alarmowy</label>
                <button type="submit">Pokaż</button>
            </form>
        </nav>
        <main>
            <h3>Stany na dzień 2022-05-05</h3>
            <?php if ($dbError !== ''): ?>
                <p><?= e($dbError) ?></p>
            <?php endif; ?>
            <table>
                <thead>
                    <tr>
                        <th scope="col">Wodomierz</th>
                        <th scope="col">Rzeka</th>
                        <th scope="col">Ostrzegawczy</th>
                        <th scope="col">Alarmowy</th>
                        <th scope="col">Aktualny</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($gauges as $gauge): ?>
                        <tr>
                            <td><?= e($gauge['nazwa']) ?></td>
                            <td><?= e($gauge['rzeka']) ?></td>
                            <td><?= e($gauge['stanOstrzegawczy']) ?></td>
                            <td><?= e($gauge['stanAlarmowy']) ?></td>
                            <td><?= e($gauge['stanWody']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <aside>
            <h3>Informacje</h3>
            <ul>
                <li>Brak ostrzeżeń o burzach z gradem</li>
                <li>Smog w mieście Wrocław</li>
                <li>Silny wiatr w Karkonoszach</li>
            </ul>
            <h3>Średnie stany wód</h3>
            <?php foreach ($averages as $average): ?>
                <p><?= e($average['dataPomiaru']) ?>: <?= number_format((float) $average['sredni_stan'], 2, ',', '') ?></p>
            <?php endforeach; ?>
            <a href="https://komunikaty.pl">Dowiedz się więcej</a>
            <img src="obraz2.jpg" alt="rzeka">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (poziomRzek.php)
  45. Etap 5: Porównanie z wersją podstawową

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

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

    ok. 2 min

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

    wersja podstawowawersja nowoczesna
    układfloat: left banerów i bloków, clear: both menu i stopkigrid-template-areas, kolumny 70% 30%, banery width: 50% + justify-self
    kolorywpisane w każdej regulezmienne CSS w :root
    tabelawiersz <th> i wiersze ze skryptu w jednym <table><thead> / <tbody>, scope="col", podświetlenie tylko danych
    zapytaniapełne nazwy tabelaliasy w i p, alias sredni_stan
    bazamysqli_connect, mysqli_fetch_row, $row[0]PDO z wyjątkami, zapytanie przygotowane z :date, $gauge['nazwa']
    filtrdrabinka if / elseif dokleja warunek (w dobrych praktykach tablica)stała FILTERS od początku, wszystko spoza listy = all
    gdzie PHPdwa bloki w środku HTML-adane na górze pliku, połączenie zamknięte przed HTML-em
    dane na stroniewprost (w dobrych praktykach htmlspecialchars)e() przy każdej wartości, średnie z number_format

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i jest szybsza do napisania - tabela wypełnia się w miejscu, w którym stoi skrypt. Nowoczesna wymaga chwili więcej, ale oddziela pobieranie danych od wyglądu, filtr nie dopuszcza do zapytania niczego spoza listy, a układ nie zależy od tego, czy pływające bloki zmieszczą się w jednej linii. Tokeny CSRF (ochrona przed wysłaniem formularza z obcej strony) mają sens dopiero przy formularzach, które coś zapisują - tu formularz tylko filtruje.

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

  46. Etap 6: Oddanie pracy

    Krok 46 · po tym etapie masz 39 z 39 kryteriów (pkt)

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

    ok. 5 min
    Polecenie z arkusza

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

    W przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana przez http://localhost/.... W folderze z numerem zdającego: import.png, kw1.jpg - kw4.jpg, kwerendy.txt, obraz1.png (po obróbce), obraz2.jpg, poziomRzek.php, przeglądarka.txt, styl.css. Nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.