Przejdź do głównej treści
INF.03-08-25.01-SGStyczeń 2025 · 26 kroków · ok. 62 min · 37 kryteriów z klucza

Rozwiązanie krok po kroku: Mieszalnia farb (Nowoczesny kod (PDO, walidacja dat))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza mieszalnia z czterema zapytaniami, baner, okrągły favicon i strona index.php, która bez dat pokazuje wszystkie zamówienia, a z datami - tylko te z wybranego okresu, z komórką koloru w barwie farby - i tak samo spełnia każde kryterium klucza. Bloki stoją tu po prostu jeden pod drugim, więc nie ma czego przenosić na siatkę CSS; inna jest droga w PHP i w formularzu:

  • PHP na PDO z logiką nad HTML-em: zapytanie wykonane i połączenie zamknięte, zanim zacznie się strona, a tabela wypisywana pętlą foreach w szablonie,
  • daty sprawdzane od pierwszej linii: tylko prawdziwa data w formacie RRRR-MM-DD włącza filtr, odwrócony zakres jest poprawiany, a jedna wpisana data kończy się podpowiedzią,
  • zapytanie przygotowane z parametrami nazwanymi - tekst z formularza nie zmieni treści zapytania,
  • kod koloru sprawdzany, zanim trafi do atrybutu style,
  • formularz pamięta wybrane daty po wysłaniu, a tabela ma część nagłówkową <thead> i dane w <tbody>.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafiki pochodzą z prawdziwego archiwum pliki7, zapytania zostały wykonane na tej bazie w MariaDB z XAMPP-a, a podgląd strony to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2 - od kroku z formularzem po wybraniu dat od 2021-11-06 do 2021-11-10, jak na ilustracji 5.

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 mieszalnia

    ok. 2 min
    Polecenie z arkusza

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

    Archiwum pliki7.7z rozpakuj hasłem Miesz@nie&F@rB do folderu z numerem zdającego: mieszalnia.sql, tlo.jpg, nazwa.png i fav.jpg. W phpMyAdmin Nowa → mieszalnia, porównywanie utf8_unicode_ci, albo:

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

    Nazwa bazy musi być dokładnie mieszalnia - skrypt łączy się z nią po nazwie (R.5.2).

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

    ok. 2 min
    Polecenie z arkusza

    Do utworzonej bazy zaimportuj tabele z pliku mieszalnia.sql z rozpakowanego archiwum.

    Import → mieszalnia.sql. Dwie tabele w relacji jeden do wielu: klienci (17, kolumny pisane wielką literą: Id, Nazwisko, Imie, Plec) i zamowienia (23, klucz obcy id_klienta). Kod koloru zapisano bez znaku #, np. 1b69aa.

  3. Krok 3: Zrzut ekranu import.jpg

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych mieszalnia , co jest udokumentowane w pliku import w formacie JPEG

    ok. 2 min
    Polecenie z arkusza

    Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie JPEG 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.

    Cały ekran z paskiem zadań. Uwaga na formaty: import w JPEG (import.jpg), zrzuty zapytań w PNG.

    Typowe błędy

    Wykadrowany zrzut albo zrzut importu zapisany jako PNG to strata R.1.1.

  4. Krok 4: Zapytanie 1: liczba klientów

    Kryteria: ,

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

    R.1.3 Wykonane zapytanie 1 wybierające liczbę klientów mieszalni farb. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT COUNT(*) FROM klienci; oraz wynik działania udokumentowany zrzutem, na którym widoczna jest wartość 17

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 1: zliczające liczbę klientów mieszalni farb.

    COUNT(*) liczy wiersze tabeli, a alias liczba_klientow daje kolumnie wyniku nazwę zamiast COUNT(*). Wynik: 17. Zapytania zapisuj w kwerendy.txt, zrzut jako kw1.png.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT COUNT(*) AS liczba_klientow
    FROM klienci;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: zamówienia z klientami według daty odbioru

    Kryteria:

    R.1.4 Wykonane zapytanie 2 wybierające jedynie nazwiska, imiona oraz odpowiadające im numery zamówień (pole id), kody kolorów, pojemności i daty odbioru posortowane rosnąco po dacie odbioru. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwisko, imie, zamowienia.id, kod_koloru, pojemnosc, data_odbioru FROM klienci JOIN zamowienia ON klienci.id = id_klienta ORDER BY data_odbioru; (poprawne też z ASC, możliwe również INNER JOIN lub porównanie kluczy po WHERE ) oraz wynik działania udokumentowany zrzutem, na którym znajduje się komunikat o wybraniu 23 wierszy, jedynie pola nazwisko, imie, id, kod_koloru, pojemnosc, data_odbioru

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie nazwiska, imiona klientów oraz odpowiadające im numery zamówień (pole id), kody kolorów, pojemności i daty odbioru zamówień posortowane rosnąco po dacie odbioru. Należy posłużyć się relacją.

    Aliasy tabel k (klienci) i z (zamówienia): z.id to od razu widać numer zamówienia, a nie klienta - kolumna id jest w obu tabelach. JOIN zamowienia AS z ON z.id_klienta = k.id to relacja, ORDER BY z.data_odbioru - rosnąco.

    Nazwy kolumn są w tabeli klienci pisane wielką literą, a w zapytaniu małą - MySQL i MariaDB nie rozróżniają wielkości liter w nazwach kolumn. Wynik: 23 wiersze. Zrzut: kw2.png. To zapytanie wyśle skrypt, gdy formularz jest pusty.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT COUNT(*) AS liczba_klientow
    FROM klienci;
    
    -- Zapytanie 2
    SELECT k.nazwisko, k.imie, z.id, z.kod_koloru, z.pojemnosc, z.data_odbioru
    FROM klienci AS k
    JOIN zamowienia AS z ON z.id_klienta = k.id
    ORDER BY z.data_odbioru;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: odbiory od 5 do 7 listopada

    Kryteria:

    R.1.5 Wykonane zapytanie 3 wybierające jedynie nazwiska, imiona oraz odpowiadające im numery zamówień (pole id), kody kolorów, pojemności i daty odbioru posortowane rosnąco po dacie odbioru, jedynie dla zamówień, w których data odbioru jest od 5 listopada do 7 listopada 2021 roku. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwisko, imie, zamowienia.id, kod_koloru, pojemnosc, data_odbioru FROM klienci JOIN zamowienia ON klienci.id = id_klienta WHERE data_odbioru >= "2021-11-05" AND data_odbioru <= "2021-11-07" ORDER BY data_odbioru; (poprawne też z ASC, możliwe również INNER JOIN lub porównanie kluczy po WHERE ) oraz wynik działania udokumentowany zrzutem, na którym znajdują się dokładnie 2 rekordy, dla id = 7, 1

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie nazwiska, imiona klientów oraz odpowiadające im numery zamówień (pole id), kody kolorów, pojemności i daty odbioru zamówień posortowane rosnąco po dacie odbioru, jedynie dla zamówień, w których data odbioru jest od 5 listopada do 7 listopada 2021 roku. Należy posłużyć się relacją.

    Zapytanie 2 plus BETWEEN '2021-11-05' AND '2021-11-07' - zakres z obiema granicami włącznie. Kolumna jest typu DATE, więc porównanie działa na datach, nie na napisach. Wynik: zamówienia id 7 i 1. Zrzut: kw3.png. W skrypcie w miejscu dat staną parametry z formularza.

    kwerendy.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT COUNT(*) AS liczba_klientow
    FROM klienci;
    
    -- Zapytanie 2
    SELECT k.nazwisko, k.imie, z.id, z.kod_koloru, z.pojemnosc, z.data_odbioru
    FROM klienci AS k
    JOIN zamowienia AS z ON z.id_klienta = k.id
    ORDER BY z.data_odbioru;
    
    -- Zapytanie 3
    SELECT k.nazwisko, k.imie, z.id, z.kod_koloru, z.pojemnosc, z.data_odbioru
    FROM klienci AS k
    JOIN zamowienia AS z ON z.id_klienta = k.id
    WHERE z.data_odbioru BETWEEN '2021-11-05' AND '2021-11-07'
    ORDER BY z.data_odbioru;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: klientki

    Kryteria:

    R.1.6 Wykonane zapytanie 4 wybierające jedynie imiona i nazwiska klientów mieszalni farb o płci kobieta. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT imie, nazwisko FROM klienci WHERE plec = "k"; oraz wynik działania udokumentowany zrzutem, na którym widocznych jest dokładnie 5 wierszy dla Joanny, Katarzyny, Karoliny, Wiktorii i Ilony

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie imiona i nazwiska kobiet, które są klientami mieszalni farb.

    Płeć zapisano jedną literą w polu Plec: 'k'. Wynik: Joanna, Katarzyna, Karolina, Wiktoria, Ilona. Zrzut: kw4.png.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT COUNT(*) AS liczba_klientow
    FROM klienci;
    
    -- Zapytanie 2
    SELECT k.nazwisko, k.imie, z.id, z.kod_koloru, z.pojemnosc, z.data_odbioru
    FROM klienci AS k
    JOIN zamowienia AS z ON z.id_klienta = k.id
    ORDER BY z.data_odbioru;
    
    -- Zapytanie 3
    SELECT k.nazwisko, k.imie, z.id, z.kod_koloru, z.pojemnosc, z.data_odbioru
    FROM klienci AS k
    JOIN zamowienia AS z ON z.id_klienta = k.id
    WHERE z.data_odbioru BETWEEN '2021-11-05' AND '2021-11-07'
    ORDER BY z.data_odbioru;
    
    -- Zapytanie 4
    SELECT imie, nazwisko
    FROM klienci
    WHERE plec = 'k';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

    Kroki 8-16 · po tym etapie masz 20 z 37 kryteriów (pkt)

    Krok 8: Baner: tło i napis

    Kryteria: ,

    R.2.1 Utworzone pliki graficzne baner.png o szerokości 1000 px i wysokości 270 px oraz fav.png o rozmiarze 64 px na 64 px

    R.2.2 Cały obraz z banerem wypełnia tło z pliku tlo.jpg oraz na pierwszym planie osadzony jest napis pobrany z pliku nazwa.png . Napis widoczny jest w całości

    ok. 5 min
    Polecenie z arkusza

    Na potrzeby strony należy przygotować grafikę z banerem o nazwie baner.png i cechach: grafika z pliku tlo.jpg, wypakowanego z archiwum, w całości stanowi tło banera. Szerokość banera 1000 px, wysokość 270 px. Zawartość pliku nazwa.png, wypakowanego z archiwum, stanowi pierwszy plan banera. Napis musi być widoczny w całości.

    Grafiki są takie same w obu wersjach.

    1. tlo.jpg (3840 × 1200): Obraz → Skaluj obraz do 1000 × 270 z rozpiętym łańcuchem - „w całości stanowi tło”, więc skalujesz cały obraz, a nie wycinasz fragmentu,
    2. Plik → Otwórz jako warstwy nazwa.png (napis na przezroczystym tle), Warstwa → Skaluj warstwę, aż napis zajmie prawie całą szerokość, i ustaw go tak, żeby był widoczny w całości,
    3. Eksportuj jako baner.png (R.2.1, R.2.2).
    Typowe błędy

    Kadrowanie tła do 1000 × 270 zamiast skalowania pokazuje tylko fragment tlo.jpg - klucz wymaga tła w całości.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Favicon: koło wycięte z tekstury

    Kryteria: ,

    R.2.1 Utworzone pliki graficzne baner.png o szerokości 1000 px i wysokości 270 px oraz fav.png o rozmiarze 64 px na 64 px

    R.2.3 Plik z faviconem ma przezroczyste tło oraz znajduje się w nim kolorowe koło wycięte z pliku fav.jpg . Koło wypełnia cały obraz

    ok. 4 min
    Polecenie z arkusza

    Na potrzeby strony należy przygotować favicon o nazwie fav.png i cechach: rozmiary 64 px na 64 px, tło przezroczyste, z grafiki fav.jpg należy wyciąć koło, i przygotować tak aby wypełniało cały obraz favicona.

    fav.jpg ma 100 × 100. Warstwa → Przezroczystość → Dodaj kanał alfa, Zaznaczenie eliptyczne od narożnika do narożnika z wciśniętym Shift (koło na cały obraz), Zaznaczenie → Odwróć i Delete - poza kołem przezroczystość. Potem Obraz → Skaluj obraz do 64 × 64 i Eksportuj jako fav.png (R.2.1, R.2.3).

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

    Kryteria:

    R.2.4 Witryna internetowa zapisana w pliku index.php oraz zapisany jawnie standard kodowania polskich znaków, deklaracja HTML5 <!DOCTYPE HTML>, zadeklarowany język witryny: polski np. <html lang="pl">. Strona zawiera sekcje head i body

    ok. 1 min
    Polecenie z arkusza

    Cechy witryny: Składa się ze strony o nazwie index.php. Zapisana w języku HTML5. Jawnie zastosowany właściwy standard kodowania polskich znaków. Zadeklarowany polski język zawartości witryny.

    Szkielet od razu z lang="pl" i <meta charset="UTF-8"> jako pierwszym elementem <head> (R.2.4). Bez kodu PHP podgląd pokazuje plik wprost.

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

    Kryteria: ,

    R.2.5 Na karcie przeglądarki widoczne są favicon oraz tytuł strony „Mieszalnia farb”

    R.3.4 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie style.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 #) (nie dotyczy stylu generowanego skryptem dla kolorów w tabeli)

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony „Mieszalnia farb” oraz grafika fav.png umieszczona jako favicon witryny, widoczne na karcie przeglądarki. Arkusz stylów w pliku o nazwie style.css prawidłowo połączony z kodem strony.

    <title> i <link rel="icon"> z typem pliku (R.2.5), <link> do style.css (R.3.4) i viewport dla telefonów.

    index.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Mieszalnia farb</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  12. Krok 12: Bloki jeden pod drugim

    Kryteria: ,

    R.2.6 Zdefiniowany układ strony, dzielący ją na bloki: nagłówkowy, formularza, główny i stopki przy pomocy semantycznych znaczników sekcji języka HTML5 zgodnie z ich znaczeniem, w tym do części nagłówkowej - header, do stopki - footer oraz dopuszcza się dodatkowe użycie bloków div lub section (dopuszcza się zamiast bloku formularza - formularz)

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 4 w arkuszu egzaminacyjnym

    ok. 1 min
    Polecenie z arkusza

    Podział strony na znajdujące się jeden pod drugim bloki: nagłówkowy, formularz, główny, stopka. Podział zrealizowany 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 ilustracją 4.

    <header>, <section class="search"> na formularz, <main> i <footer> (R.2.6). Bloki to elementy blokowe, więc stają jeden pod drugim same - układ nie potrzebuje ani siatki, ani float, a dodawanie ich tylko dla zasady byłoby zbędnym kodem (R.3.1).

    index.php PHP zmienione: 8 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Mieszalnia farb</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
        </header>
        <section class="search">
        </section>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  13. Krok 13: Nagłówek: baner

    Kryteria:

    R.2.7 W bloku nagłówkowym umieszczony obraz banera z tekstem alternatywnym "Mieszalnia farb"

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego: obraz z pliku baner.png z tekstem alternatywnym „Mieszalnia farb”.

    Obraz z tekstem alternatywnym z arkusza (R.2.7).

    index.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>Mieszalnia farb</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <img src="baner.png" alt="Mieszalnia farb">
        </header>
        <section class="search">
        </section>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  14. Krok 14: Formularz: dwa pola daty i przycisk

    Kryteria: , ,

    R.2.8 Formularz zawiera dwa pola edycyjne i przycisk, zastosowany znacznik <form>

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

    R.3.3 Przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu ) oraz przynajmniej jedno pole edycyjne jest typu date

    ok. 3 min
    Polecenie z arkusza

    Zawartość bloku formularza: Formularz wysyłający dane metodą bezpieczną do tego samego pliku, o elementach: Pole edycyjne przeznaczone do wybrania daty, poprzedzone etykietą: „Data odbioru od: ”. Pole edycyjne przeznaczone do wybrania daty, poprzedzone etykietą: „do: ”. Przycisk „Wyszukaj” wysyłający dane z formularza do skryptu.

    • method="post" bez action - dane wracają do tego samego pliku (R.3.2). „Metoda bezpieczna” w rozumieniu arkusza to POST: dane idą w treści żądania, a nie w adresie strony; w specyfikacji HTTP „bezpieczne” są co prawda metody, które niczego nie zmieniają na serwerze (GET i HEAD), ale punkt daje method="post",
    • pola type="date" z kalendarzem; przeglądarka wysyła datę w formacie RRRR-MM-DD, takim jak w bazie (R.3.3),
    • <label for> powiązane z id pola - kliknięcie etykiety otwiera pole, a czytnik ekranu czyta jego nazwę (R.2.8).

    Pól nie oznaczam jako required: pusty formularz jest poprawny - pokazuje wszystkie zamówienia.

    index.php PHP zmienione: 7 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Mieszalnia farb</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <img src="baner.png" alt="Mieszalnia farb">
        </header>
        <section class="search">
            <form method="post">
                <label for="from">Data odbioru od: </label>
                <input type="date" id="from" name="from">
                <label for="to">do: </label>
                <input type="date" id="to" name="to">
                <button type="submit">Wyszukaj</button>
            </form>
        </section>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Blok główny: tabela z częścią nagłówkową

    Kryteria:

    R.2.9 W bloku głównym umieszczona tabela o 6 kolumnach, wierszu z komórkami nagłówkowymi oraz przynajmniej jednym wierszu z danymi (w skrypcie lub HTML ) zastosowane znaczniki <table>, <tr>, <th>, <td>

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku głównego: Tabela o sześciu kolumnach. Pierwszy wiersz zawiera komórki nagłówkowe: „Nr zamówienia”, „Nazwisko”, „Imię”, „Kolor”, „Pojemność [ml]”, „Data odbioru”. Zawartość tabeli jest generowana skryptem.

    Wiersz komórek nagłówkowych <th> w <thead>, a pod nim puste <tbody> - tam skrypt wpisze zamówienia. Podział mówi przeglądarce i czytnikowi ekranu, który wiersz opisuje kolumny; przy wydruku długiej tabeli <thead> powtarza się na każdej stronie.

    index.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>Mieszalnia farb</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <img src="baner.png" alt="Mieszalnia farb">
        </header>
        <section class="search">
            <form method="post">
                <label for="from">Data odbioru od: </label>
                <input type="date" id="from" name="from">
                <label for="to">do: </label>
                <input type="date" id="to" name="to">
                <button type="submit">Wyszukaj</button>
            </form>
        </section>
        <main>
            <table>
                <thead>
                    <tr>
                        <th>Nr zamówienia</th>
                        <th>Nazwisko</th>
                        <th>Imię</th>
                        <th>Kolor</th>
                        <th>Pojemność [ml]</th>
                        <th>Data odbioru</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Stopka

    Kryteria:

    R.2.10 W bloku stopki umieszczone nagłówek <h3>: "Egzamin INF.03" oraz paragraf z numerem zdającego

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku stopki: Nagłówek trzeciego stopnia o treści: „Egzamin INF.03”. Akapit z napisem „Autor: ”, dalej wstawiony numer zdającego.

    <h3> i <p>. Na egzaminie po „Autor:” wpisujesz swój numer zdającego (PESEL).

    index.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>Mieszalnia farb</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <img src="baner.png" alt="Mieszalnia farb">
        </header>
        <section class="search">
            <form method="post">
                <label for="from">Data odbioru od: </label>
                <input type="date" id="from" name="from">
                <label for="to">do: </label>
                <input type="date" id="to" name="to">
                <button type="submit">Wyszukaj</button>
            </form>
        </section>
        <main>
            <table>
                <thead>
                    <tr>
                        <th>Nr zamówienia</th>
                        <th>Nazwisko</th>
                        <th>Imię</th>
                        <th>Kolor</th>
                        <th>Pojemność [ml]</th>
                        <th>Data odbioru</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <footer>
            <h3>Egzamin INF.03</h3>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  17. Etap 3: Styl CSS

    Kroki 17-19 · po tym etapie masz 29 z 37 kryteriów (pkt)

    Krok 17: Plik style.css: kolory w zmiennych, ciało strony

    Kryteria:

    R.4.1 Ustawione dla ciała strony lub kontenera całej strony: krój czcionki Georgia, tekst wyśrodkowany, kolor tła LightSteelBlue

    ok. 1 min
    Polecenie z arkusza

    Dla ciała strony: krój czcionki Georgia, tekst wyśrodkowany, kolor tła LightSteelBlue.

    Dwa odcienie niebieskiego z arkusza w zmiennych CSS (--page - tło strony, --accent - wiersz pod kursorem), potem reguła body (R.4.1).

    style.css CSS nowy plik
    :root {
        --page: LightSteelBlue;
        --accent: SteelBlue;
    }
    
    body {
        font-family: Georgia;
        text-align: center;
        background-color: var(--page);
    }
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Formularz, blok główny i stopka

    Kryteria: , , ,

    R.4.2 Ustawiony kolor tła Orange dla stopki

    R.4.4 Ustawiona wysokość 400px dla bloku głównego

    R.4.5 Ustawione marginesy wewnętrzne (padding) 30px dla bloku głównego, 10px dla stopki oraz jedynie górny 20px dla bloku formularza lub formularza

    R.4.7 Ustawiony dla bloku głównego pasek przewijania (overflow) na auto

    ok. 2 min
    Polecenie z arkusza

    Dla bloku formularza: margines wewnętrzny górny 20 px. Dla bloku głównego: pasek przewijania pojawiający się tylko w przypadku, gdy treść nie mieści się w bloku, marginesy wewnętrzne 30 px, wysokość 400 px. Dla bloku stopki: kolor tła Orange, marginesy wewnętrzne 10 px.

    padding-top - tylko górny margines wewnętrzny; overflow: auto - pasek tylko wtedy, gdy 23 zamówienia nie zmieszczą się w 400 px (R.4.2, R.4.4, R.4.5, R.4.7).

    style.css CSS zmienione: 15 linii
    :root {
        --page: LightSteelBlue;
        --accent: SteelBlue;
    }
    
    body {
        font-family: Georgia;
        text-align: center;
        background-color: var(--page);
    }
    
    .search {
        padding-top: 20px;
    }
    
    main {
        height: 400px;
        padding: 30px;
        overflow: auto;
    }
    
    footer {
        background-color: Orange;
        padding: 10px;
    }
    Podgląd strony po tym kroku (index.php)
  19. Krok 19: Obraz, tabela i wiersz pod kursorem

    Kryteria: , , ,

    R.4.3 Ustawiona szerokość 80% dla selektora img oraz 70% dla selektora table

    R.4.6 Ustawione marginesy zewnętrzne (margin) auto dla tabeli

    R.4.8 Ustawione dla tabeli obramowanie 1px dotted Maroon

    R.4.9 Gdy kursor znajdzie się na wierszu tabeli kolor tła zmienia się na SteelBlue, a kolor czcionki na biały

    ok. 2 min
    Polecenie z arkusza

    Dla selektora obrazu: szerokość 80%. Dla selektora tabeli: szerokość 70%, marginesy zewnętrzne automatycznie wyliczane przez przeglądarkę, obramowana linią kropkowaną o szerokości 1 px i kolorze Maroon. Gdy kursor znajdzie się na wierszu tabeli kolor tła zmienia się na SteelBlue, a kolor czcionki na biały.

    Selektory znaczników img i table - wymaga ich uwaga arkusza (R.4.3, R.4.6, R.4.8). tbody tr:hover podświetla wiersze z zamówieniami, a wiersz nagłówkowy zostaje bez zmian - jak przy zamówieniu Wiktorii na ilustracji 4 (R.4.9).

    style.css CSS zmienione: 15 linii
    :root {
        --page: LightSteelBlue;
        --accent: SteelBlue;
    }
    
    body {
        font-family: Georgia;
        text-align: center;
        background-color: var(--page);
    }
    
    .search {
        padding-top: 20px;
    }
    
    main {
        height: 400px;
        padding: 30px;
        overflow: auto;
    }
    
    footer {
        background-color: Orange;
        padding: 10px;
    }
    
    img {
        width: 80%;
    }
    
    table {
        width: 70%;
        margin: auto;
        border: 1px dotted Maroon;
    }
    
    tbody tr:hover {
        background-color: var(--accent);
        color: white;
    }
    Podgląd strony po tym kroku (index.php)
  20. Etap 4: Skrypt PHP

    Kroki 20-24 · po tym etapie masz 37 z 37 kryteriów (pkt)

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

    Kryteria: , , ,

    R.5.1 Skrypt jest napisany w języku PHP i zawiera przynajmniej jedną zgodną z treścią zadania instrukcję

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

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

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

    ok. 4 min
    Polecenie z arkusza

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

    Praca z bazą stoi przed <!DOCTYPE html>: najpierw dane, potem HTML, który je tylko wypisuje.

    • DSN 'mysql:host=localhost;dbname=mieszalnia;charset=utf8mb4' z użytkownikiem root i pustym hasłem (R.5.1, R.5.2),
    • ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze z nazwami kolumn,
    • $pdo = null zamyka połączenie; zapytania trafią przed tę linię, więc zamknięcie zostanie ostatnią operacją na bazie (R.5.3),
    • catch (PDOException $exception) - szczegóły do dziennika serwera, dla użytkownika ogólny komunikat,
    • funkcja e() - krótka nazwa htmlspecialchars(..., ENT_QUOTES) przy każdej wartości z bazy.

    Nazwy po angielsku mówią, co przechowują (R.5.8). Od tego kroku podgląd to wynik prawdziwego uruchomienia.

    index.php PHP zmienione: 20 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $orders = [];
    $message = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=mieszalnia;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());
        $message = 'Nie udało się pobrać zamówień. 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>Mieszalnia farb</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <img src="baner.png" alt="Mieszalnia farb">
        </header>
        <section class="search">
            <form method="post">
                <label for="from">Data odbioru od: </label>
                <input type="date" id="from" name="from">
                <label for="to">do: </label>
                <input type="date" id="to" name="to">
                <button type="submit">Wyszukaj</button>
            </form>
        </section>
        <main>
            <table>
                <thead>
                    <tr>
                        <th>Nr zamówienia</th>
                        <th>Nazwisko</th>
                        <th>Imię</th>
                        <th>Kolor</th>
                        <th>Pojemność [ml]</th>
                        <th>Data odbioru</th>
                    </tr>
                </thead>
                <tbody>
                </tbody>
            </table>
        </main>
        <footer>
            <h3>Egzamin INF.03</h3>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  21. Krok 21: Zapytanie 2 i wiersze tabeli

    Kryteria: ,

    R.2.9 W bloku głównym umieszczona tabela o 6 kolumnach, wierszu z komórkami nagłówkowymi oraz przynajmniej jednym wierszu z danymi (w skrypcie lub HTML ) zastosowane znaczniki <table>, <tr>, <th>, <td>

    R.5.6 Skrypt w odpowiednich komórkach wierszy tabeli wyświetla wartości pobrane z bazy danych

    ok. 3 min
    Polecenie z arkusza

    Poszczególne wiersze zwrócone zapytaniem są wyświetlane w odpowiednich kolumnach tabeli, w bloku głównym.

    • nad HTML-em zapytanie 2: kolumny w SELECT ustawione w kolejności kolumn tabeli na stronie (numer, nazwisko, imię, kolor, pojemność, data), fetchAll() odbiera 23 wiersze do $orders,
    • w <tbody> pętla foreach ($orders as $order): i wiersz z sześcioma komórkami - wartości po nazwach ($order['nazwisko']), więc nie trzeba pamiętać, że numer zamówienia jest trzecim polem wyniku, jak przy $row[2] (R.2.9, R.5.6).

    Podgląd: 23 zamówienia od 2021-08-18.

    index.php PHP zmienione: 17 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $orders = [];
    $message = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=mieszalnia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $statement = $pdo->query(
            'SELECT z.id, k.nazwisko, k.imie, z.kod_koloru, z.pojemnosc, z.data_odbioru
                 FROM klienci AS k
                 JOIN zamowienia AS z ON z.id_klienta = k.id
                 ORDER BY z.data_odbioru'
        );
        $orders = $statement->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $message = 'Nie udało się pobrać zamówień. 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>Mieszalnia farb</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <img src="baner.png" alt="Mieszalnia farb">
        </header>
        <section class="search">
            <form method="post">
                <label for="from">Data odbioru od: </label>
                <input type="date" id="from" name="from">
                <label for="to">do: </label>
                <input type="date" id="to" name="to">
                <button type="submit">Wyszukaj</button>
            </form>
        </section>
        <main>
            <table>
                <thead>
                    <tr>
                        <th>Nr zamówienia</th>
                        <th>Nazwisko</th>
                        <th>Imię</th>
                        <th>Kolor</th>
                        <th>Pojemność [ml]</th>
                        <th>Data odbioru</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($orders as $order): ?>
                        <tr>
                            <td><?= e($order['id']) ?></td>
                            <td><?= e($order['nazwisko']) ?></td>
                            <td><?= e($order['imie']) ?></td>
                            <td><?= e($order['kod_koloru']) ?></td>
                            <td><?= e($order['pojemnosc']) ?></td>
                            <td><?= e($order['data_odbioru']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <footer>
            <h3>Egzamin INF.03</h3>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Tło komórki w kolorze farby

    Kryteria:

    R.5.7 Ustawione dla komórek tabeli z kolumny z kolorem formatowanie koloru tła na podstawie kodu koloru wpisanego do bazy np. <td style='background-color: #$wiersz[3]'>. Kolory odpowiednio wyświetlają się na stronie internetowej

    ok. 3 min
    Polecenie z arkusza

    Tło komórek w kolumnie z kolorem farby ustawiane jest zgodnie z kodem szesnastkowym koloru zapisanym dla tego wiersza w bazie.

    Komórka koloru dostaje styl w atrybucie: style="background-color: #1b69aa" (R.5.7). Kod przechodzi przez funkcję paintColor(): wyrażenie regularne ^[0-9a-f]{6}$ z flagą i przepuszcza tylko dokładnie sześć cyfr szesnastkowych i wtedy dokleja # (w bazie go nie ma); wszystko inne daje transparent.

    Po co, skoro kody są w bazie? Atrybut style przyjmuje dowolny CSS, a cudzysłów w danych zamyka atrybut. Kto zmieni wpis w bazie (np. przez inny formularz sklepu), mógłby wstawić na stronę własny styl albo kod - funkcja to zamyka. htmlspecialchars tu nie wystarcza: kod red; position: fixed nie ma znaków HTML, a i tak zmienia stronę.

    Typowe błędy

    Bez # przed kodem (background-color: 1b69aa) przeglądarka odrzuca wartość i komórka zostaje bez koloru.

    index.php PHP zmienione: 6 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    function paintColor(string $code): string
    {
        return preg_match('/^[0-9a-f]{6}$/i', $code) === 1 ? '#' . $code : 'transparent';
    }
    
    $orders = [];
    $message = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=mieszalnia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $statement = $pdo->query(
            'SELECT z.id, k.nazwisko, k.imie, z.kod_koloru, z.pojemnosc, z.data_odbioru
                 FROM klienci AS k
                 JOIN zamowienia AS z ON z.id_klienta = k.id
                 ORDER BY z.data_odbioru'
        );
        $orders = $statement->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $message = 'Nie udało się pobrać zamówień. 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>Mieszalnia farb</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <img src="baner.png" alt="Mieszalnia farb">
        </header>
        <section class="search">
            <form method="post">
                <label for="from">Data odbioru od: </label>
                <input type="date" id="from" name="from">
                <label for="to">do: </label>
                <input type="date" id="to" name="to">
                <button type="submit">Wyszukaj</button>
            </form>
        </section>
        <main>
            <table>
                <thead>
                    <tr>
                        <th>Nr zamówienia</th>
                        <th>Nazwisko</th>
                        <th>Imię</th>
                        <th>Kolor</th>
                        <th>Pojemność [ml]</th>
                        <th>Data odbioru</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($orders as $order): ?>
                        <tr>
                            <td><?= e($order['id']) ?></td>
                            <td><?= e($order['nazwisko']) ?></td>
                            <td><?= e($order['imie']) ?></td>
                            <td style="background-color: <?= paintColor($order['kod_koloru']) ?>"><?= e($order['kod_koloru']) ?></td>
                            <td><?= e($order['pojemnosc']) ?></td>
                            <td><?= e($order['data_odbioru']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <footer>
            <h3>Egzamin INF.03</h3>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  23. Krok 23: Zapytanie 3 z datami z formularza - przygotowane

    Kryteria: ,

    R.5.4 Skrypt zawiera instrukcję wysyłającą zapytanie 2 gdy nie wypełniono formularza. W przeciwnym wypadku zawiera instrukcję wysyłającą zmodyfikowane zapytanie 3, tak że w miejscu dat są wartości z formularza

    R.5.5 Skrypt zawiera instrukcję pobierającą dane z tablicy globalnej odpowiadającej zastosowanej metodzie wysłania danych z formularza ($_POST, $_GET, $_REQUEST)

    ok. 4 min
    Polecenie z arkusza

    Gdy nie wypełniono formularza, wysyła zapytanie 2, w przeciwnym wypadku wysyła zmodyfikowane zapytanie 3, w ten sposób, że do zapytania pobierane są daty z formularza.

    • $_POST['from'] ?? null pobiera datę z tablicy $_POST - tej tablicy szuka klucz R.5.5 - a operator ?? daje null, gdy pola nie wysłano, bez ostrzeżenia „Undefined array key” (R.5.5),
    • funkcja validDate() przepuszcza tylko prawdziwą datę: najpierw sprawdza, czy dostała napis (spreparowane żądanie może przysłać tablicę), potem createFromFormat('!Y-m-d', ...) czyta napis według wzorca, a porównanie wyniku z powrotem z napisem odrzuca daty, które PHP „naprawiłby” po cichu (2021-02-31 stałoby się 3 marca); pusty napis i dowolny tekst dają null,
    • gdy obie daty są poprawne - zapytanie przygotowane z parametrami :from i :to, a wartości idą osobno w execute([...]); w przeciwnym wypadku (else) zapytanie 2 bez filtra (R.5.4).

    Reszta - pętla i tło komórki - działa dla obu zapytań bez zmian. Podgląd po wybraniu dat od 2021-11-06 do 2021-11-10: trzy zamówienia, jak na ilustracji 5.

    index.php PHP zmienione: 28 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    function paintColor(string $code): string
    {
        return preg_match('/^[0-9a-f]{6}$/i', $code) === 1 ? '#' . $code : 'transparent';
    }
    
    function validDate(mixed $value): ?string
    {
        if (!is_string($value)) {
            return null;
        }
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $value);
        return $date !== false && $date->format('Y-m-d') === $value ? $value : null;
    }
    
    $from = validDate($_POST['from'] ?? null);
    $to = validDate($_POST['to'] ?? null);
    $orders = [];
    $message = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=mieszalnia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($from !== null && $to !== null) {
            $statement = $pdo->prepare(
                'SELECT z.id, k.nazwisko, k.imie, z.kod_koloru, z.pojemnosc, z.data_odbioru
                     FROM klienci AS k
                     JOIN zamowienia AS z ON z.id_klienta = k.id
                     WHERE z.data_odbioru BETWEEN :from AND :to
                     ORDER BY z.data_odbioru'
            );
            $statement->execute(['from' => $from, 'to' => $to]);
        } else {
            $statement = $pdo->query(
                'SELECT z.id, k.nazwisko, k.imie, z.kod_koloru, z.pojemnosc, z.data_odbioru
                     FROM klienci AS k
                     JOIN zamowienia AS z ON z.id_klienta = k.id
                     ORDER BY z.data_odbioru'
            );
        }
        $orders = $statement->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $message = 'Nie udało się pobrać zamówień. 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>Mieszalnia farb</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <img src="baner.png" alt="Mieszalnia farb">
        </header>
        <section class="search">
            <form method="post">
                <label for="from">Data odbioru od: </label>
                <input type="date" id="from" name="from">
                <label for="to">do: </label>
                <input type="date" id="to" name="to">
                <button type="submit">Wyszukaj</button>
            </form>
        </section>
        <main>
            <table>
                <thead>
                    <tr>
                        <th>Nr zamówienia</th>
                        <th>Nazwisko</th>
                        <th>Imię</th>
                        <th>Kolor</th>
                        <th>Pojemność [ml]</th>
                        <th>Data odbioru</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($orders as $order): ?>
                        <tr>
                            <td><?= e($order['id']) ?></td>
                            <td><?= e($order['nazwisko']) ?></td>
                            <td><?= e($order['imie']) ?></td>
                            <td style="background-color: <?= paintColor($order['kod_koloru']) ?>"><?= e($order['kod_koloru']) ?></td>
                            <td><?= e($order['pojemnosc']) ?></td>
                            <td><?= e($order['data_odbioru']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </main>
        <footer>
            <h3>Egzamin INF.03</h3>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Odwrócony zakres, jedna data, zapamiętane daty

    ok. 4 min
    Polecenie z arkusza

    Gdy nie wypełniono formularza, wysyła zapytanie 2, w przeciwnym wypadku wysyła zmodyfikowane zapytanie 3, w ten sposób, że do zapytania pobierane są daty z formularza.

    Sytuacje, które wersja podstawowa przemilcza, sprawdzone uruchomieniem:

    • daty w odwrotnej kolejności (od 10 do 6 listopada) - BETWEEN dałby pustą tabelę, więc daty są zamieniane miejscami jednym przypisaniem [$from, $to] = [$to, $from],
    • jedna data albo data nieistniejąca (2021-02-31) - wszystkie zamówienia i podpowiedź „Wybierz obie daty…”; warunek ($from === null) !== ($to === null) jest prawdziwy, gdy dokładnie jedna z dat jest pusta,
    • brak zamówień w okresie - zdanie zamiast pustej tabeli; tekst wstrzyknięty w pole daty nie przechodzi przez validDate(),
    • awaria bazy - komunikat zamiast błędu PHP.

    Pola formularza dostają value z wybraną datą (przez e()), więc po wysłaniu formularz pokazuje, jaki okres widać w tabeli. Podgląd: 2021-11-06 - 2021-11-10, trzy zamówienia.

    index.php PHP zmienione: 13 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    function paintColor(string $code): string
    {
        return preg_match('/^[0-9a-f]{6}$/i', $code) === 1 ? '#' . $code : 'transparent';
    }
    
    function validDate(mixed $value): ?string
    {
        if (!is_string($value)) {
            return null;
        }
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $value);
        return $date !== false && $date->format('Y-m-d') === $value ? $value : null;
    }
    
    $from = validDate($_POST['from'] ?? null);
    $to = validDate($_POST['to'] ?? null);
    $orders = [];
    $message = '';
    
    if ($from !== null && $to !== null && $from > $to) {
        [$from, $to] = [$to, $from];
    } elseif (($from === null) !== ($to === null)) {
        $message = 'Wybierz obie daty, żeby zawęzić listę zamówień.';
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=mieszalnia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($from !== null && $to !== null) {
            $statement = $pdo->prepare(
                'SELECT z.id, k.nazwisko, k.imie, z.kod_koloru, z.pojemnosc, z.data_odbioru
                     FROM klienci AS k
                     JOIN zamowienia AS z ON z.id_klienta = k.id
                     WHERE z.data_odbioru BETWEEN :from AND :to
                     ORDER BY z.data_odbioru'
            );
            $statement->execute(['from' => $from, 'to' => $to]);
        } else {
            $statement = $pdo->query(
                'SELECT z.id, k.nazwisko, k.imie, z.kod_koloru, z.pojemnosc, z.data_odbioru
                     FROM klienci AS k
                     JOIN zamowienia AS z ON z.id_klienta = k.id
                     ORDER BY z.data_odbioru'
            );
        }
        $orders = $statement->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $message = 'Nie udało się pobrać zamówień. 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>Mieszalnia farb</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <img src="baner.png" alt="Mieszalnia farb">
        </header>
        <section class="search">
            <form method="post">
                <label for="from">Data odbioru od: </label>
                <input type="date" id="from" name="from" value="<?= e($from ?? '') ?>">
                <label for="to">do: </label>
                <input type="date" id="to" name="to" value="<?= e($to ?? '') ?>">
                <button type="submit">Wyszukaj</button>
            </form>
        </section>
        <main>
            <table>
                <thead>
                    <tr>
                        <th>Nr zamówienia</th>
                        <th>Nazwisko</th>
                        <th>Imię</th>
                        <th>Kolor</th>
                        <th>Pojemność [ml]</th>
                        <th>Data odbioru</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($orders as $order): ?>
                        <tr>
                            <td><?= e($order['id']) ?></td>
                            <td><?= e($order['nazwisko']) ?></td>
                            <td><?= e($order['imie']) ?></td>
                            <td style="background-color: <?= paintColor($order['kod_koloru']) ?>"><?= e($order['kod_koloru']) ?></td>
                            <td><?= e($order['pojemnosc']) ?></td>
                            <td><?= e($order['data_odbioru']) ?></td>
                        </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
            <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
            <?php elseif ($orders === []): ?>
                <p>Brak zamówień do odbioru w wybranym okresie.</p>
            <?php endif; ?>
        </main>
        <footer>
            <h3>Egzamin INF.03</h3>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  25. Etap 5: Porównanie z wersją podstawową

    Krok 25 · po tym etapie masz 37 z 37 kryteriów (pkt)

    Krok 25: 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
    bazamysqli_connect w tabeli, mysqli_fetch_row, $row[2]PDO nad HTML-em, fetchAll, $order['id']
    daty!empty($_POST[...]) i wklejenie w zapytanie$_POST z ?? + validDate(), zapytanie przygotowane
    kolor#$row[3] wprost w atrybuciepaintColor() przepuszcza tylko sześć cyfr szesnastkowych
    tabelawiersze z echo w napisach<thead> / <tbody>, szablon foreach
    formularzpo wysłaniu pustypamięta wybrane daty
    złe dane, awaria bazypusta tabela albo błąd PHPkomunikaty

    Na egzaminie wersja podstawowa jest szybsza do napisania i każde kryterium R.5 ma w niej jedną linię. Nowoczesna pokazuje, jak to samo zadanie wygląda w aplikacji, z której korzystają prawdziwi klienci - tam pola daty da się wypełnić z pominięciem kalendarza przeglądarki.

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

  26. Etap 6: Oddanie pracy

    Krok 26 · po tym etapie masz 37 z 37 kryteriów (pkt)

    Krok 26: 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 folderze z numerem zdającego, powinny znajdować się pliki: baner.png, fav.png, import.jpg, index.php, kw1.png, kw2.png, kw3.png, kw4.png, kwerendy.txt, przeglądarka.txt, style.css, ewentualnie inne przygotowane pliki.

    W przeglądarka.txt wpisz nazwę przeglądarki, w której strona była sprawdzana przez http://localhost/.... Sprawdź listę plików z arkusza (import w JPEG, zrzuty zapytań w PNG), nagraj płytę i sprawdź jej odczyt.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.