Przejdź do głównej treści
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.

  1. 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).

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

    ok. 2 min
    Polecenie z arkusza

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

    Import → baza.sql. Dwie tabele z obrazu 1: 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.

  3. 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).

  4. 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.txt Tekst nowy plik
    -- Zapytanie 1
    UPDATE wagi
    SET dzien = CURDATE(), czas = CURTIME()
    WHERE lokalizacje_id IN (2, 3, 4);
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. 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.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    UPDATE wagi
    SET dzien = CURDATE(), czas = CURTIME()
    WHERE lokalizacje_id IN (2, 3, 4);
    
    -- Zapytanie 2
    SELECT ulica
    FROM lokalizacje;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. 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.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    UPDATE wagi
    SET dzien = CURDATE(), czas = CURTIME()
    WHERE lokalizacje_id IN (2, 3, 4);
    
    -- Zapytanie 2
    SELECT ulica
    FROM lokalizacje;
    
    -- Zapytanie 3
    INSERT INTO wagi (lokalizacje_id, waga, rejestracja, dzien, czas)
    VALUES (5, FLOOR(1 + RAND() * 10), 'DW12345', CURDATE(), CURTIME());
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. 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.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    UPDATE wagi
    SET dzien = CURDATE(), czas = CURTIME()
    WHERE lokalizacje_id IN (2, 3, 4);
    
    -- Zapytanie 2
    SELECT ulica
    FROM lokalizacje;
    
    -- Zapytanie 3
    INSERT INTO wagi (lokalizacje_id, waga, rejestracja, dzien, czas)
    VALUES (5, FLOOR(1 + RAND() * 10), 'DW12345', CURDATE(), CURTIME());
    
    -- Zapytanie 4
    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;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. 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
    Podgląd strony po kroku 8
  9. 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).

    Typowe błędy

    Odbij pionowo stawia ciężarówkę kołami do góry.

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

    Kryteria:

    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).

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

    Kryteria:

    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.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  12. 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.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  13. 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.

    wazenie.php PHP zmienione: 12 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="title">
        </header>
        <header id="sign">
        </header>
        <aside id="locations">
        </aside>
        <main>
        </main>
        <aside id="photo">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  14. Krok 14: Banery: nagłówek i znak

    ok. 1 min
    Polecenie z arkusza

    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.

    wazenie.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="title">
            <h1>Ważenie pojazdów we Wrocławiu</h1>
        </header>
        <header id="sign">
            <img src="obraz1.png" alt="waga">
        </header>
        <aside id="locations">
        </aside>
        <main>
        </main>
        <aside id="photo">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  15. Krok 15: Blok lewy: lokalizacje i kontakt

    Kryteria:

    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.

    wazenie.php PHP zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="title">
            <h1>Ważenie pojazdów we Wrocławiu</h1>
        </header>
        <header id="sign">
            <img src="obraz1.png" alt="waga">
        </header>
        <aside id="locations">
            <h2>Lokalizacje wag</h2>
            <ol>
            </ol>
            <h2>Kontakt</h2>
            <a href="mailto:[email protected]">napisz</a>
        </aside>
        <main>
        </main>
        <aside id="photo">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  16. Krok 16: Blok środkowy: tabela alertów

    Kryteria:

    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.

    wazenie.php PHP zmienione: 14 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="title">
            <h1>Ważenie pojazdów we Wrocławiu</h1>
        </header>
        <header id="sign">
            <img src="obraz1.png" alt="waga">
        </header>
        <aside id="locations">
            <h2>Lokalizacje wag</h2>
            <ol>
            </ol>
            <h2>Kontakt</h2>
            <a href="mailto:[email protected]">napisz</a>
        </aside>
        <main>
            <h2>Alerty</h2>
            <table>
                <thead>
                    <tr>
                        <th scope="col">rejestracja</th>
                        <th scope="col">ulica</th>
                        <th scope="col">waga</th>
                        <th scope="col">dzień</th>
                        <th scope="col">czas</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside id="photo">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  17. Krok 17: Blok prawy i stopka

    Kryteria: ,

    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.

    wazenie.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="title">
            <h1>Ważenie pojazdów we Wrocławiu</h1>
        </header>
        <header id="sign">
            <img src="obraz1.png" alt="waga">
        </header>
        <aside id="locations">
            <h2>Lokalizacje wag</h2>
            <ol>
            </ol>
            <h2>Kontakt</h2>
            <a href="mailto:[email protected]">napisz</a>
        </aside>
        <main>
            <h2>Alerty</h2>
            <table>
                <thead>
                    <tr>
                        <th scope="col">rejestracja</th>
                        <th scope="col">ulica</th>
                        <th scope="col">waga</th>
                        <th scope="col">dzień</th>
                        <th scope="col">czas</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside id="photo">
            <img src="obraz2.jpg" alt="tir">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  18. Etap 3: Styl CSS

    Kroki 18-25 · po tym etapie masz 27 z 37 kryteriów (pkt)

    Krok 18: Plik styl.css: zmienne i krój Tahoma

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    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).

    styl.css CSS nowy plik
    :root {
        --banner-bg: #519657;
        --block-bg: #E1E1E1;
        --accent: #80C683;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    Podgląd strony po tym kroku (wazenie.php)
  19. Krok 19: Układ bloków na siatce

    Kryteria:

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

    ok. 3 min
    Polecenie z arkusza

    Podział strony na bloki: na górze dwa bloki banera, poniżej 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 pole banner na całą szerokość, a swoją szerokość ustawi każdy w następnym kroku. Do tego czasu leżą w tym polu jeden na drugim.

    styl.css CSS zmienione: 29 linii
    :root {
        --banner-bg: #519657;
        --block-bg: #E1E1E1;
        --accent: #80C683;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "banner    banner banner"
            "locations alerts photo"
            "footer    footer footer";
    }
    
    header {
        grid-area: banner;
    }
    
    #locations {
        grid-area: locations;
    }
    
    main {
        grid-area: alerts;
    }
    
    #photo {
        grid-area: photo;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (wazenie.php)
  20. Krok 20: Banery: wspólne cechy i szerokości

    ok. 3 min
    Polecenie z arkusza

    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.

    styl.css CSS zmienione: 14 linii
    :root {
        --banner-bg: #519657;
        --block-bg: #E1E1E1;
        --accent: #80C683;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "banner    banner banner"
            "locations alerts photo"
            "footer    footer footer";
    }
    
    header {
        grid-area: banner;
        height: 150px;
        background-color: var(--banner-bg);
        color: white;
        text-align: center;
    }
    
    #title {
        width: 70%;
        justify-self: start;
    }
    
    #sign {
        width: 30%;
        justify-self: end;
    }
    
    #locations {
        grid-area: locations;
    }
    
    main {
        grid-area: alerts;
    }
    
    #photo {
        grid-area: photo;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (wazenie.php)
  21. Krok 21: Trzy bloki: tło, wysokość, przewijanie

    Kryteria: ,

    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.

    styl.css CSS zmienione: 7 linii
    :root {
        --banner-bg: #519657;
        --block-bg: #E1E1E1;
        --accent: #80C683;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "banner    banner banner"
            "locations alerts photo"
            "footer    footer footer";
    }
    
    header {
        grid-area: banner;
        height: 150px;
        background-color: var(--banner-bg);
        color: white;
        text-align: center;
    }
    
    #title {
        width: 70%;
        justify-self: start;
    }
    
    #sign {
        width: 30%;
        justify-self: end;
    }
    
    aside,
    main {
        height: 450px;
        background-color: var(--block-bg);
    }
    
    #locations {
        grid-area: locations;
    }
    
    main {
        grid-area: alerts;
        overflow: auto;
    }
    
    #photo {
        grid-area: photo;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (wazenie.php)
  22. Krok 22: Stopka

    Kryteria: ,

    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).

    styl.css CSS zmienione: 2 linie
    :root {
        --banner-bg: #519657;
        --block-bg: #E1E1E1;
        --accent: #80C683;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "banner    banner banner"
            "locations alerts photo"
            "footer    footer footer";
    }
    
    header {
        grid-area: banner;
        height: 150px;
        background-color: var(--banner-bg);
        color: white;
        text-align: center;
    }
    
    #title {
        width: 70%;
        justify-self: start;
    }
    
    #sign {
        width: 30%;
        justify-self: end;
    }
    
    aside,
    main {
        height: 450px;
        background-color: var(--block-bg);
    }
    
    #locations {
        grid-area: locations;
    }
    
    main {
        grid-area: alerts;
        overflow: auto;
    }
    
    #photo {
        grid-area: photo;
    }
    
    footer {
        grid-area: footer;
        background-color: var(--accent);
        text-align: center;
    }
    Podgląd strony po tym kroku (wazenie.php)
  23. Krok 23: Zdjęcie ciężarówki: szerokość i odstęp

    Kryteria:

    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).

    styl.css CSS zmienione: 5 linii
    :root {
        --banner-bg: #519657;
        --block-bg: #E1E1E1;
        --accent: #80C683;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "banner    banner banner"
            "locations alerts photo"
            "footer    footer footer";
    }
    
    header {
        grid-area: banner;
        height: 150px;
        background-color: var(--banner-bg);
        color: white;
        text-align: center;
    }
    
    #title {
        width: 70%;
        justify-self: start;
    }
    
    #sign {
        width: 30%;
        justify-self: end;
    }
    
    aside,
    main {
        height: 450px;
        background-color: var(--block-bg);
    }
    
    #locations {
        grid-area: locations;
    }
    
    main {
        grid-area: alerts;
        overflow: auto;
    }
    
    #photo {
        grid-area: photo;
    }
    
    footer {
        grid-area: footer;
        background-color: var(--accent);
        text-align: center;
    }
    
    #photo img {
        width: 100%;
        padding: 100px 0;
    }
    Podgląd strony po tym kroku (wazenie.php)
  24. 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).

    styl.css CSS zmienione: 12 linii
    :root {
        --banner-bg: #519657;
        --block-bg: #E1E1E1;
        --accent: #80C683;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "banner    banner banner"
            "locations alerts photo"
            "footer    footer footer";
    }
    
    header {
        grid-area: banner;
        height: 150px;
        background-color: var(--banner-bg);
        color: white;
        text-align: center;
    }
    
    #title {
        width: 70%;
        justify-self: start;
    }
    
    #sign {
        width: 30%;
        justify-self: end;
    }
    
    aside,
    main {
        height: 450px;
        background-color: var(--block-bg);
    }
    
    #locations {
        grid-area: locations;
    }
    
    main {
        grid-area: alerts;
        overflow: auto;
    }
    
    #photo {
        grid-area: photo;
    }
    
    footer {
        grid-area: footer;
        background-color: var(--accent);
        text-align: center;
    }
    
    #photo img {
        width: 100%;
        padding: 100px 0;
    }
    
    table {
        border: 1px solid var(--accent);
    }
    
    td {
        padding: 2px;
    }
    
    tr:hover {
        background-color: var(--accent);
    }
    Podgląd strony po tym kroku (wazenie.php)
  25. Krok 25: Ostatni wiersz z danymi na czerwono

    Kryteria:

    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.

    styl.css CSS zmienione: 4 linie
    :root {
        --banner-bg: #519657;
        --block-bg: #E1E1E1;
        --accent: #80C683;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "banner    banner banner"
            "locations alerts photo"
            "footer    footer footer";
    }
    
    header {
        grid-area: banner;
        height: 150px;
        background-color: var(--banner-bg);
        color: white;
        text-align: center;
    }
    
    #title {
        width: 70%;
        justify-self: start;
    }
    
    #sign {
        width: 30%;
        justify-self: end;
    }
    
    aside,
    main {
        height: 450px;
        background-color: var(--block-bg);
    }
    
    #locations {
        grid-area: locations;
    }
    
    main {
        grid-area: alerts;
        overflow: auto;
    }
    
    #photo {
        grid-area: photo;
    }
    
    footer {
        grid-area: footer;
        background-color: var(--accent);
        text-align: center;
    }
    
    #photo img {
        width: 100%;
        padding: 100px 0;
    }
    
    table {
        border: 1px solid var(--accent);
    }
    
    td {
        padding: 2px;
    }
    
    tr:hover {
        background-color: var(--accent);
    }
    
    tbody tr:last-child {
        background-color: red;
    }
    Podgląd strony po tym kroku (wazenie.php)
  26. Etap 4: Skrypty PHP

    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.

    • PDO z DSN mysql:host=localhost;dbname=wazenietirow;charset=utf8mb4, użytkownik root, puste hasło (R.5.1); ERRMODE_EXCEPTION, FETCH_ASSOC,
    • $pdo = null zamyka połączenie; trzy skrypty trafią przed tę linię,
    • try / catch - przy awarii ogólny komunikat, szczegóły w dzienniku serwera; e() zabezpiecza wartości wstawiane w HTML.

    Od tego kroku podgląd to wynik prawdziwego uruchomienia.

    wazenie.php PHP zmienione: 20 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $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,
        ]);
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać pomiarów. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="title">
            <h1>Ważenie pojazdów we Wrocławiu</h1>
        </header>
        <header id="sign">
            <img src="obraz1.png" alt="waga">
        </header>
        <aside id="locations">
            <h2>Lokalizacje wag</h2>
            <ol>
            </ol>
            <h2>Kontakt</h2>
            <a href="mailto:[email protected]">napisz</a>
        </aside>
        <main>
            <h2>Alerty</h2>
            <table>
                <thead>
                    <tr>
                        <th scope="col">rejestracja</th>
                        <th scope="col">ulica</th>
                        <th scope="col">waga</th>
                        <th scope="col">dzień</th>
                        <th scope="col">czas</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside id="photo">
            <img src="obraz2.jpg" alt="tir">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  27. Krok 27: Skrypt 1: lista ulic

    Kryteria: , , ,

    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).

    wazenie.php PHP zmienione: 7 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $streets = [];
    $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);
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać pomiarów. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="title">
            <h1>Ważenie pojazdów we Wrocławiu</h1>
        </header>
        <header id="sign">
            <img src="obraz1.png" alt="waga">
        </header>
        <aside id="locations">
            <h2>Lokalizacje wag</h2>
            <ol>
                <?php foreach ($streets as $street): ?>
                    <li>ulica <?= e($street) ?></li>
                <?php endforeach; ?>
            </ol>
            <h2>Kontakt</h2>
            <a href="mailto:[email protected]">napisz</a>
        </aside>
        <main>
            <h2>Alerty</h2>
            <table>
                <thead>
                    <tr>
                        <th scope="col">rejestracja</th>
                        <th scope="col">ulica</th>
                        <th scope="col">waga</th>
                        <th scope="col">dzień</th>
                        <th scope="col">czas</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <aside id="photo">
            <img src="obraz2.jpg" alt="tir">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  28. Krok 28: Skrypt 2: wiersze tabeli alertów

    Kryteria: ,

    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.php PHP zmienione: 19 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($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>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="title">
            <h1>Ważenie pojazdów we Wrocławiu</h1>
        </header>
        <header id="sign">
            <img src="obraz1.png" alt="waga">
        </header>
        <aside id="locations">
            <h2>Lokalizacje wag</h2>
            <ol>
                <?php foreach ($streets as $street): ?>
                    <li>ulica <?= e($street) ?></li>
                <?php endforeach; ?>
            </ol>
            <h2>Kontakt</h2>
            <a href="mailto:[email protected]">napisz</a>
        </aside>
        <main>
            <h2>Alerty</h2>
            <table>
                <thead>
                    <tr>
                        <th scope="col">rejestracja</th>
                        <th scope="col">ulica</th>
                        <th scope="col">waga</th>
                        <th scope="col">dzień</th>
                        <th scope="col">czas</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($alerts as $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>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <aside id="photo">
            <img src="obraz2.jpg" alt="tir">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  29. 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.

    wazenie.php PHP zmienione: 9 linii
    <?php
    const TEST_LOCATION_ID = 5;
    const TEST_PLATE = 'DW12345';
    
    function e(string $text): string
    {
        return htmlspecialchars($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ę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="title">
            <h1>Ważenie pojazdów we Wrocławiu</h1>
        </header>
        <header id="sign">
            <img src="obraz1.png" alt="waga">
        </header>
        <aside id="locations">
            <h2>Lokalizacje wag</h2>
            <ol>
                <?php foreach ($streets as $street): ?>
                    <li>ulica <?= e($street) ?></li>
                <?php endforeach; ?>
            </ol>
            <h2>Kontakt</h2>
            <a href="mailto:[email protected]">napisz</a>
        </aside>
        <main>
            <h2>Alerty</h2>
            <table>
                <thead>
                    <tr>
                        <th scope="col">rejestracja</th>
                        <th scope="col">ulica</th>
                        <th scope="col">waga</th>
                        <th scope="col">dzień</th>
                        <th scope="col">czas</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($alerts as $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>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <aside id="photo">
            <img src="obraz2.jpg" alt="tir">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  30. Krok 30: Skrypt 3: odświeżanie co 10 sekund

    Kryteria:

    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.php PHP zmienione: 3 linie
    <?php
    const TEST_LOCATION_ID = 5;
    const TEST_PLATE = 'DW12345';
    
    function e(string $text): string
    {
        return htmlspecialchars($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 sekund
    header('Refresh: 10');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="title">
            <h1>Ważenie pojazdów we Wrocławiu</h1>
        </header>
        <header id="sign">
            <img src="obraz1.png" alt="waga">
        </header>
        <aside id="locations">
            <h2>Lokalizacje wag</h2>
            <ol>
                <?php foreach ($streets as $street): ?>
                    <li>ulica <?= e($street) ?></li>
                <?php endforeach; ?>
            </ol>
            <h2>Kontakt</h2>
            <a href="mailto:[email protected]">napisz</a>
        </aside>
        <main>
            <h2>Alerty</h2>
            <table>
                <thead>
                    <tr>
                        <th scope="col">rejestracja</th>
                        <th scope="col">ulica</th>
                        <th scope="col">waga</th>
                        <th scope="col">dzień</th>
                        <th scope="col">czas</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($alerts as $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>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <aside id="photo">
            <img src="obraz2.jpg" alt="tir">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  31. 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.php PHP zmienione: 3 linie
    <?php
    const TEST_LOCATION_ID = 5;
    const TEST_PLATE = 'DW12345';
    
    function e(string $text): string
    {
        return htmlspecialchars($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 sekund
    header('Refresh: 10');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Ważenie samochodów ciężarowych</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header id="title">
            <h1>Ważenie pojazdów we Wrocławiu</h1>
        </header>
        <header id="sign">
            <img src="obraz1.png" alt="waga">
        </header>
        <aside id="locations">
            <h2>Lokalizacje wag</h2>
            <ol>
                <?php foreach ($streets as $street): ?>
                    <li>ulica <?= e($street) ?></li>
                <?php endforeach; ?>
            </ol>
            <h2>Kontakt</h2>
            <a href="mailto:[email protected]">napisz</a>
        </aside>
        <main>
            <h2>Alerty</h2>
            <?php if ($dbError !== ''): ?>
                <p><?= e($dbError) ?></p>
            <?php endif; ?>
            <table>
                <thead>
                    <tr>
                        <th scope="col">rejestracja</th>
                        <th scope="col">ulica</th>
                        <th scope="col">waga</th>
                        <th scope="col">dzień</th>
                        <th scope="col">czas</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($alerts as $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>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <aside id="photo">
            <img src="obraz2.jpg" alt="tir">
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wazenie.php)
  32. 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

    ok. 2 min

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

    wersja podstawowawersja nowoczesna
    układfloat: left banerów i bloków, clear: both stopkigrid-template-areas, kolumny 25% 50% 25%, banery width + justify-self
    blokitrzy <section><aside>, <main>, <aside>
    tabelajeden wiersz <th>, tr:last-child<thead> / <tbody>, tbody tr:last-child
    zapytanie 4bez sortowaniaORDER BY w.id - ostatni wiersz to najnowszy pomiar
    bazamysqli_connect, mysqli_fetch_row, $measurement[0]PDO, FETCH_COLUMN, $alert['rejestracja'], INSERT z parametrami
    odświeżanieheader() w środku strony (w dobrych praktykach przeniesione na górę)header() przed <!DOCTYPE html> od początku
    dane na stroniewprost (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.

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

  33. Etap 6: Oddanie pracy

    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).

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

Podgląd

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

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