Przejdź do głównej treści
INF.03-07-26.01-SGStyczeń 2026 · 49 kroków · ok. 99 min · 38 kryteriów z klucza

Rozwiązanie krok po kroku: Aplikacja internetowa stacji meteorologicznej (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza: stacja meteorologiczna z bazą pogoda, trzema ikonami wyciętymi z jednej grafiki i stroną index.php z tabelą temperatur lipca oraz średnią temperaturą miesiąca wybranego odnośnikiem. Obie wersje spełniają wszystkie 38 kryteriów klucza - różni je sposób pisania kodu, nie wynik.

Czym ta wersja różni się od podstawowej:

  • siatka CSS zamiast float + clear i flex: bloki nagłówkowe dostają szerokości z grid-template-columns: 20% 80%, sekcje z repeat(2, 50%), a kolory z arkusza stoją w zmiennych CSS,
  • tabela z <thead> i <tbody>, odnośniki miesięcy w bloku <nav>, generowane pętlą z tablicy PHP - numer w adresie i nazwa miesiąca nie mogą się rozjechać,
  • zapytania z aliasami tabel (AS m, AS p) i zapytanie 4 w kolejności kalendarza,
  • PHP na PDO z wyjątkami: obie operacje na bazie na górze pliku, połączenie zamknięte przed HTML-em, a w HTML tylko pętla foreach i proste warunki,
  • ikona pogody z funkcji weatherIcon() z wyrażeniem match, z tekstem alternatywnym,
  • numer miesiąca z adresu sprawdzony filter_var i wstawiony do zapytania przygotowanego; wartości z bazy wypisywane przez escape() (htmlspecialchars).

Kroki nadal idą zdanie po zdaniu za treścią arkusza. Podgląd w każdym kroku z PHP to wynik prawdziwego uruchomienia kodu z tego kroku w PHP 8.2 na bazie z arkusza; od skryptu 2 pokazuje stan po kliknięciu „Luty”, jak na ilustracji 5. Baza i obraz pogoda.png pochodzą z prawdziwego archiwum pliki9-2 (załącznik do arkusza, hasło w treści zadania).

Jak liczą się punkty. Klucz ma 38 kryteriów (R.1.1 - R.5.9), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 29 punktów. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.

  1. Etap 1: Baza danych

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

    Krok 1: Baza danych pogoda

    ok. 2 min
    Polecenie z arkusza

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

    Archiwum pliki9-2.7z ma hasło &M3t3oPogoDa% (cyfry 3 zamiast „e”, znaki & i % na brzegach); w środku są pogoda.sql i pogoda.png. W phpMyAdmin: Nowa, nazwa pogoda, porównywanie utf8_polish_ci, albo w zakładce SQL:

    CREATE DATABASE pogoda CHARACTER SET utf8 COLLATE utf8_polish_ci;
    
    Typowe błędy

    Skrypt łączy się z bazą po nazwie - literówka w pogoda zabiera także punkt za połączenie (R.5.1).

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

    ok. 2 min
    Polecenie z arkusza

    Z rozpakowanego archiwum zaimportuj tabele z pliku pogoda.sql do utworzonej bazy

    Zaznacz bazę pogoda, potem Import → pogoda.sql → Wykonaj. Powstają trzy tabele z ilustracji 1: miejscowosc, miesiace i pomiary. Każdy wiersz pomiary to temperatura jednej miejscowości w jednym miesiącu - klucze obce id_miejscowosc i id_miesiac wskazują, której i w którym.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych pogoda - plik import.png

    ok. 2 min
    Polecenie z arkusza

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

    Print Screen (cały ekran z paskiem zadań), wklej do edytora grafiki i zapisz jako import.png: komunikat o udanym imporcie i trzy tabele w drzewku bazy. Zrzuty zapytań w tym arkuszu są w formacie JPEG - tylko import idzie do PNG.

    Typowe błędy

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

  4. Krok 4: Zapytanie 1: średnia temperatura lipca

    Kryteria: ,

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

    R.1.3 Wykonane zapytanie 1 liczące średnią temperaturę dla miesiąca o id = 7. Wyliczona wartość jest zaokrąglona do dwóch miejsc po przecinku. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT ROUND(AVG(temperatura), 2) FROM pomiary WHERE id_miesiac = 7; oraz na zrzucie widoczna jest jedynie wartość 29,33

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: liczące średnią temperaturę dla miesiąca o identyfikatorze 7. Wyliczona wartość jest zaokrąglona do dwóch miejsc po przecinku

    • ROUND(AVG(temperatura), 2) - średnia z pomiarów zaokrąglona do dwóch miejsc; ROUND obejmuje całe AVG,
    • AS srednia - alias: kolumna wyniku ma czytelną nazwę zamiast całego wyrażenia,
    • WHERE id_miesiac = 7 - tylko lipiec.

    Wynik: jedna wartość, 29.33. Zapytanie rozpisane w kilku liniach - SQL nie zwraca uwagi na znaki nowej linii, a każda klauzula stoi osobno. Zapisz je w kwerendy.txt, zrzut jako kw1.jpg.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT ROUND(AVG(temperatura), 2) AS srednia
    FROM pomiary
    WHERE id_miesiac = 7;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: nowa miejscowość Kijów

    Kryteria:

    R.1.4 Wykonane zapytanie 2 wstawiające do tabeli miejscowosc wiersz z danymi „Kijów”, „Ukraina”. Klucz nadawany automatycznie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO miejscowosc(nazwa, kraj) VALUES ('Kijów','Ukraina'); (klucz główny pominięty lub NULL) oraz na zrzucie widoczny jest komunikat o poprawnym dodaniu rekordu lub tabela ze wstawionym rekordem

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: wstawiające do tabeli miejscowosc wiersz z danymi „Kijów”, „Ukraina”. Klucz nadawany automatycznie

    Lista kolumn (nazwa, kraj) bez id - klucz nada baza (AUTO_INCREMENT), tu 11. Jawna lista kolumn chroni też przed zmianą struktury tabeli: gdyby ktoś dodał kolumnę, INSERT bez listy przestałby działać. Zrzut z komunikatem albo z tabelą po wstawieniu: kw2.jpg.

    Typowe błędy

    Wpisanie klucza ręcznie (VALUES (11, ...)) przeczy wymaganiu „klucz nadawany automatycznie” - klucz trzeba pominąć albo podać NULL.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT ROUND(AVG(temperatura), 2) AS srednia
    FROM pomiary
    WHERE id_miesiac = 7;
    
    -- Zapytanie 2
    INSERT INTO miejscowosc (nazwa, kraj)
    VALUES ('Kijów', 'Ukraina');
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: miasta i temperatury lipca

    Kryteria:

    R.1.5 Wykonane zapytanie 3 wybierające jedynie nazwy miast i krajów z tabeli miejscowosc oraz odpowiadające im temperatury w miesiącu o id = 7. Należy posłużyć się relacją. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, kraj, temperatura FROM miejscowosc JOIN pomiary ON miejscowosc.id = id_miejscowosc WHERE id_miesiac = 7; oraz na zrzucie widocznych jest 6 rekordów z danymi: Warszawa Polska 25, Ostrawa Czechy 30, Gdańsk Polska 33, Brno Czechy 21, Praga Czechy 32, Hamburg Niemcy 35

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie nazwy miast i krajów z tabeli miejscowosc oraz odpowiadające im temperatury w miesiącu o identyfikatorze 7. Należy posłużyć się relacją

    • FROM miejscowosc AS m JOIN pomiary AS p - aliasy tabel: krótkie m. i p. zamiast pełnych nazw przed każdym polem,
    • ON p.id_miejscowosc = m.id - relacja: klucz obcy pomiaru wskazuje miejscowość,
    • WHERE p.id_miesiac = 7 - tylko lipiec,
    • SELECT m.nazwa, m.kraj, p.temperatura - trzy pola, każde z prefiksem tabeli.

    Wynik: sześć wierszy - Warszawa 25, Ostrawa 30, Gdańsk 33, Brno 21, Praga 32, Hamburg 35. To zawartość tabeli z ilustracji 3. Zrzut: kw3.jpg.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT ROUND(AVG(temperatura), 2) AS srednia
    FROM pomiary
    WHERE id_miesiac = 7;
    
    -- Zapytanie 2
    INSERT INTO miejscowosc (nazwa, kraj)
    VALUES ('Kijów', 'Ukraina');
    
    -- Zapytanie 3
    SELECT m.nazwa, m.kraj, p.temperatura
    FROM miejscowosc AS m
    JOIN pomiary AS p ON p.id_miejscowosc = m.id
    WHERE p.id_miesiac = 7;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: średnie temperatury miesięcy

    Kryteria:

    R.1.6 Wykonane zapytanie 4 wybierające jedynie nazwy miesięcy oraz odpowiadające im średnie temperatury. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, AVG(temperatura) FROM miesiace JOIN pomiary ON miesiace.id = id_miesiac GROUP BY nazwa; oraz na zrzucie widocznych jest dokładnie 12 miesięcy ze średnimi temperaturami

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie nazwy miesięcy z tabeli miesiace oraz odpowiadające im średnie temperatury. Należy posłużyć się relacją

    • FROM miesiace AS ms JOIN pomiary AS p ON p.id_miesiac = ms.id - relacja miesiąc - pomiar (alias ms, bo m mogłoby się mylić z miejscowością),
    • GROUP BY ms.id, ms.nazwa - grupa to jeden miesiąc; grupowanie po identyfikatorze nie skleiłoby dwóch miesięcy o tej samej nazwie, a nazwa jest dopisana, bo stoi w SELECT,
    • AVG(p.temperatura) AS srednia - średnia w każdej grupie,
    • ORDER BY ms.id - kolejność kalendarza zamiast alfabetycznej.

    Wynik: dokładnie 12 wierszy, od stycznia do grudnia. Średnie mają cztery miejsca po przecinku; arkusz nie każe ich zaokrąglać. Zrzut: kw4.jpg.

    kwerendy.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT ROUND(AVG(temperatura), 2) AS srednia
    FROM pomiary
    WHERE id_miesiac = 7;
    
    -- Zapytanie 2
    INSERT INTO miejscowosc (nazwa, kraj)
    VALUES ('Kijów', 'Ukraina');
    
    -- Zapytanie 3
    SELECT m.nazwa, m.kraj, p.temperatura
    FROM miejscowosc AS m
    JOIN pomiary AS p ON p.id_miejscowosc = m.id
    WHERE p.id_miesiac = 7;
    
    -- Zapytanie 4
    SELECT ms.nazwa, AVG(p.temperatura) AS srednia
    FROM miesiace AS ms
    JOIN pomiary AS p ON p.id_miesiac = ms.id
    GROUP BY ms.id, ms.nazwa
    ORDER BY ms.id;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

    Kroki 8-27 · po tym etapie masz 17 z 38 kryteriów (pkt)

    Krok 8: Grafika slonce.png: kadr do kształtu słońca

    Kryteria:

    R.2.2 Przynajmniej jedna grafika została przycięta dokładnie do kształtu: kształt nie został obcięty oraz nie ma wolnej przestrzeni z żadnej strony (sprawdzić bez powiększania grafiki)

    ok. 4 min
    Polecenie z arkusza

    Z pliku pogoda.png należy wyciąć kształty pełnego słońca, słońca za chmurą i deszczu. Kształty mają wypełniać całą przestrzeń obrazu, tak jak zaznaczony obszar kadrowania na ilustracji 2a. Następnie każdy obraz należy przeskalować do wymiarów dokładnie 100 px na 100 px. Zapisać grafiki jako slonce.png, chmury.png, deszcz.png, zgodnie z ilustracjami 2b, 2c, 2d z zachowaniem przezroczystości.

    Grafika jest taka sama w obu wersjach. pogoda.png ma przezroczyste tło i sześć ikon. W GIMP-ie zaznacz prostokątem słońce z lewego górnego rogu, Obraz → Przytnij do zaznaczenia, a potem Obraz → Przytnij do zawartości - GIMP obetnie przezroczyste pasy, aż promienie dotkną wszystkich czterech krawędzi. Kadr: 211 × 212 px. Obraz → Skaluj obraz z rozpiętym łańcuchem, 100 × 100 px, i Plik → Wyeksportuj jako slonce.png - PNG zachowa przezroczystość.

    Typowe błędy

    Kadr „na oko” z marginesem zostawia wolną przestrzeń przy brzegach - klucz sprawdza, czy kształt dotyka krawędzi i nie jest obcięty (R.2.2).

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Grafika chmury.png: słońce za chmurą

    ok. 3 min
    Polecenie z arkusza

    Zapisać grafiki jako slonce.png, chmury.png, deszcz.png, zgodnie z ilustracjami 2b, 2c, 2d z zachowaniem przezroczystości.

    Ikona z prawego górnego rogu. Kadr 229 × 212 px nie jest kwadratem, więc skalowanie do 100 × 100 lekko spłaszcza ją w poziomie - arkusz wymaga dokładnie tych wymiarów, a na ilustracji 2c widać to samo.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 9
  10. Krok 10: Grafika deszcz.png: chmura z deszczem

    Kryteria:

    R.2.1 Grafiki slonce.png , chmury.png , deszcz.png przedstawiają kolejno słońce, słońce za chmurą i chmurę z deszczem. Rozmiary przynajmniej jednej grafiki to dokładnie 100 px na 100 px oraz zachowana przezroczystość

    ok. 3 min
    Polecenie z arkusza

    Zapisać grafiki jako slonce.png, chmury.png, deszcz.png, zgodnie z ilustracjami 2b, 2c, 2d z zachowaniem przezroczystości.

    Chmura z kroplami z prawej kolumny środkowego rzędu, kadr 185 × 158 px, po skalowaniu 100 × 100. Komplet: słońce, słońce za chmurą i deszcz, każda ikona 100 × 100 z przezroczystym tłem (R.2.1).

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

    ok. 2 min
    Polecenie z arkusza

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

    Deklaracja <!DOCTYPE html> i szkielet. Rozszerzenie .php, bo dojdą skrypty - strona otwiera się przez http://localhost/... z folderu htdocs XAMPP-a.

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

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Zadeklarowany polski język zawartości witryny

    Atrybut lang="pl" - przeglądarka i czytnik ekranu wiedzą, w jakim języku jest treść (dzielenie wyrazów, wymowa). Z deklaracją HTML5 to jedno kryterium.

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

    ok. 1 min
    Polecenie z arkusza

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

    <meta charset="UTF-8"> jako pierwszy element <head> - przeglądarka musi znać kodowanie, zanim przeczyta pierwszy polski znak.

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

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

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

    <title> w <head>. Kodowanie i tytuł to razem jedno kryterium.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Podpięcie arkusza stylów

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

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

    Plik styl.css (nie style.css). Całe formatowanie pochodzi z tego pliku - bez atrybutów style w HTML-u.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Bloki: dwa nagłówki, główny z dwiema sekcjami, stopka

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 4

    Klucz wymaga wprost dwóch <header>, <main> z dwoma <section> i <footer> (R.2.5). W tej wersji bloki nie dostają identyfikatorów: szerokości ustawi siatka CSS według kolejności bloków w kodzie (pierwszy nagłówek - pierwsza kolumna), więc nie trzeba ich nazywać tylko po to, żeby nadać width.

    index.php PHP zmienione: 12 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
        </header>
        <header>
        </header>
        <main>
            <section>
            </section>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  17. Krok 17: Blok nagłówkowy 1: obraz słońca

    Kryteria:

    R.2.7 W bloku nagłówkowym 1 umieszczono grafikę ze słońcem z tekstem alternatywnym "Słonecznie"

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego 1: obraz slonce.png z tekstem alternatywnym „Słonecznie”

    <img> z atrybutem alt - tekst zastępuje obraz, gdy go zabraknie, i czyta go czytnik ekranu. HTML5 nie potrzebuje ukośnika na końcu (<img ...>, nie <img ... />).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
        </header>
        <main>
            <section>
            </section>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Blok nagłówkowy 2: tytuł

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego 2: nagłówek pierwszego stopnia o treści „Pogoda w Europie”

    Pierwszego stopnia, czyli <h1> - jedyny na stronie, jak powinno być w dobrze zbudowanym dokumencie.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
            <section>
            </section>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  19. Krok 19: Sekcja lewa: nagłówek

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku głównego: blok sekcji lewej i blok sekcji prawej. Zawartość bloku sekcji lewej: Nagłówek drugiego stopnia o treści: „Temperatury w lipcu”

    <h2> na początku pierwszej sekcji.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
            <section>
                <h2>Temperatury w lipcu</h2>
            </section>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  20. Krok 20: Tabela: wiersz nagłówkowy w thead

    Kryteria:

    R.2.9 Pierwszy wiersz tabeli zawiera komórki nagłówkowe zastosowane znaczniki <tr> i <th>

    ok. 2 min
    Polecenie z arkusza

    Tabela o czterech kolumnach. Pierwszy wiersz zawiera komórki nagłówkowe o treści „Miasto”, „Kraj”, „Temperatura”, „Pogoda”. Tabela jest wypełniona skryptem 1

    Pierwszy wiersz <tr> z czterema komórkami <th> stoi w <thead> (część nagłówkowa tabeli), a wiersze z danymi trafią do pustego na razie <tbody> - tam wypisze je skrypt 1. Atrybut scope="col" mówi czytnikowi ekranu, że komórka opisuje kolumnę pod sobą. Klucz sprawdza <tr> i <th> w pierwszym wierszu (R.2.9) - są.

    index.php PHP zmienione: 12 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
            </section>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  21. Krok 21: Sekcja prawa: nagłówek

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku sekcji prawej: Nagłówek drugiego stopnia o treści: „Średnie temperatury w roku”

    To samo w drugiej sekcji.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Odnośnik „Styczeń” z identyfikatorem 1 - z tablicy

    Kryteria:

    R.3.4 Przynajmniej jeden odnośnik wysyła identyfikator miesiąca (styczeń: 1, luty: 2 itd.) do tego samego pliku, np. <a href="index.php?id=1">Styczeń</a>

    ok. 3 min
    Polecenie z arkusza

    Dwanaście odnośników dla wszystkich miesięcy roku: „Styczeń” prowadzi do adresu index.php i przesyła metodą GET identyfikator miesiąca równy 1

    Dwanaście odnośników różni się tylko liczbą i nazwą, więc zamiast przepisywać dwanaście linii powstaje tablica $months na samej górze pliku (klucz = identyfikator z tabeli miesiace, wartość = nazwa) i jedną pętlę foreach, która dla każdej pary wypisuje <a href="index.php?id=1">Styczeń</a>. Pętla stoi w HTML-u w składni alternatywnej (foreach (...): ... endforeach;), a <?= ... ?> to skrót od <?php echo ... ?>.

    Parametr GET to dopisek po znaku zapytania: index.php?id=1 (R.3.4). Odnośniki stoją w bloku <nav> - to grupa nawigacji, klucz dopuszcza bloki pomocnicze. Od tego kroku plik zawiera PHP, więc podgląd to wynik jego uruchomienia.

    index.php PHP zmienione: 8 linii
    <?php
    $months = [1 => 'Styczeń'];
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  23. Krok 23: Odnośnik „Luty”

    ok. 1 min
    Polecenie z arkusza

    „Luty” prowadzi do adresu index.php i przesyła metodą GET identyfikator miesiąca równy 2

    Jeden nowy element tablicy: 2 => 'Luty'. Pętla sama zrobi z niego odnośnik index.php?id=2 - numer i nazwa stoją obok siebie w jednej parze, więc nie mogą się rozjechać.

    index.php PHP zmienione: 1 linia
    <?php
    $months = [1 => 'Styczeń', 2 => 'Luty'];
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Odnośnik „Marzec”

    ok. 1 min
    Polecenie z arkusza

    „Marzec” prowadzi do adresu index.php i przesyła metodą GET identyfikator miesiąca równy 3

    Trzecia para, 3 => 'Marzec'.

    index.php PHP zmienione: 1 linia
    <?php
    $months = [1 => 'Styczeń', 2 => 'Luty', 3 => 'Marzec'];
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  25. Krok 25: Odnośniki od „Kwiecień” do „Grudzień”

    Kryteria:

    R.3.3 Utworzono 12 odnośników z nazwami miesięcy (dopuszczalne literówki w nazwach miesięcy)

    ok. 2 min
    Polecenie z arkusza

    „Grudzień” prowadzi do adresu index.php i przesyła metodą GET identyfikator miesiąca równy 12

    Arkusz wypisuje trzy pierwsze miesiące i grudzień, resztę zastępuje wielokropkiem. Tablica ma teraz 12 par, każda w osobnej linii (przecinek po ostatnim elemencie jest w PHP dozwolony i ułatwia dopisywanie). Pętla wypisuje 12 odnośników (R.3.3).

    Typowe błędy

    Kolejność par nie ma znaczenia dla adresów - liczy się klucz przy nazwie. Zła para (5 => 'Kwiecień') daje jednak zły miesiąc, tak samo jak w wersji z ręcznie pisanymi odnośnikami.

    index.php PHP zmienione: 14 linii
    <?php
    $months = [
        1 => 'Styczeń',
        2 => 'Luty',
        3 => 'Marzec',
        4 => 'Kwiecień',
        5 => 'Maj',
        6 => 'Czerwiec',
        7 => 'Lipiec',
        8 => 'Sierpień',
        9 => 'Wrzesień',
        10 => 'Październik',
        11 => 'Listopad',
        12 => 'Grudzień',
    ];
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  26. Krok 26: Paragraf pod odnośnikami

    ok. 1 min
    Polecenie z arkusza

    Paragraf o treści „Średnia temperatura dla wybranego miesiąca wynosi”

    Akapit <p> pod blokiem odnośników - pod nim stanie wynik skryptu 2.

    index.php PHP zmienione: 1 linia
    <?php
    $months = [
        1 => 'Styczeń',
        2 => 'Luty',
        3 => 'Marzec',
        4 => 'Kwiecień',
        5 => 'Maj',
        6 => 'Czerwiec',
        7 => 'Lipiec',
        8 => 'Sierpień',
        9 => 'Wrzesień',
        10 => 'Październik',
        11 => 'Listopad',
        12 => 'Grudzień',
    ];
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
                <p>Średnia temperatura dla wybranego miesiąca wynosi</p>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  27. Krok 27: Stopka

    Kryteria:

    R.2.6 W bloku nagłówkowym 2 znajduje się <h1>: "Pogoda w Europie", w bloku sekcji lewej <h2>: "Temperatury w lipcu", w bloku sekcji prawej <h2>: "Średnie temperatury w roku" i <p> "Średnia temperatura dla wybranego miesiąca wynosi" oraz w stopce <p> z numerem zdającego

    ok. 1 min
    Polecenie z arkusza

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

    Akapit w <footer>. Na egzaminie po dwukropku wpisujesz swój numer zdającego (PESEL) - tutaj stoi imię i nazwisko autora rozwiązania. Komplet nagłówków i akapitów z klucza: <h1>, dwa <h2>, akapit w sekcji prawej i w stopce (R.2.6).

    index.php PHP zmienione: 1 linia
    <?php
    $months = [
        1 => 'Styczeń',
        2 => 'Luty',
        3 => 'Marzec',
        4 => 'Kwiecień',
        5 => 'Maj',
        6 => 'Czerwiec',
        7 => 'Lipiec',
        8 => 'Sierpień',
        9 => 'Wrzesień',
        10 => 'Październik',
        11 => 'Listopad',
        12 => 'Grudzień',
    ];
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
                <p>Średnia temperatura dla wybranego miesiąca wynosi</p>
            </section>
        </main>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  28. Etap 3: Styl CSS

    Kroki 28-38 · po tym etapie masz 28 z 38 kryteriów (pkt)

    Krok 28: Plik styl.css: zmienne kolorów, krój i wyrównanie

    Kryteria:

    R.4.1 dla selektora body: krój czcionki Georgia, wyrównanie tekstu do środka

    ok. 2 min
    Polecenie z arkusza

    Dla selektora ciała strony: krój czcionki Georgia, wyrównanie tekstu do środka

    Arkusz używa dwóch kolorów w kilku miejscach, więc najpierw trafiają do zmiennych CSS w :root (cały dokument): --green i --lime. Kolejne reguły będą pisać var(--green) - zmiana odcienia to jedna linia, a nazwa mówi więcej niż #388E3C.

    Reguła body (selektor znacznika - uwaga arkusza): krój Georgia z rodziną zapasową serif na wypadek komputera bez tego kroju i środkowanie. Oba ustawienia dziedziczą elementy strony (R.4.1).

    styl.css CSS nowy plik
    :root {
        --green: #388E3C;
        --lime: #DCE775;
    }
    
    body {
        font-family: Georgia, serif;
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  29. Krok 29: Bloki nagłówkowe: tło, kolor i wysokość

    ok. 1 min
    Polecenie z arkusza

    Wspólne dla obu bloków nagłówkowych: kolor tła #388E3C, biały kolor czcionki, wysokość 100 px

    Selektor header obejmuje oba bloki naraz. W inspektorze kolor tła pokazuje się jako rgb(56, 142, 60) - to ta sama wartość co #388E3C ze zmiennej.

    styl.css CSS zmienione: 6 linii
    :root {
        --green: #388E3C;
        --lime: #DCE775;
    }
    
    body {
        font-family: Georgia, serif;
        text-align: center;
    }
    
    header {
        background-color: var(--green);
        color: white;
        height: 100px;
    }
    Podgląd strony po tym kroku (index.php)
  30. Krok 30: Bloki nagłówkowe: szerokości w siatce

    ok. 2 min
    Polecenie z arkusza

    Dodatkowo szerokość pierwszego bloku nagłówkowego 20%, drugiego 80%

    <body> staje się siatką (display: grid) o dwóch kolumnach: grid-template-columns: 20% 80%. Dzieci siatki układają się w komórkach po kolei: pierwszy <header> w kolumnie 20%, drugi w kolumnie 80% - stoją obok siebie bez float. Klucz wprost dopuszcza szerokości z grid-template-columns (R.4.5).

    Blok główny i stopka też trafiają na razie do kolejnych komórek (każdy w jednej kolumnie) - na całą szerokość rozciągnie je krok o układzie.

    styl.css CSS zmienione: 2 linie
    :root {
        --green: #388E3C;
        --lime: #DCE775;
    }
    
    body {
        font-family: Georgia, serif;
        text-align: center;
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header {
        background-color: var(--green);
        color: white;
        height: 100px;
    }
    Podgląd strony po tym kroku (index.php)
  31. Krok 31: Sekcje: tło, szerokość i wysokość

    Kryteria:

    R.4.4 wysokość: oba bloki nagłówkowe - 100px, oba bloki sekcji - 600px

    ok. 2 min
    Polecenie z arkusza

    Wspólne dla bloków sekcji lewej i prawej: kolor tła #DCE775, szerokość 50%, wysokość 600 px

    Blok główny to druga siatka: repeat(2, 50%) to skrót od 50% 50% - dwie kolumny po połowie, w każdej jedna sekcja. Szerokość sekcji wynika więc z siatki, a reguła section ma tylko tło i wysokość. Komplet wysokości z klucza (R.4.4).

    styl.css CSS zmienione: 10 linii
    :root {
        --green: #388E3C;
        --lime: #DCE775;
    }
    
    body {
        font-family: Georgia, serif;
        text-align: center;
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header {
        background-color: var(--green);
        color: white;
        height: 100px;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(2, 50%);
    }
    
    section {
        background-color: var(--lime);
        height: 600px;
    }
    Podgląd strony po tym kroku (index.php)
  32. Krok 32: Stopka

    Kryteria:

    R.4.2 kolor tła: oba bloki nagłówkowe i stopka - #388E3C, blok sekcji lewej i prawej - #DCE775

    ok. 1 min
    Polecenie z arkusza

    Dla bloku stopki: kolor tła #388E3C, biały kolor czcionki

    Te same zmienne co w blokach nagłówkowych. Kolory tła z klucza są kompletne (R.4.2).

    styl.css CSS zmienione: 5 linii
    :root {
        --green: #388E3C;
        --lime: #DCE775;
    }
    
    body {
        font-family: Georgia, serif;
        text-align: center;
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header {
        background-color: var(--green);
        color: white;
        height: 100px;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(2, 50%);
    }
    
    section {
        background-color: var(--lime);
        height: 600px;
    }
    
    footer {
        background-color: var(--green);
        color: white;
    }
    Podgląd strony po tym kroku (index.php)
  33. Krok 33: Układ bloków zgodny z ilustracją 4

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 4

    grid-column: 1 / -1 rozciąga element od pierwszej do ostatniej linii siatki (-1 to ostatnia linia, niezależnie od liczby kolumn). Blok główny i stopka zajmują więc każdy cały wiersz, a nagłówki dzielą pierwszy wiersz 20% / 80% - układ z ilustracji 4 (R.3.1). Nie ma float ani clear, więc nie ma też ryzyka, że blok główny „wjedzie” pod nagłówki.

    Typowe błędy

    Bez tej reguły blok główny zostaje w kolumnie 20%, a stopka w kolumnie 80% - sekcje są wąskie, a punkt za układ przepada.

    styl.css CSS zmienione: 5 linii
    :root {
        --green: #388E3C;
        --lime: #DCE775;
    }
    
    body {
        font-family: Georgia, serif;
        text-align: center;
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header {
        background-color: var(--green);
        color: white;
        height: 100px;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(2, 50%);
    }
    
    section {
        background-color: var(--lime);
        height: 600px;
    }
    
    footer {
        background-color: var(--green);
        color: white;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    Podgląd strony po tym kroku (index.php)
  34. Krok 34: Tabela

    Kryteria: ,

    R.4.5 szerokość: pierwszy blok nagłówkowy - 20%, drugi - 80%, oba bloki sekcji - 50% (width lub grid-template-columns lub flex) , selektor table - 80%

    R.4.7 styl obramowania dla tabeli - 2px dashed #388E3C

    ok. 2 min
    Polecenie z arkusza

    Dla selektora tabeli: szerokość 80%, marginesy zewnętrzne automatycznie wyliczane przez przeglądarkę, obramowanie linią kreskowaną o szerokości 2 px i kolorze #388E3C

    margin: auto przy ustalonej szerokości dzieli wolne miejsce po równo - tabela stoi na środku sekcji. Linia kreskowana to dashed. Skrót border łączy grubość, styl i kolor (tu ze zmiennej). Szerokości z klucza są kompletne (R.4.5).

    styl.css CSS zmienione: 6 linii
    :root {
        --green: #388E3C;
        --lime: #DCE775;
    }
    
    body {
        font-family: Georgia, serif;
        text-align: center;
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header {
        background-color: var(--green);
        color: white;
        height: 100px;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(2, 50%);
    }
    
    section {
        background-color: var(--lime);
        height: 600px;
    }
    
    footer {
        background-color: var(--green);
        color: white;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    table {
        width: 80%;
        margin: auto;
        border: 2px dashed var(--green);
    }
    Podgląd strony po tym kroku (index.php)
  35. Krok 35: Obrazy w tabeli: wysokość 70 px

    Kryteria:

    R.4.8 wysokość jedynie dla obrazów w tabeli - 70px

    ok. 1 min
    Polecenie z arkusza

    Jedynie dla obrazów w tabeli: wysokość 70 px

    „Jedynie w tabeli” to selektor potomka table img - słońce w nagłówku zostaje 100 px.

    styl.css CSS zmienione: 4 linie
    :root {
        --green: #388E3C;
        --lime: #DCE775;
    }
    
    body {
        font-family: Georgia, serif;
        text-align: center;
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header {
        background-color: var(--green);
        color: white;
        height: 100px;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(2, 50%);
    }
    
    section {
        background-color: var(--lime);
        height: 600px;
    }
    
    footer {
        background-color: var(--green);
        color: white;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    table {
        width: 80%;
        margin: auto;
        border: 2px dashed var(--green);
    }
    
    table img {
        height: 70px;
    }
    Podgląd strony po tym kroku (index.php)
  36. Krok 36: Odnośniki

    Kryteria: ,

    R.4.3 kolor czcionki: oba bloki nagłówkowe i stopka - biały, selektor a - #388E3C

    R.4.9 styl dla odnośnika: czcionka pochylona o rozmiarze 200%

    ok. 2 min
    Polecenie z arkusza

    Dla selektora odnośnika: kolor czcionki #388E3C, marginesy zewnętrzne 20 px, czcionka pochylona o rozmiarze 200%

    Selektor znacznika a (uwaga arkusza). Pochylenie to font-style: italic. Odnośnik jest elementem liniowym, więc margin rozsuwa odnośniki w poziomie. Kolory czcionki z klucza są kompletne (R.4.3).

    styl.css CSS zmienione: 7 linii
    :root {
        --green: #388E3C;
        --lime: #DCE775;
    }
    
    body {
        font-family: Georgia, serif;
        text-align: center;
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header {
        background-color: var(--green);
        color: white;
        height: 100px;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(2, 50%);
    }
    
    section {
        background-color: var(--lime);
        height: 600px;
    }
    
    footer {
        background-color: var(--green);
        color: white;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    table {
        width: 80%;
        margin: auto;
        border: 2px dashed var(--green);
    }
    
    table img {
        height: 70px;
    }
    
    a {
        color: var(--green);
        margin: 20px;
        font-style: italic;
        font-size: 200%;
    }
    Podgląd strony po tym kroku (index.php)
  37. Krok 37: Paragrafy bez marginesów

    Kryteria:

    R.4.6 marginesy zewnętrzne (margin) : dla tabeli - auto, dla odnośnika - 20px, selektora p - 0

    ok. 1 min
    Polecenie z arkusza

    Dla selektora paragrafu: marginesy zewnętrzne 0 px

    Zero nie potrzebuje jednostki: margin: 0. Stopka robi się niska jak na ilustracji 3. Komplet marginesów z klucza (R.4.6).

    styl.css CSS zmienione: 4 linie
    :root {
        --green: #388E3C;
        --lime: #DCE775;
    }
    
    body {
        font-family: Georgia, serif;
        text-align: center;
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header {
        background-color: var(--green);
        color: white;
        height: 100px;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(2, 50%);
    }
    
    section {
        background-color: var(--lime);
        height: 600px;
    }
    
    footer {
        background-color: var(--green);
        color: white;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    table {
        width: 80%;
        margin: auto;
        border: 2px dashed var(--green);
    }
    
    table img {
        height: 70px;
    }
    
    a {
        color: var(--green);
        margin: 20px;
        font-style: italic;
        font-size: 200%;
    }
    
    p {
        margin: 0;
    }
    Podgląd strony po tym kroku (index.php)
  38. Krok 38: Odnośnik pod kursorem i z klawiatury

    Kryteria:

    R.4.10 gdy kursor znajdzie się na odnośniku jego kolor tła zmienia się na #388E3C, a kolor czcionki na #DCE775

    ok. 1 min
    Polecenie z arkusza

    Gdy kursor znajdzie się na odnośniku jego kolor tła zmienia się na #388E3C, a kolor czcionki na #DCE775

    Pseudoklasa :hover zamienia kolory miejscami (R.4.10). Ten sam wygląd dostaje :focus-visible - odnośnik wybrany klawiszem Tab, bo osoba bez myszy też powinna widzieć, gdzie jest. Klucz sprawdza :hover; dodatkowy selektor po przecinku niczego w nim nie zmienia.

    styl.css CSS zmienione: 6 linii
    :root {
        --green: #388E3C;
        --lime: #DCE775;
    }
    
    body {
        font-family: Georgia, serif;
        text-align: center;
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header {
        background-color: var(--green);
        color: white;
        height: 100px;
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(2, 50%);
    }
    
    section {
        background-color: var(--lime);
        height: 600px;
    }
    
    footer {
        background-color: var(--green);
        color: white;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    table {
        width: 80%;
        margin: auto;
        border: 2px dashed var(--green);
    }
    
    table img {
        height: 70px;
    }
    
    a {
        color: var(--green);
        margin: 20px;
        font-style: italic;
        font-size: 200%;
    }
    
    p {
        margin: 0;
    }
    
    a:hover,
    a:focus-visible {
        background-color: var(--green);
        color: var(--lime);
    }
    Podgląd strony po tym kroku (index.php)
  39. Etap 4: Skrypty PHP

    Kroki 39-46 · po tym etapie masz 38 z 38 kryteriów (pkt)

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

    Kryteria: ,

    R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy pogoda

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

    ok. 4 min
    Polecenie z arkusza

    Napisane w języku PHP. Należy stosować znaczące nazewnictwo zmiennych i funkcji w języku polskim lub angielskim. Łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie pogoda. Na końcu działania skrypt zamyka połączenie z serwerem

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

    • DSN 'mysql:host=localhost;dbname=pogoda;charset=utf8mb4' - serwer, baza i kodowanie; 'root', '' - użytkownik i puste hasło (R.5.1),
    • ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze jako tablice z nazwami kolumn,
    • $pdo = null zamyka połączenie (PDO nie ma osobnej funkcji „zamknij”) - zapytania trafią między te dwie linie, więc zamknięcie zostanie ostatnią operacją na bazie (R.5.2),
    • try / catch (PDOException $exception) - gdy serwer bazy nie działa, szczegóły idą do dziennika serwera (error_log), a w bloku głównym pojawia się ogólny komunikat z $dbError (bez nazw tabel i haseł),
    • funkcja escape() to krótka nazwa htmlspecialchars(..., ENT_QUOTES) - posłuży każdej wartości z bazy.

    Zmienne $cities, $average i $dbError mają wartości początkowe, żeby HTML działał także po błędzie bazy.

    index.php PHP zmienione: 22 linie
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $months = [
        1 => 'Styczeń',
        2 => 'Luty',
        3 => 'Marzec',
        4 => 'Kwiecień',
        5 => 'Maj',
        6 => 'Czerwiec',
        7 => 'Lipiec',
        8 => 'Sierpień',
        9 => 'Wrzesień',
        10 => 'Październik',
        11 => 'Listopad',
        12 => 'Grudzień',
    ];
    $cities = [];
    $average = null;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=pogoda;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ć danych pogodowych. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
                <p>Średnia temperatura dla wybranego miesiąca wynosi</p>
            </section>
        </main>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  40. Krok 40: Skrypt 1: zapytanie 3

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Skrypt 1: Wysyła do bazy danych zapytanie 3

    $pdo->query(...) wysyła zapytanie 3 z kwerendy.txt (bez średnika na końcu), a ->fetchAll() od razu odbiera wszystkie sześć wierszy do tablicy $cities (R.5.3). Zapytanie nie ma danych od użytkownika, więc wystarcza query; przygotowane zapytanie przyda się w skrypcie 2.

    index.php PHP zmienione: 6 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $months = [
        1 => 'Styczeń',
        2 => 'Luty',
        3 => 'Marzec',
        4 => 'Kwiecień',
        5 => 'Maj',
        6 => 'Czerwiec',
        7 => 'Lipiec',
        8 => 'Sierpień',
        9 => 'Wrzesień',
        10 => 'Październik',
        11 => 'Listopad',
        12 => 'Grudzień',
    ];
    $cities = [];
    $average = null;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=pogoda;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cities = $pdo->query(
            'SELECT m.nazwa, m.kraj, p.temperatura
             FROM miejscowosc AS m
             JOIN pomiary AS p ON p.id_miejscowosc = m.id
             WHERE p.id_miesiac = 7'
        )->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych pogodowych. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
                <p>Średnia temperatura dla wybranego miesiąca wynosi</p>
            </section>
        </main>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  41. Krok 41: Skrypt 1: miasto, kraj i temperatura

    Kryteria: ,

    R.2.8 W bloku sekcji lewej umieszczono tabelę o 4 kolumnach (w skrypcie lub HTML) zastosowano znaczniki <table>, <tr>, <td>

    R.5.4 Skrypt 1 w pierwszych trzech kolumnach tabeli wyświetla zwrócone zapytaniem dane dla wszystkich zwróconych zapytaniem rekordów

    ok. 3 min
    Polecenie z arkusza

    W pierwszych trzech kolumnach tabeli zapisuje kolejne nazwy miast, krajów i temperatury dla wszystkich zwróconych zapytaniem rekordów

    W <tbody> pętla foreach ($cities as $city): wykona się tyle razy, ile wierszy oddało zapytanie (R.5.4). Pola po nazwach kolumn - $city['kraj'] mówi więcej niż $row[1] i nie zależy od kolejności pól w SELECT. Każda wartość przechodzi przez escape(): nazwa miasta z < albo cudzysłowem nie rozbije tabeli (R.2.8). W podglądzie sześć wierszy, czwarta kolumna jeszcze pusta.

    index.php PHP zmienione: 7 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $months = [
        1 => 'Styczeń',
        2 => 'Luty',
        3 => 'Marzec',
        4 => 'Kwiecień',
        5 => 'Maj',
        6 => 'Czerwiec',
        7 => 'Lipiec',
        8 => 'Sierpień',
        9 => 'Wrzesień',
        10 => 'Październik',
        11 => 'Listopad',
        12 => 'Grudzień',
    ];
    $cities = [];
    $average = null;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=pogoda;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cities = $pdo->query(
            'SELECT m.nazwa, m.kraj, p.temperatura
             FROM miejscowosc AS m
             JOIN pomiary AS p ON p.id_miejscowosc = m.id
             WHERE p.id_miesiac = 7'
        )->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych pogodowych. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($cities as $city): ?>
                        <tr>
                            <td><?= escape($city['nazwa']) ?></td>
                            <td><?= escape($city['kraj']) ?></td>
                            <td><?= escape($city['temperatura']) ?></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
                <p>Średnia temperatura dla wybranego miesiąca wynosi</p>
            </section>
        </main>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  42. Krok 42: Skrypt 1: ikona pogody z funkcji weatherIcon()

    Kryteria:

    R.5.5 Skrypt 1 wstawia obraz ze słońcem dla temperatury > 30, z deszczem dla temperatury < 26, z chmurą w innych przypadkach

    ok. 4 min
    Polecenie z arkusza

    Czwarta kolumna wypełniona jest obrazem: slonce.png, gdy temperatura dla tego rekordu jest powyżej 30 oC. deszcz.png, gdy temperatura dla tego rekordu jest poniżej 26 oC. chmury.png, w innych przypadkach

    Wybór ikony to osobna funkcja weatherIcon() na górze pliku: dostaje temperaturę, oddaje nazwę pliku i tekst alternatywny. W środku wyrażenie match (true) (PHP 8): sprawdza warunki po kolei i zwraca wartość pierwszego prawdziwego - > 30 słońce, < 26 deszcz, default chmury (R.5.5). W przeciwieństwie do switch nie potrzebuje break i samo jest wartością.

    W pętli (int) zamienia temperaturę z bazy (PDO oddaje ją jako tekst "30") na liczbę zgodną z typem parametru. Wynik jak na ilustracji 3: Ostrawa (dokładnie 30) - chmury, Warszawa i Brno - deszcz, reszta - słońce. Ikony dostają tekst alternatywny, którego wersja podstawowa nie ma.

    Typowe błędy

    >= 30 zamiast > 30 daje Ostrawie słońce - na ilustracji 3 widać, że 30 stopni to jeszcze „inny przypadek”.

    index.php PHP zmienione: 11 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function weatherIcon(int $temperature): array
    {
        return match (true) {
            $temperature > 30 => ['file' => 'slonce.png', 'alt' => 'słonecznie'],
            $temperature < 26 => ['file' => 'deszcz.png', 'alt' => 'deszczowo'],
            default => ['file' => 'chmury.png', 'alt' => 'pochmurno'],
        };
    }
    
    $months = [
        1 => 'Styczeń',
        2 => 'Luty',
        3 => 'Marzec',
        4 => 'Kwiecień',
        5 => 'Maj',
        6 => 'Czerwiec',
        7 => 'Lipiec',
        8 => 'Sierpień',
        9 => 'Wrzesień',
        10 => 'Październik',
        11 => 'Listopad',
        12 => 'Grudzień',
    ];
    $cities = [];
    $average = null;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=pogoda;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cities = $pdo->query(
            'SELECT m.nazwa, m.kraj, p.temperatura
             FROM miejscowosc AS m
             JOIN pomiary AS p ON p.id_miejscowosc = m.id
             WHERE p.id_miesiac = 7'
        )->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych pogodowych. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($cities as $city): ?>
    <?php $icon = weatherIcon((int) $city['temperatura']); ?>
                        <tr>
                            <td><?= escape($city['nazwa']) ?></td>
                            <td><?= escape($city['kraj']) ?></td>
                            <td><?= escape($city['temperatura']) ?></td>
                            <td><img src="<?= $icon['file'] ?>" alt="<?= $icon['alt'] ?>"></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
                <p>Średnia temperatura dla wybranego miesiąca wynosi</p>
            </section>
        </main>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  43. Krok 43: Skrypt 2: tylko po kliknięciu odnośnika

    Kryteria:

    R.5.6 Skrypt 2 jest wykonywany tylko, gdy dane zostały wysłane (np. przez isset)

    ok. 3 min
    Polecenie z arkusza

    Skrypt 2, wykonywany tylko jeżeli zostały wysłane dane poprzez kliknięcie odnośnika (ilustracja 5)

    isset($_GET['id']) - przy pierwszym wejściu adres to samo index.php i blok się nie wykona (R.5.6). Po kliknięciu miesiąca wartość z adresu sprawdza filter_var z filtrem FILTER_VALIDATE_INT i zakresem 1 - 12: oddaje liczbę całkowitą albo false, gdy w adresie jest coś innego (13, abc, 2 OR 1=1). Adres może wpisać każdy, więc wartość z niego sprawdza się zawsze, zanim trafi dalej.

    index.php PHP zmienione: 3 linie
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function weatherIcon(int $temperature): array
    {
        return match (true) {
            $temperature > 30 => ['file' => 'slonce.png', 'alt' => 'słonecznie'],
            $temperature < 26 => ['file' => 'deszcz.png', 'alt' => 'deszczowo'],
            default => ['file' => 'chmury.png', 'alt' => 'pochmurno'],
        };
    }
    
    $months = [
        1 => 'Styczeń',
        2 => 'Luty',
        3 => 'Marzec',
        4 => 'Kwiecień',
        5 => 'Maj',
        6 => 'Czerwiec',
        7 => 'Lipiec',
        8 => 'Sierpień',
        9 => 'Wrzesień',
        10 => 'Październik',
        11 => 'Listopad',
        12 => 'Grudzień',
    ];
    $cities = [];
    $average = null;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=pogoda;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cities = $pdo->query(
            'SELECT m.nazwa, m.kraj, p.temperatura
             FROM miejscowosc AS m
             JOIN pomiary AS p ON p.id_miejscowosc = m.id
             WHERE p.id_miesiac = 7'
        )->fetchAll();
        if (isset($_GET['id'])) {
            $monthId = filter_var($_GET['id'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 12]]);
        }
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych pogodowych. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($cities as $city): ?>
    <?php $icon = weatherIcon((int) $city['temperatura']); ?>
                        <tr>
                            <td><?= escape($city['nazwa']) ?></td>
                            <td><?= escape($city['kraj']) ?></td>
                            <td><?= escape($city['temperatura']) ?></td>
                            <td><img src="<?= $icon['file'] ?>" alt="<?= $icon['alt'] ?>"></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
                <p>Średnia temperatura dla wybranego miesiąca wynosi</p>
            </section>
        </main>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  44. Krok 44: Skrypt 2: zapytanie 1 dla wybranego miesiąca

    Kryteria:

    R.5.7 Skrypt 2 zawiera instrukcję wysyłającą do bazy danych zmodyfikowane zapytanie 1, tak że w warunku zapytania jest wstawiona wartość przesłana metodą GET

    ok. 3 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 1, zmodyfikowane tak, żeby wybierane były dane dotyczące wybranego przez użytkownika miesiąca (w miejscu id miesiąca jest wstawiona wartość przesłana metodą GET)

    Zapytanie 1, w którym zamiast 7 stoi parametr nazwany :month. prepare wysyła do bazy samą strukturę zapytania, a execute(['month' => $monthId]) - wartość z adresu osobno (R.5.7). Wartość nigdy nie staje się częścią tekstu SQL, więc nie da się nią dopisać warunku - to druga warstwa ochrony obok filter_var.

    fetchColumn() oddaje pierwszą kolumnę pierwszego wiersza - tu jedyną liczbę, średnią. Od tego kroku podgląd pokazuje stronę po kliknięciu „Luty” (index.php?id=2); wyniku strona jeszcze nie wypisuje.

    index.php PHP zmienione: 7 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function weatherIcon(int $temperature): array
    {
        return match (true) {
            $temperature > 30 => ['file' => 'slonce.png', 'alt' => 'słonecznie'],
            $temperature < 26 => ['file' => 'deszcz.png', 'alt' => 'deszczowo'],
            default => ['file' => 'chmury.png', 'alt' => 'pochmurno'],
        };
    }
    
    $months = [
        1 => 'Styczeń',
        2 => 'Luty',
        3 => 'Marzec',
        4 => 'Kwiecień',
        5 => 'Maj',
        6 => 'Czerwiec',
        7 => 'Lipiec',
        8 => 'Sierpień',
        9 => 'Wrzesień',
        10 => 'Październik',
        11 => 'Listopad',
        12 => 'Grudzień',
    ];
    $cities = [];
    $average = null;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=pogoda;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cities = $pdo->query(
            'SELECT m.nazwa, m.kraj, p.temperatura
             FROM miejscowosc AS m
             JOIN pomiary AS p ON p.id_miejscowosc = m.id
             WHERE p.id_miesiac = 7'
        )->fetchAll();
        if (isset($_GET['id'])) {
            $monthId = filter_var($_GET['id'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 12]]);
            if ($monthId !== false) {
                $statement = $pdo->prepare(
                    'SELECT ROUND(AVG(temperatura), 2) FROM pomiary WHERE id_miesiac = :month'
                );
                $statement->execute(['month' => $monthId]);
                $average = $statement->fetchColumn();
            }
        }
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych pogodowych. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($cities as $city): ?>
    <?php $icon = weatherIcon((int) $city['temperatura']); ?>
                        <tr>
                            <td><?= escape($city['nazwa']) ?></td>
                            <td><?= escape($city['kraj']) ?></td>
                            <td><?= escape($city['temperatura']) ?></td>
                            <td><img src="<?= $icon['file'] ?>" alt="<?= $icon['alt'] ?>"></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
                <p>Średnia temperatura dla wybranego miesiąca wynosi</p>
            </section>
        </main>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  45. Krok 45: Skrypt 2: temperatura w nagłówku trzeciego stopnia

    Kryteria: ,

    R.5.8 Zwróconą zapytaniem temperaturę wyświetla w sposób „<temp> stopni”

    R.5.9 W skryptach zastosowana przynajmniej jedna zmienna oraz nazwy wszystkich zmiennych są w języku polskim lub angielskim i odzwierciedlają cel zastosowania

    ok. 2 min
    Polecenie z arkusza

    Zwróconą zapytaniem temperaturę wyświetla w nagłówku trzeciego stopnia w sposób „<temp> stopni”, gdzie <> oznacza wartość zwróconą zapytaniem

    Pod akapitem warunek w składni alternatywnej: gdy $average ma wartość, wypisz <h3>3.50 stopni</h3> - jak na ilustracji 5 (R.5.8). Przy pierwszym wejściu $average zostaje null i nagłówka nie ma.

    Nazwy zmiennych i funkcji mówią, co przechowują i robią (R.5.9): $months, $monthId, $monthName, $cities, $city, $icon, $average, $statement, $pdo, $dbError, escape(), weatherIcon().

    index.php PHP zmienione: 3 linie
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function weatherIcon(int $temperature): array
    {
        return match (true) {
            $temperature > 30 => ['file' => 'slonce.png', 'alt' => 'słonecznie'],
            $temperature < 26 => ['file' => 'deszcz.png', 'alt' => 'deszczowo'],
            default => ['file' => 'chmury.png', 'alt' => 'pochmurno'],
        };
    }
    
    $months = [
        1 => 'Styczeń',
        2 => 'Luty',
        3 => 'Marzec',
        4 => 'Kwiecień',
        5 => 'Maj',
        6 => 'Czerwiec',
        7 => 'Lipiec',
        8 => 'Sierpień',
        9 => 'Wrzesień',
        10 => 'Październik',
        11 => 'Listopad',
        12 => 'Grudzień',
    ];
    $cities = [];
    $average = null;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=pogoda;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cities = $pdo->query(
            'SELECT m.nazwa, m.kraj, p.temperatura
             FROM miejscowosc AS m
             JOIN pomiary AS p ON p.id_miejscowosc = m.id
             WHERE p.id_miesiac = 7'
        )->fetchAll();
        if (isset($_GET['id'])) {
            $monthId = filter_var($_GET['id'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 12]]);
            if ($monthId !== false) {
                $statement = $pdo->prepare(
                    'SELECT ROUND(AVG(temperatura), 2) FROM pomiary WHERE id_miesiac = :month'
                );
                $statement->execute(['month' => $monthId]);
                $average = $statement->fetchColumn();
            }
        }
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych pogodowych. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($cities as $city): ?>
    <?php $icon = weatherIcon((int) $city['temperatura']); ?>
                        <tr>
                            <td><?= escape($city['nazwa']) ?></td>
                            <td><?= escape($city['kraj']) ?></td>
                            <td><?= escape($city['temperatura']) ?></td>
                            <td><img src="<?= $icon['file'] ?>" alt="<?= $icon['alt'] ?>"></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
                <p>Średnia temperatura dla wybranego miesiąca wynosi</p>
    <?php if ($average !== null): ?>
                <h3><?= escape($average) ?> stopni</h3>
    <?php endif; ?>
            </section>
        </main>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  46. Krok 46: Komunikat dla złego numeru miesiąca

    ok. 2 min
    Polecenie z arkusza

    Zwróconą zapytaniem temperaturę wyświetla w nagłówku trzeciego stopnia w sposób „<temp> stopni”, gdzie <> oznacza wartość zwróconą zapytaniem

    Sprawdzone adresy: ?id=13, ?id=abc, ?id=2 OR 1=1 i puste ?id=. filter_var odrzuca każdy z nich, zapytanie się nie wykonuje, a strona pokazuje „brak pomiarów dla tego miesiąca” zamiast samego słowa „stopni” albo średniej ze wszystkich pomiarów podanej jako luty. Podgląd: index.php?id=13. Klucz tego nie sprawdza - to obsługa adresu wpisanego ręcznie.

    index.php PHP zmienione: 2 linie
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function weatherIcon(int $temperature): array
    {
        return match (true) {
            $temperature > 30 => ['file' => 'slonce.png', 'alt' => 'słonecznie'],
            $temperature < 26 => ['file' => 'deszcz.png', 'alt' => 'deszczowo'],
            default => ['file' => 'chmury.png', 'alt' => 'pochmurno'],
        };
    }
    
    $months = [
        1 => 'Styczeń',
        2 => 'Luty',
        3 => 'Marzec',
        4 => 'Kwiecień',
        5 => 'Maj',
        6 => 'Czerwiec',
        7 => 'Lipiec',
        8 => 'Sierpień',
        9 => 'Wrzesień',
        10 => 'Październik',
        11 => 'Listopad',
        12 => 'Grudzień',
    ];
    $cities = [];
    $average = null;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=pogoda;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $cities = $pdo->query(
            'SELECT m.nazwa, m.kraj, p.temperatura
             FROM miejscowosc AS m
             JOIN pomiary AS p ON p.id_miejscowosc = m.id
             WHERE p.id_miesiac = 7'
        )->fetchAll();
        if (isset($_GET['id'])) {
            $monthId = filter_var($_GET['id'], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 12]]);
            if ($monthId !== false) {
                $statement = $pdo->prepare(
                    'SELECT ROUND(AVG(temperatura), 2) FROM pomiary WHERE id_miesiac = :month'
                );
                $statement->execute(['month' => $monthId]);
                $average = $statement->fetchColumn();
            }
        }
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych pogodowych. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Pogoda</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <img src="slonce.png" alt="Słonecznie">
        </header>
        <header>
            <h1>Pogoda w Europie</h1>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <section>
                <h2>Temperatury w lipcu</h2>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">Miasto</th>
                            <th scope="col">Kraj</th>
                            <th scope="col">Temperatura</th>
                            <th scope="col">Pogoda</th>
                        </tr>
                    </thead>
                    <tbody>
    <?php foreach ($cities as $city): ?>
    <?php $icon = weatherIcon((int) $city['temperatura']); ?>
                        <tr>
                            <td><?= escape($city['nazwa']) ?></td>
                            <td><?= escape($city['kraj']) ?></td>
                            <td><?= escape($city['temperatura']) ?></td>
                            <td><img src="<?= $icon['file'] ?>" alt="<?= $icon['alt'] ?>"></td>
                        </tr>
    <?php endforeach; ?>
                    </tbody>
                </table>
            </section>
            <section>
                <h2>Średnie temperatury w roku</h2>
                <nav aria-label="Miesiące">
    <?php foreach ($months as $monthId => $monthName): ?>
                    <a href="index.php?id=<?= $monthId ?>"><?= $monthName ?></a>
    <?php endforeach; ?>
                </nav>
                <p>Średnia temperatura dla wybranego miesiąca wynosi</p>
    <?php if ($average !== null): ?>
                <h3><?= escape($average) ?> stopni</h3>
    <?php elseif (isset($_GET['id'])): ?>
                <h3>brak pomiarów dla tego miesiąca</h3>
    <?php endif; ?>
            </section>
        </main>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  47. Etap 5: Porównanie z wersją podstawową

    Kroki 47-48 · po tym etapie masz 38 z 38 kryteriów (pkt)

    Krok 47: Co jest inaczej i dlaczego

    ok. 3 min
    Polecenie z arkusza

    Ocenie podlegać będzie 5 rezultatów: operacje na bazie danych, zawartość witryny internetowej, działanie witryny internetowej, styl CSS witryny internetowej, skrypt połączenia z bazą.

    Obie wersje dają te same punkty. Różnice:

    wersja podstawowata wersja
    układfloat + clear dla nagłówków, flex w <main>siatka: 20% 80% na <body>, repeat(2, 50%) w <main>
    identyfikatory blokówlogo, title, left, rightniepotrzebne - kolumny wynikają z kolejności
    kolorywpisane w regułachzmienne w :root
    odnośniki miesięcy12 linii HTMLtablica PHP i pętla foreach
    zapytaniapełne nazwy tabel, grupowanie po nazwiealiasy, grupowanie po id, kolejność kalendarza
    bazamysqli_* w miejscu wypisywaniaPDO na górze pliku, $pdo = null przed HTML-em
    ikonadrabinka if w pętlifunkcja weatherIcon() z match, z alt
    miesiąc z adresuwstawiony do SQLfilter_var i parametr :month
    zły adressamo „stopni” albo średnia wszystkich pomiarówkomunikat

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

  48. Krok 48: Którą wersję wybrać na egzaminie

    ok. 2 min
    Polecenie z arkusza

    Czas przeznaczony na wykonanie zadania wynosi 150 minut.

    • Wersja podstawowa, gdy liczy się czas albo PDO i siatka nie są opanowane: funkcje mysqli_* i isset są w tabeli 1 arkusza, każdy skrypt stoi tam, gdzie widać jego efekt, a odnośniki da się sprawdzić w kodzie HTML bez uruchamiania PHP.
    • Ta wersja, gdy PDO, match i siatka CSS są pisane z pamięci. Odnośniki z pętli mają jedną słabość: błąd składni PHP zatrzymuje całą stronę, więc nie widać wtedy także odnośników - sprawdzaj stronę w przeglądarce po każdej zmianie.

    Mieszanie też daje pełne punkty: siatka z tej wersji i mysqli_* z podstawowej. W projekcie z formularzami doszłyby jeszcze tokeny CSRF i dane logowania do bazy w osobnym pliku config.php - tu arkusz ma tylko odczyt z bazy i jedną stronę.

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

  49. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

    UWAGA: 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/…/index.php.

    W folderze z numerem zdającego: chmury.png, deszcz.png, import.png, index.php, kw1.jpg - kw4.jpg, kwerendy.txt, przeglądarka.txt, slonce.png, styl.css. Po nagraniu sprawdź odczyt płyty.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.