INF.03-11-25.06-SGCzerwiec 2025 · 51 kroków · ok. 83 min · 37 kryteriów z klucza
Rozwiązanie krok po kroku: Portal sprzedaży opon (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza opony, cztery zapytania (w tym losowy rekord i UPDATE), opona z przezroczystym tłem, strona opony.php odświeżana co 10 sekund, style w styl.css - i tak samo spełnia każde kryterium klucza. Inna jest droga:
układ na siatce CSS: grid-template-areas rysuje ilustrację 3 wprost w kodzie (blok boczny na dwa wiersze, sekcje jedna pod drugą), szerokości 40% i 60% są w grid-template-columns, bez float i clear w układzie,
kolory z arkusza w zmiennych CSS, każda opona w znaczniku <article>, a pływająca ikona nie wystaje z bloku dzięki display: flow-root,
PHP na PDO z wyjątkami i danymi połączenia w osobnym pliku config.php; wszystkie zapytania wykonują się na początku pliku, a szablon niżej tylko wypisuje gotowe dane przez funkcję e(),
ikonę sezonu wybiera wyrażenie match zamiast drabinki if, a wartość zamówienia jest zapisana po polsku (number_format).
Wersja podstawowa (zakładka obok) pisze kod najprostszymi funkcjami z tabeli 1 arkusza i dopiero na końcu go poprawia; tu kod jest od razu taki, jak w prawdziwym projekcie. Na końcu jest krótkie porównanie obu wersji i podpowiedź, którą wybrać na egzaminie.
Baza i grafiki pochodzą z prawdziwego archiwum pliki8 (załącznik na stronie arkusza, hasło w treści zadania), a podgląd każdego kroku z PHP to wynik prawdziwego uruchomienia strony na serwerze z tą bazą.
Jak liczą się punkty. Klucz ma 37 kryteriów (R.1.1 - R.5.10), każde za 1 punkt. Próg zdania części praktycznej 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 opony
Kryteria:
R.1.1 Wykonany import tabel do bazy danych opony , czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie opony, z zestawem polskich znaków (np. utf8_polish_ci).
W phpMyAdmin (http://localhost/phpmyadmin) kliknij Nowa, wpisz nazwę opony i wybierz metodę porównywania utf8_polish_ci. To samo w SQL:
CREATE DATABASE opony CHARACTER SET utf8 COLLATE utf8_polish_ci;
Trzy podobne nazwy: baza opony, plik do importu opona.sql i tabela opony w środku bazy.
Typowe błędy
Literówka w nazwie bazy odcina też punkty za skrypty - połączenie w PHP podaje nazwę bazy (R.5.2).
Krok 2: Import tabel z pliku opona.sql
Kryteria:
R.1.1 Wykonany import tabel do bazy danych opony , czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Z rozpakowanego archiwum zaimportuj tabele z pliku opona.sql do utworzonej bazy.
Zaznacz bazę opony w lewym panelu, karta Import, plik opona.sql, Wykonaj. Powstają trzy tabele z ilustracji 1: klient (9 klientów), opony (27 opon: nr_kat, producent, model, sezon, cena) i zamowienie (325 zamówień z dwoma kluczami obcymi: id wskazuje klienta, nr_kat oponę).
Typowe błędy
Import bez zaznaczonej bazy opony tworzy tabele w innej bazie - strona ich nie znajdzie.
Krok 3: Zrzut ekranu import.png
Kryteria:
R.1.1 Wykonany import tabel do bazy danych opony , czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie PNG pod nazwą import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań. Na zrzucie powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel.
Cały ekran (Print Screen), z komunikatem o udanym imporcie albo listą trzech tabel, zapis jako import.png. W tym arkuszu wszystkie zrzuty są w PNG.
Typowe błędy
Wykadrowany zrzut (bez paska zadań) nie jest oceniany.
Krok 4: Zapytanie 1: dziesięć najtańszych opon
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 pierwsze 10 rekordów z najtańszymi oponami (wszystkie pola). W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nr_kat, producent, model, sezon, cena FROM opony ORDER BY cena LIMIT 10; (lub SELECT * FROM opony ORDER BY cena LIMIT 10;) oraz wynik działania zapytania udokumentowany zrzutem, na którym widocznych jest dokładnie 10 rekordów z wszystkimi polami
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wybierające pierwsze 10 rekordów z najtańszymi oponami (wszystkie pola).
Każda klauzula w osobnej linii - zapytanie czyta się od góry: co (SELECT z pięcioma kolumnami), skąd (FROM opony), w jakiej kolejności (ORDER BY cena ASC) i ile (LIMIT 10).
„Wszystkie pola” można zapisać gwiazdką (klucz przyjmuje obie formy), ale jawna lista kolumn ma przewagę w kodzie strony: kolejność kolumn jest pewna, a dopisanie kiedyś do tabeli nowej kolumny (np. opisu na kilka tysięcy znaków) nie spowolni strony, która jej nie używa.
Wynik: 10 opon, od uLFA AL135 za 101,50 (R.1.3). Zapytanie do kwerendy.txt, zrzut kw1.png. Zrzuty zapytań 1-3 rób przed zapytaniem 4, które zmieni ceny.
Typowe błędy
LIMIT 10 bez ORDER BY cena daje dziesięć pierwszych opon z tabeli, nie dziesięć najtańszych.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT nr_kat, producent, model, sezon, cenaFROM oponyORDER BY cena ASCLIMIT 10;
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: opona o numerze katalogowym 9
Kryteria:
R.1.4 Wykonane zapytanie 2 wybierające jedynie producenta, model, sezon i cenę opony o numerze katalogowym 9. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT producent, model, sezon, cena FROM opony WHERE nr_kat = 9; oraz wynik działania zapytania udokumentowany zrzutem, na którym widoczny jest dokładnie jeden rekord z danymi: uLFA, AL135 zimowa; oraz widoczna jest cena
ok. 1 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie producenta, model, sezon i cenę opony o numerze katalogowym 9.
Cztery kolumny w kolejności z arkusza i warunek na klucz główny - wynik to jeden wiersz: uLFA, AL135, zimowa, 101,50 (R.1.4). Zrzut kw2.png.
Krok 6: Zapytanie 3: jedno losowe zamówienie z modelem i ceną
Kryteria:
R.1.5 Wykonane zapytanie 3 wybierające jedynie identyfikator zamówienia i ilość oraz odpowiadające im model i cenę opony. Wybierany jest tylko jeden losowy rekord. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id_zam, ilosc, model, cena FROM zamowienie JOIN opony USING (nr_kat) ORDER BY RAND() LIMIT 1; (lub porównanie kluczy po ON lub WHERE) oraz wynik działania zapytania udokumentowany zrzutem, na którym widoczny jest dokładnie jeden rekord z 4 polami
ok. 3 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie identyfikator zamówienia i ilość oraz odpowiadające im model i cenę opony. Wybierany jest tylko jeden losowy rekord. Należy zastosować relację.
Złączenie z aliasami tabel: zamowienie AS z, opony AS o. Każda kolumna mówi, z której tabeli pochodzi (z.ilosc, o.model), więc nie trzeba pamiętać, że nr_kat jest w obu tabelach, a reszta nazw akurat się nie powtarza.
JOIN opony AS o ON o.nr_kat = z.nr_kat - relacja z ilustracji 1: do zamówienia dołączona jego opona,
ORDER BY RAND() i LIMIT 1 - przepis z ramki w arkuszu: wiersze w losowej kolejności, z nich pierwszy.
Wynik: jeden wiersz z czterema polami (R.1.5), przy każdym wykonaniu inne zamówienie. Na zrzucie kw3.png zamówienie 7: 4 sztuki modelu AL100 po 290,50.
Typowe błędy
Złączenie bez warunku (FROM zamowienie, opony bez WHERE) łączy każde zamówienie z każdą oponą - losowy wiersz ma wtedy cenę obcej opony.
kwerendy.txtTekstzmienione: 7 linii
-- Zapytanie 1SELECT nr_kat, producent, model, sezon, cenaFROM oponyORDER BY cena ASCLIMIT 10;-- Zapytanie 2SELECT producent, model, sezon, cenaFROM oponyWHERE nr_kat = 9;-- Zapytanie 3SELECT z.id_zam, z.ilosc, o.model, o.cenaFROM zamowienie AS zJOIN opony AS o ON o.nr_kat = z.nr_katORDER BY RAND()LIMIT 1;
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: obniżka cen opon letnich o 25%
Kryteria:
R.1.6 Wykonane zapytanie 4 aktualizujące cenę opon letnich obniżając ją o 25%. W pliku z kwerendami lub na zrzucie istnieje zapis np.: UPDATE opony SET cena = cena - cena * 0.25 WHERE sezon = "letnia"; (opcjonalnie cena = 0.75 * cena) oraz wynik działania zapytania udokumentowany zrzutem, na którym jest komunikat o pomyślnym wykonaniu zapytania lub widoczna tabela ze zmienionymi wartościami
ok. 2 min
Polecenie z arkusza
Zapytanie 4: aktualizujące cenę opon letnich obniżając ją o 25%.
UPDATE zmienia dane: cena po obniżce o 25% to 75% ceny, więc cena * 0.75. Warunek WHERE sezon = 'letnia' ogranicza zmianę do opon letnich - bez niego przecena objęłaby wszystkie 27 opon.
Napis w SQL zapisany jest w apostrofach ('letnia'). MySQL przyjmuje też cudzysłów (tak pokazuje klucz), ale w standardzie SQL cudzysłów oznacza nazwę kolumny, a nie tekst - apostrofy działają w każdej bazie.
Komunikat „Zmodyfikowanych rekordów: 17” to zrzut kw4.png (R.1.6).
Typowe błędy
Dwukrotne wykonanie UPDATE przecenia opony dwa razy. Przed ponownym uruchomieniem przywróć tabelę ponownym importem.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT nr_kat, producent, model, sezon, cenaFROM oponyORDER BY cena ASCLIMIT 10;-- Zapytanie 2SELECT producent, model, sezon, cenaFROM oponyWHERE nr_kat = 9;-- Zapytanie 3SELECT z.id_zam, z.ilosc, o.model, o.cenaFROM zamowienie AS zJOIN opony AS o ON o.nr_kat = z.nr_katORDER BY RAND()LIMIT 1;-- Zapytanie 4UPDATE oponySET cena = cena * 0.75WHERE sezon = 'letnia';
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 8-20 · po tym etapie masz 15 z 37 kryteriów (pkt)
Krok 8: Grafika opona.png: przezroczyste tło
Kryteria:
R.2.1 Grafika opona.png ma przezroczyste tło i widoczna jest opona
ok. 4 min
Polecenie z arkusza
Cechy grafiki opona.png: wykonana na podstawie grafiki opona.jpg i zapisana w formacie PNG. Tło przezroczyste.
W GIMP-ie: opona.jpg, Warstwa → Przezroczystość → Dodaj kanał alfa (JPEG nie ma przezroczystości), Zaznaczenie rozmyte z progiem ok. 20, klik w białe tło, Delete. Eksport do opona.png - tylko PNG zachowa kanał alfa (R.2.1). Białe odblaski na feldze nie łączą się z tłem, więc zostają.
Typowe błędy
Eksport do JPEG zamienia przezroczystość z powrotem w białe tło.
Podgląd strony po tym kroku
Krok 9: Grafika opona.png: wysokość 250 px
Kryteria:
R.2.2 Grafika przeskalowana do wysokości 250 px, odpowiadająca temu szerokość wynosi 178 px (± 5 px)
ok. 2 min
Polecenie z arkusza
Przeskalowana z zachowaniem proporcji do wysokości 250 px.
Obraz → Skaluj obraz, wysokość 250 px przy zamkniętym łańcuchu proporcji; szerokość wychodzi 178 px (R.2.2: 178 ±5). Skalowanie po usunięciu tła - różdżka pracuje dokładniej na pełnej rozdzielczości.
R.2.3 W pliku opony.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. 1 min
Polecenie z arkusza
Składa się ze strony o nazwie opony.php. Zapisana w języku HTML5.
Plik opony.php w folderze zdającego. <!DOCTYPE html> włącza tryb standardów HTML5, dalej szkielet: <html>, <head> i <body>. Rozszerzenie .php, bo na początku pliku stanie kod PHP.
R.2.3 W pliku opony.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. 1 min
Polecenie z arkusza
Zadeklarowany polski język zawartości witryny.
lang="pl" w <html> - czytnik ekranu czyta polską wymową, przeglądarka dobiera słownik i dzielenie wyrazów.
R.2.3 W pliku opony.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. 1 min
Polecenie z arkusza
Jawnie zastosowany właściwy standard kodowania polskich znaków.
<meta charset="UTF-8"> jako pierwszy element <head>. Ten sam standard ustawi połączenie z bazą (charset=utf8mb4 w PDO), więc polskie litery przejdą bez zmian całą drogę: baza → PHP → przeglądarka.
<title>OPONY</title> - napis na karcie przeglądarki. Obok <meta name="viewport">: bez niego telefon pokazałby stronę pomniejszoną jak na monitorze. Arkusz tego nie wymaga, a w każdym współczesnym szablonie ta linia jest.
R.2.5 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML 5: main->aside+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. Zawartość bloku głównego: blok boczny, sekcja 1 i sekcja 2.
Struktura z ilustracji 3 i klucza R.2.5: main z aside i dwiema sekcjami, pod nim footer. Kolejność w kodzie odpowiada kolejności czytania - blok boczny pierwszy, bo jest pierwszy z lewej; o tym, że stanie obok sekcji, zdecyduje CSS.
Sekcje dostają klasyoffer i order (nie identyfikatory): klasa opisuje rolę bloku i ma niską „wagę” w CSS, więc później łatwo ją nadpisać. Nazwy po angielsku; nazwy narzucone przez arkusz (opony.php, styl.css, klasa opona, kolumny bazy) zostają.
Typowe błędy
<aside> poza <main> nie spełnia R.2.5 - blok boczny jest częścią bloku głównego.
R.3.3 Utworzony odnośnik o treści „więcej ofert” jego kliknięcie otwiera stronę https://opona.pl/
ok. 1 min
Polecenie z arkusza
Zawartość bloku bocznego: efekt działania skryptu 1. Paragraf z odnośnikiem o treści „więcej ofert” prowadzący do strony https://opona.pl/.
Paragraf z odnośnikiem pod pełny adres z https:// (R.3.3). Bloki z oponami (skrypt 1) staną nad nim. Odnośnik otwiera się w tej samej karcie - arkusz nie każe otwierać nowej, a decyzję o nowej karcie lepiej zostawić użytkownikowi.
Typowe błędy
href="opona.pl" bez https:// to ścieżka do pliku w folderze strony, nie adres serwisu.
R.2.6 Umieszczone w pierwszej sekcji <h2>: „Opona dnia”, w drugiej sekcji <h2>: „Najnowsze zamówienie", oraz paragraf z numerem zdającego w bloku stopki
ok. 1 min
Polecenie z arkusza
Nagłówek drugiego stopnia o treści: „Opona dnia”. Efekt działania skryptu 2.
<h2> pod obrazem; trzy kolejne <h2> z danymi opony numer 9 dopisze szablon PHP.
R.2.6 Umieszczone w pierwszej sekcji <h2>: „Opona dnia”, w drugiej sekcji <h2>: „Najnowsze zamówienie", oraz paragraf z numerem zdającego w bloku stopki
ok. 1 min
Polecenie z arkusza
Zawartość sekcji 2: nagłówek drugiego stopnia o treści: „Najnowsze zamówienie”. Efekt działania skryptu 3.
<h2> w drugiej sekcji; dwa kolejne z losowym zamówieniem dopisze PHP.
R.2.6 Umieszczone w pierwszej sekcji <h2>: „Opona dnia”, w drugiej sekcji <h2>: „Najnowsze zamówienie", oraz paragraf z numerem zdającego w bloku stopki
ok. 1 min
Polecenie z arkusza
Zawartość stopki: paragraf o treści „Stronę wykonał: ”, dalej wstawiony numer zdającego.
Paragraf w <footer>. Tu stoi nazwisko autora rozwiązania - na egzaminie wpisujesz swój numer zdającego (PESEL), bo tego szuka klucz R.2.6.
Kroki 21-31 · po tym etapie masz 26 z 37 kryteriów (pkt)
Krok 21: Zmienne CSS z kolorami arkusza
ok. 2 min
Polecenie z arkusza
Styl CSS zdefiniowany jest w całości w zewnętrznym pliku o nazwie styl.css.
Plik styl.css zaczyna się od własnych właściwości (zmiennych CSS) w :root. Arkusz używa czterech odcieni granatu, a dwa z nich powtarzają się: #9FA8DA (sekcja 1 i bloki opon) i #7986CB (sekcja 2 i nagłówki h3). Wpisane raz, w regułach występują jako var(--light-bg) - zmiana palety to zmiana jednej linii.
Nazwy opisują rolę koloru (jasne tło, ciemne tło), a nie sam kolor. Egzaminator w inspektorze widzi obliczoną wartość (rgb(159, 168, 218)), więc kryteria kolorów sprawdza tak samo.
Krok 23: Układ bloków zgodny z ilustracją 3: siatka
Kryteria: ,
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.4 Ustawiona wysokość bloku bocznego 600px obu sekcji 300px oraz szerokość bloku bocznego 40%, obu sekcji 60% (width lub grid-template-columns lub flex)
ok. 3 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.
Blok główny staje się siatką (display: grid) o dwóch kolumnach: 40% i 60% - to są szerokości bloku bocznego i sekcji z arkusza (klucz R.4.4 przyjmuje je wprost w grid-template-columns).
grid-template-areas to mapa ilustracji 3 zapisana tekstem: każdy napis w cudzysłowie to wiersz siatki, każde słowo to komórka. Słowo side stoi w obu wierszach, więc blok boczny zajmie lewą kolumnę na całą wysokość, a offer i order ułożą się jedna pod drugą po prawej. Trzy krótkie reguły przypisują bloki do obszarów (grid-area).
W wersji podstawowej ten sam układ robi float: left i clear: both - tam druga sekcja „spada” pod pierwszą, bo nie mieści się w wierszu. Siatka mówi wprost, co gdzie stoi, i nie zależy od tego, czy szerokości sumują się co do piksela (R.3.1).
Typowe błędy
width: 60% na sekcji wewnątrz siatki liczy się od szerokości komórki, nie strony - sekcja zajęłaby 60% z 60%. Szerokości trzymaj w grid-template-columns.
R.4.2 Ustawiony kolor tła dla bloku bocznego #C5CAE9, pierwszej sekcji #9FA8DA, drugiej sekcji i selektora h3 #7986CB, stopki #283593
R.4.4 Ustawiona wysokość bloku bocznego 600px obu sekcji 300px oraz szerokość bloku bocznego 40%, obu sekcji 60% (width lub grid-template-columns lub flex)
R.4.7 Ustawiona dla bloku bocznego cecha overflow: auto;
ok. 1 min
Polecenie z arkusza
Dla bloku bocznego: kolor tła #C5CAE9, szerokość 40%, wysokość 600 px, paski przewijania tylko w przypadku przepełnienia bloku.
Szerokość 40% już jest (pierwsza kolumna siatki). Dochodzą: wysokość 600 px (dwie sekcje po 300 px), overflow: auto - pasek przewijania pojawia się tylko, gdy treść się nie mieści (R.4.7), i tło ze zmiennej (R.4.2). Dziesięć bloków z oponami przekroczy 600 px, więc pasek będzie widoczny jak na ilustracji 2.
R.4.4 Ustawiona wysokość bloku bocznego 600px obu sekcji 300px oraz szerokość bloku bocznego 40%, obu sekcji 60% (width lub grid-template-columns lub flex)
ok. 1 min
Polecenie z arkusza
Dla obu sekcji: szerokość 60%, wysokość 300 px.
Selektor section obejmuje obie sekcje: 300 px wysokości. Szerokość 60% daje druga kolumna siatki - osobne width byłoby tu błędem (patrz poprzedni krok). Klucz R.4.4 wymienia grid-template-columns jako dopuszczalny sposób.
R.4.2 Ustawiony kolor tła dla bloku bocznego #C5CAE9, pierwszej sekcji #9FA8DA, drugiej sekcji i selektora h3 #7986CB, stopki #283593
ok. 1 min
Polecenie z arkusza
Dodatkowo kolor tła sekcji 1: #9FA8DA, sekcji 2: #7986CB.
Tła trafiają do reguł klas .offer i .order, które już ustawiają obszar siatki - wszystko o danej sekcji jest w jednym miejscu (R.4.2). Przez przezroczyste tło opony prześwituje teraz fiolet sekcji.
Krok 28: Klasa opona: tło, marginesy, obramowanie, zaokrąglenie
Kryteria:
R.4.10 Zdefiniowana klasa dla opony oraz jej styl: kolor tła #9FA8DA, margin: 40px; border: 2px dotted #283593; zaokrąglenie rogów 30px
ok. 2 min
Polecenie z arkusza
Dla klasy CSS opona, przypisanej do bloków generowanych w skrypcie 1: kolor tła #9FA8DA, marginesy zewnętrzne 40 px, obramowanie linią kropkowaną o szerokości 2 px i kolorze #283593, zaokrąglenie rogów 30 px.
Nazwa klasy narzucona przez arkusz. Obramowanie skrótem border: 2px dotted … (szerokość, styl „kropkowana”, kolor), border-radius: 30px i tło (R.4.10).
Dodatkowo display: flow-root: ikona sezonu w bloku będzie pływać w lewo (float z następnego kroku), a pływający element nie rozciąga rodzica - przy krótkim tekście wystawałby poza zaokrągloną ramkę. flow-root sprawia, że blok obejmuje pływające dzieci. To następca dawnego triku clearfix z pustym elementem i clear: both.
Typowe błędy
dashed to linia kreskowana; arkusz chce kropkowanej - dotted.
R.4.6 Ustawione marginesy wewnętrzne (padding) dla stopki oraz selektora img 20px
R.4.8 Ustawione dla obrazu opływanie tekstem po prawej stronie float: left;
ok. 1 min
Polecenie z arkusza
Dla selektora obrazu: opływanie tekstem po prawej stronie (obraz jest po lewej stronie), marginesy wewnętrzne 20 px.
Tu zostaje float: left: arkusz wymaga opływania tekstem, a klucz R.4.8 sprawdza właśnie tę właściwość. To jest zresztą jedyne zadanie, do którego float powstał - obraz w tekście, a nie układ strony. Selektor znacznika img, bez klas (uwaga arkusza). Reguła działa i na oponę w sekcji 1, i na ikony sezonów w blokach (R.4.6, R.4.8).
R.4.2 Ustawiony kolor tła dla bloku bocznego #C5CAE9, pierwszej sekcji #9FA8DA, drugiej sekcji i selektora h3 #7986CB, stopki #283593
R.4.3 Ustawiony biały kolor czcionki dla stopki i nagłówka trzeciego stopnia
R.4.9 Ustawiony dla nagłówka trzeciego stopnia rozmiar czcionki 140%
ok. 1 min
Polecenie z arkusza
Dla selektora nagłówka trzeciego stopnia: kolor tła #7986CB, biały kolor czcionki, rozmiar czcionki 140%.
Selektor h3 (R.4.2, R.4.3, R.4.9). Nagłówków h3 jeszcze nie ma - wypisze je szablon PHP z ceną każdej opony. Kolor tła to ta sama zmienna co tło sekcji 2.
Kroki 32-49 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 32: Odświeżanie strony co 10 sekund
Kryteria:
R.5.1 Strona jest odświeżana co 10 sekund
ok. 2 min
Polecenie z arkusza
Napisane w języku PHP. Strona jest odświeżana co 10 sekund.
header('refresh: 10') z ramki w arkuszu wysyła nagłówek HTTP „odśwież za 10 sekund” (R.5.1). Nagłówki idą przed treścią strony, dlatego cały kod PHP tej wersji stanie na samym początku pliku, przed <!DOCTYPE html> - i nic, nawet pusta linia, nie może stać przed <?php.
Od tego kroku podgląd to prawdziwy wynik strony z serwera (odświeżania w podglądzie nie widać).
Typowe błędy
header() po wypisaniu czegokolwiek kończy się ostrzeżeniem „headers already sent” i strona się nie odświeża.
R.5.2 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy opony oraz ostatnią operacją na bazie jest jej zamknięcie
ok. 2 min
Polecenie z arkusza
Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie opony.
Dane logowania do bazy w osobnym pliku config.php, który tylko zwraca tablicę (return [...]). Na prawdziwym serwerze hasło jest inne niż na komputerze programisty i nie trafia do repozytorium z kodem - podmienia się jeden plik. Arkusz dopuszcza dodatkowe pliki („ewentualnie inne przygotowane pliki”).
DSN (mysql:host=localhost;dbname=opony;charset=utf8mb4) to adres bazy dla PDO w jednym napisie: rodzaj serwera, host, nazwa bazy i kodowanie połączenia. Plik bez HTML-u nie ma końcowego ?>, więc nic nie wypisze się przypadkiem.
Typowe błędy
config.php musi trafić do folderu z rezultatami - bez niego strona się nie połączy.
config.phpPHPnowy plik
<?php// Dane połączenia z bazą - w jednym miejscu, poza plikiem stronyreturn ['dsn' => 'mysql:host=localhost;dbname=opony;charset=utf8mb4','user' => 'root','password' => '',];
R.5.2 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy opony oraz ostatnią operacją na bazie jest jej zamknięcie
R.5.10 W skryptach zastosowana przynajmniej jedna zmienna oraz nazwy wszystkich zmiennych są w języku polskim lub angielskim i odzwierciedlają cel zastosowania
ok. 3 min
Polecenie z arkusza
Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie opony.
Klucz R.5 przyjmuje PDO tak samo jak MySQLi (R.5.2, R.5.10).
require 'config.php' - dane z poprzedniego kroku trafiają do $config,
new PDO(...) - połączenie i wybór bazy opony; opcja ERRMODE_EXCEPTION zamienia każdy błąd SQL w wyjątek (zamiast cichego false), a FETCH_ASSOC sprawia, że wiersze przychodzą jako tablice z nazwami kolumn ($tyre['cena'], nie $row[4]),
try … catch (PDOException $error) - gdy baza nie działa, użytkownik widzi zwykły komunikat zamiast treści błędu z nazwą serwera; http_response_code(500) mówi przeglądarce, że zawiódł serwer.
Typowe błędy
Wypisanie $error->getMessage() na ekran zdradza szczegóły serwera. Komunikat dla użytkownika ma być ogólny.
Krok 35: Funkcja e(): bezpieczne wypisywanie tekstu
ok. 2 min
Polecenie z arkusza
Należy stosować znaczące nazewnictwo zmiennych i funkcji w języku polskim lub angielskim.
Każdy napis z bazy, który trafi do HTML-u, przejdzie przez e() (od escape). Strona nie ma formularza, ale nazwy producentów i modeli ktoś kiedyś wpisał do bazy - napis <script> w nazwie modelu wykonałby się u każdego odwiedzającego (XSS). htmlspecialchars z ENT_QUOTES zamienia <, >, & i oba cudzysłowy na encje, więc taki napis pokaże się jako tekst. Escapuje się na wyjściu; w bazie zostają dane surowe.
Krótka nazwa to znana konwencja (tak samo w Laravelu) - funkcja występuje w szablonie wiele razy.
R.5.3 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1 lub skrypt 2 - zapytanie 2 lub skrypt 3 - zapytanie 3
ok. 2 min
Polecenie z arkusza
Skrypt 1 wysyła do bazy danych zapytanie 1.
Zapytanie 1 trafia do bloku try, zaraz po połączeniu - ten sam catch obsłuży błąd każdego zapytania. $db->query(...) wysyła zapytanie (R.5.3), a fetchAll() odbiera od razu wszystkie dziesięć wierszy do tablicy $cheapestTyres. W zapytaniu nie ma danych od użytkownika, więc nie trzeba go przygotowywać.
Dane są gotowe, zanim zacznie się HTML; szablon niżej tylko je wypisze.
opony.phpPHPzmienione: 4 linie
<?phpheader('refresh: 10');$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cheapestTyres = $db->query('SELECT nr_kat, producent, model, sezon, cena FROM opony ORDER BY cena LIMIT 10' )->fetchAll();} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>OPONY</title> <linkrel="stylesheet"href="styl.css"></head><body> <main> <aside> <p><ahref="https://opona.pl/">więcej ofert</a></p> </aside> <sectionclass="offer"> <imgsrc="opona.png"alt="Opona"> <h2>Opona dnia</h2> </section> <sectionclass="order"> <h2>Najnowsze zamówienie</h2> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 37: Skrypt 1: blok z klasą opona dla każdego rekordu
Kryteria: ,
R.2.8 W bloku bocznym utworzony przynajmniej jeden blok z klasą CSS opona oraz nagłówek <h3>, <h4> (w HTML lub skrypcie)
R.5.4 Skrypt 1 tworzy tyle bloków, ile wybiera zapytanie
ok. 2 min
Polecenie z arkusza
Dla każdego zwróconego zapytaniem rekordu wyświetla blok z przypisaną klasą CSS opona, w którym znajdują się: obraz sezonu, nagłówek czwartego stopnia i nagłówek trzeciego stopnia.
W bloku bocznym, nad odnośnikiem, pętla w składni szablonu: foreach (...): i endforeach; zamiast nawiasów klamrowych - HTML bloku zostaje zwykłym HTML-em z wcięciami. Jeden obieg = jeden blok, więc bloków jest tyle, ile wierszy zwróciło zapytanie (R.5.4).
Blok to <article>: każda opona to samodzielna porcja treści (jak karta produktu), a tabela 2 arkusza opisuje article właśnie tak. Klasa opona z arkusza daje tło, kropkowaną ramkę i zaokrąglenie (R.2.8). Podgląd: dziesięć pustych bloków.
opony.phpPHPzmienione: 4 linie
<?phpheader('refresh: 10');$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cheapestTyres = $db->query('SELECT nr_kat, producent, model, sezon, cena FROM opony ORDER BY cena LIMIT 10' )->fetchAll();} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>OPONY</title> <linkrel="stylesheet"href="styl.css"></head><body> <main> <aside><?phpforeach ($cheapestTyresas$tyre): ?> <articleclass="opona"> </article><?phpendforeach; ?> <p><ahref="https://opona.pl/">więcej ofert</a></p> </aside> <sectionclass="offer"> <imgsrc="opona.png"alt="Opona"> <h2>Opona dnia</h2> </section> <sectionclass="order"> <h2>Najnowsze zamówienie</h2> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (opony.php)
Krok 38: Skrypt 1: ikona sezonu
Kryteria:
R.5.6 Skrypt 1 wyświetla obraz lato.png, gdy opona jest letnia, zima.png , gdy jest zimowa, uniwer.png gdy jest uniwersalna
ok. 3 min
Polecenie z arkusza
W przypadku opony letniej obraz lato.png, w przypadku zimowej obraz zima.png, w przypadku uniwersalnej obraz uniwer.png.
Wybór ikony to osobna funkcja seasonIcon() z wyrażeniem match (PHP 8): wartość sezonu porównywana jest ściśle (===) z kolejnymi przypadkami i od razu zwracana jest nazwa pliku. default obsługuje sezon uniwersalny - i każdą inną wartość, więc nieznany sezon w bazie nie zostawi pustego obrazka (R.5.6).
Zamiast trzech gałęzi if z trzema echo jest tabela „sezon → plik” w jednym miejscu, a w szablonie jedno <img>. Tekst alternatywny z nazwą sezonu przechodzi przez e().
Typowe błędy
match porównuje ściśle - 'Letnia' z wielkiej litery nie pasuje do 'letnia' i trafi do default. Wartości muszą być dokładnie takie jak w kolumnie sezon.
Krok 39: Skrypt 1: nagłówek czwartego stopnia z producentem i modelem
Kryteria: ,
R.2.8 W bloku bocznym utworzony przynajmniej jeden blok z klasą CSS opona oraz nagłówek <h3>, <h4> (w HTML lub skrypcie)
R.5.5 Skrypt 1 wyświetla przynajmniej dla jednego rekordu teksty „Opona: <producent> <model>” oraz „Cena: <cena>”
ok. 1 min
Polecenie z arkusza
Nagłówek czwartego stopnia o treści „Opona: <producent><model>”.
<h4> ze stałym tekstem w HTML i dwiema wartościami wstawionymi przez <?= ... ?> (skrót od <?php echo ... ?>), każda przez e() (R.2.8, R.5.5). Kolumny po nazwach - kolejność w SELECT nie ma już znaczenia.
Krok 40: Skrypt 1: nagłówek trzeciego stopnia z ceną
Kryteria:
R.5.5 Skrypt 1 wyświetla przynajmniej dla jednego rekordu teksty „Opona: <producent> <model>” oraz „Cena: <cena>”
ok. 1 min
Polecenie z arkusza
Nagłówek trzeciego stopnia o treści „Cena: <cena>”.
<h3> z ceną (R.5.5). Cena przychodzi z bazy jako napis „101.50” (kolumna decimal - PDO oddaje ją jako tekst, żeby nie zgubić groszy na zaokrągleniach) i tak zostaje wypisana, jak na ilustracji 2. Teraz działa reguła h3 z CSS, a blok wygląda jak na ilustracji; pasek przewijania bloku bocznego pojawia się sam.
W podglądzie przy równych cenach (110,00 i 110,50) kolejność opon może być inna niż na zrzucie - zapytanie sortuje tylko po cenie, a remisy baza układa dowolnie.
R.5.3 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1 lub skrypt 2 - zapytanie 2 lub skrypt 3 - zapytanie 3
ok. 2 min
Polecenie z arkusza
Skrypt 2 wysyła do bazy zapytanie 2.
Zapytanie 2 jako zapytanie przygotowane: w miejscu numeru katalogowego stoi parametr nazwany :catalogNumber, a wartość 9 idzie osobno w execute([...]) (R.5.3). Dziś to stała, ale „opona dnia” jutro będzie inna - może przyjść z panelu administratora albo z adresu strony. Gdy wartość przychodzi z zewnątrz, oddzielenie jej od treści zapytania jest jedyną pewną ochroną przed SQL injection, więc tę formę warto mieć w palcach od razu.
Wynik ma jeden wiersz, więc wystarcza fetch() - zwraca ten wiersz albo false, gdy opony o takim numerze nie ma.
Typowe błędy
fetchAll() przy jednym wierszu daje tablicę tablic - wtedy producent to $offer[0]['producent'], nie $offer['producent'].
Krok 42: Skrypt 2: nagłówek z producentem i modelem
Kryteria:
R.5.7 Skrypt 2 wyświetla zwrócony rekord w postaci: „<producent> model <model>”, „Sezon: <sezon>”, „Tylko <cena> zł!”
ok. 1 min
Polecenie z arkusza
Wyświetla w nagłówku drugiego stopnia treść „<producent> model <model>”.
<?php if ($offer): ?> … <?php endif; ?> - nagłówki z ofertą pojawią się tylko, gdy wiersz istnieje. Bez tego warunku usunięcie opony numer 9 z bazy kończyłoby się błędem „Trying to access array offset on value of type bool” w miejscu oferty. Podgląd: „uLFA model AL135” (R.5.7).
R.5.3 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1 lub skrypt 2 - zapytanie 2 lub skrypt 3 - zapytanie 3
ok. 2 min
Polecenie z arkusza
Skrypt 3 wysyła do bazy zapytanie 3.
Zapytanie 3 z aliasami tabel, tak samo jak w kwerendy.txt (R.5.3). Napis z zapytaniem może mieć kilka linii - baza ignoruje znaki nowej linii, a kod czyta się jak w phpMyAdmin. Jeden losowy wiersz trafia do $latestOrder przez fetch().
Strona odświeża się co 10 sekund, więc przy każdym odświeżeniu baza wylosuje inne zamówienie. Podgląd pokazuje zamówienie 7 z ilustracji 2.
opony.phpPHPzmienione: 8 linii
<?phpheader('refresh: 10');$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cheapestTyres = $db->query('SELECT nr_kat, producent, model, sezon, cena FROM opony ORDER BY cena LIMIT 10' )->fetchAll();$offerQuery = $db->prepare('SELECT producent, model, sezon, cena FROM opony WHERE nr_kat = :catalogNumber');$offerQuery->execute(['catalogNumber' => 9]);$offer = $offerQuery->fetch();$latestOrder = $db->query('SELECT z.id_zam, z.ilosc, o.model, o.cena FROM zamowienie AS z JOIN opony AS o ON o.nr_kat = z.nr_kat ORDER BY RAND() LIMIT 1' )->fetch();} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}functionseasonIcon(string $season): string{returnmatch ($season) {'letnia' => 'lato.png','zimowa' => 'zima.png',default => 'uniwer.png', };}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>OPONY</title> <linkrel="stylesheet"href="styl.css"></head><body> <main> <aside><?phpforeach ($cheapestTyresas$tyre): ?> <articleclass="opona"> <imgsrc="<?= seasonIcon($tyre['sezon']) ?>"alt="<?= e($tyre['sezon']) ?>"> <h4>Opona: <?= e($tyre['producent']) ?><?= e($tyre['model']) ?></h4> <h3>Cena: <?= e($tyre['cena']) ?></h3> </article><?phpendforeach; ?> <p><ahref="https://opona.pl/">więcej ofert</a></p> </aside> <sectionclass="offer"> <imgsrc="opona.png"alt="Opona"> <h2>Opona dnia</h2><?phpif ($offer): ?> <h2><?= e($offer['producent']) ?> model <?= e($offer['model']) ?></h2> <h2>Sezon: <?= e($offer['sezon']) ?></h2> <h2>Tylko <?= e($offer['cena']) ?> zł!</h2><?phpendif; ?> </section> <sectionclass="order"> <h2>Najnowsze zamówienie</h2> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 46: Skrypt 3: nagłówek z identyfikatorem, ilością i modelem
Kryteria:
R.5.9 Skrypt 3 wyświetla teksty „<id_zam> <ilosc> sztuki modelu <model>” lub „Wartość zamówienia <wartość> zł”, z wyliczoną wartością
ok. 1 min
Polecenie z arkusza
Wyświetla w nagłówku drugiego stopnia treść „<id_zam><ilosc> sztuki modelu <model>”.
Ten sam wzór co w sekcji 1: warunek na istnienie wiersza (pusta tabela zamówień nie wywoła błędu) i nagłówek z trzema wartościami. Podgląd: „7 4 sztuki modelu AL100”, jak na ilustracji 2 (R.5.9).
opony.phpPHPzmienione: 3 linie
<?phpheader('refresh: 10');$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cheapestTyres = $db->query('SELECT nr_kat, producent, model, sezon, cena FROM opony ORDER BY cena LIMIT 10' )->fetchAll();$offerQuery = $db->prepare('SELECT producent, model, sezon, cena FROM opony WHERE nr_kat = :catalogNumber');$offerQuery->execute(['catalogNumber' => 9]);$offer = $offerQuery->fetch();$latestOrder = $db->query('SELECT z.id_zam, z.ilosc, o.model, o.cena FROM zamowienie AS z JOIN opony AS o ON o.nr_kat = z.nr_kat ORDER BY RAND() LIMIT 1' )->fetch();} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}functionseasonIcon(string $season): string{returnmatch ($season) {'letnia' => 'lato.png','zimowa' => 'zima.png',default => 'uniwer.png', };}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>OPONY</title> <linkrel="stylesheet"href="styl.css"></head><body> <main> <aside><?phpforeach ($cheapestTyresas$tyre): ?> <articleclass="opona"> <imgsrc="<?= seasonIcon($tyre['sezon']) ?>"alt="<?= e($tyre['sezon']) ?>"> <h4>Opona: <?= e($tyre['producent']) ?><?= e($tyre['model']) ?></h4> <h3>Cena: <?= e($tyre['cena']) ?></h3> </article><?phpendforeach; ?> <p><ahref="https://opona.pl/">więcej ofert</a></p> </aside> <sectionclass="offer"> <imgsrc="opona.png"alt="Opona"> <h2>Opona dnia</h2><?phpif ($offer): ?> <h2><?= e($offer['producent']) ?> model <?= e($offer['model']) ?></h2> <h2>Sezon: <?= e($offer['sezon']) ?></h2> <h2>Tylko <?= e($offer['cena']) ?> zł!</h2><?phpendif; ?> </section> <sectionclass="order"> <h2>Najnowsze zamówienie</h2><?phpif ($latestOrder): ?> <h2><?= e($latestOrder['id_zam']) ?><?= e($latestOrder['ilosc']) ?> sztuki modelu <?= e($latestOrder['model']) ?></h2><?phpendif; ?> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (opony.php)
Krok 47: Skrypt 3: wartość zamówienia
Kryteria:
R.5.8 Skrypt 3 wylicza wartość zamówienia jako iloczyn ceny i ilości opon
ok. 1 min
Polecenie z arkusza
Wartość oznacza wyliczoną wartość na podstawie zwróconych z bazy ilości i ceny.
Wartość to iloczyn ilości i ceny policzony w PHP (R.5.8): $latestOrder['ilosc'] * $latestOrder['cena'], dla zamówienia z ilustracji 4 × 290,50 = 1162. Operator warunkowy ? : daje 0, gdy zamówienia nie ma - mnożenie false przez cokolwiek byłoby błędem.
Liczenie stoi w części z logiką, nie w szablonie: szablon tylko wypisuje gotowy wynik.
Typowe błędy
Wyliczenie w SQL (SELECT ilosc * cena AS wartosc) daje ten sam wynik, ale arkusz każe liczyć „na podstawie zwróconych z bazy ilości i ceny”, a klucz R.5.8 sprawdza mnożenie w skrypcie.
opony.phpPHPzmienione: 2 linie
<?phpheader('refresh: 10');$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cheapestTyres = $db->query('SELECT nr_kat, producent, model, sezon, cena FROM opony ORDER BY cena LIMIT 10' )->fetchAll();$offerQuery = $db->prepare('SELECT producent, model, sezon, cena FROM opony WHERE nr_kat = :catalogNumber');$offerQuery->execute(['catalogNumber' => 9]);$offer = $offerQuery->fetch();$latestOrder = $db->query('SELECT z.id_zam, z.ilosc, o.model, o.cena FROM zamowienie AS z JOIN opony AS o ON o.nr_kat = z.nr_kat ORDER BY RAND() LIMIT 1' )->fetch();$orderValue = $latestOrder ? $latestOrder['ilosc'] * $latestOrder['cena'] : 0;} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}functionseasonIcon(string $season): string{returnmatch ($season) {'letnia' => 'lato.png','zimowa' => 'zima.png',default => 'uniwer.png', };}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>OPONY</title> <linkrel="stylesheet"href="styl.css"></head><body> <main> <aside><?phpforeach ($cheapestTyresas$tyre): ?> <articleclass="opona"> <imgsrc="<?= seasonIcon($tyre['sezon']) ?>"alt="<?= e($tyre['sezon']) ?>"> <h4>Opona: <?= e($tyre['producent']) ?><?= e($tyre['model']) ?></h4> <h3>Cena: <?= e($tyre['cena']) ?></h3> </article><?phpendforeach; ?> <p><ahref="https://opona.pl/">więcej ofert</a></p> </aside> <sectionclass="offer"> <imgsrc="opona.png"alt="Opona"> <h2>Opona dnia</h2><?phpif ($offer): ?> <h2><?= e($offer['producent']) ?> model <?= e($offer['model']) ?></h2> <h2>Sezon: <?= e($offer['sezon']) ?></h2> <h2>Tylko <?= e($offer['cena']) ?> zł!</h2><?phpendif; ?> </section> <sectionclass="order"> <h2>Najnowsze zamówienie</h2><?phpif ($latestOrder): ?> <h2><?= e($latestOrder['id_zam']) ?><?= e($latestOrder['ilosc']) ?> sztuki modelu <?= e($latestOrder['model']) ?></h2><?phpendif; ?> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 48: Skrypt 3: nagłówek z wartością zamówienia
Kryteria:
R.5.9 Skrypt 3 wyświetla teksty „<id_zam> <ilosc> sztuki modelu <model>” lub „Wartość zamówienia <wartość> zł”, z wyliczoną wartością
ok. 1 min
Polecenie z arkusza
W kolejnym nagłówku drugiego stopnia treść „Wartość zamówienia <wartość> zł”.
number_format($orderValue, 2, ',', ' ') zapisuje kwotę po polsku: dwa miejsca po przecinku, przecinek dziesiętny, spacja między tysiącami - „1 162,00 zł” zamiast „1162 zł”. Wartość jest ta sama, zmienia się tylko zapis (R.5.9). Wynik number_format to cyfry, spacje i przecinek, więc nie wymaga e().
Ceny opon w nagłówkach zostają w zapisie z bazy, jak na ilustracji 2 - formatowana jest tylko kwota wyliczona przez skrypt.
opony.phpPHPzmienione: 1 linia
<?phpheader('refresh: 10');$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cheapestTyres = $db->query('SELECT nr_kat, producent, model, sezon, cena FROM opony ORDER BY cena LIMIT 10' )->fetchAll();$offerQuery = $db->prepare('SELECT producent, model, sezon, cena FROM opony WHERE nr_kat = :catalogNumber');$offerQuery->execute(['catalogNumber' => 9]);$offer = $offerQuery->fetch();$latestOrder = $db->query('SELECT z.id_zam, z.ilosc, o.model, o.cena FROM zamowienie AS z JOIN opony AS o ON o.nr_kat = z.nr_kat ORDER BY RAND() LIMIT 1' )->fetch();$orderValue = $latestOrder ? $latestOrder['ilosc'] * $latestOrder['cena'] : 0;} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}functionseasonIcon(string $season): string{returnmatch ($season) {'letnia' => 'lato.png','zimowa' => 'zima.png',default => 'uniwer.png', };}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>OPONY</title> <linkrel="stylesheet"href="styl.css"></head><body> <main> <aside><?phpforeach ($cheapestTyresas$tyre): ?> <articleclass="opona"> <imgsrc="<?= seasonIcon($tyre['sezon']) ?>"alt="<?= e($tyre['sezon']) ?>"> <h4>Opona: <?= e($tyre['producent']) ?><?= e($tyre['model']) ?></h4> <h3>Cena: <?= e($tyre['cena']) ?></h3> </article><?phpendforeach; ?> <p><ahref="https://opona.pl/">więcej ofert</a></p> </aside> <sectionclass="offer"> <imgsrc="opona.png"alt="Opona"> <h2>Opona dnia</h2><?phpif ($offer): ?> <h2><?= e($offer['producent']) ?> model <?= e($offer['model']) ?></h2> <h2>Sezon: <?= e($offer['sezon']) ?></h2> <h2>Tylko <?= e($offer['cena']) ?> zł!</h2><?phpendif; ?> </section> <sectionclass="order"> <h2>Najnowsze zamówienie</h2><?phpif ($latestOrder): ?> <h2><?= e($latestOrder['id_zam']) ?><?= e($latestOrder['ilosc']) ?> sztuki modelu <?= e($latestOrder['model']) ?></h2> <h2>Wartość zamówienia <?= number_format($orderValue, 2, ',', ' ') ?> zł</h2><?phpendif; ?> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (opony.php)
Krok 49: Zamknięcie połączenia z bazą
Kryteria:
R.5.2 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy opony oraz ostatnią operacją na bazie jest jej zamknięcie
ok. 1 min
Polecenie z arkusza
Na końcu zamykane jest połączenie z bazą.
W PDO nie ma metody close() - połączenie zamyka się, gdy znika ostatnie odwołanie do obiektu, czyli po $db = null; (R.5.2). Stoi na końcu bloku try, po wszystkich zapytaniach - to ostatnia operacja na bazie. Szablon korzysta już tylko z gotowych tablic, połączenia nie potrzebuje.
Typowe błędy
$db->close() nie istnieje w PDO - to błąd „Call to undefined method”.
opony.phpPHPzmienione: 2 linie
<?phpheader('refresh: 10');$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$cheapestTyres = $db->query('SELECT nr_kat, producent, model, sezon, cena FROM opony ORDER BY cena LIMIT 10' )->fetchAll();$offerQuery = $db->prepare('SELECT producent, model, sezon, cena FROM opony WHERE nr_kat = :catalogNumber');$offerQuery->execute(['catalogNumber' => 9]);$offer = $offerQuery->fetch();$latestOrder = $db->query('SELECT z.id_zam, z.ilosc, o.model, o.cena FROM zamowienie AS z JOIN opony AS o ON o.nr_kat = z.nr_kat ORDER BY RAND() LIMIT 1' )->fetch();$orderValue = $latestOrder ? $latestOrder['ilosc'] * $latestOrder['cena'] : 0;$db = null;} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}functionseasonIcon(string $season): string{returnmatch ($season) {'letnia' => 'lato.png','zimowa' => 'zima.png',default => 'uniwer.png', };}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>OPONY</title> <linkrel="stylesheet"href="styl.css"></head><body> <main> <aside><?phpforeach ($cheapestTyresas$tyre): ?> <articleclass="opona"> <imgsrc="<?= seasonIcon($tyre['sezon']) ?>"alt="<?= e($tyre['sezon']) ?>"> <h4>Opona: <?= e($tyre['producent']) ?><?= e($tyre['model']) ?></h4> <h3>Cena: <?= e($tyre['cena']) ?></h3> </article><?phpendforeach; ?> <p><ahref="https://opona.pl/">więcej ofert</a></p> </aside> <sectionclass="offer"> <imgsrc="opona.png"alt="Opona"> <h2>Opona dnia</h2><?phpif ($offer): ?> <h2><?= e($offer['producent']) ?> model <?= e($offer['model']) ?></h2> <h2>Sezon: <?= e($offer['sezon']) ?></h2> <h2>Tylko <?= e($offer['cena']) ?> zł!</h2><?phpendif; ?> </section> <sectionclass="order"> <h2>Najnowsze zamówienie</h2><?phpif ($latestOrder): ?> <h2><?= e($latestOrder['id_zam']) ?><?= e($latestOrder['ilosc']) ?> sztuki modelu <?= e($latestOrder['model']) ?></h2> <h2>Wartość zamówienia <?= number_format($orderValue, 2, ',', ' ') ?> zł</h2><?phpendif; ?> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 50 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 50: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 2 min
Polecenie z arkusza
Czas przeznaczony na wykonanie zadania wynosi 150 minut.
Obie wersje dają komplet 37 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ
float: left na blokach i clear: both w stopce
siatka: grid-template-columns i grid-template-areas
kolory
wpisane w każdej regule
zmienne w :root
blok opony
<div>
<article> z display: flow-root
baza
funkcje mysqli_* z tabeli 1
PDO, wyjątki, config.php
wybór ikony
if / elseif / else
funkcja z match
wypisywanie
echo w pętli while
logika na górze, szablon z foreach/endforeach i e()
pusta tabela
błąd PHP
warunek if ($offer) / if ($latestOrder)
float został w jednym miejscu - przy obrazie, bo opływanie tekstem to jego właściwe zadanie i klucz R.4.8 go wymaga.
Na egzaminie: wersja podstawowa używa tylko funkcji z tabeli 1 arkusza - mniej miejsc na pomyłkę, gdy brakuje czasu. Wersję nowoczesną pisz, jeśli znasz PDO na pamięć: arkusz nie podaje jego składni, a literówka w PDO::ATTR_ERRMODE to błąd, którego nikt nie podpowie. Formularza tu nie ma, więc nie ma też potrzeby walidacji wejścia ani tokenu CSRF - pojawią się w arkuszach, w których strona zapisuje dane.
Krok 51 · po tym etapie masz 37 z 37 kryteriów (pkt)
Krok 51: 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.
W przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana przez http://localhost/…. W folderze z numerem zdającego: import.png, kw1.png … kw4.png, kwerendy.txt, lato.png, opona.png, opony.php, config.php (bez niego strona się nie połączy), przeglądarka.txt, styl.css, uniwer.png, zima.png. Płytę sprawdź po nagraniu i opisz numerem zdającego.
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.