INF.03-01-24.06-SGCzerwiec 2024 · 32 kroki · ok. 68 min · 35 kryteriów z klucza
Rozwiązanie krok po kroku: Witryna o motocyklach (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza motory z czterema zapytaniami, przeskalowane zdjęcie i strona motor.php z listą definicji wycieczek, licznikiem i motocyklem prześwitującym przez półprzezroczyste bloki - i tak samo spełnia każde kryterium klucza. Inna jest droga:
siatka CSS na całej stronie: grid-template-areas stawia blok lewy na dwa wiersze, a oba bloki prawe jeden pod drugim obok niego - wprost, a nie dzięki temu, że drugi blok „nie mieści się” w linii i opada; szerokości 75% i 15% stoją w grid-template-columns (klucz R.4.4 przyjmuje je wprost),
kolory w zmiennych CSS: dwa półprzezroczyste tła i kolor cienia zapisane raz, w :root,
PHP na PDO: oba zapytania na górze pliku, połączenie zamknięte, zanim zacznie się HTML, lista wypisywana pętlą foreach w szablonie,
bezpieczeństwo: escape() przy każdej wartości z bazy, rawurlencode() dla nazwy pliku w adresie, try/catch przy awarii bazy,
zapytanie 2 z aliasami tabel (w, z) - przy każdej kolumnie widać, z której tabeli pochodzi.
Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafiki pochodzą z prawdziwego archiwum pliki1, 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.
Jak liczą się punkty. Klucz ma 35 kryteriów (R.1.1 - R.5.8), każde za 1 punkt. Próg zdania to 75%, czyli 27 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 35 kryteriów (pkt)
Krok 1: Baza danych motory
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie motory, z zestawem polskich znaków (np. utf8_unicode_ci).
Archiwum pliki1.zip rozpakuj hasłem ^moTocyKle^ (daszki na początku i na końcu, wielkość liter ma znaczenie) do folderu z numerem zdającego. W phpMyAdmin Nowa → motory, porównywanie utf8_unicode_ci, albo zapytaniem:
CREATE DATABASE motory CHARACTER SET utf8 COLLATE utf8_unicode_ci;
Typowe błędy
Nazwa bazy to motory, nie motocykle - skrypty łączą się z bazą 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 w relacji 1..n (obraz 1): zdjecia (5 zdjęć; zrodlo to nazwa pliku bez rozszerzenia, np. droga1) i wycieczki (10 tras z kluczem obcym zdjecia_id). Jedno zdjęcie może ilustrować kilka wycieczek - stąd „jeden do wielu”.
Krok 3: Zrzut ekranu import.png
Kryteria:
R.1.1 Wykonano import tabel do bazy danych motory 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ń. Uwaga na formaty w tym arkuszu: import w PNG (import.png), zrzuty zapytań w JPEG (kw1.jpg - kw4.jpg).
Typowe błędy
import.jpg zamiast import.png to strata R.1.1 - klucz podaje format wprost.
Krok 4: Zapytanie 1: wycieczki z Muszyny i Wieliczki
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 wycieczek, których początek jest w Muszynie, Wieliczce. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa FROM wycieczki WHERE poczatek="Muszyna" OR poczatek="Wieliczka"; oraz wynik działania udokumentowano zrzutem, na którym są widoczne dokładnie dwa rekordy: Dolina Popradu i Alter-Zakopianka
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie nazwy wycieczek, których początek jest w Muszynie, Wieliczce.
IN (...) sprawdza, czy wartość jest na liście - to krótszy zapis dwóch warunków z OR (z przykładu w kluczu) i łatwiej go rozbudować o trzecie miasto. W bazie miasta są w mianowniku („Muszyna”), choć arkusz odmienia je w zdaniu. Wynik: Dolina Popradu i Alter-Zakopianka, jak w kluczu. Zapytanie zapisz w kwerendy.txt, zrzut jako kw1.jpg.
Typowe błędy
AND zamiast OR (albo zamiast IN) szuka trasy, która zaczyna się w dwóch miastach naraz - wynik pusty.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT nazwaFROM wycieczkiWHERE poczatek IN ('Muszyna', 'Wieliczka');
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: wycieczki ze źródłem zdjęcia
Kryteria:
R.1.4 Wykonano zapytanie 2 wybierające jedynie pola nazwa, opis i poczatek z tabeli wycieczki oraz odpowiadające im pole zrodlo z tabeli zdjecia . W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, opis, poczatek, zrodlo FROM wycieczki JOIN zdjecia ON zdjecia_id = zdjecia.id; (możliwe również INNER JOIN lub bez JOIN i porównanie kluczy po WHERE) oraz wynik działania udokumentowano zrzutem, na którym jest widocznych dokładnie 10 rekordów, jedynie pola nazwa, opis, poczatek, zrodlo
ok. 2 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie pola nazwa, opis i poczatek z tabeli wycieczki oraz odpowiadające im pole zrodlo z tabeli zdjecia. Należy posłużyć się relacją.
Aliasy tabel w (wycieczki) i z (zdjęcia): przy każdej kolumnie widać, skąd pochodzi, a ON z.id = w.zdjecia_id pokazuje parę kluczy relacji - klucz główny zdjęcia i klucz obcy wycieczki. Kolumna id jest w obu tabelach, więc bez przedrostka byłaby niejednoznaczna. Wynik: 10 wycieczek z czterema kolumnami. Zrzut: kw2.jpg. To zapytanie wyśle skrypt 1.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT nazwaFROM wycieczkiWHERE poczatek IN ('Muszyna', 'Wieliczka');-- Zapytanie 2SELECT w.nazwa, w.opis, w.poczatek, z.zrodloFROM wycieczki AS wJOIN zdjecia AS z ON z.id = w.zdjecia_id;
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: liczba wycieczek
Kryteria:
R.1.5 Wykonano zapytanie 3 zliczające liczbę wycieczek wpisanych do tabeli wycieczki . W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT COUNT(*) FROM wycieczki; (lub dowolne pole zamiast *) oraz wynik działania udokumentowano zrzutem, na którym jest widoczna wartość 10
ok. 1 min
Polecenie z arkusza
Zapytanie 3: zliczające liczbę wycieczek wpisanych do tabeli wycieczki.
COUNT(*) liczy wiersze tabeli; alias liczba_wycieczek nadaje kolumnie wyniku czytelną nazwę zamiast COUNT(*). Wynik: 10. Zrzut: kw3.jpg. To zapytanie wyśle skrypt 2.
kwerendy.txtTekstzmienione: 4 linie
-- Zapytanie 1SELECT nazwaFROM wycieczkiWHERE poczatek IN ('Muszyna', 'Wieliczka');-- Zapytanie 2SELECT w.nazwa, w.opis, w.poczatek, z.zrodloFROM wycieczki AS wJOIN zdjecia AS z ON z.id = w.zdjecia_id;-- Zapytanie 3SELECT COUNT(*) AS liczba_wycieczekFROM wycieczki;
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: nowa kolumna ocena
Kryteria:
R.1.6 Wykonano zapytanie 4 modyfikujące strukturę tabeli wycieczki . Dodana jest kolumna ocena typu całkowitego. W pliku z kwerendami lub na zrzucie istnieje zapis np.: ALTER TABLE wycieczki ADD COLUMN ocena INT; (słowo COLUMN jest opcjonalne, dowolny typ całkowity) oraz wynik działania udokumentowano zrzutem, na którym jest widoczny komunikat o poprawnym wykonaniu zapytania lub struktura tabeli z dodanym polem ocena typu całkowitego
ok. 2 min
Polecenie z arkusza
Zapytanie 4: modyfikujące strukturę tabeli wycieczki, poprzez dodanie kolumny ocena typu całkowitego.
Strukturę zmienia ALTER TABLE ... ADD COLUMN (UPDATE zmienia tylko dane). Typ TINYINT UNSIGNED - liczba całkowita od 0 do 255 w jednym bajcie: na ocenę w rodzaju 1-5 wystarczy z zapasem, a klucz przyjmuje dowolny typ całkowity. Zapytanie nie zwraca wierszy; zrzut kw4.jpg pokazuje komunikat albo strukturę tabeli z nową kolumną.
Typowe błędy
Drugie wykonanie kończy się błędem „Duplicate column name” - kolumna już istnieje.
kwerendy.txtTekstzmienione: 4 linie
-- Zapytanie 1SELECT nazwaFROM wycieczkiWHERE poczatek IN ('Muszyna', 'Wieliczka');-- Zapytanie 2SELECT w.nazwa, w.opis, w.poczatek, z.zrodloFROM wycieczki AS wJOIN zdjecia AS z ON z.id = w.zdjecia_id;-- Zapytanie 3SELECT COUNT(*) AS liczba_wycieczekFROM wycieczki;-- Zapytanie 4ALTER TABLE wycieczkiADD COLUMN ocena TINYINT UNSIGNED;
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 8-15 · po tym etapie masz 14 z 35 kryteriów (pkt)
Krok 8: droga1.jpg: szerokość 640 px z zachowaniem proporcji
Kryteria:
R.2.1 Plik droga1.jpg przeskalowano z zachowaniem proporcji do szerokości 640 px, odpowiadająca temu wysokość wynosi 480 px (± 5 px)
ok. 2 min
Polecenie z arkusza
Przygotowanie grafiki: Plik droga1.jpg, wypakowany z archiwum, należy przeskalować z zachowaniem proporcji tak, aby jego szerokość wynosiła dokładnie 640 px.
Zdjęcie ma 1000 × 750 px. W GIMP-ie Obraz → Skaluj obraz, łańcuch proporcji zamknięty, szerokość 640 - wysokość 750 × 640 / 1000 = 480 px (R.2.1 sprawdza 480 ± 5). Eksportuj jakodroga1.jpg, nadpisując plik.
Typowe błędy
Otwarty łańcuch i 640 × 640 zniekształca zdjęcie.
Podgląd strony po tym kroku
Krok 9: Plik motor.php: HTML5, język, kodowanie
Kryteria:
R.2.2 Witrynę internetową zapisano w pliku motor.php, oraz zapisano jawnie standard kodowania polskich znaków i deklarację HTML5 <!DOCTYPE HTML> oraz zadeklarowano język witryny polski np.<HTML lang="pl">. Strona zawiera sekcje head i body
ok. 2 min
Polecenie z arkusza
Cechy witryny: Składa się ze strony o nazwie motor.php zapisanej w języku HTML5. Ustawiony język zawartości strony na polski. Jawnie zastosowany właściwy standard kodowania polskich znaków.
Plik motor.php w folderze z rozpakowanymi plikami: <!DOCTYPE html>, <html lang="pl">, <head> z <meta charset="UTF-8"> jako pierwszym elementem i <body> (R.2.2 wymaga obu sekcji).
R.3.3 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
Tytuł strony widoczny na karcie przeglądarki: „Motocykle”. Arkusz stylów w pliku o nazwie styl.css prawidłowo połączony z kodem strony.
<title> (R.2.3), <link> do styl.css (R.3.3) i viewport, żeby telefon nie pomniejszał strony.
R.2.8 W sekcji body, poza blokami umieszczono obraz moto r.png wraz z tekstem alternatywnym „motocykl”, sprawdzić w kodzie źródłowym
ok. 1 min
Polecenie z arkusza
Umieszczony poza blokami obraz motor.png o tekście alternatywnym „motocykl”.
Obraz stoi bezpośrednio w <body>, przed blokami - nie należy do żadnego z nich (R.2.8). Identyfikator bike pozwoli nadać styl „jedynie dla obrazu motor.png”. Na razie zajmuje pełne 1280 px; pod bloki przeniesie go CSS.
R.2.4 Układ strony zdefiniowano dzieląc ją na bloki: baner, lewy, dwa prawe 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: na górze baner, poniżej trzy bloki: lewy, i dwa prawe, 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 to <header>, blok lewy z główną treścią (lista wycieczek) - <main>, dwa bloki prawe z dodatkami - dwa <aside>, stopka - <footer> (R.2.4). Identyfikatory buy i stats (od treści, nie od położenia) rozróżnią bloki prawe w siatce.
Krok 13: Baner i blok lewy: nagłówki, lista definicji
ok. 2 min
Polecenie z arkusza
Zawartość banera: nagłówek pierwszego stopnia o treści „Motocykle - moja pasja”. Zawartość bloku lewego: Nagłówek drugiego stopnia o treści: „Gdzie pojechać?”. Lista definicji (Description List) wypełniona przez skrypt 1.
<h1> w banerze, <h2> i pusta lista definicji<dl> w bloku lewym. W niej staną pary: termin <dt> (nazwa i początek trasy) i opis <dd> - wypisze je skrypt 1.
R.2.7 W pierwszym bloku prawym umieszczono jedną listę numerowaną z pięcioma elementami, zastosowano znaczniki <ol>, <li>
ok. 2 min
Polecenie z arkusza
Zawartość pierwszego bloku prawego: Nagłówek drugiego stopnia o treści: „Co kupić?”. Lista numerowana (uporządkowana) o 5 elementach: „Honda CBR125R”, „Yamaha YBR125”, „Honda VFR800i”, „Honda CBR1100XX”, „BMW R1200GS LC”.
Numerowana = <ol>, pięć <li> w kolejności z arkusza (R.2.7).
R.2.5 W banerze zapisano nagłówek h1: "Motocykle - moja pasja", w bloku lewym h2: "Gdzie pojechać?", w blokach prawych h2: "Co kupić?" i "Statystyki" oraz zapisano w drugim bloku prawym trzy paragrafy i w stopce paragraf z numerem zdającego
ok. 2 min
Polecenie z arkusza
Zawartość drugiego bloku prawego: Nagłówek drugiego stopnia o treści: „Statystyki”, paragraf (akapit) o treści „Wpisanych wycieczek: ”, dalej efekt działania skryptu 2, paragraf o treści „Użytkowników forum: 200”, paragraf o treści „Przesłanych zdjęć: 1300”. Zawartość stopki: akapit o treści „Stronę wykonał: ”, dalej wstawiony numer zdającego.
Trzy akapity; w pierwszym po dwukropku liczbę wstawi skrypt 2. W stopce akapit: na egzaminie po „Stronę wykonał:” wpisujesz swój numer zdającego (PESEL), tu stoi autor rozwiązania. Komplet nagłówków i akapitów to R.2.5.
Domyślne formatowanie wszystkich selektorów: krój czcionki Verdana.
Dwa półprzezroczyste tła i kolor cienia z arkusza zapisane raz, w zmiennych CSS na :root - każde występuje w dwóch albo trzech blokach. Selektor * z Verdaną i zapasowym krojem bezszeryfowym (R.4.1).
R.4.4 Ustawiono szerokość: bloku lewego 75%, obu prawych 15% (width lub grid-template- columns lub flex) oraz jedynie dla obrazu motor.png 70%
R.4.8 Dla obrazu ustawiono właściwości display: block; position: absolute; left: 15% oraz obraz znajduje się za blokami strony
ok. 3 min
Polecenie z arkusza
Jedynie dla obrazu motor.png: wyświetlanie blokowe, pozycjonowanie na stronie: element przesunięty 15% od lewej strony względem swojego rodzica, szerokość 70%, element jest zakryty przez bloki strony (znajduje się za blokami strony).
Tu nie ma wyboru techniki: klucz R.4.8 ocenia wprost display: block; position: absolute; left: 15%.
position: absolute wyjmuje obraz z układu - siatka, którą za chwilę dostanie <body>, nie zrobi mu miejsca ani komórki. left: 15% i width: 70% liczą się od bloku zawierającego: najbliższego przodka z ustawionym position, a gdy takiego nie ma - od okna. Arkusz upraszcza to do „względem rodzica”,
z-index: -1 kładzie obraz pod bloki (R.4.8).
Selektor #bike dotyczy tylko tego obrazu (R.4.4 - szerokość 70% „jedynie dla obrazu”).
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu (prawidłowo zastosowano właściwości CSS decydujące o układzie strony np. float i clear albo display flex albo grid)
ok. 3 min
Polecenie z arkusza
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.
<body> staje się siatką. grid-template-areas rysuje obraz 2 słowami: baner na dwie kolumny, potem dwa wiersze, w których main zajmuje lewą kolumnę w obu (blok lewy jest wysoki na dwa bloki prawe), a buy i stats stoją po prawej jeden pod drugim, na dole stopka (R.3.1). Każdy blok trafia w swoje pole przez grid-area.
grid-template-columns: 75% 15% 1fr - pierwsze dwie kolumny to szerokości bloku lewego i prawych (klucz R.4.4 przyjmuje je w tej postaci), a trzecia, 1fr, zabiera resztę szerokości. Dzięki niej baner i stopka rozciągają się na całą stronę (w ich wierszach nazwa pola powtarza się trzy razy), a kropka w pozostałych wierszach oznacza puste pole. align-items: start - bloki nie rozciągają się do wysokości wiersza, tylko mają swoje 600 i 300 px. W wersji podstawowej drugi blok prawy trafiał pod pierwszy dlatego, że nie mieścił się w linii obok bloku lewego; tu jego miejsce jest zapisane wprost i nie zależy od sumy szerokości.
Dla banera: kolor tła RGB z wartościami 60, 120, 160 i przezroczystością 0.9; biały kolor czcionki, rozmiar czcionki 150%, marginesy wewnętrzne 10 px.
rgba(60, 120, 160, 0.9) - trzy składowe koloru i krycie 0.9: przez tło baneru prześwituje kawałek motocykla, a tekst zostaje w pełni biały (opacity zrobiłby przezroczystym cały blok razem z tekstem). Klucz R.4.2 podaje zapis rgba(...), więc zostaje on, choć nowa składnia CSS pozwala pisać rgb(60 120 160 / 0.9).
R.4.3 Ustawiono wysokość bloku lewego na 600px, obu prawych na 300px
R.4.7 Ustawiono dla bloku lewego automatyczne paski przewijania (overflow: auto lub scroll) oraz rozmiar czcionki 150% dla banera i 120% dla bloku lewego
R.4.9 Ustawiono przynajmniej dla jednego bloku (lewy, lub dowolny prawy) cień bloku o przesunięciu 10 px w obu osiach, rozmyciu 7 px i kolorze DimGray (box-shadow: 10px 10px 7px DimGray;)
ok. 3 min
Polecenie z arkusza
Dla bloku lewego: kolor tła RGB z wartościami 0, 100, 0 i przezroczystością 0.6; marginesy zewnętrzne 2%, szerokość 75%, wysokość 600 px, rozmiar czcionki 120%, paski przewijania widoczne tylko gdy zawartość nie mieści się w bloku, cień bloku o przesunięciu 10 px w obu osiach, rozmyciu 7 px i kolorze DimGray.
overflow: auto - pasek przewijania tylko wtedy, gdy dziesięć opisów nie zmieści się w 600 px (R.4.7),
box-shadow: 10px 10px 7px - przesunięcie w poziomie, w pionie, rozmycie, kolor (R.4.9),
margin: 2% - uwaga na siatkę: procentowy margines elementu siatki liczy się od szerokości jego pola (tu kolumny 75%), a nie całej strony, więc odstęp wychodzi trochę mniejszy niż w wersji podstawowej. Zapis i kryterium R.4.6 się nie zmieniają.
Szerokość 75% daje kolumna siatki - dodatkowe width: 75% liczyłoby się od kolumny i zwęziło blok do 3/4 z 3/4.
Wspólne dla bloków prawych: kolor tła RGB z wartościami 0, 100, 0 i przezroczystością 0.6; marginesy zewnętrzne 1%, szerokość 15%, wysokość 300 px, cień bloku o przesunięciu 10 px w obu osiach, rozmyciu 7 px i kolorze DimGray.
Selektor aside obejmuje oba bloki prawe naraz - tak „wspólne” przekłada się na CSS; identyfikatory służą tylko do wskazania pola siatki. Szerokość 15% daje druga kolumna siatki. Oba bloki mają 300 px, a z marginesami zajmują razem tyle, ile blok lewy obok - jak na obrazie 2.
R.4.2 Ustawiono kolor tła rgba(60, 120, 160, 0.9) dla banera i stopki, rgba(0, 100, 0, 0.6) dla bloków lewego i obu prawych oraz biały kolor czcionki dla banera, stopki, odnośników, lub kolor tła bloków ustawiony za pomocą rgb i opacity
R.4.6 Ustawiono marginesy wewnętrzne (padding) 10px dla banera i stopki oraz marginesy zewnętrzne (margin) 2% dla bloku lewego, 1% dla obu bloków prawych
ok. 1 min
Polecenie z arkusza
Dla stopki: kolor tła RGB z wartościami 60, 120, 160 i przezroczystością 0.9; biały kolor czcionki, marginesy wewnętrzne 10 px.
Te same zmienne co baner, bez powiększenia czcionki. Tła i biały tekst bloków to R.4.2, marginesy wewnętrzne baneru i stopki oraz zewnętrzne bloków - R.4.6, wysokości 600 i 300 px - R.4.3.
R.4.5 Ustawiono dla selektorów h1 i h2 wyrównanie tekstu do środka i czcionkę pochyloną
R.4.10 Ustawiono dla terminu w liście definicji biały kolor czcionki, padding: 1% dla elementu definicji w liście oraz gdy kursor znajdzie się na elemencie definicji, jego kolor tła zmienia się na LightGreen
ok. 2 min
Polecenie z arkusza
Dla selektora nagłówka pierwszego i drugiego stopnia: wyrównanie tekstu do środka, czcionka pochylona. Wspólne dla odnośnika i terminu w liście definicji: biały kolor czcionki. Dla elementu definicji w liście: marginesy wewnętrzne 1%. Gdy kursor znajdzie się na elemencie definicji w liście, jego kolor tła zmienia się na LightGreen (widoczne na obrazie 2).
Arkusz każe użyć selektorów znaczników h1, h2 (R.4.5 nie uzna klasy). a, dt - odnośniki i terminy białe, dd z marginesem wewnętrznym 1%, a dd:hover podświetla opis pod kursorem (R.4.10). Lista jest jeszcze pusta - wypełni ją skrypt 1.
Kroki 24-30 · po tym etapie masz 35 z 35 kryteriów (pkt)
Krok 24: Połączenie PDO na górze pliku
Kryteria: ,
R.5.1 Skrypt zawiera instrukcje połączenia z serwerem bazy danych i wyboru bazy motory
R.5.2 Ostatnią operacją na bazie jest jej zamknięcie
ok. 4 min
Polecenie z arkusza
Napisane w języku PHP. Należy stosować znaczące nazewnictwo wszystkich zmiennych lub funkcji. Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie motory. Na końcu jest zamykane połączenie z serwerem.
Praca z bazą stoi przed<!DOCTYPE html>: najpierw dane, potem HTML, który je tylko wypisuje. Połączenie przez PDO (klucz R.5 przyjmuje MySQLi i PDO):
R.5.3 Skrypt 1 zawiera instrukcje wysyłające do bazy danych zapytanie 2 lub skrypt 2: zapytanie 3
R.5.8 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.
$pdo->query(...) wysyła zapytanie 2 - te same linie co w kwerendy.txt - a ->fetchAll() odbiera wszystkie 10 wycieczek do tablicy $trips (R.5.3). W zapytaniu nie ma wartości od użytkownika, więc query wystarcza. Nazwy zmiennych po angielsku mówią, co przechowują (R.5.8).
motor.phpPHPzmienione: 8 linii
<?phpfunctionescape(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$trips = [];$tripCount = 0;$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=motory;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: wycieczki ze zdjęciami$trips = $pdo->query('SELECT w.nazwa, w.opis, w.poczatek, z.zrodlo FROM wycieczki AS w JOIN zdjecia AS z ON z.id = w.zdjecia_id' )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać wycieczek. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Motocykle</title> <linkrel="stylesheet"href="styl.css"></head><body> <imgid="bike"src="motor.png"alt="motocykl"> <header> <h1>Motocykle - moja pasja</h1> </header> <main> <h2>Gdzie pojechać?</h2> <dl> </dl> </main> <asideid="buy"> <h2>Co kupić?</h2> <ol> <li>Honda CBR125R</li> <li>Yamaha YBR125</li> <li>Honda VFR800i</li> <li>Honda CBR1100XX</li> <li>BMW R1200GS LC</li> </ol> </aside> <asideid="stats"> <h2>Statystyki</h2> <p>Wpisanych wycieczek: </p> <p>Użytkowników forum: 200</p> <p>Przesłanych zdjęć: 1300</p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Krok 26: Skrypt 1: termin z nazwą i początkiem trasy
Kryteria:
R.5.4 Skrypt 1 wyświetla tekst: „<nazwa>, rozpoczyna się w <poczatek>,”, gdzie wartości w <> są pobrane z bazy danych
ok. 3 min
Polecenie z arkusza
Zwrócone zapytaniem wartości są wyświetlane jako poszczególne terminy i elementy definicji listy według wzoru: Termin listy: „<nazwa>, rozpoczyna się w <poczatek>, ”.
W <dl> pętla foreach ($trips as $trip): w składni alternatywnej - HTML terminu zostaje zwykłym HTML-em, a wartości wchodzą przez <?= escape(...) ?> (skrót od <?php echo ... ?>). Wzór z arkusza łącznie z przecinkiem i spacją na końcu - za nimi dojdzie odnośnik (R.5.4). Podgląd: dziesięć terminów.
motor.phpPHPzmienione: 3 linie
<?phpfunctionescape(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$trips = [];$tripCount = 0;$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=motory;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: wycieczki ze zdjęciami$trips = $pdo->query('SELECT w.nazwa, w.opis, w.poczatek, z.zrodlo FROM wycieczki AS w JOIN zdjecia AS z ON z.id = w.zdjecia_id' )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać wycieczek. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Motocykle</title> <linkrel="stylesheet"href="styl.css"></head><body> <imgid="bike"src="motor.png"alt="motocykl"> <header> <h1>Motocykle - moja pasja</h1> </header> <main> <h2>Gdzie pojechać?</h2> <dl><?phpforeach ($tripsas$trip): ?> <dt><?= escape($trip['nazwa']) ?>, rozpoczyna się w <?= escape($trip['poczatek']) ?>, </dt><?phpendforeach; ?> </dl> </main> <asideid="buy"> <h2>Co kupić?</h2> <ol> <li>Honda CBR125R</li> <li>Yamaha YBR125</li> <li>Honda VFR800i</li> <li>Honda CBR1100XX</li> <li>BMW R1200GS LC</li> </ol> </aside> <asideid="stats"> <h2>Statystyki</h2> <p>Wpisanych wycieczek: </p> <p>Użytkowników forum: 200</p> <p>Przesłanych zdjęć: 1300</p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (motor.php)
Krok 27: Skrypt 1: odnośnik „zobacz zdjęcie” z rozszerzeniem .jpg
Kryteria: ,
R.3.2 Zapisano na stronie przynajmniej jeden odnośnik o treści „zobacz zdjęcie", prowadzący do dowolnego zdjęcia droga1.jpg ÷ droga5.jpg
R.5.5 Skrypt1 wyświetla odnośnik, którego kliknięcie powoduje otwarcie zdjęcia. Nazwa zdjęcia została pobrana z bazy, zostało dopisane rozszerzenie .jpg
ok. 3 min
Polecenie z arkusza
Następnie odnośnik o treści „zobacz zdjęcie”, prowadzący do zdjęcia, którego nazwa została zwrócona zapytaniem.
Pole zrodlo ma samą nazwę (droga1), więc rozszerzenie .jpg stoi w HTML-u zaraz za wartością (R.5.5, R.3.2). Nazwa przechodzi przez dwie funkcje, każda od czego innego:
rawurlencode() - zapisuje nazwę jako bezpieczny fragment adresu: spacja staje się %20, polska litera - kodem. Dziś nazwy są proste, ale plik „droga górska” bez tego dałby zepsuty odnośnik,
escape() - zabezpiecza atrybut href w HTML-u.
W podglądzie kliknięcia są zablokowane.
Typowe błędy
href bez .jpg prowadzi do nieistniejącego pliku droga1 - przeglądarka pokaże błąd 404.
motor.phpPHPzmienione: 3 linie
<?phpfunctionescape(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$trips = [];$tripCount = 0;$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=motory;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: wycieczki ze zdjęciami$trips = $pdo->query('SELECT w.nazwa, w.opis, w.poczatek, z.zrodlo FROM wycieczki AS w JOIN zdjecia AS z ON z.id = w.zdjecia_id' )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać wycieczek. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Motocykle</title> <linkrel="stylesheet"href="styl.css"></head><body> <imgid="bike"src="motor.png"alt="motocykl"> <header> <h1>Motocykle - moja pasja</h1> </header> <main> <h2>Gdzie pojechać?</h2> <dl><?phpforeach ($tripsas$trip): ?> <dt><?= escape($trip['nazwa']) ?>, rozpoczyna się w <?= escape($trip['poczatek']) ?>, <ahref="<?= escape(rawurlencode($trip['zrodlo'])) ?>.jpg">zobacz zdjęcie</a> </dt><?phpendforeach; ?> </dl> </main> <asideid="buy"> <h2>Co kupić?</h2> <ol> <li>Honda CBR125R</li> <li>Yamaha YBR125</li> <li>Honda VFR800i</li> <li>Honda CBR1100XX</li> <li>BMW R1200GS LC</li> </ol> </aside> <asideid="stats"> <h2>Statystyki</h2> <p>Wpisanych wycieczek: </p> <p>Użytkowników forum: 200</p> <p>Przesłanych zdjęć: 1300</p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (motor.php)
Krok 28: Skrypt 1: opis jako element definicji
Kryteria: ,
R.2.6 W bloku lewym umieszczono listę definicji, zastosowano znacznik <dl> oraz zagnieżdżony w nim przynajmniej jeden z <dt>, <dd> (listę wygenerowano skryptem lub w HTML)
R.5.6 Skrypt 1 wyświetla opis pobrany z bazy
ok. 1 min
Polecenie z arkusza
Element definicji listy: zwrócone zapytaniem pole opis.
Po terminie <dd> z opisem (R.5.6, R.2.6). Lista ma dziesięć par; opisy nie mieszczą się w 600 px bloku lewego, więc pojawia się pasek przewijania. Najedź kursorem na opis - podświetla się na LightGreen, jak na obrazie 2.
motor.phpPHPzmienione: 1 linia
<?phpfunctionescape(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$trips = [];$tripCount = 0;$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=motory;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: wycieczki ze zdjęciami$trips = $pdo->query('SELECT w.nazwa, w.opis, w.poczatek, z.zrodlo FROM wycieczki AS w JOIN zdjecia AS z ON z.id = w.zdjecia_id' )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać wycieczek. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Motocykle</title> <linkrel="stylesheet"href="styl.css"></head><body> <imgid="bike"src="motor.png"alt="motocykl"> <header> <h1>Motocykle - moja pasja</h1> </header> <main> <h2>Gdzie pojechać?</h2> <dl><?phpforeach ($tripsas$trip): ?> <dt><?= escape($trip['nazwa']) ?>, rozpoczyna się w <?= escape($trip['poczatek']) ?>, <ahref="<?= escape(rawurlencode($trip['zrodlo'])) ?>.jpg">zobacz zdjęcie</a> </dt> <dd><?= escape($trip['opis']) ?></dd><?phpendforeach; ?> </dl> </main> <asideid="buy"> <h2>Co kupić?</h2> <ol> <li>Honda CBR125R</li> <li>Yamaha YBR125</li> <li>Honda VFR800i</li> <li>Honda CBR1100XX</li> <li>BMW R1200GS LC</li> </ol> </aside> <asideid="stats"> <h2>Statystyki</h2> <p>Wpisanych wycieczek: </p> <p>Użytkowników forum: 200</p> <p>Przesłanych zdjęć: 1300</p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (motor.php)
Krok 29: Skrypt 2: liczba wycieczek w paragrafie
Kryteria:
R.5.7 Skrypt 2 wyświetla napis o treści "Wpisanych wycieczek:" wartość pobraną zapytaniem
ok. 2 min
Polecenie z arkusza
Skrypt 2: Wysyła do bazy danych zapytanie 3. Wartość zwrócona zapytaniem jest wyświetlana w paragrafie po słowach: „Wpisanych wycieczek: ”.
COUNT(*) zwraca jeden wiersz z jedną kolumną, więc zamiast fetchAll jest fetchColumn() - od razu sama wartość. (int) zamienia ją z napisu na liczbę: liczba nie wymaga escape(), bo nie zawiera znaków HTML. W akapicie <?= $tripCount ?> wypisuje 10 w tej samej linii co tekst (R.5.7).
motor.phpPHPzmienione: 4 linie
<?phpfunctionescape(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$trips = [];$tripCount = 0;$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=motory;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: wycieczki ze zdjęciami$trips = $pdo->query('SELECT w.nazwa, w.opis, w.poczatek, z.zrodlo FROM wycieczki AS w JOIN zdjecia AS z ON z.id = w.zdjecia_id' )->fetchAll();// Skrypt 2: liczba wycieczek$tripCount = (int) $pdo->query('SELECT COUNT(*) FROM wycieczki')->fetchColumn();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać wycieczek. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Motocykle</title> <linkrel="stylesheet"href="styl.css"></head><body> <imgid="bike"src="motor.png"alt="motocykl"> <header> <h1>Motocykle - moja pasja</h1> </header> <main> <h2>Gdzie pojechać?</h2> <dl><?phpforeach ($tripsas$trip): ?> <dt><?= escape($trip['nazwa']) ?>, rozpoczyna się w <?= escape($trip['poczatek']) ?>, <ahref="<?= escape(rawurlencode($trip['zrodlo'])) ?>.jpg">zobacz zdjęcie</a> </dt> <dd><?= escape($trip['opis']) ?></dd><?phpendforeach; ?> </dl> </main> <asideid="buy"> <h2>Co kupić?</h2> <ol> <li>Honda CBR125R</li> <li>Yamaha YBR125</li> <li>Honda VFR800i</li> <li>Honda CBR1100XX</li> <li>BMW R1200GS LC</li> </ol> </aside> <asideid="stats"> <h2>Statystyki</h2> <p>Wpisanych wycieczek: <?= $tripCount?></p> <p>Użytkowników forum: 200</p> <p>Przesłanych zdjęć: 1300</p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (motor.php)
Krok 30: Komunikat, gdy baza nie odpowiada
ok. 2 min
Polecenie z arkusza
Na końcu jest zamykane połączenie z serwerem.
Nad listą akapit z $dbError - tylko gdy catch coś do niego wpisał. Sprawdzone uruchomieniem z błędną nazwą bazy: strona się wyświetla, w bloku lewym stoi „Nie udało się pobrać wycieczek…”, licznik pokazuje 0, a w treści nie ma nazwy bazy ani kodu błędu SQL. W normalnym działaniu połączenie jest zamykane ($pdo = null) zaraz po obu skryptach, przed pierwszą linią HTML.
motor.phpPHPzmienione: 3 linie
<?phpfunctionescape(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$trips = [];$tripCount = 0;$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=motory;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: wycieczki ze zdjęciami$trips = $pdo->query('SELECT w.nazwa, w.opis, w.poczatek, z.zrodlo FROM wycieczki AS w JOIN zdjecia AS z ON z.id = w.zdjecia_id' )->fetchAll();// Skrypt 2: liczba wycieczek$tripCount = (int) $pdo->query('SELECT COUNT(*) FROM wycieczki')->fetchColumn();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać wycieczek. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Motocykle</title> <linkrel="stylesheet"href="styl.css"></head><body> <imgid="bike"src="motor.png"alt="motocykl"> <header> <h1>Motocykle - moja pasja</h1> </header> <main> <h2>Gdzie pojechać?</h2><?phpif ($dbError !== ''): ?> <p><?= escape($dbError) ?></p><?phpendif; ?> <dl><?phpforeach ($tripsas$trip): ?> <dt><?= escape($trip['nazwa']) ?>, rozpoczyna się w <?= escape($trip['poczatek']) ?>, <ahref="<?= escape(rawurlencode($trip['zrodlo'])) ?>.jpg">zobacz zdjęcie</a> </dt> <dd><?= escape($trip['opis']) ?></dd><?phpendforeach; ?> </dl> </main> <asideid="buy"> <h2>Co kupić?</h2> <ol> <li>Honda CBR125R</li> <li>Yamaha YBR125</li> <li>Honda VFR800i</li> <li>Honda CBR1100XX</li> <li>BMW R1200GS LC</li> </ol> </aside> <asideid="stats"> <h2>Statystyki</h2> <p>Wpisanych wycieczek: <?= $tripCount?></p> <p>Użytkowników forum: 200</p> <p>Przesłanych zdjęć: 1300</p> </aside> <footer> <p>Stronę wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (motor.php)
Etap 5: Porównanie z wersją podstawową
Krok 31 · po tym etapie masz 35 z 35 kryteriów (pkt)
Krok 31: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 2 min
Obie wersje dają komplet 35 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ
float: left bloków; drugi blok prawy „opada”, bo nie mieści się w linii
grid-template-areas: miejsce każdego bloku zapisane wprost
szerokości bloków
width: 75% i width: 15%
grid-template-columns: 75% 15%
kolory
wpisane w każdej regule
zmienne CSS w :root
zapytania
OR, JOIN ... ON zdjecia_id = zdjecia.id, INT
IN (...), aliasy w i z, TINYINT UNSIGNED
baza
mysqli_connect, mysqli_fetch_row, $row[3]
PDO z wyjątkami, fetchAll, fetchColumn, $trip['zrodlo']
gdzie PHP
rozproszone po HTML-u, połączenie zamykane po stopce
dane na górze pliku, połączenie zamknięte przed HTML-em
dane na stronie
wprost (w dobrych praktykach htmlspecialchars)
escape() przy każdej wartości, rawurlencode() w adresie
awaria bazy
biała strona z błędem
komunikat w bloku lewym, licznik 0
Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i jest szybsza do napisania. Nowoczesna oddziela pobieranie danych od wyglądu, a jej układ nie rozsypie się po zmianie marginesu czy szerokości - w wersji z float za szeroki blok prawy spadłby pod lewy. Obraz motocykla w obu wersjach jest pozycjonowany absolutnie, bo tego wymaga klucz.
Krok 32 · po tym etapie masz 35 z 35 kryteriów (pkt)
Krok 32: Plik przeglądarka.txt i nagranie płyty
ok. 5 min
Polecenie z arkusza
Po zakończeniu pracy utwórz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Umieść go w folderze z numerem zdającego. Nagraj płytę z rezultatami pracy. W folderze z numerem zdającego, powinny znajdować się pliki: droga1.jpg, droga2.jpg, droga3.jpg, droga4.jpg, droga5.jpg, import.png, kw1.jpg, kw2.jpg, kw3.jpg, kw4.jpg, kwerendy.txt, motor.php, motor.png, przeglądarka.txt, styl.css, ewentualnie inne przygotowane pliki.
W przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana przez http://localhost/.... Sprawdź listę plików z arkusza (w tym przeskalowany droga1.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.