INF.03-05-24.06-SGCzerwiec 2024 · 40 kroków · ok. 87 min · 41 kryteriów z klucza
Rozwiązanie krok po kroku: Aplikacja komisu samochodowego (Nowoczesny kod (flex, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza kupauto z czterema zapytaniami, przeskalowane zdjęcie Yarisa i strona KupAuto.php z ofertą dnia, czterema ofertami wyróżnionymi i wyszukiwaniem samochodów wybranej marki - i tak samo spełnia każde kryterium klucza. Inna jest droga:
układ bez sztuczek: bloki główne stoją jeden pod drugim w zwykłym przepływie (siatka nic by tu nie dodała), a bloki ofert układa kontener z display: flex i zawijaniem zamiast inline-block - bez odstępów ze spacji w kodzie i bez vertical-align,
kolory w zmiennych CSS, blok oferty dnia z display: flow-root - pływające zdjęcie nigdy z niego nie wystaje,
PHP na PDO nad HTML-em: wszystkie cztery zapytania wykonane i połączenie zamknięte, zanim zacznie się strona; jedna funkcja fetchRows() do zapytań przygotowanych,
marka z białej listy: Skrypt 4 pyta bazę tylko o markę, która jest na liście z zapytania 1, i przekazuje ją jako parametr - ' OR '1'='1 ani apostrof w nazwie nic nie zepsują,
jedna funkcja-szablon showOffer() dla bloków ofert ze Skryptów 2 i 4, lista pamięta wybraną markę, e() przy każdej wartości z bazy, ORDER BY przed LIMIT.
Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i zdjęcia pochodzą z prawdziwego archiwum pliki5, zapytania zostały wykonane na tej bazie w MariaDB z XAMPP-a, a podgląd strony od etapu PHP to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2; od kroku ze Skryptem 4 pokazuje stan z obrazu 3, czyli po wybraniu marki „Opel” i kliknięciu „Wyszukaj”.
Jak liczą się punkty. Klucz ma 41 kryteriów (R.1.1 - R.5.10), każde za 1 punkt. Próg zdania to 75%, czyli 31 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 41 kryteriów (pkt)
Krok 1: Baza danych kupauto
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie kupauto, z zestawem polskich znaków (np. utf8_unicode_ci)
Archiwum pliki5.zip rozpakuj hasłem S@moCHody^ (małpa zamiast „a”, daszek na końcu) do folderu z numerem zdającego. W phpMyAdmin Nowa → kupauto, porównywanie utf8_unicode_ci, albo zapytaniem:
CREATE DATABASE kupauto CHARACTER SET utf8 COLLATE utf8_unicode_ci;
Typowe błędy
Nazwa bazy musi brzmieć dokładnie kupauto - skrypty łączą się z nią po nazwie (R.5.1).
Krok 2: Import tabel z pliku baza.sql
ok. 2 min
Polecenie z arkusza
Do bazy zaimportuj tabele z pliku baza.sql z rozpakowanego archiwum
Import → baza.sql. Dwie tabele z obrazu 1: marki (9 marek) i samochody (17 samochodów z kluczem obcym marki_id) - jedna marka, wiele samochodów. Pole wyrozniony typu TINYINT(1): 1 - samochód wyróżniony, 0 - zwykły.
Krok 3: Zrzut ekranu import.png
Kryteria:
R.1.1 Wykonano import tabel do bazy danych kupauto czynność udokumentowano plikiem o nazwie import w formacie PNG
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie PNG i nazwij 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ń, zapis jako import.png. Uwaga na formaty: import w PNG, zrzuty zapytań w JPEG (kw1.jpg - kw4.jpg).
Typowe błędy
Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany (uwaga w kluczu przy R.1).
Krok 4: Zapytanie 1: nazwy marek
Kryteria: ,
R.1.2 Zapisano plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Wykonano zapytanie 1 wybierające jedynie nazwy marek samochodów. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa FROM marki; oraz wynik działania udokumentowano zrzutem, na którym jest widocznych dokładnie 9 wierszy, nazwy wszystkich marek: Audi, BMW, Volkswagen, Opel, Ford, Mercedes, Toyota, Fiat, Jeep
ok. 1 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie nazwy marek samochodów
Jedno pole, bez identyfikatora („jedynie nazwy”). Wynik: 9 marek od Audi do Jeep, jak w kluczu. To zapytanie wypełni listę rozwijaną (Skrypt 3), a jego wynik posłuży też jako lista dozwolonych marek w Skrypcie 4. Zapytania trafiają do kwerendy.txt, zrzut do kw1.jpg.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT nazwaFROM marki;
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: samochód o identyfikatorze 10
Kryteria:
R.1.4 Wykonano zapytanie 2 wybierające jedynie pola: model, rocznik, przebieg, paliwo, cena, zdjecie dla samochodu o id równym 10. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT model, rocznik, przebieg, paliwo, cena, zdjecie FROM samochody WHERE id=10; oraz wynik działania udokumentowano zrzutem, na którym jest widoczny dokładnie 1 wiersz: Yaris 2022 0 Benzyna 94000 ToyotaYaris.jpg
ok. 1 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie pola: model, rocznik, przebieg, paliwo, cena, zdjecie dla samochodu o id równym 10
id jest kluczem głównym, więc WHERE id = 10 zwraca najwyżej jeden wiersz: Yaris 2022 0 Benzyna 94000 ToyotaYaris.jpg, jak w kluczu. Zrzut: kw2.jpg.
Krok 6: Zapytanie 3: cztery oferty wyróżnione z nazwą marki
Kryteria:
R.1.5 Wykonano zapytanie 3 wybierające jedynie pole nazwa z tabeli marki i odpowiadające jej pola: model, rocznik, cena, zdjecie z tabeli samochody jedynie dla samochodów wyróżnionych. Wybierane są dokładnie 4 wiersze. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, model, rocznik, cena, zdjecie FROM samochody JOIN marki ON marki_id = marki.id WHERE wyrozniony=1 LIMIT 4; (możliwe również INNER JOIN lub bez JOIN porównanie kluczy po WHERE) oraz wynik działania udokumentowano zrzutem, na którym są widoczne dokładnie 4 wiersze, jedynie pola nazwa, model, rocznik, cena, zdjecie dla modeli: A8 TDI QUattro, A8, Astra, Corsa
ok. 3 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie pole nazwa z tabeli marki i odpowiadające jej pola: model, rocznik, cena, zdjecie z tabeli samochody jedynie dla samochodów wyróżnionych. Zapytanie wybiera dokładnie 4 wiersze. Należy posłużyć się relacją
aliasy s (samochody) i m (marki), JOIN ... ON m.id = s.marki_id - relacja z obrazu 1,
WHERE s.wyrozniony = 1 - tylko wyróżnione; jest ich 6, a arkusz chce dokładnie 4, stąd LIMIT 4 (klucz podaje go wprost),
ORDER BY s.id przed LIMIT - bez sortowania baza może oddać „pierwsze cztery” w dowolnej kolejności, więc wynik LIMIT nie jest gwarantowany. Z sortowaniem po identyfikatorze zawsze są to te same cztery samochody.
Wynik: A8 TDI QUattro, A8, Astra, Corsa - jak w kluczu. Zrzut: kw3.jpg.
Typowe błędy
Bez LIMIT 4 zapytanie zwraca 6 wierszy, a klucz wymaga na zrzucie dokładnie 4.
kwerendy.txtTekstzmienione: 8 linii
-- Zapytanie 1SELECT nazwaFROM marki;-- Zapytanie 2SELECT model, rocznik, przebieg, paliwo, cena, zdjecieFROM samochodyWHERE id = 10;-- Zapytanie 3SELECT m.nazwa, s.model, s.rocznik, s.cena, s.zdjecieFROM samochody AS sJOIN marki AS m ON m.id = s.marki_idWHERE s.wyrozniony = 1ORDER BY s.idLIMIT 4;
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: samochody marki Audi
Kryteria:
R.1.6 Wykonano zapytanie 4 wybierające jedynie pola: model, cena, zdjecie z tabeli samochody dla samochodów o nazwie marki Audi. Należy wykorzystać relację. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT model, cena, zdjecie FROM samochody JOIN marki ON marki_id = marki.id WHERE nazwa = "Audi"; (możliwe również INNER JOIN lub bez JOIN porównanie kluczy po WHERE) oraz wynik działania udokumentowano zrzutem, na którym są widoczne są dokładnie 4 wiersze, jedynie pola model, cena, zdjecie, dla modeli: A3, A8 TDI QUattro, A3, A8
ok. 2 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie pola: model, cena, zdjecie z tabeli samochody dla samochodów o nazwie marki Audi. Należy posłużyć się relacją
Warunek na nazwę marki przez złączenie (m.nazwa = 'Audi'), a nie marki_id = 1 - arkusz każe posłużyć się relacją, a Skrypt 4 dostanie z listy właśnie nazwę. Wynik: A3, A8 TDI QUattro, A3, A8, jak w kluczu. Zrzut: kw4.jpg.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT nazwaFROM marki;-- Zapytanie 2SELECT model, rocznik, przebieg, paliwo, cena, zdjecieFROM samochodyWHERE id = 10;-- Zapytanie 3SELECT m.nazwa, s.model, s.rocznik, s.cena, s.zdjecieFROM samochody AS sJOIN marki AS m ON m.id = s.marki_idWHERE s.wyrozniony = 1ORDER BY s.idLIMIT 4;-- Zapytanie 4SELECT s.model, s.cena, s.zdjecieFROM samochody AS sJOIN marki AS m ON m.id = s.marki_idWHERE m.nazwa = 'Audi';
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 8-17 · po tym etapie masz 16 z 41 kryteriów (pkt)
Krok 8: ToyotaYaris.jpg: szerokość 500 px
Kryteria:
R.2.1 Grafikę ToyotaYaris.jpg przeskalowano z zachowaniem proporcji do szerokości, dokładnie 500 px, odpowiadająca temu wysokość wynosi 332 px (±5 px)
ok. 2 min
Polecenie z arkusza
Grafikę ToyotaYaris.jpg, wypakowaną z archiwum, należy przeskalować z zachowaniem proporcji do szerokości, dokładnie 500 px
W GIMP-ie Obraz → Skaluj obraz z zamkniętym łańcuchem proporcji, szerokość 500. Oryginał ma 640 × 425 px, więc wysokość wychodzi 332 px, jak w kluczu (332 ± 5). Zapis pod tą samą nazwą (Plik → Nadpisz ToyotaYaris.jpg), bo tę nazwę ma pole zdjecie w bazie.
Typowe błędy
Zapis pod nową nazwą - strona i tak wczyta oryginał, bo nazwę pliku bierze z bazy.
Podgląd strony po tym kroku
Krok 9: Plik KupAuto.php: HTML5, język, kodowanie
Kryteria:
R.2.2 Witrynę internetową zapisano w pliku KupAuto.php oraz zapisano jawnie standard kodowania polskich znaków i deklarację HTML5 <!DOCTYPE HTML> i zadeklarowano język witryny polski np.<HTML lang="pl">. Strona zawiera sekcje head i body
ok. 2 min
Polecenie z arkusza
Składa się ze strony o nazwie KupAuto.php zapisanej w języku HTML5. Zastosowany język witryny polski. Jawnie zastosowany właściwy standard kodowania polskich znaków
Plik KupAuto.php (wielkie K i A, jak w arkuszu): <!DOCTYPE html>, <html lang="pl">, <meta charset="UTF-8"> jako pierwszy element <head> i sekcja <body> (R.2.2).
R.3.6 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.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 #)
ok. 1 min
Polecenie z arkusza
Arkusz stylów w pliku o nazwie styl.css prawidłowo połączony z kodem strony
Nazwa styl.css; całe formatowanie z tego pliku, bez atrybutów style (R.3.6).
Typowe błędy
Klucz odbiera R.3.6 także wtedy, gdy choć jeden selektor nie trafia w blok (np. kropka zamiast # przy identyfikatorze).
Krok 12: Bloki: baner, trzy bloki główne w main, stopka
Kryteria:
R.2.4 Układ strony zdefiniowano dzieląc ją na bloki: banera, trzy główne oraz stopkę. Zastosowano semantyczne znaczniki sekcji HTML5 zgodnie z ich znaczeniem, w tym do części nagłówkowej - header, stopki - footer, dopuszcza się zagnieżdżenie bloków div w znacznikach semantycznych
ok. 2 min
Polecenie z arkusza
Podział strony na bloki: baner, trzy bloki główne umiejscowione jeden pod drugim, na dole blok stopki. Podział zrealizowany wyłącznie 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 obrazem 2
Baner <header>, stopka <footer> (klucz R.2.4 wymienia je wprost). Trzy bloki główne to trzy <section> - każdy jest odrębną częścią oferty komisu - zebrane w <main>, który oznacza główną treść strony: czytnik ekranu pozwala do niego przeskoczyć z pominięciem banera. Identyfikatory od treści: day (oferta dnia), featured (wyróżnione), search (wyszukiwanie).
R.2.6 Frazę „KupAuto!” zapisano za pomocą znacznika semantycznego <em>
ok. 1 min
Polecenie z arkusza
Zawartość banera: nagłówek pierwszego stopnia o treści „KupAuto! Internetowy Komis Samochodowy”. Fraza „KupAuto!” jest zapisana za pomocą znacznika semantycznego oznaczającego uwydatnienie i zapisywanego czcionką pochyłą
Uwydatnienie to <em> - przeglądarka pisze je kursywą (R.2.6). <i> też daje kursywę, ale nic nie znaczy.
Typowe błędy
<i>KupAuto!</i> wygląda tak samo, ale klucz wymaga <em>.
Krok 14: Drugi blok główny: nagłówek i kontener ofert
ok. 2 min
Polecenie z arkusza
Zawartość drugiego bloku głównego: Nagłówek drugiego stopnia o treści „Oferty Wyróżnione”. Efekt działania skryptu 2
<h2> i pod nim pusty <div class="offers"> - kontener, w którym Skrypt 2 postawi cztery bloki ofert. Wspólny rodzic samych ofert pozwoli ułożyć je w rzędzie jedną regułą CSS, bez dotykania nagłówka (klucz dopuszcza div wewnątrz znaczników semantycznych). Pierwszy blok główny zostaje pusty - całą jego zawartość wypisze Skrypt 1.
Krok 15: Trzeci blok główny: nagłówek, formularz POST, lista
Kryteria:
R.3.5 Formularz wysyła dane metodą POST do skryptu
ok. 3 min
Polecenie z arkusza
Zawartość trzeciego bloku głównego: Nagłówek drugiego stopnia o treści „Wybierz markę”. Formularz wysyłający dane metodą bezpieczną, zawierający elementy: Listę rozwijalną wypełnioną skryptem 3
method="post" - „metoda bezpieczna” w rozumieniu arkusza: marka jedzie w treści żądania, nie w adresie. W specyfikacji HTTP „bezpieczne” są co prawda metody, które niczego nie zmieniają na serwerze (GET i HEAD), ale arkusz używa tego słowa w innym sensie, a klucz R.3.5 wymaga POST; bez action formularz wysyła dane do tego samego pliku,
<select name="brand"> - pod tą nazwą marka trafi do skryptu; opcje dopisze Skrypt 3,
aria-labelledby="search-title" - lista nie ma własnej etykiety, więc wskazuje nagłówek „Wybierz markę” jako swój opis; czytnik ekranu przeczyta go przy liście.
Typowe błędy
Lista bez name nic nie wysyła - Skrypt 4 nigdy nie dostanie marki.
R.2.5 W banerze zapisano nagłówek h1: "KupAuto! Internetowy Komis Samochodowy" , w drugim bloku głównym h2: "Oferty Wyróżnione" , w trzecim bloku głównym h2: "Wybierz markę" oraz w stopce dwa paragrafy z numerem zdającego i z odnośnikiem
R.3.4 Odnośnik o treści "Znajdź nas także" inicjuje otwarcie strony "http://firmy.pl/komis"
ok. 2 min
Polecenie z arkusza
Zawartość stopki: Paragraf (akapit) o treści: „Stronę wykonał: ”, dalej wstawiony numer zdającego. Paragraf z odnośnikiem o treści „Znajdź nas także”, prowadzącym do strony http://firmy.pl/komis
Dwa akapity. Na egzaminie po „Stronę wykonał:” wpisujesz swój numer zdającego (PESEL); tu stoi autor rozwiązania, Bartosz Bryniarski. Adres odnośnika przepisany z http:// (R.3.4). Komplet nagłówków i akapitów z R.2.5.
Kroki 18-26 · po tym etapie masz 28 z 41 kryteriów (pkt)
Krok 18: Plik styl.css: zmienne i krój Roboto albo szeryfowy
Kryteria:
R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (dla *): krój czcionki Roboto, Serif (tylko taka kolejność jest prawidłowa)
ok. 2 min
Polecenie z arkusza
Domyślne formatowanie wszystkich selektorów: krój czcionki Roboto, w przypadku jej braku - Serif
Crimson występuje w banerze, stopce, ramkach i nagłówkach h4 - zapisany raz w zmiennej--accent, obok teł bloków. Krój: Roboto, a gdy go brak - ogólna rodzina serif; klucz R.4.1 uznaje tylko tę kolejność. Na komputerze bez Roboto strona dostaje krój szeryfowy, jak na obrazie 2.
Typowe błędy
font-family: serif, Roboto - odwrotna kolejność, klucz jej nie uznaje.
Krok 19: Baner: kolory, wyśrodkowanie, odstępy liter i słów
ok. 2 min
Polecenie z arkusza
Dla banera: kolor tła Crimson, biały kolor czcionki, wyrównanie tekstu do środka, odległości pomiędzy literami 5 px, odległości pomiędzy słowami 30 px, marginesy wewnętrzne 10 px
Odstęp między literami to letter-spacing, między słowami - word-spacing; stąd rozstrzelony napis z obrazu 2. Kolory i wyśrodkowanie zaliczą się razem ze stopką i nagłówkami h4.
Krok 20: Pierwszy blok główny: tło, wysokość, marginesy, ramka
Kryteria: ,
R.4.7 Ustawiono marginesy wewnętrzne (padding) 10px dla banera i pierwszego bloku głównego oraz marginesy zewnętrzne (margin) górny i dolny 10px, lewy i prawy 50px dla pierwszego bloku głównego
R.4.9 Ustawiono obramowanie dla pierwszego bloku głównego: 1px solid Crimson
ok. 3 min
Polecenie z arkusza
Dla pierwszego bloku głównego: kolor tła MistyRose, wysokość 300 px, marginesy wewnętrzne 10 px, zewnętrzne górny i dolny 10 px, lewy i prawy 50 px, obramowanie linią ciągłą o szerokości 1 px i kolorze Crimson
margin: 10px 50px - pierwsza wartość to góra i dół, druga - boki. Ramka 1px solid w kolorze ze zmiennej (R.4.9), padding i marginesy (R.4.7).
display: flow-root to dodatek: blok zamyka w sobie pływające elementy. Zdjęcie oferty dnia będzie float: left; dziś blok ma stałą wysokość, więc i tak by nie wystawało, ale po zdjęciu height (np. na telefonie) pływające zdjęcie wyszłoby poza ramkę i wjechało pod nagłówek „Oferty Wyróżnione”. flow-root zastępuje dawne sztuczki z overflow: hidden czy clearfix.
Krok 21: Zdjęcie oferty dnia: po lewej, na całą wysokość
Kryteria: ,
R.3.3 W głównym bloku pierwszym obraz wyświetla się po lewej stronie tekstu
R.4.4 Ustawiono wysokość pierwszego bloku głównego 300px oraz 100% jedynie dla obrazu z pierwszego bloku głównego
ok. 2 min
Polecenie z arkusza
Dla obrazu jedynie z pierwszego bloku głównego: oblewanie tekstem po prawej stronie (obraz jest po lewej stronie) wysokość 100%
Tu zostaje float: left - arkusz mówi wprost o oblewaniu tekstem, a to jest dokładnie opływanie: nagłówki i akapit stają z prawej strony zdjęcia (R.3.3). Selektor #day img dotyczy tylko tego bloku; height: 100% liczy się od wysokości bloku, czyli 300 px (R.4.4).
Typowe błędy
Samo img { height: 100% } rozciągnęłoby także zdjęcia w blokach ofert do wysokości bloku 450 px.
R.4.8 Ustawiono styl bloków z ofertami w drugim i trzecim bloku głównym: kolor tła Snow, wysokość 450 px, szerokość 21%, marginesy zewnętrzne 1%, wyrównanie tekstu do środka, obramowanie linią ciągłą o szerokości 1 px i kolorze Crimson (np. za pomocą klasy, id jest błędem)
ok. 3 min
Polecenie z arkusza
Dla bloków z ofertami generowanych skryptem w drugim i trzecim bloku głównym: kolor tła Snow, wysokość 450 px, szerokość 21%, marginesy zewnętrzne 1%, wyrównanie tekstu do środka, obramowanie linią ciągłą o szerokości 1 px i kolorze Crimson
klasa .offer - bloków jest wiele, a identyfikator musi być jedyny (klucz R.4.8: „id jest błędem”); szerokość 21% z marginesem 1% z każdej strony to 23% na blok, czyli cztery w rzędzie,
.offers { display: flex; flex-wrap: wrap; } - kontener ustawia bloki obok siebie, a piąty przenosi do nowego wiersza. W wersji podstawowej robi to inline-block, które traktuje bloki jak słowa: między nimi zostają odstępy ze spacji i nowych linii w kodzie, a wyrównanie trzeba poprawiać vertical-align. W kontenerze flex tych odstępów nie ma.
Siatka (grid) nie pasuje tu tak dobrze: szerokość 21% z arkusza liczyłaby się od kolumny siatki, a nie od bloku. Elementy flex liczą procent od kontenera, więc wartości z arkusza działają wprost.
R.4.3 Ustawiono biały kolor czcionki dla banera, stopki i odnośnika oraz Crimson dla selektora h4
R.4.5 Ustawiono cechy tekstu banera: odległości pomiędzy literami (letter-spacing) 5px, odległości pomiędzy słowami (word-spacing) 30px oraz rozmiar czcionki nagłówka czwartego stopnia 180%
R.4.6 Ustawiono wyrównanie tekstu do środka dla banera i nagłówka czwartego stopnia
ok. 2 min
Polecenie z arkusza
Dla selektora nagłówka czwartego stopnia: rozmiar czcionki 180%, wyrównanie tekstu do środka, kolor czcionki Crimson. Dla odnośnika: biały kolor czcionki
Selektory znaczników h4 i a - tak każe uwaga arkusza. Komplet kryteriów tekstu: odstępy liter i słów oraz rozmiar 180% (R.4.5), wyśrodkowanie banera i h4 (R.4.6), biel banera, stopki i odnośnika oraz Crimson h4 (R.4.3). Bez białego koloru odnośnik byłby niebieski na czerwonym tle.
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu - nie należy brać pod uwagę bloków z ofertami (prawidłowo zastosowano właściwości CSS decydujące o układzie strony, np. float i clear albo display flex albo grid)
ok. 1 min
Polecenie z arkusza
Podział strony na bloki: baner, trzy bloki główne umiejscowione jeden pod drugim, na dole blok stopki. Podział zrealizowany wyłącznie 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 obrazem 2
Bez dodatkowej reguły: header, main, section i footer są elementami blokowymi, więc stają jeden pod drugim na całą szerokość - dokładnie układ z obrazu 2 (R.3.1). Siatka albo flex na <body> niczego by tu nie dodały; najprostszy kod, który robi swoje, jest najlepszy.
Kroki 27-38 · po tym etapie masz 41 z 41 kryteriów (pkt)
Krok 27: Połączenie PDO i funkcja fetchRows()
Kryteria:
R.5.1 Skrypt zawiera instrukcje połączenia z serwerem bazy danych i wyboru bazy kupauto
ok. 4 min
Polecenie z arkusza
Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie kupauto
Praca z bazą stoi przed<!DOCTYPE html>: najpierw dane, potem HTML, który je tylko wypisuje.
PDO z DSN mysql:host=localhost;dbname=kupauto;charset=utf8mb4, użytkownik root, puste hasło (R.5.1); ERRMODE_EXCEPTION i FETCH_ASSOC,
fetchRows() - cztery skrypty wysyłają cztery zapytania, więc przygotowanie, wykonanie z parametrami i odbiór wierszy stoją w jednej funkcji. Zapytanie ($statement) żyje tylko wewnątrz funkcji, więc po jej powrocie nic nie trzyma połączenia,
$pdo = null zamyka połączenie po wszystkich skryptach, try / catch daje ogólny komunikat przy awarii, e() zabezpiecza wartości wstawiane w HTML.
Od tego kroku podgląd to wynik prawdziwego uruchomienia.
R.5.3 Skrypt 1 zawiera instrukcje wysyłające do bazy danych zapytanie 2 lub skrypt 2: zapytanie 3 lub skrypt 3: zapytanie 1
R.5.10 Zastosowano znaczące nazewnictwo dla wszystkich zmiennych lub funkcji w języku polskim lub angielskim. Nazwy odzwierciedlają cel zastosowania
ok. 2 min
Polecenie z arkusza
Skrypt 1: Wysyła do bazy danych zapytanie 2
Zapytanie 2 z parametrem :id, a wartość ze stałej OFFER_OF_THE_DAY_ID - numer oferty dnia ma nazwę i jedno miejsce do zmiany (R.5.3). fetchRows() zwraca listę wierszy; [0] ?? null bierze pierwszy, a gdy samochodu o tym numerze nie ma - null zamiast ostrzeżenia. Nazwy po angielsku mówią, co przechowują (R.5.10).
W pierwszym bloku głównym wyświetla dane pobrane z bazy danych, w następujący sposób: Obraz, którego źródłem jest pole zdjecie, a tekstem alternatywnym „oferta dnia”
Szablon pierwszego bloku stoi w <?php if ($dayOffer): ?> - bez oferty blok zostaje pusty zamiast pokazać zepsuty obraz. Źródło z kolumny zdjecie po nazwie. Podgląd: przeskalowane zdjęcie Yarisa po lewej, na całą wysokość bloku.
R.5.5 Skrypt 1 wyświetla w pierwszym bloku głównym dane zwrócone z bazy danych: zdjęcie, model, rocznik, przebieg, rodzaj paliwa oraz cenę
ok. 3 min
Polecenie z arkusza
Nagłówek czwartego stopnia o treści „Oferta Dnia: Toyota ”, następnie pole model. Paragraf o treści „Rocznik: <rocznik>, przebieg: <przebieg>, rodzaj paliwa: <paliwo>”, gdzie pola w nawiasach <> są pobrane z bazy danych. Nagłówek czwartego stopnia o treści „Cena: ” następnie pole cena
Trzy linie szablonu: „Toyota” jest stałym tekstem (zapytanie 2 nie pobiera marki), reszta to pola po nazwach. Pierwszy blok wygląda jak na obrazie 2: „Oferta Dnia: Toyota Yaris”, „Rocznik: 2022, przebieg: 0, rodzaj paliwa: Benzyna”, „Cena: 94000” obok zdjęcia (R.5.5).
R.3.2 W głównym bloku drugim lub trzecim każda oferta jest umieszczona w osobnym bloku, bloki są wyświetlone obok siebie (w skrypcie lub HTML), dopuszcza się że bloki są puste
ok. 4 min
Polecenie z arkusza
Skrypt 2: Wysyła do bazy danych zapytanie 3. Każdy zwrócony wiersz jest wyświetlony w osobnym bloku, co razem daje cztery bloki. Bloki są wyświetlone obok siebie w jednej linii. W każdym bloku znajduje się: Obraz, którego nazwą pliku źródłowego jest pole zdjecie, a tekstem alternatywnym pole model
Zapytanie 3 przez fetchRows() do tablicy $featured. Blok oferty pojawi się na stronie dwa razy (Skrypty 2 i 4), więc od razu stoi w funkcji-szablonie showOffer(): ?> wewnątrz funkcji przechodzi do zwykłego HTML-u, a <?php wraca do PHP - przy wywołaniu funkcja wypisuje swój kawałek strony. Definicja na górze pliku niczego nie wypisuje.
W kontenerze .offers pętla foreach wywołuje showOffer($car) dla każdego wiersza: cztery bloki offer w jednym rzędzie, ze zdjęciem i modelem w tekście alternatywnym (R.3.2).
KupAuto.phpPHPzmienione: 21 linii
<?phpconst OFFER_OF_THE_DAY_ID = 10;functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES);}functionfetchRows(PDO $pdo, string $sql, array$params = []): array{$statement = $pdo->prepare($sql);$statement->execute($params);return$statement->fetchAll();}functionshowOffer(array$car): void{?> <divclass="offer"> <imgsrc="<?= e($car['zdjecie']) ?>"alt="<?= e($car['model']) ?>"> </div><?php}$dayOffer = null;$featured = [];$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=kupauto;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 2$dayOffer = fetchRows($pdo,'SELECT model, rocznik, przebieg, paliwo, cena, zdjecie FROM samochody WHERE id = :id', ['id' => OFFER_OF_THE_DAY_ID] )[0] ?? null;// Skrypt 2: zapytanie 3$featured = fetchRows($pdo,'SELECT m.nazwa, s.model, s.rocznik, s.cena, s.zdjecie FROM samochody AS s JOIN marki AS m ON m.id = s.marki_id WHERE s.wyrozniony = 1 ORDER BY s.id LIMIT 4' );$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać ofert. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Komis aut</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1><em>KupAuto!</em> Internetowy Komis Samochodowy</h1> </header> <main> <sectionid="day"><?phpif ($dayOffer): ?> <imgsrc="<?= e($dayOffer['zdjecie']) ?>"alt="oferta dnia"> <h4>Oferta Dnia: Toyota <?= e($dayOffer['model']) ?></h4> <p>Rocznik: <?= e($dayOffer['rocznik']) ?>, przebieg: <?= e($dayOffer['przebieg']) ?>, rodzaj paliwa: <?= e($dayOffer['paliwo']) ?></p> <h4>Cena: <?= e($dayOffer['cena']) ?></h4><?phpendif; ?> </section> <sectionid="featured"> <h2>Oferty Wyróżnione</h2> <divclass="offers"><?phpforeach ($featuredas$car) { showOffer($car); } ?> </div> </section> <sectionid="search"> <h2id="search-title">Wybierz markę</h2> <formmethod="post"> <selectname="brand"aria-labelledby="search-title"> </select> <buttontype="submit">Wyszukaj</button> </form> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> <p><ahref="http://firmy.pl/komis">Znajdź nas także</a></p> </footer></body></html>
Podgląd strony po tym kroku (KupAuto.php)
Krok 32: Skrypt 2: marka z modelem, rocznik i cena
Kryteria: , ,
R.2.7 W pierwszym i drugim bloku głównym umieszczono przynajmniej dla jednej oferty nazwę i model samochodu oraz cenę w nagłówku h4 oraz inne dane w paragrafie (w skrypcie lub HTML)
R.2.8 W pierwszym i drugim bloku głównym umieszczono przynajmniej jedno zdjęcie z tekstem alternatywnym (w skrypcie lub HTML)
R.5.6 Skrypt 2 wyświetla w drugim bloku głównym dane zwrócone z bazy danych dotyczące czterech samochodów: zdjęcie, markę, model, rocznik oraz cenę
ok. 3 min
Polecenie z arkusza
Nagłówek czwartego stopnia z nazwą marki i modelem, pobranymi z bazy. Paragraf o treści „Rocznik: ”, następnie pole rocznik. Nagłówek czwartego stopnia o treści „Cena: ” następnie pole cena
Szablon bloku rośnie o nagłówek z marką i modelem (marka przychodzi dzięki złączeniu w zapytaniu 3), akapit z rocznikiem i cenę. Rocznik pokazuje tylko Skrypt 2, dlatego funkcja dostała drugi parametr bool $showYear - wywołanie showOffer($car, true) mówi wprost, co zobaczy użytkownik, zamiast zgadywać po zawartości wiersza.
Podgląd: cztery bloki jak na obrazie 2 - Audi A8 TDI QUattro, Audi A8, Opel Astra, Opel Corsa (R.5.6). Pierwszy i drugi blok mają już nazwy z modelami i ceny w <h4>, dane w akapitach (R.2.7) i zdjęcia z tekstem alternatywnym (R.2.8).
R.5.7 Skrypt 3 dodaje do listy rozwijalnej jako elementy <option> nazwy marek samochodów zwrócone zapytaniem
ok. 2 min
Polecenie z arkusza
Skrypt 3: Wysyła do bazy danych zapytanie 1. Zwrócone zapytaniem nazwy marek samochodów są wyświetlone jako elementy listy rozwijalnej
Zapytanie 1 zwraca wiersze z jedną kolumną; array_column(..., 'nazwa') robi z nich zwykłą listę napisów ['Audi', 'BMW', ...] - taka przyda się w Skrypcie 4 do sprawdzenia marki. W <select> pętla wypisuje <option> dla każdej marki; bez atrybutu value przeglądarka wyśle treść opcji, czyli nazwę (R.5.7). Na liście 9 marek, pierwsza Audi.
R.5.8 Skrypt 4 jest wykonywany tylko, gdy wybrano opcję z listy rozwijalnej
ok. 3 min
Polecenie z arkusza
Skrypt 4: Po zatwierdzeniu formularza tylko, gdy użytkownik wybrał element listy rozwijalnej wysyła do bazy danych zmodyfikowane zapytanie 4, w ten sposób, że jest w nim sprawdzana nazwa marki wybrana w liście
filter_input(INPUT_POST, 'brand') czyta wybór: null, gdy formularza nie wysłano. Warunek Skryptu 4 sprawdza dwie rzeczy:
is_string - przyszedł napis (a nie brak danych czy tablica),
in_array($selectedBrand, $brands, true) - marka jest na liście z zapytania 1, czyli „użytkownik wybrał element listy rozwijalnej” dosłownie. To biała lista: wartość spoza listy, choćby spreparowana ręcznie, w ogóle nie dojdzie do zapytania (R.5.8).
Od tego kroku podgląd pokazuje stronę po wybraniu „Opel” i kliknięciu „Wyszukaj”.
Krok 35: Skrypt 4: zapytanie 4 z parametrem :brand
Kryteria:
R.5.4 Skrypt 4 zawiera instrukcje wysyłające do bazy danych zmodyfikowane zapytanie 4, w ten sposób, że jest w nim sprawdzana nazwa marki wybrana w liście
ok. 3 min
Polecenie z arkusza
Wysyła do bazy danych zmodyfikowane zapytanie 4, w ten sposób, że jest w nim sprawdzana nazwa marki wybrana w liście
Zapytanie 4 z dwiema zmianami: w miejscu 'Audi' stoi parametr :brand, a wartość podaje fetchRows() osobno - baza nigdy nie potraktuje marki jak kodu SQL, nawet z apostrofem (R.5.4). Na początek listy pól trafia m.nazwa, bo blok oferty pokazuje markę „pobraną z bazy danych” (klucz R.5.9 też o niej mówi), a ORDER BY s.id ustala kolejność wyników - taką jak na obrazie 3.
Biała lista z poprzedniego kroku i parametr to dwie niezależne zapory: jedna pilnuje sensu wartości, druga - bezpieczeństwa zapytania.
KupAuto.phpPHPzmienione: 8 linii
<?phpconst OFFER_OF_THE_DAY_ID = 10;functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES);}functionfetchRows(PDO $pdo, string $sql, array$params = []): array{$statement = $pdo->prepare($sql);$statement->execute($params);return$statement->fetchAll();}functionshowOffer(array$car, bool $showYear): void{?> <divclass="offer"> <imgsrc="<?= e($car['zdjecie']) ?>"alt="<?= e($car['model']) ?>"> <h4><?= e($car['nazwa']) ?><?= e($car['model']) ?></h4><?phpif ($showYear): ?> <p>Rocznik: <?= e($car['rocznik']) ?></p><?phpendif; ?> <h4>Cena: <?= e($car['cena']) ?></h4> </div><?php}$dayOffer = null;$featured = [];$brands = [];$brandCars = [];$selectedBrand = filter_input(INPUT_POST, 'brand');$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=kupauto;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 2$dayOffer = fetchRows($pdo,'SELECT model, rocznik, przebieg, paliwo, cena, zdjecie FROM samochody WHERE id = :id', ['id' => OFFER_OF_THE_DAY_ID] )[0] ?? null;// Skrypt 2: zapytanie 3$featured = fetchRows($pdo,'SELECT m.nazwa, s.model, s.rocznik, s.cena, s.zdjecie FROM samochody AS s JOIN marki AS m ON m.id = s.marki_id WHERE s.wyrozniony = 1 ORDER BY s.id LIMIT 4' );// Skrypt 3: zapytanie 1$brands = array_column(fetchRows($pdo, 'SELECT nazwa FROM marki'), 'nazwa');// Skrypt 4: tylko po wybraniu marki z listyif (is_string($selectedBrand) && in_array($selectedBrand, $brands, true)) {$brandCars = fetchRows($pdo,'SELECT m.nazwa, s.model, s.cena, s.zdjecie FROM samochody AS s JOIN marki AS m ON m.id = s.marki_id WHERE m.nazwa = :brand ORDER BY s.id', ['brand' => $selectedBrand] ); }$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać ofert. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Komis aut</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1><em>KupAuto!</em> Internetowy Komis Samochodowy</h1> </header> <main> <sectionid="day"><?phpif ($dayOffer): ?> <imgsrc="<?= e($dayOffer['zdjecie']) ?>"alt="oferta dnia"> <h4>Oferta Dnia: Toyota <?= e($dayOffer['model']) ?></h4> <p>Rocznik: <?= e($dayOffer['rocznik']) ?>, przebieg: <?= e($dayOffer['przebieg']) ?>, rodzaj paliwa: <?= e($dayOffer['paliwo']) ?></p> <h4>Cena: <?= e($dayOffer['cena']) ?></h4><?phpendif; ?> </section> <sectionid="featured"> <h2>Oferty Wyróżnione</h2> <divclass="offers"><?phpforeach ($featuredas$car) { showOffer($car, true); } ?> </div> </section> <sectionid="search"> <h2id="search-title">Wybierz markę</h2> <formmethod="post"> <selectname="brand"aria-labelledby="search-title"><?phpforeach ($brandsas$brand): ?> <option><?= e($brand) ?></option><?phpendforeach; ?> </select> <buttontype="submit">Wyszukaj</button> </form> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> <p><ahref="http://firmy.pl/komis">Znajdź nas także</a></p> </footer></body></html>
Krok 36: Skrypt 4: bloki samochodów wybranej marki
Kryteria:
R.5.9 Skrypt 4 wyświetla w trzecim bloku głównym dane zwrócone z bazy danych dotyczące samochodów wybranej marki: zdjęcie, nazwę marki, model i cenę
ok. 3 min
Polecenie z arkusza
Każdy zwrócony wiersz jest wyświetlony w osobnym bloku. Bloki są wyświetlone obok siebie. W każdym bloku znajduje się: Obraz, którego nazwą pliku źródłowego jest pole zdjecie, a tekstem alternatywnym pole model. Nagłówek czwartego stopnia z nazwą marki i modelem, pobranymi z bazy danych. Nagłówek czwartego stopnia o treści „Cena: ” następnie pole cena
Pod formularzem drugi kontener .offers i ta sama funkcja - tym razem showOffer($car, false), bez rocznika. Zmiana wyglądu bloku oferty to teraz jedno miejsce w kodzie dla obu skryptów.
Podgląd po wybraniu „Opel”: pięć bloków - Astra, Corsa, Vectra, Corsa w pierwszym wierszu i Corsa w drugim, jak na obrazie 3 (R.5.9). Sprawdzone uruchomieniem: wysłanie ' OR '1'='1 albo Alfa'Romeo z pominięciem listy nie dodaje żadnego bloku i nie daje błędu - wartości nie ma na białej liście.
KupAuto.phpPHPzmienione: 3 linie
<?phpconst OFFER_OF_THE_DAY_ID = 10;functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES);}functionfetchRows(PDO $pdo, string $sql, array$params = []): array{$statement = $pdo->prepare($sql);$statement->execute($params);return$statement->fetchAll();}functionshowOffer(array$car, bool $showYear): void{?> <divclass="offer"> <imgsrc="<?= e($car['zdjecie']) ?>"alt="<?= e($car['model']) ?>"> <h4><?= e($car['nazwa']) ?><?= e($car['model']) ?></h4><?phpif ($showYear): ?> <p>Rocznik: <?= e($car['rocznik']) ?></p><?phpendif; ?> <h4>Cena: <?= e($car['cena']) ?></h4> </div><?php}$dayOffer = null;$featured = [];$brands = [];$brandCars = [];$selectedBrand = filter_input(INPUT_POST, 'brand');$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=kupauto;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 2$dayOffer = fetchRows($pdo,'SELECT model, rocznik, przebieg, paliwo, cena, zdjecie FROM samochody WHERE id = :id', ['id' => OFFER_OF_THE_DAY_ID] )[0] ?? null;// Skrypt 2: zapytanie 3$featured = fetchRows($pdo,'SELECT m.nazwa, s.model, s.rocznik, s.cena, s.zdjecie FROM samochody AS s JOIN marki AS m ON m.id = s.marki_id WHERE s.wyrozniony = 1 ORDER BY s.id LIMIT 4' );// Skrypt 3: zapytanie 1$brands = array_column(fetchRows($pdo, 'SELECT nazwa FROM marki'), 'nazwa');// Skrypt 4: tylko po wybraniu marki z listyif (is_string($selectedBrand) && in_array($selectedBrand, $brands, true)) {$brandCars = fetchRows($pdo,'SELECT m.nazwa, s.model, s.cena, s.zdjecie FROM samochody AS s JOIN marki AS m ON m.id = s.marki_id WHERE m.nazwa = :brand ORDER BY s.id', ['brand' => $selectedBrand] ); }$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać ofert. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Komis aut</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1><em>KupAuto!</em> Internetowy Komis Samochodowy</h1> </header> <main> <sectionid="day"><?phpif ($dayOffer): ?> <imgsrc="<?= e($dayOffer['zdjecie']) ?>"alt="oferta dnia"> <h4>Oferta Dnia: Toyota <?= e($dayOffer['model']) ?></h4> <p>Rocznik: <?= e($dayOffer['rocznik']) ?>, przebieg: <?= e($dayOffer['przebieg']) ?>, rodzaj paliwa: <?= e($dayOffer['paliwo']) ?></p> <h4>Cena: <?= e($dayOffer['cena']) ?></h4><?phpendif; ?> </section> <sectionid="featured"> <h2>Oferty Wyróżnione</h2> <divclass="offers"><?phpforeach ($featuredas$car) { showOffer($car, true); } ?> </div> </section> <sectionid="search"> <h2id="search-title">Wybierz markę</h2> <formmethod="post"> <selectname="brand"aria-labelledby="search-title"><?phpforeach ($brandsas$brand): ?> <option><?= e($brand) ?></option><?phpendforeach; ?> </select> <buttontype="submit">Wyszukaj</button> </form> <divclass="offers"><?phpforeach ($brandCarsas$car) { showOffer($car, false); } ?> </div> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> <p><ahref="http://firmy.pl/komis">Znajdź nas także</a></p> </footer></body></html>
Podgląd strony po tym kroku (KupAuto.php)
Krok 37: Lista pamięta wybraną markę
ok. 2 min
Polecenie z arkusza
Zwrócone zapytaniem nazwy marek samochodów są wyświetlone jako elementy listy rozwijalnej
Na obrazie 3 w liście stoi „Opel”, a pod spodem Ople. Po wysłaniu formularza strona ładuje się od nowa, więc lista sama by wróciła do „Audi”. Atrybut selected przy opcji równej $selectedBrand (porównanie ===, bez zamiany typów) zostawia w liście wybraną markę. Arkusz tego nie wymaga.
KupAuto.phpPHPzmienione: 1 linia
<?phpconst OFFER_OF_THE_DAY_ID = 10;functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES);}functionfetchRows(PDO $pdo, string $sql, array$params = []): array{$statement = $pdo->prepare($sql);$statement->execute($params);return$statement->fetchAll();}functionshowOffer(array$car, bool $showYear): void{?> <divclass="offer"> <imgsrc="<?= e($car['zdjecie']) ?>"alt="<?= e($car['model']) ?>"> <h4><?= e($car['nazwa']) ?><?= e($car['model']) ?></h4><?phpif ($showYear): ?> <p>Rocznik: <?= e($car['rocznik']) ?></p><?phpendif; ?> <h4>Cena: <?= e($car['cena']) ?></h4> </div><?php}$dayOffer = null;$featured = [];$brands = [];$brandCars = [];$selectedBrand = filter_input(INPUT_POST, 'brand');$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=kupauto;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 2$dayOffer = fetchRows($pdo,'SELECT model, rocznik, przebieg, paliwo, cena, zdjecie FROM samochody WHERE id = :id', ['id' => OFFER_OF_THE_DAY_ID] )[0] ?? null;// Skrypt 2: zapytanie 3$featured = fetchRows($pdo,'SELECT m.nazwa, s.model, s.rocznik, s.cena, s.zdjecie FROM samochody AS s JOIN marki AS m ON m.id = s.marki_id WHERE s.wyrozniony = 1 ORDER BY s.id LIMIT 4' );// Skrypt 3: zapytanie 1$brands = array_column(fetchRows($pdo, 'SELECT nazwa FROM marki'), 'nazwa');// Skrypt 4: tylko po wybraniu marki z listyif (is_string($selectedBrand) && in_array($selectedBrand, $brands, true)) {$brandCars = fetchRows($pdo,'SELECT m.nazwa, s.model, s.cena, s.zdjecie FROM samochody AS s JOIN marki AS m ON m.id = s.marki_id WHERE m.nazwa = :brand ORDER BY s.id', ['brand' => $selectedBrand] ); }$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać ofert. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Komis aut</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1><em>KupAuto!</em> Internetowy Komis Samochodowy</h1> </header> <main> <sectionid="day"><?phpif ($dayOffer): ?> <imgsrc="<?= e($dayOffer['zdjecie']) ?>"alt="oferta dnia"> <h4>Oferta Dnia: Toyota <?= e($dayOffer['model']) ?></h4> <p>Rocznik: <?= e($dayOffer['rocznik']) ?>, przebieg: <?= e($dayOffer['przebieg']) ?>, rodzaj paliwa: <?= e($dayOffer['paliwo']) ?></p> <h4>Cena: <?= e($dayOffer['cena']) ?></h4><?phpendif; ?> </section> <sectionid="featured"> <h2>Oferty Wyróżnione</h2> <divclass="offers"><?phpforeach ($featuredas$car) { showOffer($car, true); } ?> </div> </section> <sectionid="search"> <h2id="search-title">Wybierz markę</h2> <formmethod="post"> <selectname="brand"aria-labelledby="search-title"><?phpforeach ($brandsas$brand): ?> <option<?= $brand === $selectedBrand ? ' selected' : ''?>><?= e($brand) ?></option><?phpendforeach; ?> </select> <buttontype="submit">Wyszukaj</button> </form> <divclass="offers"><?phpforeach ($brandCarsas$car) { showOffer($car, false); } ?> </div> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> <p><ahref="http://firmy.pl/komis">Znajdź nas także</a></p> </footer></body></html>
Podgląd strony po tym kroku (KupAuto.php)
Krok 38: Komunikat, gdy baza nie odpowiada; zamknięcie połączenia
Kryteria:
R.5.2 Ostatnią operacją na bazie jest jej zamknięcie
ok. 2 min
Polecenie z arkusza
Na końcu jest zamykane połączenie z serwerem.
Połączenie zamyka $pdo = null zaraz po czterech zapytaniach - ostatnia operacja na bazie (R.5.2); żadne zapytanie go nie trzyma, bo każde żyło tylko wewnątrz fetchRows(). Na początku <main> akapit z $dbError, gdy catch coś do niego wpisał. Sprawdzone uruchomieniem z błędną nazwą bazy: strona się wyświetla z komunikatem „Nie udało się pobrać ofert…”, bez bloków ofert, bez nazwy bazy i kodu błędu SQL.
KupAuto.phpPHPzmienione: 3 linie
<?phpconst OFFER_OF_THE_DAY_ID = 10;functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES);}functionfetchRows(PDO $pdo, string $sql, array$params = []): array{$statement = $pdo->prepare($sql);$statement->execute($params);return$statement->fetchAll();}functionshowOffer(array$car, bool $showYear): void{?> <divclass="offer"> <imgsrc="<?= e($car['zdjecie']) ?>"alt="<?= e($car['model']) ?>"> <h4><?= e($car['nazwa']) ?><?= e($car['model']) ?></h4><?phpif ($showYear): ?> <p>Rocznik: <?= e($car['rocznik']) ?></p><?phpendif; ?> <h4>Cena: <?= e($car['cena']) ?></h4> </div><?php}$dayOffer = null;$featured = [];$brands = [];$brandCars = [];$selectedBrand = filter_input(INPUT_POST, 'brand');$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=kupauto;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 2$dayOffer = fetchRows($pdo,'SELECT model, rocznik, przebieg, paliwo, cena, zdjecie FROM samochody WHERE id = :id', ['id' => OFFER_OF_THE_DAY_ID] )[0] ?? null;// Skrypt 2: zapytanie 3$featured = fetchRows($pdo,'SELECT m.nazwa, s.model, s.rocznik, s.cena, s.zdjecie FROM samochody AS s JOIN marki AS m ON m.id = s.marki_id WHERE s.wyrozniony = 1 ORDER BY s.id LIMIT 4' );// Skrypt 3: zapytanie 1$brands = array_column(fetchRows($pdo, 'SELECT nazwa FROM marki'), 'nazwa');// Skrypt 4: tylko po wybraniu marki z listyif (is_string($selectedBrand) && in_array($selectedBrand, $brands, true)) {$brandCars = fetchRows($pdo,'SELECT m.nazwa, s.model, s.cena, s.zdjecie FROM samochody AS s JOIN marki AS m ON m.id = s.marki_id WHERE m.nazwa = :brand ORDER BY s.id', ['brand' => $selectedBrand] ); }$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać ofert. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Komis aut</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1><em>KupAuto!</em> Internetowy Komis Samochodowy</h1> </header> <main><?phpif ($dbError !== ''): ?> <p><?= e($dbError) ?></p><?phpendif; ?> <sectionid="day"><?phpif ($dayOffer): ?> <imgsrc="<?= e($dayOffer['zdjecie']) ?>"alt="oferta dnia"> <h4>Oferta Dnia: Toyota <?= e($dayOffer['model']) ?></h4> <p>Rocznik: <?= e($dayOffer['rocznik']) ?>, przebieg: <?= e($dayOffer['przebieg']) ?>, rodzaj paliwa: <?= e($dayOffer['paliwo']) ?></p> <h4>Cena: <?= e($dayOffer['cena']) ?></h4><?phpendif; ?> </section> <sectionid="featured"> <h2>Oferty Wyróżnione</h2> <divclass="offers"><?phpforeach ($featuredas$car) { showOffer($car, true); } ?> </div> </section> <sectionid="search"> <h2id="search-title">Wybierz markę</h2> <formmethod="post"> <selectname="brand"aria-labelledby="search-title"><?phpforeach ($brandsas$brand): ?> <option<?= $brand === $selectedBrand ? ' selected' : ''?>><?= e($brand) ?></option><?phpendforeach; ?> </select> <buttontype="submit">Wyszukaj</button> </form> <divclass="offers"><?phpforeach ($brandCarsas$car) { showOffer($car, false); } ?> </div> </section> </main> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> <p><ahref="http://firmy.pl/komis">Znajdź nas także</a></p> </footer></body></html>
Podgląd strony po tym kroku (KupAuto.php)
Etap 5: Porównanie z wersją podstawową
Krok 39 · po tym etapie masz 41 z 41 kryteriów (pkt)
Krok 39: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 2 min
Obie wersje dają komplet 41 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
bloki ofert w rzędzie
display: inline-block + vertical-align: top
kontener .offers z display: flex; flex-wrap: wrap
blok oferty dnia
stała wysokość trzyma pływające zdjęcie
dodatkowo display: flow-root
kolory
wpisane w każdej regule
zmienne CSS w :root
zapytanie 3
LIMIT 4 bez sortowania
ORDER BY s.id LIMIT 4, aliasy tabel
baza
mysqli_connect, mysqli_fetch_row, $car[4]
PDO, fetchRows() z parametrami, $car['zdjecie']
Skrypt 4
marka wklejona do SQL (w dobrych praktykach zapytanie przygotowane)
biała lista z zapytania 1 + parametr :brand
blok oferty
dwie kopie echo (w dobrych praktykach funkcja)
funkcja-szablon showOffer($car, $showYear) od początku
gdzie PHP
cztery bloki w środku HTML-a
dane na górze pliku, połączenie zamknięte przed HTML-em
Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i jest szybsza do napisania - każdy skrypt stoi tam, gdzie pojawia się jego wynik. Nowoczesna wymaga kilku minut więcej, ale skrypt formularza jest odporny na spreparowane dane, a wygląd bloku oferty zmienia się w jednym miejscu. Formularz tylko wyszukuje, więc token CSRF nie jest tu potrzebny - ma sens przy formularzach, które coś zapisują.
Krok 40 · po tym etapie masz 41 z 41 kryteriów (pkt)
Krok 40: 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 przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana przez http://localhost/.... W folderze z numerem zdającego: KupAuto.php, styl.css, kwerendy.txt, import.png, kw1.jpg - kw4.jpg, przeglądarka.txt i zdjęcia z archiwum (w tym przeskalowany ToyotaYaris.jpg). Nagraj płytę, sprawdź jej odczyt i opisz ją 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.