INF.03-07-24.06-SGCzerwiec 2024 · 33 kroki · ok. 77 min · 37 kryteriów z klucza
Rozwiązanie krok po kroku: Witryna ważenia pojazdów (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza wazenietirow z czterema operacjami, poprawiony znak drogowy, odbite zdjęcie ciężarówki i strona wazenie.php, która co 10 sekund dopisuje testowy pomiar i pokazuje alerty o przekroczonym nacisku na oś - i tak samo spełnia każde kryterium klucza. Inna jest droga:
siatka CSS na całej stronie: kolumny 25% 50% 25% dla trzech bloków w grid-template-columns (klucz R.4.4 przyjmuje je wprost), a oba banery w jednym pasie siatki, z szerokościami 70% i 30% przyklejonymi do swoich krawędzi - zamiast float i clear,
kolory w zmiennych CSS, tabela z <thead> i <tbody>, czerwony zawsze ostatni wiersz z danymi - nigdy wiersz nagłówkowy,
PHP na PDO nad HTML-em: wszystkie trzy skrypty wykonane i połączenie zamknięte, zanim zacznie się strona; alerty posortowane po identyfikatorze, więc ostatni wiersz to zawsze najnowszy pomiar,
nagłówek odświeżania przed jakąkolwiek treścią - w wersji podstawowej header() w środku strony przestaje działać po kilkudziesięciu odświeżeniach, tu działa zawsze,
e() przy każdej wartości z bazy, wstawianie testowego pomiaru jako zapytanie przygotowane ze stałymi, komunikat przy awarii bazy.
Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafiki pochodzą z prawdziwego archiwum pliki7, a podgląd strony od etapu PHP to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2 na bazie po zapytaniu 1 (stała chwila 2026-10-04, 11:17:40 i stałe „losowania”, jak w wersji podstawowej - inaczej każda przebudowa dawałaby inny podgląd).
Jak liczą się punkty. Klucz ma 37 kryteriów (R.1.1 - R.5.8), każde za 1 punkt. Próg zdania to 75%, czyli 28 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 37 kryteriów (pkt)
Krok 1: Baza danych wazenietirow
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie wazenietirow, z zestawem polskich znaków (np. utf8_unicode_ci)
Archiwum pliki7.zip rozpakuj hasłem %WAg@Poj@zdoW (procent na początku, dwie małpy) do folderu z numerem zdającego. W phpMyAdmin Nowa → wazenietirow (jedno słowo, bez polskich liter), porównywanie utf8_unicode_ci, albo zapytaniem:
CREATE DATABASE wazenietirow CHARACTER SET utf8 COLLATE utf8_unicode_ci;
Typowe błędy
Nazwa bazy musi brzmieć dokładnie wazenietirow - 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: lokalizacje (6 ulic z wagami) i wagi (22 pomiary z kluczem obcym lokalizacje_id, naciskiem na oś w tonach, rejestracją, dniem i czasem) - jedna lokalizacja, wiele pomiarów.
Krok 3: Zrzut ekranu import.png
Kryteria:
R.1.1 Wykonano import tabel do bazy danych wazenietirow 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. Zrzuty zapytań idą do JPEG.
Typowe błędy
Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany (uwaga w kluczu przy R.1).
Krok 4: Zapytanie 1: aktualna data i czas dla lokalizacji 2, 3 i 4
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 aktualizujące tabelę wagi . Rekordy, w których pole lokalizacje_id jest równe 2, 3, 4 mają zmienione datę i czas na wartości aktualne. W pliku z kwerendami lub na zrzucie istnieje zapis np.: UPDATE wagi SET dzien=CURRENT_DATE,czas=CURRENT_TIME WHERE lokalizacje_id BETWEEN 2 AND 4; (dowolne funkcje wstawiające aktualną datę/czas, lub także z pustymi nawiasami; dowolny warunek sprawdzający przedział 2..4) oraz wynik działania udokumentowano zrzutem, na którym jest widoczny komunikat o poprawnie wykonanym zapytaniu lub wiersze z lokalizacje_id równym 2, 3, 4 mają datę i czas zgodny z terminem egzaminu
ok. 2 min
Polecenie z arkusza
Zapytanie 1: aktualizujące tabelę wagi. Rekordy, w których pole lokalizacje_id jest równe: 2, 3, 4 mają zmienione datę i czas na wartości aktualne. Zapytanie ma charakter uniwersalny, zawsze zmienia wartość na aktualną datę / czas
SET zmienia dwie kolumny naraz; CURDATE() i CURTIME() z tabeli 2 arkusza zwracają datę i czas w chwili wykonania - to jest „charakter uniwersalny”. Warunek IN (2, 3, 4) wymienia wartości dokładnie tak, jak arkusz (klucz przyjmuje każdy zapis tego przedziału, np. BETWEEN 2 AND 4). Zmienionych rekordów: 13, wszystkie z datą 2026-10-04 i tym samym czasem. Zapytania trafiają do kwerendy.txt, zrzut do kw1.jpg.
Typowe błędy
Data wpisana ręcznie nie jest uniwersalna. Bez WHERE zmieniłyby się wszystkie 22 pomiary.
kwerendy.txtTekstnowy plik
-- Zapytanie 1UPDATE wagiSET dzien = CURDATE(), czas = CURTIME()WHERE lokalizacje_id IN (2, 3, 4);
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: ulice
Kryteria:
R.1.4 Wykonano zapytanie 2 wybierające jedynie ulicę z tabeli lokalizacje . W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT ulica FROM lokalizacje; oraz wynik działania udokumentowano zrzutem, na którym są widoczne jedynie dane: Ślężna, Tyska, Średzka, Wilanowska, Żmigrodzka, Karkonowska
ok. 1 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie ulicę z tabeli lokalizacje
Jedno pole. Wynik: Ślężna, Tyska, Średzka, Wilanowska, Żmigrodzka, Karkonowska, jak w kluczu. To zapytanie wypełni listę (Skrypt 1). Zrzut: kw2.jpg.
kwerendy.txtTekstzmienione: 4 linie
-- Zapytanie 1UPDATE wagiSET dzien = CURDATE(), czas = CURTIME()WHERE lokalizacje_id IN (2, 3, 4);-- Zapytanie 2SELECT ulicaFROM lokalizacje;
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: pomiar z losową wagą
Kryteria:
R.1.5 Wykonano zapytanie 3 wstawiające rekord do tabeli wagi z danymi: lokalizacje_id: 5, waga: liczba pseudolosowa z przedziału 1..10, rejestracja: DW12345, aktualna data i czas. Klucz główny nadawany automatycznie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO `wagi`(`lokalizacje_id`, `waga`, `rejestracja`, `dzien`, `czas`) VALUES ('5', FLOOR(1+RAND()*10), 'DW12345', CURRENT_DATE, CURRENT_TIME); (dowolny sposób liczenia liczby pseudolosowej z przedziału 1..10; dowolne funkcje wstawiające aktualny czas/datę, też z pustymi nawiasami) oraz wynik działania udokumentowano zrzutem, na którym jest widoczny komunikat o poprawnie wykonanym zapytaniu lub wstawiony do tabeli wiersz
ok. 3 min
Polecenie z arkusza
Zapytanie 3: wstawiające do tabeli wagi rekord z danymi: lokalizacje_id: 5, waga: losowa liczba z przedziału 1..10 (wygenerowana funkcją), rejestracja: DW12345, aktualna data (do pola dzien) i aktualny czas (do pola czas). Klucz główny nadawany automatycznie. Zapytanie ma charakter uniwersalny, zawsze wstawia wartość aktualnej daty i czasu
Lista kolumn bez id - klucz główny nada baza (AUTO_INCREMENT). FLOOR(1 + RAND() * 10) to wzór z pomocy do RAND w arkuszu: RAND() daje 0 ≤ x < 1, po przemnożeniu i dodaniu 1 - od 1 do prawie 11, FLOOR obcina ułamek, więc wychodzą liczby całkowite 1-10 z równym prawdopodobieństwem. Wstawiony pomiar dostał wagę 5. Zrzut: kw3.jpg.
Typowe błędy
ROUND(RAND() * 10) daje także 0 (i rzadziej 10) - przedział z arkusza to 1..10.
Krok 7: Zapytanie 4: pomiary powyżej 5 t z nazwą ulicy
Kryteria:
R.1.6 Wykonano zapytanie 4 wybierające jedynie pola rejestracja, waga, dzien, czas z tabeli wagi i odpowiadające mu pole ulica z tabeli lokalizacje dla pojazdów, których waga na oś przekracza 5 t. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT rejestracja, ulica, waga, dzien, czas FROM wagi JOIN lokalizacje ON wagi.lokalizacje_id = lokalizacje.id WHERE waga > 5; (możliwe również INNER JOIN lub bez JOIN i porównanie kluczy po WHERE) oraz wynik działania udokumentowano zrzutem, na którym widoczne są rekordy z wagą powyżej 5; jedynie pola: rejestracja, waga, dzien, czas, ulica. Liczba wierszy dowolna, w zależności od wykonania zapytań 1 i 3
ok. 3 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie pola rejestracja, waga, dzien, czas z tabeli wagi i odpowiadające mu pole ulica z tabeli lokalizacje dla pojazdów, których waga na oś przekracza 5 t. Należy posłużyć się relacją
kolejność pól jak w tabeli na obrazie 3 (rejestracja, ulica, waga, dzień, czas) - arkusz wymaga jej przy Skrypcie 2,
aliasy w i l, JOIN ... ON l.id = w.lokalizacje_id - relacja z obrazu 1,
WHERE w.waga > 5 - „przekracza”, czyli ostro większe; dwa pomiary z wagą równo 5 t się nie łapią,
ORDER BY w.id - pomiary w kolejności wpisania. Bez sortowania kolejność wierszy złączenia nie jest gwarantowana; z nim ostatni wiersz tabeli zawsze jest najnowszym pomiarem, a to on ma być czerwony (obraz 3).
Wynik: 9 wierszy - dziewięć pomiarów z obrazu 2 i ewentualnie wstawiony DW12345. Zrzut: kw4.jpg.
Typowe błędy
waga >= 5 dorzuca dwa pomiary z wagą dokładnie 5 t, które limitu nie przekraczają.
kwerendy.txtTekstzmienione: 7 linii
-- Zapytanie 1UPDATE wagiSET dzien = CURDATE(), czas = CURTIME()WHERE lokalizacje_id IN (2, 3, 4);-- Zapytanie 2SELECT ulicaFROM lokalizacje;-- Zapytanie 3INSERT INTO wagi (lokalizacje_id, waga, rejestracja, dzien, czas)VALUES (5, FLOOR(1 + RAND() * 10), 'DW12345', CURDATE(), CURTIME());-- Zapytanie 4SELECT w.rejestracja, l.ulica, w.waga, w.dzien, w.czasFROM wagi AS wJOIN lokalizacje AS l ON l.id = w.lokalizacje_idWHERE w.waga > 5ORDER BY w.id;
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 8-17 · po tym etapie masz 16 z 37 kryteriów (pkt)
Krok 8: obraz1.png: 5T zamiast 8T
Kryteria:
R.2.1 Plik obraz1.png zmodyfikowano w ten sposób że przy samochodzie widnieje tonaż 5T. Napis 8T nie jest widoczny
ok. 4 min
Polecenie z arkusza
Plik obraz1.png, wypakowany z archiwum, należy zmodyfikować w ten sposób, że zamiast informacji 8T na przedstawionym znaku powinna być zapisana wartość 5T. Zmodyfikowany obraz jest widoczny na obrazie 2.
W GIMP-ie zaznaczenie prostokątne wokół cyfry 8, wypełnienie bielą, potem narzędzie Tekst: czarna cyfra 5 w wąskim kroju bezszeryfowym, wysokości litery T (ok. 21 px; tu krój Bahnschrift, wzorowany na kroju DIN ze znaków drogowych). Litera T bez zmian. Plik → Nadpisz obraz1.png. Podgląd: znak przed i po, powiększony trzy razy.
Typowe błędy
Cyfra 5 dopisana obok ósemki albo na niej bez zamalowania - klucz wymaga, żeby napis 8T nie był widoczny.
Podgląd strony po tym kroku
Krok 9: obraz2.jpg: odbicie i szerokość 350 px
Kryteria:
R.2.2 Plik obraz2.jpg odbito wzdłuż osi pionowej oraz przeskalowano z zachowaniem proporcji do szerokości 350 px. Odpowiadająca temu wysokość wynosi 285 px (± 5 px)
ok. 3 min
Polecenie z arkusza
Plik obraz2.jpg odbić wzdłuż osi pionowej oraz przeskalować z zachowaniem proporcji tak, aby jego szerokość wynosiła dokładnie 350 px. Zmodyfikowany obraz jest widoczny na obrazie 2.
Odbicie „wzdłuż osi pionowej” zamienia lewą stronę z prawą: w GIMP-ie Obraz → Przekształć → Odbij poziomo (menu nazywa kierunek ruchu pikseli, arkusz - oś). Kabina przechodzi na lewą stronę, jak na obrazie 2. Potem Obraz → Skaluj obraz z zamkniętym łańcuchem: szerokość 350, wysokość 285 px (klucz: 285 ± 5).
R.2.3 Witrynę internetową zapisano w pliku wazenie.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 wazenie.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 wazenie.php: <!DOCTYPE html>, <html lang="pl">, <meta charset="UTF-8"> jako pierwszy element <head> - inaczej „Ślężna” z bazy wyjdzie krzakami - i sekcja <body> (R.2.3).
R.2.4 Nadano tytuł stronie: Ważenie samochodów ciężarowych
ok. 1 min
Polecenie z arkusza
Tytuł strony widoczny na karcie przeglądarki: „Ważenie samochodów ciężarowych”
<title> (R.2.4) - mówi o samochodach ciężarowych, nagłówek strony o pojazdach; to dwa różne teksty. Obok viewport dla telefonów.
wazenie.phpPHPzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Ważenie samochodów ciężarowych</title></head><body></body></html>
Podgląd strony po tym kroku (wazenie.php)
Krok 12: Podpięcie arkusza stylów
Kryteria:
R.3.3 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> do styl.css - całe formatowanie z tego pliku (R.3.3).
Typowe błędy
Klucz odbiera R.3.3 także wtedy, gdy choć jeden selektor nie trafia w blok (np. kropka zamiast #).
wazenie.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Ważenie samochodów ciężarowych</title> <linkrel="stylesheet"href="styl.css"></head><body></body></html>
Podgląd strony po tym kroku (wazenie.php)
Krok 13: Bloki semantyczne
Kryteria:
R.2.5 Układ strony zdefiniowano dzieląc ją na bloki: dwa bloki banera, blok lewy, środkowy, 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 trzy bloki: lewy, środkowy 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>, stopka <footer> (klucz R.2.5 wymienia je wprost). Główna treść - tabela alertów - stoi w <main>, a bloki po bokach (lokalizacje z kontaktem, zdjęcie) to treść dodatkowa, więc <aside>. Identyfikatory od treści, nie od położenia: title, sign, locations, photo.
Zawartość pierwszego banera: nagłówek pierwszego stopnia o treści „Ważenie pojazdów we Wrocławiu”. Zawartość drugiego banera: obraz1.png z tekstem alternatywnym o treści: „waga”
<h1> w pierwszym banerze, znak z limitem 5 t w drugim. Znak ma 150 px wysokości - tyle co baner.
R.3.2 Odnośnik zapisano jako pocztowy (zastosowano "mailto:" ), prowadzi on do adresu [email protected]
ok. 2 min
Polecenie z arkusza
Zawartość bloku lewego: Nagłówek drugiego stopnia o treści: „Lokalizacje wag”. Lista numerowana (uporządkowana) wypełniona przez skrypt 1. Nagłówek drugiego stopnia o treści: „Kontakt”. Odnośnik pocztowy o treści „napisz” prowadzący do adresu [email protected]
Dwa <h2>, pusta lista <ol> (elementy wypisze Skrypt 1) i odnośnik pocztowy: przedrostek mailto: w href otwiera program pocztowy z gotowym adresem (R.3.2).
Typowe błędy
href="[email protected]" bez mailto: - przeglądarka szuka pliku o takiej nazwie obok strony.
R.2.9 Pierwszy wiersz tabeli zawiera komórki nagłówkowe o treści, kolejno: „rejestracja”, „ulica”, „waga”, „dzień”, „czas”, zastosowano znaczniki <th>
ok. 2 min
Polecenie z arkusza
Zawartość bloku środkowego: Nagłówek drugiego stopnia o treści: „Alerty”. Tabela o pięciu kolumnach wypełniona danymi: pierwszy wiersz zawiera komórki nagłówkowe o treści kolejno: „rejestracja”, „ulica”, „waga”, „dzień”, „czas”
Wiersz komórek nagłówkowych <th> w <thead>, małymi literami jak w arkuszu (R.2.9), a miejsce na pomiary w <tbody>. scope="col" - komórka jest nagłówkiem kolumny. Podział na thead i tbody przyda się w stylach: czerwony ma być ostatni wiersz z danymi, nie nagłówek.
R.2.6 W pierwszym banerze zapisano nagłówek h1: "Ważenie pojazdów we Wrocławiu" , w bloku lewym h2: "Lokalizacje wag" i "Kontakt" , w bloku środkowym h2: "Alerty" oraz w stopce paragraf z numerem zdającego
R.2.10 W drugim banerze umieszczono obraz z limitem wagi z tekstem alternatywnym "waga" oraz w bloku prawym umieszczono obraz z ciężarówką z tekstem alternatywnym "tir"
ok. 2 min
Polecenie z arkusza
Zawartość bloku prawego: obraz2.jpg z tekstem alternatywnym o treści: „tir”. Zawartość stopki: akapit (paragraf) o treści: „Stronę wykonał: ”, następnie numer zdającego.
Zdjęcie ciężarówki w bloku photo - oba obrazy mają teksty alternatywne z arkusza (R.2.10). Na egzaminie po „Stronę wykonał:” wpisujesz swój numer zdającego (PESEL); tu stoi autor rozwiązania, Bartosz Bryniarski. Komplet nagłówków i akapitu z R.2.6.
Domyślne formatowanie wszystkich selektorów: krój czcionki Tahoma
Trzy kolory z arkusza w zmiennych na :root; #80C683 występuje w stopce, ramce tabeli i podświetleniu wiersza. Selektor * z Tahomą i zapasowym krojem bezszeryfowym (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 trzy bloki: lewy, środkowy 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 bloki lewy i prawy mają szerokość 25%. Dodatkowo blok środkowy ma szerokość 50%
<body> to siatka o kolumnach 25% 50% 25% - szerokości bloków z arkusza (klucz R.4.4 przyjmuje grid-template-columns). grid-template-areas rysuje obraz 2: pas banerów, trzy bloki, stopka (R.3.1).
Banery mają 70% i 30%, czyli inny podział niż bloki - dlatego oba header dostają jedno wspólne polebanner na całą szerokość, a swoją szerokość ustawi każdy w następnym kroku. Do tego czasu leżą w tym polu jeden na drugim.
Wspólne dla obu bloków banera: kolor tła #519657, biały kolor czcionki, wyrównanie tekstu do środka, wysokość 150 px. Dodatkowo pierwszy baner ma szerokość 70%, drugi 30%
Wspólne cechy w regule header. width: 70% i 30% liczą się od pola siatki - a pole banner ma całą szerokość strony, więc wartości z arkusza działają wprost. justify-self przykleja baner z nagłówkiem do lewej krawędzi pola, a baner ze znakiem - do prawej; razem wypełniają pas (obraz 2). text-align: center wyśrodkowuje też znak - <img> zachowuje się w linii jak tekst.
Typowe błędy
Banery w osobnych kolumnach siatki z width w procentach - procent liczyłby się od kolumny, nie od strony.
R.4.3 Ustawiono wysokość obu banerów na 150px, bloków lewego, środkowego i prawego na 450px
R.4.7 Ustawiono dla bloku środkowego automatyczne paski przewijania (overflow: auto lub scroll )
ok. 2 min
Polecenie z arkusza
Wspólne dla bloku lewego, środkowego i prawego: kolor tła #E1E1E1, wysokość 450 px. Dodatkowo blok środkowy ma szerokość 50% oraz paski przewijania, które pojawiają się jedynie, gdy zawartość nie mieści się w bloku
Wspólna reguła aside, main; wysokości banerów i bloków są komplet (R.4.3). overflow: auto - pasek przewijania tylko wtedy, gdy tabela przestanie się mieścić w 450 px; a przestanie, bo strona co 10 sekund dopisuje pomiar (R.4.7). Szerokości dają kolumny siatki.
R.4.2 Ustawiono kolor tła #519657 dla obu banerów, #E1E1E1 dla bloku lewego, środkowego i prawego oraz #80C683 dla stopki oraz biały kolor czcionki dla obu banerów
R.4.5 Ustawiono wyrównanie tekstu do środka dla obu banerów i stopki
ok. 1 min
Polecenie z arkusza
Dla stopki: kolor tła #80C683, wyrównanie tekstu do środka
Kolory tła banerów, bloków i stopki oraz biel banerów są komplet (R.4.2), tak samo wyśrodkowanie banerów i stopki (R.4.5).
R.4.4 Ustawiono szerokość pierwszego banera 70%, drugiego banera 30%, bloku lewego i prawego na 25%, bloku środkowego 50% (width lub grid-template-columns lub flex ) oraz 100% szerokości jedynie dla obrazu o nazwie obraz2.jpg
ok. 2 min
Polecenie z arkusza
Jedynie dla obrazu o nazwie obraz2.jpg: szerokość 100%, marginesy wewnętrzne górny i dolny 100 px, pozostałe 0
Selektor potomka #photo img trafia tylko w zdjęcie ciężarówki - znak w banerze to też <img>, a dostałby szerokość całego banera. padding: 100px 0 - góra i dół 100 px, boki 0; ciężarówka stoi mniej więcej na środku wysokości bloku. Szerokości są komplet: banery 70% i 30%, kolumny 25% 50% 25%, zdjęcie 100% (R.4.4).
Krok 24: Tabela: ramka, komórki, wiersz pod kursorem
Kryteria: , ,
R.4.6 Ustawiono marginesy wewnętrzne (padding) dla selektora td 2px oraz jedynie dla obrazu o nazwie obraz2.jpg : jedynie górny i dolny 100px
R.4.8 Ustawiono dla selektora table: obramowanie 1px solid #80C683
R.4.9 Gdy kursor myszy znajdzie się na wierszu tabeli, kolor tła wiersza zmienia się na #80C683 (dla tr:hover )
ok. 2 min
Polecenie z arkusza
Dla selektora tabeli: obramowanie linią ciągłą o szerokości 1 px i kolorze #80C683. Dla selektora komórki tabeli: marginesy wewnętrzne 2 px. Gdy kursor myszy znajdzie się na wierszu tabeli kolor tła wiersza zmienia się na #80C683
Selektory znaczników table, td i tr, jak każe uwaga arkusza (klucz nie uzna klasy). Ramka 1px solid (R.4.8), dopełnienie komórek - razem z dopełnieniem zdjęcia to R.4.6, tr:hover podświetla wiersz pod kursorem (R.4.9).
R.4.10 Ostatni wiersz tabeli ma tło czerwone np. dla tr:nth-last-child(1)
ok. 2 min
Polecenie z arkusza
Ostatni wiersz tabeli ma tło czerwone
:last-child wybiera element, który jest ostatnim dzieckiem rodzica. Tabela ma dwie grupy wierszy, więc samo tr:last-child trafiłoby dwa wiersze: ostatni w <tbody> i jedyny w <thead> - nagłówek też byłby czerwony. tbody tr:last-child zawęża wybór do wierszy z danymi (R.4.10). Przy pustej tabeli nic nie jest czerwone; w wersji podstawowej czerwony zrobiłby się wtedy wiersz nagłówkowy.
Typowe błędy
tr:last-child przy tabeli z <thead> zabarwia na czerwono także wiersz nagłówkowy.
Kroki 26-31 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 26: Połączenie PDO na górze pliku
Kryteria:
R.5.1 Skrypt zawiera działające połączenie z serwerem bazy danych i wybór bazy wazenietirow
ok. 4 min
Polecenie z arkusza
Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie wazenietirow
Praca z bazą stoi przed<!DOCTYPE html>: najpierw dane, potem HTML, który je tylko wypisuje. Tu ma to jeszcze jeden powód - Skrypt 3 wysyła nagłówek HTTP, a nagłówki muszą wyjść przed pierwszym znakiem treści strony.
R.2.7 W bloku lewym umieszczono jedną listę numerowaną z elementami, zastosowano znaczniki <ol>, <li> (listę wygenerowano skryptem lub w HTML)
R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 2 lub skrypt 2 wysyła zapytanie 4 lub skrypt 3 wysyła zapytanie 3
R.5.4 Skrypt 1 wyświetla zwrócone zapytaniem nazwy ulic według wzoru: ”ulica <nazwa_ulicy>”, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych
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 1: Wysyła do bazy danych zapytanie 2. Zwrócone nazwy ulic są wyświetlane w elementach listy numerowanej z bloku lewego, według wzoru: ”ulica <nazwa_ulicy>”, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych
Zapytanie 2 zwraca jedną kolumnę, więc fetchAll(PDO::FETCH_COLUMN) oddaje od razu listę napisów ['Ślężna', 'Tyska', ...] zamiast tablicy wierszy (R.5.3). W <ol> pętla foreach w składni alternatywnej wypisuje „ulica ” i nazwę przez e() (R.5.4); numery dopisuje lista. Nazwy po angielsku mówią, co przechowują (R.5.8).
R.2.8 W bloku środkowym umieszczono jedną tabelę o pięciu kolumnach, zastosowano znaczniki <table>, <tr>, <td> (tabelę wygenerowano skryptem lub w HTML)
R.5.5 Skrypt 2 wyświetla zwrócone zapytaniem dane z każdego wybranego z bazy wiersza
ok. 3 min
Polecenie z arkusza
Skrypt 2: Wysyła do bazy danych zapytanie 4 (kolejność wybieranych pól w zapytaniu zgodna z obrazem 3). Każdy zwrócony zapytaniem wiersz jest wyświetlany w osobnym wierszu tabeli z bloku środkowego
Zapytanie 4 z ORDER BY w.id do tablicy $alerts. W <tbody> pętla wypisuje wiersz na pomiar, pięć komórek po nazwach kolumn (R.5.5) - tabela ma pięć kolumn z table, tr, td (R.2.8). Podgląd: dziewięć pomiarów jak na obrazie 2, ostatni (DW54N32) na czerwono; trzy z nich mają datę i godzinę zapytania 1.
wazenie.phpPHPzmienione: 19 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES);}$streets = [];$alerts = [];$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=wazenietirow;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 2$streets = $pdo->query('SELECT ulica FROM lokalizacje')->fetchAll(PDO::FETCH_COLUMN);// Skrypt 2: zapytanie 4$alerts = $pdo->query('SELECT w.rejestracja, l.ulica, w.waga, w.dzien, w.czas FROM wagi AS w JOIN lokalizacje AS l ON l.id = w.lokalizacje_id WHERE w.waga > 5 ORDER BY w.id' )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać pomiarów. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Ważenie samochodów ciężarowych</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="title"> <h1>Ważenie pojazdów we Wrocławiu</h1> </header> <headerid="sign"> <imgsrc="obraz1.png"alt="waga"> </header> <asideid="locations"> <h2>Lokalizacje wag</h2> <ol><?phpforeach ($streetsas$street): ?> <li>ulica <?= e($street) ?></li><?phpendforeach; ?> </ol> <h2>Kontakt</h2> <ahref="mailto:[email protected]">napisz</a> </aside> <main> <h2>Alerty</h2> <table> <thead> <tr> <thscope="col">rejestracja</th> <thscope="col">ulica</th> <thscope="col">waga</th> <thscope="col">dzień</th> <thscope="col">czas</th> </tr> </thead> <tbody><?phpforeach ($alertsas$alert): ?> <tr> <td><?= e($alert['rejestracja']) ?></td> <td><?= e($alert['ulica']) ?></td> <td><?= e($alert['waga']) ?></td> <td><?= e($alert['dzien']) ?></td> <td><?= e($alert['czas']) ?></td> </tr><?phpendforeach; ?> </tbody> </table> </main> <asideid="photo"> <imgsrc="obraz2.jpg"alt="tir"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wazenie.php)
Krok 29: Skrypt 3: testowy pomiar
Kryteria:
R.5.7 Skrypt 3 wstawia rekord do bazy (sprawdzić w phpMyAdmin)
ok. 3 min
Polecenie z arkusza
Skrypt 3: Wysyła do bazy danych zapytanie 3
Zapytanie 3 jako zapytanie przygotowane: numer lokalizacji i rejestracja to stałe TEST_LOCATION_ID i TEST_PLATE na górze pliku, przekazane parametrami; losową wagę, datę i czas nadal liczy baza (RAND, CURDATE, CURTIME), jak w zapytaniu z arkusza. Sprawdzone uruchomieniem: każde wejście dopisuje do tabeli wagi jeden pomiar DW12345 z lokalizacji 5 (R.5.7).
Skrypt 3 stoi po Skrypcie 2, tak jak w arkuszu - pomiar dopisany przy tym wejściu pojawi się w tabeli przy następnym, jeśli wylosował więcej niż 5 t.
R.5.6 Skrypt 3 odświeża aktualną stronę co 10 sekund, np. header("refresh: 10");
ok. 3 min
Polecenie z arkusza
Odświeża aktualną stronę co 10 sekund.
header('Refresh: 10') wysyła nagłówek HTTP „wczytaj tę stronę ponownie za 10 sekund” (R.5.6). Każde odświeżenie to nowe wejście: Skrypt 3 dopisuje pomiar, Skrypt 2 pokazuje te powyżej 5 t, a czerwony jest zawsze ostatni, czyli najnowszy (obraz 3). Podgląd: strona po 5 odświeżeniach (2 z wylosowanych pomiarów przekroczyło 5 t).
Nagłówki HTTP idą przed treścią strony - po pierwszym wysłanym znaku HTML jest na nie za późno. Tu header() stoi w bloku PHP przed <!DOCTYPE html>, więc działa zawsze. Sprawdzone: 60 wejść z rzędu, tabela urosła do 35 alertów, a nagłówek Refresh przyszedł za każdym razem. W wersji podstawowej header() stoi w środku strony i działa tylko dzięki buforowi wyjścia XAMPP-a - po kilkudziesięciu odświeżeniach tabela go zapełnia i strona przestaje się odświeżać.
Typowe błędy
Pusta linia albo spacja przed <?php na początku pliku to już treść strony - nagłówek nie wyjdzie, a strona się nie odświeży.
wazenie.phpPHPzmienione: 3 linie
<?phpconst TEST_LOCATION_ID = 5;const TEST_PLATE = 'DW12345';functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES);}$streets = [];$alerts = [];$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=wazenietirow;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 2$streets = $pdo->query('SELECT ulica FROM lokalizacje')->fetchAll(PDO::FETCH_COLUMN);// Skrypt 2: zapytanie 4$alerts = $pdo->query('SELECT w.rejestracja, l.ulica, w.waga, w.dzien, w.czas FROM wagi AS w JOIN lokalizacje AS l ON l.id = w.lokalizacje_id WHERE w.waga > 5 ORDER BY w.id' )->fetchAll();// Skrypt 3: zapytanie 3$pdo->prepare('INSERT INTO wagi (lokalizacje_id, waga, rejestracja, dzien, czas) VALUES (:location, FLOOR(1 + RAND() * 10), :plate, CURDATE(), CURTIME())' )->execute(['location' => TEST_LOCATION_ID, 'plate' => TEST_PLATE]);$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać pomiarów. Spróbuj ponownie za chwilę.';}// Skrypt 3: odświeżenie strony za 10 sekundheader('Refresh: 10');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Ważenie samochodów ciężarowych</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="title"> <h1>Ważenie pojazdów we Wrocławiu</h1> </header> <headerid="sign"> <imgsrc="obraz1.png"alt="waga"> </header> <asideid="locations"> <h2>Lokalizacje wag</h2> <ol><?phpforeach ($streetsas$street): ?> <li>ulica <?= e($street) ?></li><?phpendforeach; ?> </ol> <h2>Kontakt</h2> <ahref="mailto:[email protected]">napisz</a> </aside> <main> <h2>Alerty</h2> <table> <thead> <tr> <thscope="col">rejestracja</th> <thscope="col">ulica</th> <thscope="col">waga</th> <thscope="col">dzień</th> <thscope="col">czas</th> </tr> </thead> <tbody><?phpforeach ($alertsas$alert): ?> <tr> <td><?= e($alert['rejestracja']) ?></td> <td><?= e($alert['ulica']) ?></td> <td><?= e($alert['waga']) ?></td> <td><?= e($alert['dzien']) ?></td> <td><?= e($alert['czas']) ?></td> </tr><?phpendforeach; ?> </tbody> </table> </main> <asideid="photo"> <imgsrc="obraz2.jpg"alt="tir"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wazenie.php)
Krok 31: Komunikat, gdy baza nie odpowiada; zamknięcie połączenia
Kryteria:
R.5.2 Ostatnią operacją na bazie jest jej zamknięcie (zgodne ze składnią)
ok. 2 min
Polecenie z arkusza
Na końcu jest zamykane połączenie z serwerem.
Połączenie zamyka $pdo = null zaraz po trzech skryptach - ostatnia operacja na bazie (R.5.2); żadne zapytanie nie zostaje w zmiennej, więc nic go nie przytrzymuje. Pod nagłówkiem „Alerty” akapit z $dbError, gdy catch coś do niego wpisał. Sprawdzone z błędną nazwą bazy: strona się wyświetla z komunikatem i pustymi listą i tabelą, bez nazwy bazy i kodu błędu SQL - a nagłówek odświeżania wychodzi dalej, więc strona sama spróbuje ponownie za 10 sekund.
wazenie.phpPHPzmienione: 3 linie
<?phpconst TEST_LOCATION_ID = 5;const TEST_PLATE = 'DW12345';functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES);}$streets = [];$alerts = [];$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=wazenietirow;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 2$streets = $pdo->query('SELECT ulica FROM lokalizacje')->fetchAll(PDO::FETCH_COLUMN);// Skrypt 2: zapytanie 4$alerts = $pdo->query('SELECT w.rejestracja, l.ulica, w.waga, w.dzien, w.czas FROM wagi AS w JOIN lokalizacje AS l ON l.id = w.lokalizacje_id WHERE w.waga > 5 ORDER BY w.id' )->fetchAll();// Skrypt 3: zapytanie 3$pdo->prepare('INSERT INTO wagi (lokalizacje_id, waga, rejestracja, dzien, czas) VALUES (:location, FLOOR(1 + RAND() * 10), :plate, CURDATE(), CURTIME())' )->execute(['location' => TEST_LOCATION_ID, 'plate' => TEST_PLATE]);$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać pomiarów. Spróbuj ponownie za chwilę.';}// Skrypt 3: odświeżenie strony za 10 sekundheader('Refresh: 10');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Ważenie samochodów ciężarowych</title> <linkrel="stylesheet"href="styl.css"></head><body> <headerid="title"> <h1>Ważenie pojazdów we Wrocławiu</h1> </header> <headerid="sign"> <imgsrc="obraz1.png"alt="waga"> </header> <asideid="locations"> <h2>Lokalizacje wag</h2> <ol><?phpforeach ($streetsas$street): ?> <li>ulica <?= e($street) ?></li><?phpendforeach; ?> </ol> <h2>Kontakt</h2> <ahref="mailto:[email protected]">napisz</a> </aside> <main> <h2>Alerty</h2><?phpif ($dbError !== ''): ?> <p><?= e($dbError) ?></p><?phpendif; ?> <table> <thead> <tr> <thscope="col">rejestracja</th> <thscope="col">ulica</th> <thscope="col">waga</th> <thscope="col">dzień</th> <thscope="col">czas</th> </tr> </thead> <tbody><?phpforeach ($alertsas$alert): ?> <tr> <td><?= e($alert['rejestracja']) ?></td> <td><?= e($alert['ulica']) ?></td> <td><?= e($alert['waga']) ?></td> <td><?= e($alert['dzien']) ?></td> <td><?= e($alert['czas']) ?></td> </tr><?phpendforeach; ?> </tbody> </table> </main> <asideid="photo"> <imgsrc="obraz2.jpg"alt="tir"> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wazenie.php)
Etap 5: Porównanie z wersją podstawową
Krok 32 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 32: Co jest inaczej i którą wersję wybrać na egzaminie
ORDER BY w.id - ostatni wiersz to najnowszy pomiar
baza
mysqli_connect, mysqli_fetch_row, $measurement[0]
PDO, FETCH_COLUMN, $alert['rejestracja'], INSERT z parametrami
odświeżanie
header() w środku strony (w dobrych praktykach przeniesione na górę)
header() przed <!DOCTYPE html> od początku
dane na stronie
wprost (w dobrych praktykach htmlspecialchars)
e() przy każdej wartości
Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i jest szybsza do napisania; jej header() w środku strony działa przez pierwsze minuty, a tyle trwa sprawdzenie. Z wersji nowoczesnej warto zapamiętać przynajmniej jedno: nagłówki HTTP (header(), przekierowania, ciasteczka) wysyła się przed jakąkolwiek treścią strony - wtedy działają na każdym serwerze, niezależnie od bufora wyjścia.
Krok 33 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 33: 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 (z 5T), obraz2.jpg (odbity, 350 px), przeglądarka.txt, styl.css, wazenie.php. Nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego. Przed oceną egzaminator usuwa pomiary dopisane przez stronę (DELETE FROM wagi WHERE id > 22 - uwaga w kluczu).
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.