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.
Suwak i strzałki obok niego - albo ←→ na klawiaturze - przechodzą krok po kroku; wyjaśnienie, pliki i podgląd zmieniają się razem z krokiem.
Lista „Etap” przenosi od razu do wybranego etapu.
„Rozwiązuję samodzielnie” chowa rozwiązanie kroku - najpierw własna próba, potem odsłonięcie.
„Tryb prezentacji” pokazuje same kroki, bez menu strony - na rzutnik.
Etap 1: Baza danych
Kroki 1-7 · po tym etapie masz 6 z 37 kryteriów (pkt)
Krok 1: Baza danych 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).
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.
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.
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.txtTekstnowy plik
-- Zapytanie 1SELECT COUNT(*) AS liczba_klientowFROM klienci;
Podgląd strony po tym kroku
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.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT COUNT(*) AS liczba_klientowFROM klienci;-- Zapytanie 2SELECT k.nazwisko, k.imie, z.id, z.kod_koloru, z.pojemnosc, z.data_odbioruFROM klienci AS kJOIN zamowienia AS z ON z.id_klienta = k.idORDER BY z.data_odbioru;
Podgląd strony po tym kroku
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.txtTekstzmienione: 7 linii
-- Zapytanie 1SELECT COUNT(*) AS liczba_klientowFROM klienci;-- Zapytanie 2SELECT k.nazwisko, k.imie, z.id, z.kod_koloru, z.pojemnosc, z.data_odbioruFROM klienci AS kJOIN zamowienia AS z ON z.id_klienta = k.idORDER BY z.data_odbioru;-- Zapytanie 3SELECT k.nazwisko, k.imie, z.id, z.kod_koloru, z.pojemnosc, z.data_odbioruFROM klienci AS kJOIN zamowienia AS z ON z.id_klienta = k.idWHERE z.data_odbioru BETWEEN '2021-11-05' AND '2021-11-07'ORDER BY z.data_odbioru;
Podgląd strony po tym kroku
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.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT COUNT(*) AS liczba_klientowFROM klienci;-- Zapytanie 2SELECT k.nazwisko, k.imie, z.id, z.kod_koloru, z.pojemnosc, z.data_odbioruFROM klienci AS kJOIN zamowienia AS z ON z.id_klienta = k.idORDER BY z.data_odbioru;-- Zapytanie 3SELECT k.nazwisko, k.imie, z.id, z.kod_koloru, z.pojemnosc, z.data_odbioruFROM klienci AS kJOIN zamowienia AS z ON z.id_klienta = k.idWHERE z.data_odbioru BETWEEN '2021-11-05' AND '2021-11-07'ORDER BY z.data_odbioru;-- Zapytanie 4SELECT imie, nazwiskoFROM klienciWHERE plec = 'k';
Podgląd strony po tym kroku
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.
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,
Plik → Otwórz jako warstwynazwa.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,
Eksportuj jakobaner.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
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 jakofav.png (R.2.1, R.2.3).
Podgląd strony po tym kroku
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.
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.
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).
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.
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.
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).
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).
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.
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).
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.
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.phpPHPzmienione: 28 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES);}functionpaintColor(string $code): string{returnpreg_match('/^[0-9a-f]{6}$/i', $code) === 1 ? '#' . $code : 'transparent';}functionvalidDate(mixed $value): ?string{if (!is_string($value)) {returnnull; }$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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Mieszalnia farb</title> <linkrel="icon"href="fav.png"type="image/png"> <linkrel="stylesheet"href="style.css"></head><body> <header> <imgsrc="baner.png"alt="Mieszalnia farb"> </header> <sectionclass="search"> <formmethod="post"> <labelfor="from">Data odbioru od: </label> <inputtype="date"id="from"name="from"> <labelfor="to">do: </label> <inputtype="date"id="to"name="to"> <buttontype="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><?phpforeach ($ordersas$order): ?> <tr> <td><?= e($order['id']) ?></td> <td><?= e($order['nazwisko']) ?></td> <td><?= e($order['imie']) ?></td> <tdstyle="background-color: <?= paintColor($order['kod_koloru']) ?>"><?= e($order['kod_koloru']) ?></td> <td><?= e($order['pojemnosc']) ?></td> <td><?= e($order['data_odbioru']) ?></td> </tr><?phpendforeach; ?> </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)
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.phpPHPzmienione: 13 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES);}functionpaintColor(string $code): string{returnpreg_match('/^[0-9a-f]{6}$/i', $code) === 1 ? '#' . $code : 'transparent';}functionvalidDate(mixed $value): ?string{if (!is_string($value)) {returnnull; }$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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Mieszalnia farb</title> <linkrel="icon"href="fav.png"type="image/png"> <linkrel="stylesheet"href="style.css"></head><body> <header> <imgsrc="baner.png"alt="Mieszalnia farb"> </header> <sectionclass="search"> <formmethod="post"> <labelfor="from">Data odbioru od: </label> <inputtype="date"id="from"name="from"value="<?= e($from ?? '') ?>"> <labelfor="to">do: </label> <inputtype="date"id="to"name="to"value="<?= e($to ?? '') ?>"> <buttontype="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><?phpforeach ($ordersas$order): ?> <tr> <td><?= e($order['id']) ?></td> <td><?= e($order['nazwisko']) ?></td> <td><?= e($order['imie']) ?></td> <tdstyle="background-color: <?= paintColor($order['kod_koloru']) ?>"><?= e($order['kod_koloru']) ?></td> <td><?= e($order['pojemnosc']) ?></td> <td><?= e($order['data_odbioru']) ?></td> </tr><?phpendforeach; ?> </tbody> </table><?phpif ($message !== ''): ?> <p><?= e($message) ?></p><?phpelseif ($orders === []): ?> <p>Brak zamówień do odbioru w wybranym okresie.</p><?phpendif; ?> </main> <footer> <h3>Egzamin INF.03</h3> <p>Autor: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
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 podstawowa
wersja nowoczesna
baza
mysqli_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 atrybucie
paintColor() przepuszcza tylko sześć cyfr szesnastkowych
tabela
wiersze z echo w napisach
<thead> / <tbody>, szablon foreach
formularz
po wysłaniu pusty
pamięta wybrane daty
złe dane, awaria bazy
pusta tabela albo błąd PHP
komunikaty
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.
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.
Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.
To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.
Autor rozwiązania: Bartosz Bryniarski. Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.