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.
Suwak i strzałki obok niego - albo ←→ na klawiaturze - przechodzą krok po kroku; wyjaśnienie, pliki i podgląd zmieniają się razem z krokiem.
Lista „Etap” przenosi od razu do wybranego etapu.
„Rozwiązuję samodzielnie” chowa rozwiązanie kroku - najpierw własna próba, potem odsłonięcie.
„Tryb prezentacji” pokazuje same kroki, bez menu strony - na rzutnik.
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).
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.
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).
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.
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 tabelw (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.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT nazwa, rzeka, stanAlarmowyFROM wodowskazy;-- Zapytanie 2SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWodyFROM wodowskazy AS wJOIN pomiary AS p ON p.wodowskazy_id = w.idWHERE p.dataPomiaru = '2022-05-05';
Podgląd strony po tym kroku
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.txtTekstzmienione: 7 linii
-- Zapytanie 1SELECT nazwa, rzeka, stanAlarmowyFROM wodowskazy;-- Zapytanie 2SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWodyFROM wodowskazy AS wJOIN pomiary AS p ON p.wodowskazy_id = w.idWHERE p.dataPomiaru = '2022-05-05';-- Zapytanie 3SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWodyFROM wodowskazy AS wJOIN pomiary AS p ON p.wodowskazy_id = w.idWHERE p.dataPomiaru = '2022-05-05' AND p.stanWody > w.stanOstrzegawczy;
Podgląd strony po tym kroku
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.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT nazwa, rzeka, stanAlarmowyFROM wodowskazy;-- Zapytanie 2SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWodyFROM wodowskazy AS wJOIN pomiary AS p ON p.wodowskazy_id = w.idWHERE p.dataPomiaru = '2022-05-05';-- Zapytanie 3SELECT w.nazwa, w.rzeka, w.stanOstrzegawczy, w.stanAlarmowy, p.stanWodyFROM wodowskazy AS wJOIN pomiary AS p ON p.wodowskazy_id = w.idWHERE p.dataPomiaru = '2022-05-05' AND p.stanWody > w.stanOstrzegawczy;-- Zapytanie 4SELECT dataPomiaru, AVG(stanWody) AS sredni_stanFROM pomiaryGROUP BY dataPomiaru;
Podgląd strony po tym kroku
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
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 jakoobraz1.png - PNG zachowuje przezroczystość.
Typowe błędy
Eksport do JPEG gubi przezroczystość - tło znowu staje się białe i R.2.1 przepada.
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).
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.
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.
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).
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.phpPHPzmienione: 6 linii
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="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)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> </main> <aside> </aside> <footer> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="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)
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.phpPHPzmienione: 13 linii
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="col">Aktualny</th> </tr> </thead> <tbody> </tbody> </table> </main> <aside> </aside> <footer> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="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)
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.phpPHPzmienione: 5 linii
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="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)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="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)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="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> <ahref="https://komunikaty.pl">Dowiedz się więcej</a> </aside> <footer> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="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> <ahref="https://komunikaty.pl">Dowiedz się więcej</a> <imgsrc="obraz2.jpg"alt="rzeka"> </aside> <footer> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="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> <ahref="https://komunikaty.pl">Dowiedz się więcej</a> <imgsrc="obraz2.jpg"alt="rzeka"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
Etap 3: Styl CSS
Kroki 27-36 · po tym etapie masz 30 z 39 kryteriów (pkt)
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).
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 polebanner 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.
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.
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.
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.
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).
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).
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).
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.
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).
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.phpPHPzmienione: 21 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="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> <ahref="https://komunikaty.pl">Dowiedz się więcej</a> <imgsrc="obraz2.jpg"alt="rzeka"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
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.phpPHPzmienione: 13 linii
<?phpconst MEASUREMENT_DATE = '2022-05-05';functione(string $text): string{returnhtmlspecialchars($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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="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> <ahref="https://komunikaty.pl">Dowiedz się więcej</a> <imgsrc="obraz2.jpg"alt="rzeka"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
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.phpPHPzmienione: 9 linii
<?phpconst MEASUREMENT_DATE = '2022-05-05';functione(string $text): string{returnhtmlspecialchars($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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="col">Aktualny</th> </tr> </thead> <tbody><?phpforeach ($gaugesas$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><?phpendforeach; ?> </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> <ahref="https://komunikaty.pl">Dowiedz się więcej</a> <imgsrc="obraz2.jpg"alt="rzeka"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
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.phpPHPzmienione: 10 linii
<?phpconst MEASUREMENT_DATE = '2022-05-05';const FILTERS = ['all' => '','warning' => ' AND p.stanWody > w.stanOstrzegawczy',];functione(string $text): string{returnhtmlspecialchars($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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="col">Aktualny</th> </tr> </thead> <tbody><?phpforeach ($gaugesas$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><?phpendforeach; ?> </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> <ahref="https://komunikaty.pl">Dowiedz się więcej</a> <imgsrc="obraz2.jpg"alt="rzeka"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
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.phpPHPzmienione: 1 linia
<?phpconst MEASUREMENT_DATE = '2022-05-05';const FILTERS = ['all' => '','warning' => ' AND p.stanWody > w.stanOstrzegawczy','alarm' => ' AND p.stanWody > w.stanAlarmowy',];functione(string $text): string{returnhtmlspecialchars($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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="col">Aktualny</th> </tr> </thead> <tbody><?phpforeach ($gaugesas$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><?phpendforeach; ?> </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> <ahref="https://komunikaty.pl">Dowiedz się więcej</a> <imgsrc="obraz2.jpg"alt="rzeka"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
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.phpPHPzmienione: 3 linie
<?phpconst MEASUREMENT_DATE = '2022-05-05';const FILTERS = ['all' => '','warning' => ' AND p.stanWody > w.stanOstrzegawczy','alarm' => ' AND p.stanWody > w.stanAlarmowy',];functione(string $text): string{returnhtmlspecialchars($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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"<?= $option === 'all' ? ' checked' : ''?>> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"<?= $option === 'warning' ? ' checked' : ''?>> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"<?= $option === 'alarm' ? ' checked' : ''?>> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="col">Aktualny</th> </tr> </thead> <tbody><?phpforeach ($gaugesas$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><?phpendforeach; ?> </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> <ahref="https://komunikaty.pl">Dowiedz się więcej</a> <imgsrc="obraz2.jpg"alt="rzeka"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
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.phpPHPzmienione: 11 linii
<?phpconst MEASUREMENT_DATE = '2022-05-05';const FILTERS = ['all' => '','warning' => ' AND p.stanWody > w.stanOstrzegawczy','alarm' => ' AND p.stanWody > w.stanAlarmowy',];functione(string $text): string{returnhtmlspecialchars($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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"<?= $option === 'all' ? ' checked' : ''?>> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"<?= $option === 'warning' ? ' checked' : ''?>> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"<?= $option === 'alarm' ? ' checked' : ''?>> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="col">Aktualny</th> </tr> </thead> <tbody><?phpforeach ($gaugesas$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><?phpendforeach; ?> </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><?phpforeach ($averagesas$average): ?> <p><?= e($average['dataPomiaru']) ?>: <?= number_format((float) $average['sredni_stan'], 2, ',', '') ?></p><?phpendforeach; ?> <ahref="https://komunikaty.pl">Dowiedz się więcej</a> <imgsrc="obraz2.jpg"alt="rzeka"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
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.phpPHPzmienione: 3 linie
<?phpconst MEASUREMENT_DATE = '2022-05-05';const FILTERS = ['all' => '','warning' => ' AND p.stanWody > w.stanOstrzegawczy','alarm' => ' AND p.stanWody > w.stanAlarmowy',];functione(string $text): string{returnhtmlspecialchars($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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Poziomy rzek</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="map"> <imgsrc="obraz1.png"alt="Mapa Polski"> </header> <headerid="title"> <h1>Rzeki w województwie dolnośląskim</h1> </header> <nav> <formmethod="post"> <inputtype="radio"id="all"name="option"value="all"<?= $option === 'all' ? ' checked' : ''?>> <labelfor="all"class="option-label">Wszystkie</label> <inputtype="radio"id="warning"name="option"value="warning"<?= $option === 'warning' ? ' checked' : ''?>> <labelfor="warning"class="option-label">Ponad stan ostrzegawczy</label> <inputtype="radio"id="alarm"name="option"value="alarm"<?= $option === 'alarm' ? ' checked' : ''?>> <labelfor="alarm"class="option-label">Ponad stan alarmowy</label> <buttontype="submit">Pokaż</button> </form> </nav> <main> <h3>Stany na dzień 2022-05-05</h3><?phpif ($dbError !== ''): ?> <p><?= e($dbError) ?></p><?phpendif; ?> <table> <thead> <tr> <thscope="col">Wodomierz</th> <thscope="col">Rzeka</th> <thscope="col">Ostrzegawczy</th> <thscope="col">Alarmowy</th> <thscope="col">Aktualny</th> </tr> </thead> <tbody><?phpforeach ($gaugesas$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><?phpendforeach; ?> </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><?phpforeach ($averagesas$average): ?> <p><?= e($average['dataPomiaru']) ?>: <?= number_format((float) $average['sredni_stan'], 2, ',', '') ?></p><?phpendforeach; ?> <ahref="https://komunikaty.pl">Dowiedz się więcej</a> <imgsrc="obraz2.jpg"alt="rzeka"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (poziomRzek.php)
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 podstawowa
wersja nowoczesna
układ
float: left banerów i bloków, clear: both menu i stopki
<thead> / <tbody>, scope="col", podświetlenie tylko danych
zapytania
pełne nazwy tabel
aliasy w i p, alias sredni_stan
baza
mysqli_connect, mysqli_fetch_row, $row[0]
PDO z wyjątkami, zapytanie przygotowane z :date, $gauge['nazwa']
filtr
drabinka if / elseif dokleja warunek (w dobrych praktykach tablica)
stała FILTERS od początku, wszystko spoza listy = all
gdzie PHP
dwa bloki w środku HTML-a
dane na górze pliku, połączenie zamknięte przed HTML-em
dane na stronie
wprost (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.
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.
Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.
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: Bartosz Bryniarski. 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.