INF.03-04-25.06-SGCzerwiec 2025 · 32 kroki · ok. 83 min · 41 kryteriów z klucza
Rozwiązanie krok po kroku: Portal firmy przewozowej (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza: portal firmy przewozowej z bazą przewozy, grafiką auto.png, stroną przewozy.php z tabelą zadań, odnośnikami „Usuń” i formularzem dodawania. Obie wersje spełniają wszystkie 41 kryteriów klucza - różni je sposób pisania kodu.
Czym ta wersja różni się od podstawowej:
sekcje na siatce CSS - szerokości 70% / 30% stoją w grid-template-columns, bez float i clear; kolory z arkusza w zmiennych CSS,
tabela z <thead> / <tbody>, daty w znaczniku <time>,
PHP na PDO: dane połączenia w config.php, cała praca z bazą na górze pliku w jednym bloku try/catch - szczegóły błędu trafiają do dziennika serwera (error_log), a odwiedzający widzi krótki komunikat,
sprawdzanie wejścia: id z odnośnika przez filter_var (tylko dodatnia liczba całkowita), data z formularza sprawdzana jako prawdziwa data kalendarzowa, opis zadania przycięty z białych znaków,
zapytania przygotowane z parametrami nazwanymi, PRG po usunięciu i po dodaniu (odświeżenie strony niczego nie powtarza), e() przy każdym wypisaniu.
Kroki nadal idą zdanie po zdaniu za treścią arkusza. Podgląd strony PHP to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2 na bazie z arkusza, w stanie po zapytaniach 1-4 (bez zadania o id 2, ze „spotkaniem firmowym”) - jak na ilustracji 2.
Jak liczą się punkty. Klucz ma 41 kryteriów (R.1.1 - R.5.9), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 31 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.
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-5 · po tym etapie masz 6 z 41 kryteriów (pkt)
Krok 1: Baza przewozy, import i zrzut import.png
Kryteria:
R.1.1 Wykonany import tabeli do bazy danych przewozy , czynność udokumentowana w pliku import.png
ok. 4 min
Polecenie z arkusza
Utwórz bazę danych o nazwie przewozy, z zestawem polskich znaków (np. utf8_unicode_ci). Z rozpakowanego archiwum zaimportuj tabelę z pliku baza.sql do utworzonej bazy. Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie PNG pod nazwą import. Nie kadruj zrzutu.
CREATE DATABASE przewozy CHARACTER SET utf8 COLLATE utf8_unicode_ci; (albo Nowa w phpMyAdmin).
Zaznacz bazę, Import → baza.sql. Powstają osoby (6 wierszy) i zadania (7 wierszy) z kluczem obcym osoba_id → osoby.id_osoba. Klucze główne nazywają się tu id_osoba i id_zadania, nie id.
Zrzut całego ekranu z paskiem zadań jako import.png (w tym arkuszu wszystkie zrzuty w PNG).
Typowe błędy
Import bez zaznaczonej bazy przewozy ląduje w innej bazie - skrypty nie znajdą tabeli zadania.
Krok 2: Zapytanie 1: lista zadań
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 jedynie id_zadania, zadanie i datę z tabeli zadania . W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id_zadania, zadanie, data FROM zadania; oraz wynik działania udokumentowany zrzutem, na którym widocznych jest dokładnie 7 lub 8 rekordów i wyświetlone jedynie kolumny id_zadania, zadanie i data
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie id_zadania, zadanie i datę z tabeli zadania.
Trzy kolumny wymienione z nazwy, bez SELECT *. Wynik: 7 wierszy (zapytania wykonywane w kolejności arkusza, więc jeszcze przed wstawieniem i usunięciem; klucz dopuszcza 7 lub 8). Zapytania zapisuj w kwerendy.txt z komentarzem -- nad każdym; zrzut: kw1.png. To zapytanie wyśle skrypt 1.
Krok 3: Zapytanie 2: nowe zadanie dla osoby o id 1
Kryteria:
R.1.4 Wykonane zapytanie 2 wstawiające do tabeli zadania rekord z danymi: „spotkanie firmowe”, dnia „2024-05-10” dla osoby o id = 1 . W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO zadania VALUES (NULL, 'Spotkanie firmowe', '2024-05- 10', 1); oraz wynik działania udokumentowany zrzutem, na którym widoczny jest komunikat o poprawnie wstawionym rekordzie lub tabela z dodanym rekordem
ok. 3 min
Polecenie z arkusza
Zapytanie 2: wstawiające do tabeli zadania rekord z danymi: „spotkanie firmowe”, dnia „2024-05-10” dla osoby o id równym 1.
INSERT INTO z listą kolumn - bez id_zadania, które nada autonumeracja (wyjdzie 8). Wariant z klucza VALUES (NULL, ...) bez listy kolumn też działa, ale zależy od kolejności kolumn w tabeli: po dodaniu kolumny przestałby działać. Data w formacie RRRR-MM-DD. Zrzut kw2.png: komunikat o wstawieniu wiersza.
Typowe błędy
Data '10-05-2024' nie jest datą dla MySQL - zapisze 0000-00-00 albo odrzuci wiersz.
kwerendy.txtTekstzmienione: 4 linie
-- Zapytanie 1SELECT id_zadania, zadanie, dataFROM zadania;-- Zapytanie 2INSERT INTO zadania (zadanie, data, osoba_id)VALUES ('spotkanie firmowe', '2024-05-10', 1);
Podgląd strony po tym kroku
Krok 4: Zapytanie 3: usunięcie zadania o id 2
Kryteria:
R.1.5 Wykonane zapytanie 3 usuwające rekord o id_zadania = 2 z tabeli zadania. W pliku z kwerendami lub na zrzucie istnieje zapis np.: DELETE FROM zadania WHERE id_zadania = 2; oraz wynik działania udokumentowany zrzutem, na którym widoczny jest komunikat o poprawnie usuniętym rekordzie lub widoczna tabela bez rekordu o id_zadania = 2
ok. 2 min
Polecenie z arkusza
Zapytanie 3: usuwające rekord o id_zadania równym 2 z tabeli zadania.
DELETE FROM zadania WHERE id_zadania = 2 - warunek po kluczu głównym usuwa dokładnie jeden wiersz („Przewóz telewizora…”). Zrzut kw3.png: komunikat o usunięciu.
Typowe błędy
DELETE bez WHERE kasuje wszystkie zadania.
kwerendy.txtTekstzmienione: 4 linie
-- Zapytanie 1SELECT id_zadania, zadanie, dataFROM zadania;-- Zapytanie 2INSERT INTO zadania (zadanie, data, osoba_id)VALUES ('spotkanie firmowe', '2024-05-10', 1);-- Zapytanie 3DELETE FROM zadaniaWHERE id_zadania = 2;
Podgląd strony po tym kroku
Krok 5: Zapytanie 4: kto ma jakie zadanie
Kryteria:
R.1.6 Wykonane zapytanie 4 wybierające jedynie imię, nazwisko i numer telefonu osoby oraz odpowiadające im zadanie i datę z tabeli zadania . W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT imie, nazwisko, telefon, zadanie, data FROM zadania INNER JOIN osoby ON id_osoba = osoba_id; (lub samo JOIN, LEFT JOIN, możliwe też porównanie kluczy po WHERE) oraz wynik działania udokumentowany zrzutem, na którym widoczne są rekordy zawierające jedynie pola imie, nazwisko, telefon, zadanie, data
ok. 3 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie imię, nazwisko i numer telefonu osoby oraz odpowiadające im zadanie i datę z tabeli zadania. Należy posłużyć się relacją.
FROM zadania AS z JOIN osoby AS o - aliasy tabel,
ON o.id_osoba = z.osoba_id - klucz główny osoby równy kluczowi obcemu zadania,
SELECT o.imie, o.nazwisko, o.telefon, z.zadanie, z.data - pięć kolumn, każda z aliasem tabeli.
Wynik: 7 wierszy, w tym „spotkanie firmowe” Anny. Zrzut: kw4.png.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT id_zadania, zadanie, dataFROM zadania;-- Zapytanie 2INSERT INTO zadania (zadanie, data, osoba_id)VALUES ('spotkanie firmowe', '2024-05-10', 1);-- Zapytanie 3DELETE FROM zadaniaWHERE id_zadania = 2;-- Zapytanie 4SELECT o.imie, o.nazwisko, o.telefon, z.zadanie, z.dataFROM zadania AS zJOIN osoby AS o ON o.id_osoba = z.osoba_id;
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 6-15 · po tym etapie masz 20 z 41 kryteriów (pkt)
Krok 6: Grafika auto.png: przezroczyste tło i szerokość 400 px
Kryteria:
R.2.1 Grafika auto.png zawiera przezroczyste tło oraz jej szerokość to 400 px, odpowiadająca temu wysokość to 200 px (± 5 px)
ok. 5 min
Polecenie z arkusza
Grafika o nazwie auto w formacie PNG, przygotowana na podstawie grafiki auto1.jpg. Tło przezroczyste. Przeskalowana z zachowaniem proporcji do szerokości 400 px.
W GIMP-ie: Warstwa → Przezroczystość → Dodaj kanał alfa, różdżka z małym progiem na białym tle i Delete, Obraz → Skaluj obraz do 400 px szerokości (wysokość 200 px, klucz: ±5), eksport jako auto.png - tylko PNG zachowa przezroczystość. Grafika jest taka sama w obu wersjach rozwiązania.
Typowe błędy
Za duży próg różdżki usuwa razem z tłem białą karoserię.
Podgląd strony po tym kroku
Krok 7: Grafika auto.png: napisy na boku auta w perspektywie
Kryteria:
R.2.2 Tekst z grafiki napisy.jpg jest nałożony na bok samochodu utworzonej grafiki. Rozmiar tekstu nie przekracza rozmiaru boku samochodu oraz transformowano jego kształt jak na ilustracji 2 w arkuszu egzaminacyjnym
ok. 5 min
Polecenie z arkusza
Na boku samochodu wklejone napisy z pliku napisy.jpg dopasowane do wielkości i położenia samochodu, zgodnie z ilustracją 2 (należy zastosować perspektywę).
Napisy jako nowa warstwa (Plik → Otwórz jako warstwy), zielone tło usunięte różdżką, skalowanie do wielkości boku furgonu, potem narzędzie Perspektywa (Shift+P) - narożniki dopasowane do boku auta. Eksport ponownie do auto.png.
⚠️ Rozbieżność arkusz-załącznik: arkusz mówi o pliku napisy.jpg, a w archiwum pliki11 jest napisy.png. To ten sam obraz - użyj pliku z archiwum.
Typowe błędy
Napis wklejony płasko, bez perspektywy, nie spełnia R.2.2.
Krok 8: Plik przewozy.php: HTML5, język i kodowanie
Kryteria:
R.2.3 W pliku przewozy.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków
ok. 2 min
Polecenie z arkusza
Składa się ze strony o nazwie przewozy.php. Zapisana w języku HTML5. Zadeklarowany polski język zawartości witryny. Jawnie zastosowany właściwy standard kodowania polskich znaków.
<!DOCTYPE html>, <html lang="pl">, <meta charset="UTF-8"> jako pierwszy element <head>.
R.3.2 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styles.css formatowanie CSS pochodzi jedynie z tego arkusza
ok. 1 min
Polecenie z arkusza
Tytuł strony „Firma Przewozowa” widoczny na karcie przeglądarki. Arkusz stylów w pliku o nazwie styles.css prawidłowo połączony z kodem strony.
<title> (R.2.4) i <link rel="stylesheet" href="styles.css"> (R.3.2) - tu plik nazywa się styles.css. Meta viewport każe telefonom wyświetlać stronę w szerokości ekranu zamiast udawać monitor.
R.2.5 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML 5: header, nav, main -> section + section, footer
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ą 3.
Układ z klucza R.2.5: <header>, <nav>, <main> z dwiema <section>, <footer>. Sekcje mają identyfikatory mówiące, co zawierają (tasks - zadania, offer - oferta), a nie gdzie stoją: gdyby na telefonie oferta trafiła nad zadania, nazwa left byłaby myląca.
R.3.6 Wybranie przynajmniej jednego odnośnika powoduje wyświetlenie lub pobranie zrzutu ekranu z kwerendą
ok. 2 min
Polecenie z arkusza
Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści: „Firma przewozowa Półdarmo”. Zawartość bloku nawigacyjnego: odnośniki o treści kwerenda1, kwerenda2, kwerenda3, kwerenda4 do plików zawierających zrzuty ekranu kwerend.
<h1> w nagłówku. W nawigacji cztery odnośniki do plików kw1.png - kw4.png, które leżą w tym samym folderze co strona - przeglądarka otworzy obraz (R.3.6). Odnośniki rozdzielone spacjami i znakami nowej linii: arkusz każe odsunąć je od siebie przez odstępy między wyrazami, a te działają tylko tam, gdzie jest spacja.
Zawartość sekcji lewej: nagłówek drugiego stopnia o treści: „Zadania do wykonania”. Tabela o trzech kolumnach i wierszu z komórkami nagłówkowymi o treści: „Zadanie do wykonania”, „Data realizacji”, „Akcja”. Tabela jest wypełniona i modyfikowana przez skrypt 1. Jest dodatkowo modyfikowana przez skrypt 2.
<h2> i tabela podzielona na <thead> (wiersz komórek <th> z scope="col") i pusty <tbody>, który wypełni skrypt 1. Komórki <td> pojawią się w wierszach z danymi - razem z nimi tabela spełni R.2.8 (<table>, <tr>, <th>, <td>).
R.2.7 W sekcji lewej umieszczony formularz zawierający dwa pola edycyjne i przycisk "Dodaj" oraz zastosowana przynajmniej jedna etykieta <label>
R.3.3 Formularz wysyła dane metodą POST do tego samego pliku (poprawne również bez atrybutu action)
R.3.4 Przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu)
R.3.5 Pole edycyjne do wpisania daty jest typu date
ok. 3 min
Polecenie z arkusza
Formularz wysyłający dane do tego samego pliku metodą bezpieczną, obsługiwany przez skrypt 2. Zawiera: pole edycyjne, poprzedzone etykietą „Zadanie do wykonania: ”, pole edycyjne przeznaczone do wpisania daty, poprzedzone etykietą „Data realizacji: ”, przycisk „Dodaj” wysyłający dane z formularza.
<form method="post"> bez action - dane idą metodą POST do tej samej strony (R.3.3). Dwie pary <label for> + pole (R.2.7); pole daty to type="date" (R.3.5) - przeglądarka pokazuje kalendarz i zawsze wysyła datę w formacie RRRR-MM-DD, niezależnie od tego, jak ją wyświetla. required przy obu polach zatrzymuje pusty formularz już w przeglądarce. Przycisk type="submit" wysyła dane (R.3.4).
Pola stoją jedno za drugim w linii, jak na ilustracji 2 - bez <br>; gdy zabraknie miejsca, przeglądarka sama zawinie je do następnej linii.
Typowe błędy
type="text" dla daty przepuszcza dowolny tekst i nie spełnia R.3.5.
R.2.9 W sekcji prawej umieszczony obraz auta z tekstem alternatywnym "auto firmowe"
R.2.10 W sekcji prawej umieszczona jedna lista punktowana z 5 elementami, zastosowane znaczniki <ul>, <li>
ok. 2 min
Polecenie z arkusza
Zawartość sekcji prawej: obraz auto.png z tekstem alternatywnym o treści: „auto firmowe”. Nagłówek trzeciego stopnia o treści: „Nasza specjalność”. Lista punktowana (nieuporządkowana) zawierająca elementy: „Przeprowadzki”, „Przewóz mebli”, „Przesyłki gabarytowe”, „Wynajem pojazdów”, „Zakupy towarów”.
Obraz z tekstem alternatywnym z arkusza (R.2.9) i atrybutami width/height z wymiarami pliku - przeglądarka rezerwuje miejsce, zanim obraz się wczyta. Pod nim <h3> i lista <ul> z pięcioma elementami (R.2.10).
R.2.6 Zapisane w bloku nagłówkowym <h1>: "Firma przewozowa Półdarmo", w sekcji lewej <h2>: "Zadania do wykonania", w sekcji prawej <h3>: "Nasza specjalność" oraz w stopce paragraf z numerem zdającego
ok. 1 min
Polecenie z arkusza
Zawartość stopki: paragraf o treści „Stronę wykonał: ”, dalej wstawiony numer zdającego.
Tu stoi nazwisko autora rozwiązania - na egzaminie po dwukropku wpisujesz swój numer zdającego (PESEL). Z tym paragrafem komplet R.2.6: <h1>, <h2>, <h3> i paragraf w stopce.
Kroki 16-23 · po tym etapie masz 31 z 41 kryteriów (pkt)
Krok 16: Zmienne z kolorami i ciało strony
Kryteria:
R.4.1 Ustawione dla selektora body: kolor tła #FFCC99, krój czcionki Garamond, wyrównanie tekstu do środka
ok. 2 min
Polecenie z arkusza
Dla selektora ciała strony: kolor tła #FFCC99, krój czcionki Garamond, wyrównanie tekstu do środka.
Zmienne w :root: ciemna zieleń występuje w pięciu miejscach (nagłówek, stopka, ramka tabeli, podświetlony wiersz), jasna w dwóch - każda wpisana raz. Ciało strony selektorem znacznika body (zastrzeżenie arkusza) z krojem Garamond, serif - serif to zapas, gdy Garamonda nie ma (R.4.1).
Dla bloku nagłówkowego i stopki: kolor tła DarkGreen, biały kolor czcionki, marginesy wewnętrzne: 30 px, rozmiar czcionki 140%.
Jedna reguła dla obu bloków. font-size: 140% liczy się od rozmiaru rodzica - nagłówek <h1> w środku i tak jest większy, bo jego rozmiar to wielokrotność rozmiaru bloku.
Dla bloku nawigacyjnego: kolor tła MediumSeaGreen, odstępy między wyrazami: 90 px, marginesy wewnętrzne 20 px.
„Odstępy między wyrazami” to właściwość word-spacing - arkusz nazywa ją wprost, więc odstępy między odnośnikami nie idą przez gap z flexa, choć tak zrobiłoby się to w nowym projekcie. word-spacing dokłada 90 px do każdej spacji między odnośnikami.
Typowe błędy
letter-spacing rozsuwa litery w wyrazach, a nie wyrazy.
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 3 w arkuszu egzaminacyjnym (prawidłowo zastosowane 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)
R.4.5 Ustawiona wysokość obu sekcji na 500px
R.4.6 Ustawiona szerokość sekcji lewej na 70% oraz sekcji prawej na 30% (width lub grid- template-columns lub flex)
ok. 3 min
Polecenie z arkusza
Dla sekcji lewej: szerokość 70%, wysokość 500 px.
Szerokości obu sekcji ustawia siatka na bloku głównym: grid-template-columns: 70% 30% - dwie kolumny, a sekcje trafiają do nich w kolejności z HTML. Klucz R.4.6 dopuszcza zapis szerokości w grid-template-columns. Wysokość 500 px dla obu sekcji w jednej regule (R.4.5). Sekcje stoją obok siebie jak na ilustracji 3, a stopka pod blokiem głównym bez clear (R.3.1).
R.4.2 Ustawione kolory tła: DarkGreen dla bloku nagłówkowego i stopki oraz MediumSeaGreen dla bloku nawigacyjnego i sekcji prawej
R.4.4 Ustawione wyrównanie tekstu do lewej dla sekcji prawej, odstępy między wyrazami 90px dla bloku nawigacyjnego oraz rozmiar czcionki 140% dla bloku nagłówkowego i stopki
ok. 1 min
Polecenie z arkusza
Dla sekcji prawej: kolor tła MediumSeaGreen, szerokość 30%, wysokość 500 px, wyrównanie tekstu do lewej.
Tło i text-align: left - wyrównanie z body zostaje nadpisane tylko w tej sekcji. Szerokość 30% i wysokość są już ustawione. Komplet teł z R.4.2: DarkGreen nagłówka i stopki, MediumSeaGreen nawigacji i tej sekcji; word-spacing, font-size: 140% i wyrównanie do lewej dają R.4.4.
Zawartość sekcji prawej: obraz auto.png z tekstem alternatywnym o treści: „auto firmowe”.
Grafika ma 400 px, a sekcja oferty to 30% szerokości - w oknie węższym niż ok. 1350 px obraz wystawałby poza sekcję i poza ekran. max-width: 100% pozwala mu się zmniejszyć do szerokości sekcji (nigdy nie powiększa ponad 400 px), a height: auto zachowuje proporcje mimo atrybutu height="200" w HTML. Arkusz nie formatuje obrazu, więc to dodatek - plik auto.png dalej ma 400 × 200 px, jak sprawdza R.2.1.
R.4.7 Ustawione marginesy wewnętrzne (padding) dla bloku nagłówkowego i stopki 30px, dla bloku nawigacyjnego 20px, dla komórek tabeli 5px oraz marginesy zewnętrzne (margin) dla selektora table górny i dolny:30px, pozostałe: auto
R.4.8 Ustawione dla tabeli obramowanie 1px dashed DarkGreen
ok. 2 min
Polecenie z arkusza
Dla selektora tabeli: obramowanie linią kreskowaną o grubości 1 px i kolorze DarkGreen, tabela wyśrodkowana, marginesy zewnętrzne górny i dolny 30 px. Dla komórek tabeli i komórek nagłówkowych: marginesy wewnętrzne 5 px.
border: 1px dashed z kolorem ze zmiennej (R.4.8), margin: 30px auto - 30 px góra i dół, auto po bokach wyśrodkowuje tabelę. Komórki td, th z padding: 5px - z paddingami nagłówka, stopki i nawigacji to komplet R.4.7.
R.4.3 Ustawiony biały kolor czcionki dla bloków nagłówkowego i stopki oraz dla selektora a
R.4.9 Ustawiony dla selektora ul typ punktora circle
R.4.10 W momencie, gdy kursor myszy znajdzie się na wierszu tabeli jego kolor tła zmienia się na DarkGreen, a kolor czcionki na biały
ok. 2 min
Polecenie z arkusza
Dla selektora odnośnika: biały kolor czcionki. Dla selektora listy punktowanej: typ elementu okrąg. W momencie, gdy kursor myszy znajdzie się na wierszu tabeli jego kolor tła zmienia się na DarkGreen, a kolor czcionki na biały.
a i ul selektorami znaczników (zastrzeżenie arkusza): biały kolor odnośników (z nagłówkiem i stopką komplet R.4.3) i punktor circle (R.4.9). Podświetlenie tbody tr:hover obejmuje tylko wiersze z zadaniami - wiersz nagłówkowy w <thead> się nie wybiera, więc nie reaguje na kursor (R.4.10).
Kroki 24-30 · po tym etapie masz 41 z 41 kryteriów (pkt)
Krok 24: Plik config.php i połączenie PDO
Kryteria: , ,
R.5.1 Skrypt 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 przewozy
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. 4 min
Polecenie z arkusza
Napisane w języku PHP. Łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie przewozy.
config.php zwraca tablicę z danymi połączenia (dsn z bazą przewozy i charset=utf8mb4, użytkownik, hasło) - jedno miejsce na hasło, poza plikiem strony.
Na górze przewozy.php: wartości domyślne ($tasks = [], $databaseError = '') i połączenie PDO w trybie wyjątków. W catch dwie rzeczy:
error_log($exception->getMessage()) - pełny opis błędu trafia do dziennika serwera (w XAMPP: apache/logs/error.log), gdzie zajrzy administrator,
odwiedzający dostaje tylko ogólny komunikat w $databaseError, bez nazw serwera i użytkownika.
Za chwilę cała praca z bazą trafi do tego samego bloku try, więc jeden catch obsłuży każdy błąd bazy.
Typowe błędy
PDO nie ma w tabeli 1 arkusza - nazwy stałych trzeba znać na pamięć. Bez tej pewności lepiej pisać mysqli_*, jak w wersji podstawowej.
config.phpPHPnowy plik
<?php// Dane połączenia z bazą - w jednym miejscu, poza plikiem stronyreturn ['dsn' => 'mysql:host=localhost;dbname=przewozy;charset=utf8mb4','user' => 'root','password' => '',];
Kolejne zwrócone zapytaniem pola zadanie i data są wyświetlane w kolejnych wierszach tabeli w dwóch pierwszych kolumnach.
Opisy zadań wpisują ludzie przez formularz, więc wypisanie ich wprost pozwoliłoby wstawić na stronę skrypt (XSS). Funkcja e() przepuszcza wartość przez htmlspecialchars z ENT_QUOTES - każdy znak specjalny HTML zamienia się na encję i wyświetla dosłownie. Będzie używana przy każdym wypisaniu danych z bazy.
Krok 26: Skrypt 1: zapytanie 1 i dwie pierwsze kolumny
Kryteria: , ,
R.5.4 Skrypty zawierają instrukcje wysyłające do bazy danych co najmniej jedno zapytanie z: zapytanie 1 lub zapytanie 2 zmodyfikowane tak, że zawiera dane z formularza, lub zapytanie 3 zmodyfikowane tak, że usuwany jest rekord o id_zadania odpowiadającym wierszowi tabeli
R.5.5 Skrypt 1 wyświetla w wierszach tabeli dane zwrócone zapytaniem, dane są wpisane do pierwszych dwóch kolumn tabeli
R.2.8 W sekcji lewej umieszczona tabela o 3 kolumnach i wierszu z komórkami nagłówkowymi, (w skrypcie lub HTML) zastosowane znaczniki <table>, <tr>, <th>, <td>
ok. 4 min
Polecenie z arkusza
Skrypt 1: wysyła do bazy danych zapytanie 1. Kolejne zwrócone zapytaniem pola zadanie i data są wyświetlane w kolejnych wierszach tabeli w dwóch pierwszych kolumnach.
W bloku try: zapytanie 1 przez query() (bez danych od użytkownika) i fetchAll() do tablicy $tasks (R.5.4). W <tbody>: pętla foreach/endforeach z wierszem <tr> i dwiema komórkami - zadanie i data, po nazwach kolumn, przez e() (R.5.5).
Data stoi w znaczniku <time datetime="..."> - dla człowieka wygląda tak samo, a wyszukiwarki i czytniki ekranu dostają informację, że to data. Podgląd pokazuje stan po zapytaniach 1-4: siedem zadań, bez „Przewozu telewizora”, ze „spotkaniem firmowym” na końcu.
Trzecia kolumna tabeli zawiera odnośnik o treści Usuń. Jego wybranie powoduje usunięcie rekordu z bazy za pomocą zmodyfikowanego zapytania 3, w ten sposób że odnośnik dotyczy id_zadania dla wiersza tabeli w którym jest umieszczony.
Trzecia komórka z odnośnikiem przewozy.php?delete=8 - parametr adresu niesie id_zadania wiersza. Wartość jest rzutowana na liczbę(int): identyfikator to liczba, a rzutowanie gwarantuje, że do adresu nie trafi nic innego - bez potrzeby escapowania.
Arkusz wymaga tu odnośnika, więc usuwanie idzie zwykłym żądaniem GET. W nowym projekcie byłby to raczej mały formularz z przyciskiem i metodą POST: przeglądarki i wyszukiwarki potrafią same „odwiedzać” odnośniki, a odwiedzenie nie powinno niczego kasować.
Krok 28: Skrypt 1: usunięcie zadania i przekierowanie
Kryteria: ,
R.5.6 Skrypt 1 usuwa z bazy danych rekord którego id_zadania odpowiada wierszowi odnośnika (należy sprawdzić w phpMyAdmin)
R.5.8 Skrypt zawiera instrukcję pobierającą dane z tablicy globalnej odpowiadającej zastosowanej metodzie formularza ($_POST, $_GET, $_REQUEST )
ok. 4 min
Polecenie z arkusza
Jego wybranie powoduje usunięcie rekordu z bazy za pomocą zmodyfikowanego zapytania 3, w ten sposób że odnośnik dotyczy id_zadania dla wiersza tabeli w którym jest umieszczony.
Usuwanie stoi w tryprzed pobraniem listy - po kliknięciu tabela nie pokaże już skasowanego zadania.
filter_var($_GET['delete'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]) - liczba całkowita od 1 albo false. Adres ?delete=1 OR 1=1 daje false i nic się nie dzieje. Dane z $_GET, bo odnośnik wysyła je metodą GET (R.5.8),
zapytanie 3 z parametrem nazwanym :id w prepare/execute (R.5.6),
header('Location: przewozy.php') i exit - przekierowanie na czysty adres. Bez niego w pasku zostałoby ?delete=8, a odświeżenie strony próbowałoby usuwać ponownie.
Typowe błędy
header() musi być wysłany przed jakimkolwiek HTML-em - dlatego cała praca z bazą stoi nad <!DOCTYPE html>.
Krok 29: Skrypt 2: wstawienie zadania z formularza
Kryteria:
R.5.7 Skrypt 2 wstawia do bazy danych przewozy nowy rekord z danymi z formularza. (należy sprawdzić w phpMyAdmin)
ok. 5 min
Polecenie z arkusza
Skrypt 2: dane pobrane z formularza wstawia do bazy za pomocą zmodyfikowanego zapytania 2. Zadanie jest przypisane do osoby o id równym 1.
Dodawanie stoi w try obok usuwania, też przed pobraniem listy.
Wejście: opis przez trim (spacje z brzegów), data przez nową funkcję isValidDate(). DateTime::createFromFormat('!Y-m-d', ...) odczytuje datę w formacie RRRR-MM-DD, a porównanie z format('Y-m-d') odrzuca daty, które PHP po cichu „naprawia” - 2024-02-31 zamieniłby na 2 marca. Pole type="date" w przeglądarce i tak wysyła poprawny format, ale żądanie można wysłać z pominięciem formularza.
Zapytanie 2 z parametrami :task i :date, a osoba_id jako stała 1 w treści zapytania (R.5.7).
PRG: przekierowanie po zapisie - odświeżenie strony nie doda tego samego zadania drugi raz.
Typowe błędy
Bez przekierowania po INSERT przycisk F5 pyta „wysłać formularz ponownie?” - a „tak” dubluje zadanie w bazie.
przewozy.phpPHPzmienione: 18 linii
<?php$config = require'config.php';$tasks = [];$databaseError = '';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: usunięcie zadania z odnośnika „Usuń” (zapytanie 3)$deleteId = filter_var($_GET['delete'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);if ($deleteId !== false) {$statement = $pdo->prepare('DELETE FROM zadania WHERE id_zadania = :id');$statement->execute(['id' => $deleteId]);header('Location: przewozy.php');exit; }// Skrypt 2: nowe zadanie z formularza (zapytanie 2), zawsze dla osoby o id 1$newTask = trim($_POST['task'] ?? '');$newDate = $_POST['date'] ?? '';if ($newTask !== '' && isValidDate($newDate)) {$statement = $pdo->prepare('INSERT INTO zadania (zadanie, data, osoba_id) VALUES (:task, :date, 1)' );$statement->execute(['task' => $newTask, 'date' => $newDate]);header('Location: przewozy.php');exit; }// Skrypt 1: lista zadań (zapytanie 1)$tasks = $pdo->query('SELECT id_zadania, zadanie, data FROM zadania')->fetchAll();} catch (PDOException $exception) {error_log($exception->getMessage());$databaseError = 'Lista zadań jest chwilowo niedostępna.';}functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}functionisValidDate(string $date): bool{$parsed = DateTime::createFromFormat('!Y-m-d', $date);return$parsed !== false && $parsed->format('Y-m-d') === $date;}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Firma Przewozowa</title> <linkrel="stylesheet"href="styles.css"></head><body> <header> <h1>Firma przewozowa Półdarmo</h1> </header> <nav> <ahref="kw1.png">kwerenda1</a> <ahref="kw2.png">kwerenda2</a> <ahref="kw3.png">kwerenda3</a> <ahref="kw4.png">kwerenda4</a> </nav> <main> <sectionid="tasks"> <h2>Zadania do wykonania</h2> <table> <thead> <tr> <thscope="col">Zadanie do wykonania</th> <thscope="col">Data realizacji</th> <thscope="col">Akcja</th> </tr> </thead> <tbody><?phpforeach ($tasksas$task): ?> <tr> <td><?= e($task['zadanie']) ?></td> <td><timedatetime="<?= e($task['data']) ?>"><?= e($task['data']) ?></time></td> <td><ahref="przewozy.php?delete=<?= (int) $task['id_zadania'] ?>">Usuń</a></td> </tr><?phpendforeach; ?> </tbody> </table> <formmethod="post"> <labelfor="task">Zadanie do wykonania: </label> <inputtype="text"id="task"name="task"required> <labelfor="date">Data realizacji: </label> <inputtype="date"id="date"name="date"required> <buttontype="submit">Dodaj</button> </form> </section> <sectionid="offer"> <imgsrc="auto.png"alt="auto firmowe"width="400"height="200"> <h3>Nasza specjalność</h3> <ul> <li>Przeprowadzki</li> <li>Przewóz mebli</li> <li>Przesyłki gabarytowe</li> <li>Wynajem pojazdów</li> <li>Zakupy towarów</li> </ul> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 30: Zamknięcie połączenia i komunikat o błędzie
Kryteria:
R.5.3 Ostatnią operacją na bazie jest jej zamknięcie
ok. 2 min
Polecenie z arkusza
Na końcu działania skrypt zamyka połączenie z serwerem.
$pdo = null; po bloku try/catch - ostatnia operacja na bazie (R.5.3), jeszcze przed HTML. W gałęziach z przekierowaniem skrypt kończy exit, a PHP zamyka połączenie sam przy końcu skryptu.
W tabeli dochodzi wiersz z komunikatem $databaseError (colspan="3" - jedna komórka na całą szerokość), widoczny tylko wtedy, gdy praca z bazą się nie udała.
przewozy.phpPHPzmienione: 6 linii
<?php$config = require'config.php';$tasks = [];$databaseError = '';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: usunięcie zadania z odnośnika „Usuń” (zapytanie 3)$deleteId = filter_var($_GET['delete'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);if ($deleteId !== false) {$statement = $pdo->prepare('DELETE FROM zadania WHERE id_zadania = :id');$statement->execute(['id' => $deleteId]);header('Location: przewozy.php');exit; }// Skrypt 2: nowe zadanie z formularza (zapytanie 2), zawsze dla osoby o id 1$newTask = trim($_POST['task'] ?? '');$newDate = $_POST['date'] ?? '';if ($newTask !== '' && isValidDate($newDate)) {$statement = $pdo->prepare('INSERT INTO zadania (zadanie, data, osoba_id) VALUES (:task, :date, 1)' );$statement->execute(['task' => $newTask, 'date' => $newDate]);header('Location: przewozy.php');exit; }// Skrypt 1: lista zadań (zapytanie 1)$tasks = $pdo->query('SELECT id_zadania, zadanie, data FROM zadania')->fetchAll();} catch (PDOException $exception) {error_log($exception->getMessage());$databaseError = 'Lista zadań jest chwilowo niedostępna.';}$pdo = null;functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}functionisValidDate(string $date): bool{$parsed = DateTime::createFromFormat('!Y-m-d', $date);return$parsed !== false && $parsed->format('Y-m-d') === $date;}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Firma Przewozowa</title> <linkrel="stylesheet"href="styles.css"></head><body> <header> <h1>Firma przewozowa Półdarmo</h1> </header> <nav> <ahref="kw1.png">kwerenda1</a> <ahref="kw2.png">kwerenda2</a> <ahref="kw3.png">kwerenda3</a> <ahref="kw4.png">kwerenda4</a> </nav> <main> <sectionid="tasks"> <h2>Zadania do wykonania</h2> <table> <thead> <tr> <thscope="col">Zadanie do wykonania</th> <thscope="col">Data realizacji</th> <thscope="col">Akcja</th> </tr> </thead> <tbody><?phpforeach ($tasksas$task): ?> <tr> <td><?= e($task['zadanie']) ?></td> <td><timedatetime="<?= e($task['data']) ?>"><?= e($task['data']) ?></time></td> <td><ahref="przewozy.php?delete=<?= (int) $task['id_zadania'] ?>">Usuń</a></td> </tr><?phpendforeach; ?><?phpif ($databaseError !== ''): ?> <tr> <tdcolspan="3"><?= e($databaseError) ?></td> </tr><?phpendif; ?> </tbody> </table> <formmethod="post"> <labelfor="task">Zadanie do wykonania: </label> <inputtype="text"id="task"name="task"required> <labelfor="date">Data realizacji: </label> <inputtype="date"id="date"name="date"required> <buttontype="submit">Dodaj</button> </form> </section> <sectionid="offer"> <imgsrc="auto.png"alt="auto firmowe"width="400"height="200"> <h3>Nasza specjalność</h3> <ul> <li>Przeprowadzki</li> <li>Przewóz mebli</li> <li>Przesyłki gabarytowe</li> <li>Wynajem pojazdów</li> <li>Zakupy towarów</li> </ul> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 31 · po tym etapie masz 41 z 41 kryteriów (pkt)
Krok 31: Co jest inaczej i którą wersję wybrać
ok. 3 min
Polecenie z arkusza
Ocenie będzie podlegać 5 rezultatów: operacje na bazie danych, zawartość witryny internetowej, działanie witryny internetowej, styl CSS witryny internetowej, skrypt połączenia z bazą.
wersja podstawowa
ta wersja
sekcje obok siebie
float: left + clear: both w stopce
grid-template-columns: 70% 30%
id z odnośnika
wstawiane do zapytania wprost
filter_var (liczba od 1) + parametr :id
data z formularza
zapisywana bez sprawdzenia
isValidDate() odrzuca 2024-02-31
błąd bazy
ostrzeżenia PHP na stronie
error_log + krótki komunikat w tabeli
po zapisie
strona z ?delete= albo z formularzem do ponownego wysłania
przekierowanie (PRG)
Czego tu nie ma, a w prawdziwym serwisie powinno być: usuwanie przez POST z tokenem CSRF. Odnośnik „Usuń” może kliknąć za użytkownika obca strona (wystarczy obrazek z takim adresem); token z sesji w ukrytym polu formularza by to zablokował. Arkusz wymaga odnośnika, a klucz tego nie ocenia.
Na egzaminie wersja podstawowa jest szybsza: funkcje mysqli_* są w tabeli 1. Ta wersja ma więcej kodu PHP (walidacja, przekierowania), ale żaden z dodatków nie jest wymagany - można je pominąć, jeśli zaczyna brakować czasu.
Krok 32 · po tym etapie masz 41 z 41 kryteriów (pkt)
Krok 32: 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. Nagraj płytę z rezultatami pracy.
Na płycie: auto.png, config.php, import.png, kw1.png - kw4.png, kwerendy.txt, przeglądarka.txt, przewozy.php, styles.css. Bez config.php strona nie połączy się z bazą. Po nagraniu sprawdź odczyt płyty.
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.