INF.03-01-25.06-SGCzerwiec 2025 · 45 kroków · ok. 99 min · 41 kryteriów z klucza
Rozwiązanie krok po kroku: Ranking gier komputerowych (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza: portal z rankingiem gier, baza gry, strona gry.php w trzech kolumnach i cztery skrypty PHP. Obie wersje spełniają wszystkie 41 kryteriów klucza - różni je sposób pisania kodu, nie wynik.
Czym ta wersja różni się od podstawowej:
układ na siatce CSS (display: grid) pisany „od małego ekranu”: domyślnie sekcje stoją jedna pod drugą, a zapytanie @media (width > 800px) układa je w trzy kolumny 20% / 60% / 20% - bez float i clear,
kolory z arkusza w zmiennych CSS (--color-primary: Indigo), więc każdy kolor jest wpisany raz,
PHP na PDO z wyjątkami i trybem pobierania tablic asocjacyjnych, dane połączenia w osobnym pliku config.php,
logika PHP na górze pliku, HTML na dole: najpierw wszystkie zapytania i zamknięcie połączenia, a w HTML tylko pętle foreach wypisujące gotowe dane,
bezpieczeństwo od pierwszej linijki: zapytania przygotowane z parametrami nazwanymi, filter_var przy wejściu, htmlspecialchars przy wyjściu (krótka funkcja e()), wzorzec PRG po dodaniu gry.
Kroki nadal idą zdanie po zdaniu za treścią arkusza. Podgląd strony PHP w każdym kroku to wynik prawdziwego uruchomienia kodu z tego kroku w PHP 8.2 na bazie z arkusza (po zapytaniu 4 i z wpisanym 6 w stopce - jak na ilustracji 2). Zdjęcia, baza i rekord.txt pochodzą z prawdziwego archiwum pliki1.
Jak liczą się punkty. Klucz ma 41 kryteriów (R.1.1 - R.5.10), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 31 punktów. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.
Suwak i strzałki obok niego - albo ←→ na klawiaturze - przechodzą krok po kroku; wyjaśnienie, pliki i podgląd zmieniają się razem z krokiem.
Lista „Etap” przenosi od razu do wybranego etapu.
„Rozwiązuję samodzielnie” chowa rozwiązanie kroku - najpierw własna próba, potem odsłonięcie.
„Tryb prezentacji” pokazuje same kroki, bez menu strony - na rzutnik.
Etap 1: Baza danych
Kroki 1-7 · po tym etapie masz 6 z 41 kryteriów (pkt)
Krok 1: Baza danych gry
Kryteria:
R.1.1 Wykonany import tabel do bazy danych gry, czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie gry, z zestawem polskich znaków (np. utf8_unicode_ci).
W phpMyAdmin: Nowa, nazwa gry, metoda porównywania utf8_unicode_ci (albo utf8_polish_ci). Ten sam efekt da jedno zapytanie w zakładce SQL:
CREATE DATABASE gry CHARACTER SET utf8 COLLATE utf8_unicode_ci;
Importowana tabela gry ma zestaw i porównywanie zapisane we własnej definicji (utf8, utf8_unicode_ci), więc ogonki w opisach gier przetrwają niezależnie od ustawienia bazy - to dziedziczą tylko tabele zakładane później bez własnego ustawienia.
Typowe błędy
Skrypty łączą się z bazą po nazwie - Gry albo gra odetnie także punkty za połączenie (R.5.1).
Krok 2: Import tabeli z pliku baza.sql
Kryteria:
R.1.1 Wykonany import tabel do bazy danych gry, czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Do bazy zaimportuj tabele z pliku baza.sql z rozpakowanego archiwum.
Zaznacz bazę gry w lewym panelu, potem Import → Wybierz plik → baza.sql → Wykonaj. Powstaje tabela gry z ilustracji 1: id (klucz z autonumeracją), nazwa, opis, punkty, cena, zdjecie - 8 wierszy. W kolumnie zdjecie jest sama nazwa pliku, a pliki graficzne leżą w archiwum - stąd zalecenie, by trzymać je obok gry.php.
Typowe błędy
Import bez zaznaczonej bazy gry ląduje w innej bazie albo kończy się błędem „nie wybrano bazy danych”.
Krok 3: Zrzut ekranu import.png
Kryteria:
R.1.1 Wykonany import tabel do bazy danych gry, czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie PNG pod nazwą import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań.
Print Screen (cały ekran z paskiem zadań), wklej do edytora grafiki, zapisz jako import.png. Na zrzucie ma być widać komunikat o udanym imporcie albo tabelę gry. W tym arkuszu zrzut importu jest w PNG, a zrzuty kwerend w JPEG.
Typowe błędy
Wykadrowany zrzut (samo okno, bez paska zadań) nie jest oceniany - klucz mówi to wprost.
Krok 4: Zapytanie 1: id, nazwa i zdjęcie
Kryteria: ,
R.1.2 Zapisany plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Wykonane zapytanie 1 wybierające jedynie id, nazwę i zdjęcie z tabeli gry. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id, nazwa, zdjecie FROM gry; oraz wynik działania udokumentowany zrzutem, na którym jest widocznych minimum 8 rekordów, jedynie Pola id, nazwa, zdjecie
ok. 3 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie id, nazwę i zdjęcie z tabeli gry.
SELECT id, nazwa, zdjecie - dokładnie trzy kolumny, bez *,
FROM gry - tabela.
Wynik: 8 wierszy. Każde zapytanie zapisuj w kwerendy.txt z komentarzem -- nad nim (to też poprawny SQL, więc całość da się wkleić do phpMyAdmin jednym ruchem). Zrzut: kw1.jpg. To zapytanie wyśle skrypt 2.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT id, nazwa, zdjecie FROM gry;
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: gra o id 1 ze skróconym opisem
Kryteria:
R.1.4 Wykonane zapytanie 2 wybierające jedynie nazwę, pierwsze 100 znaków opisu, punkty oraz cenę z tabeli gry dla wiersza o id = 1. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id=1; oraz wynik działania udokumentowany zrzutem, na którym jest widoczny dokładnie jeden rekord, jedynie pola nazwa, opis, punkty, cena. Zastosowana jest funkcja obcinająca opis do pierwszych 100 znaków
ok. 3 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie nazwę, pierwsze 100 znaków opisu, punkty oraz cenę z tabeli gry dla wiersza o id równym 1.
LEFT(opis, 100) AS opis - funkcja z tabeli 2 arkusza obcina opis do 100 znaków, a aliasAS opis nadaje kolumnie wyniku czytelną nazwę. Bez aliasu kolumna nazywałaby się dosłownie LEFT(opis, 100) - a pod tą nazwą trzeba by ją potem odczytać w PHP,
WHERE id = 1 - jeden wiersz po kluczu głównym.
Zapytanie zapisane w kilku liniach (klauzula w linii) czyta się szybciej niż jeden długi wiersz - dla bazy to bez różnicy. Wynik: Czarodziejka, 45 punktów, cena 100. Zrzut: kw2.jpg.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT id, nazwa, zdjecie FROM gry;-- Zapytanie 2SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cenaFROM gryWHERE id = 1;
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: pięć gier z najwyższą punktacją
Kryteria:
R.1.5 Wykonane zapytanie 3 wybierające jedynie pierwsze pięć wierszy o najwyższej punktacji zawierające jedynie nazwę i punkty z tabeli gry. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5; oraz wynik działania udokumentowany zrzutem, na którym widocznych jest dokładnie 5 rekordów, jedynie pola nazwa i punkty, punkty posortowane malejąco
ok. 3 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie pola nazwa i punkty z pięciu pierwszych wierszy o najwyższej punktacji z tabeli gry.
LIMIT 5 bez ORDER BY daje pięć dowolnych wierszy, a nie pięć najlepszych.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT id, nazwa, zdjecie FROM gry;-- Zapytanie 2SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cenaFROM gryWHERE id = 1;-- Zapytanie 3SELECT nazwa, punktyFROM gryORDER BY punkty DESCLIMIT 5;
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: nowa gra z pliku rekord.txt
Kryteria:
R.1.6 Wykonane zapytanie 4 wstawiające wiersz do tabeli gry o danych zawartych w pliku rekord.txt. Sprawdzając można pominąć zgodność pola opis. Klucz główny nadawany automatycznie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO `gry`(`nazwa`, `opis`, `punkty`, `cena`, `zdjecie`) VALUES ('Zamczysko','...','200','50','zamczysko.jpg'); (możliwe różne warianty zapytania) oraz wynik działania udokumentowany zrzutem, na którym jest widoczny komunikat o poprawnym wykonaniu zapytania lub tabela z wstawionym wierszem
ok. 4 min
Polecenie z arkusza
Zapytanie 4: wstawiające do tabeli gry wiersz o danych zawartych w pliku rekord.txt (dane należy skopiować z pliku do zapytania). Klucz główny nadawany automatycznie.
Lista kolumn bez id - autonumeracja nada wartość 9. Napisy w apostrofach, punkty i cena jako liczby (w rekord.txt jest „Cena: 50 zł”, do bazy trafia samo 50). Wypisanie kolumn z nazwy zamiast INSERT INTO gry VALUES (...) chroni przed pomyłką w kolejności i działa nawet wtedy, gdy ktoś później doda kolumnę do tabeli. Zrzut kw4.jpg: komunikat o wstawieniu wiersza albo tabela z nowym rekordem.
Typowe błędy
rekord.txt jest zapisany w kodowaniu Windows-1250 - w edytorze ustawionym na UTF-8 zamiast „ó” i „ż” zobaczysz krzaki. Otwórz go Notatnikiem albo przełącz kodowanie.
kwerendy.txtTekstzmienione: 4 linie
-- Zapytanie 1SELECT id, nazwa, zdjecie FROM gry;-- Zapytanie 2SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cenaFROM gryWHERE id = 1;-- Zapytanie 3SELECT nazwa, punktyFROM gryORDER BY punkty DESCLIMIT 5;-- Zapytanie 4INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie)VALUES ('Zamczysko', 'Na odludziu stoi opuszczone zamczysko, w którym znajduje się skarb strzeżony przez złowrogie demony i duchy', 200, 50, 'zamczysko.jpg');
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 8-19 · po tym etapie masz 18 z 41 kryteriów (pkt)
Krok 8: Przeskalowanie grafiki zamczysko.jpg
Kryteria:
R.2.1 Plik zamczysko.jpg przeskalowany zachowując proporcje do szerokości 550 px odpowiadająca temu wysokość wynosi 309 px (± 5 px)
ok. 3 min
Polecenie z arkusza
Plik zamczysko.jpg, wypakowany z archiwum, należy przeskalować zachowując proporcje do szerokości 550 px.
Oryginał ma 640 × 360 px. W GIMP-ie Obraz → Skaluj obraz, szerokość 550 px przy zamkniętym łańcuchu proporcji - wysokość przeliczy się na 309 px (360 · 550 / 640). Klucz sprawdza 309 ±5 px. Skalowanie w samym CSS (width: 550px przy obrazie) nie zmienia pliku i punktu nie daje.
Podgląd strony po tym kroku
Krok 9: Plik gry.php: HTML5, język i kodowanie
Kryteria:
R.2.2 W pliku gry.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków
ok. 2 min
Polecenie z arkusza
Składa się ze strony o nazwie gry.php. Zapisana w języku HTML5. Zadeklarowany polski język zawartości witryny. Jawnie zastosowany właściwy standard kodowania polskich znaków.
Trzy zdania arkusza dotyczą tych samych czterech linii, więc idą razem: <!DOCTYPE html> (HTML5), lang="pl" w <html> (czytnik ekranu czyta polską wymową, przeglądarka dobiera słownik) i <meta charset="UTF-8"> jako pierwszy element <head> - przeglądarka musi znać kodowanie, zanim przeczyta tytuł z polskimi literami. Plik zapisz w UTF-8 bez BOM. Rozszerzenie .php, bo dojdą skrypty.
Tytuł strony widoczny na karcie przeglądarki: „Gry komputerowe”.
<title>Gry komputerowe</title> - tekst karty przeglądarki (R.2.3). Obok <meta name="viewport">: arkusz każe przełączać układ przy 800 px, a bez tej linii telefon udaje ekran szeroki na ok. 980 px i zapytanie @media na nim nie zadziała. Klucz viewportu nie ocenia, ale bez niego wersja na wąski ekran działa tylko w zwężonym oknie komputera.
R.2.4 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML 5: header, section + 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.
Klucz R.2.4 wymienia znaczniki wprost: <header>, trzy <section>, <footer>. Ten zestaw jest narzucony, więc nie ma tu miejsca na <main> czy <aside> - choć sekcja prawa z formularzem pasowałaby do <aside>, kryterium liczy trzy <section>. Identyfikatory left, middle, right (po angielsku, konsekwentnie w całym projekcie) posłużą do stylów.
W tej wersji wszystkie bloki są bezpośrednimi dziećmi <body> - dzięki temu siatka CSS ustawiona na <body> rozłoży je bez żadnego dodatkowego div.
Typowe błędy
Pięć <div> wygląda tak samo, ale R.2.4 sprawdza znaczniki w kodzie źródłowym.
R.2.5 W bloku nagłówkowym zapisany nagłówek <h1>: "Ranking gier komputerowych", w sekcji lewej przynajmniej jeden <h3> z: "Top 5 gier w tym miesiącu" lub "Nasz sklep", lub "Stronę wykonał", w sekcji prawej <h3>: "Dodaj nową grę" oraz zapisany na stronie przynajmniej jeden paragraf
ok. 1 min
Polecenie z arkusza
Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści „Ranking gier komputerowych”.
<h1> - jedyny nagłówek pierwszego stopnia na stronie. Dalej są <h3> i <h2> dokładnie tak, jak każe arkusz.
R.2.6 W sekcji lewej umieszczona jedna lista punktowana, zastosowane znaczniki <ul>, <li> (wygenerowana skryptem lub w HTML)
ok. 1 min
Polecenie z arkusza
Zawartość sekcji lewej: nagłówek trzeciego stopnia o treści: „Top 5 gier w tym miesiącu”. Lista punktowana (nieuporządkowana) wypełniona przez skrypt 1.
<h3> i pusta lista <ul> - elementy <li> wypisze skrypt 1. Lista nieuporządkowana, bo arkusz mówi „punktowana”; <ol> byłaby numerowana.
gry.phpPHPzmienione: 3 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul> </ul> </section> <sectionid="middle"> </section> <sectionid="right"> </section> <footer> </footer></body></html>
Podgląd strony po tym kroku (gry.php)
Krok 15: Sekcja lewa: sklep i odnośnik
Kryteria:
R.3.3 Odnośnik o treści „Tu kupisz gry” inicjuje otwarcie strony http://sklep.gry.pl ( lub https://)
ok. 1 min
Polecenie z arkusza
Nagłówek trzeciego stopnia o treści: „Nasz sklep”. Odnośnik o treści „Tu kupisz gry” prowadzący do adresu http://sklep.gry.pl.
Adres w href w całości, z http:// - bez protokołu przeglądarka szukałaby pliku sklep.gry.pl obok gry.php. Odnośnik prowadzi poza serwis, ale arkusz nie każe otwierać go w nowej karcie, więc bez target.
gry.phpPHPzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> </section> <sectionid="middle"> </section> <sectionid="right"> </section> <footer> </footer></body></html>
Podgląd strony po tym kroku (gry.php)
Krok 16: Sekcja lewa: autor strony
Kryteria:
R.2.5 W bloku nagłówkowym zapisany nagłówek <h1>: "Ranking gier komputerowych", w sekcji lewej przynajmniej jeden <h3> z: "Top 5 gier w tym miesiącu" lub "Nasz sklep", lub "Stronę wykonał", w sekcji prawej <h3>: "Dodaj nową grę" oraz zapisany na stronie przynajmniej jeden paragraf
ok. 1 min
Polecenie z arkusza
Nagłówek trzeciego stopnia o treści: „Stronę wykonał”. Paragraf z numerem zdającego.
<h3> i paragraf <p>. Tu stoi nazwisko autora rozwiązania - na egzaminie wpisujesz w to miejsce swój numer zdającego (PESEL), tak jak na ilustracji 2. Klucz R.2.5 wymaga na stronie co najmniej jednego paragrafu i <h3> w sekcjach.
gry.phpPHPzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"> </section> <sectionid="right"> </section> <footer> </footer></body></html>
Podgląd strony po tym kroku (gry.php)
Krok 17: Sekcja prawa: nagłówek i formularz
Kryteria:
R.3.4 Przynajmniej jeden formularz wysyła dane do tego samego pliku metodą POST (poprawne również bez atrybutu action)
ok. 1 min
Polecenie z arkusza
Zawartość sekcji środkowej: efekt działania skryptu 2. Zawartość sekcji prawej: nagłówek trzeciego stopnia o treści: „Dodaj nową grę”. Formularz wysyłający dane do tego samego pliku metodą bezpieczną.
Sekcja środkowa zostaje pusta - wypełni ją skrypt 2. W prawej: <h3> i <form method="post">. POST wysyła dane w treści żądania, a nie w adresie, więc nie zostają w historii przeglądarki ani w zakładkach. Brak action oznacza „ten sam adres” - to dokładnie „do tego samego pliku”.
gry.phpPHPzmienione: 3 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"> </section> <sectionid="right"> <h3>Dodaj nową grę</h3> <formmethod="post"> </form> </section> <footer> </footer></body></html>
Podgląd strony po tym kroku (gry.php)
Krok 18: Formularz: cztery pola z etykietami i przycisk
Kryteria: ,
R.2.7 W sekcji prawej umieszczony formularz z czterema polami edycyjnymi i przyciskiem "DODAJ". Zastosowany znacznik <form> oraz przynajmniej jedna etykieta <label>
R.3.5 Przycisk z przynajmniej jednego formularza wysyła dane do skryptu (input z typem submit lub button)
ok. 3 min
Polecenie z arkusza
Formularz zawiera cztery pola edycyjne podpisane etykietami: nazwa, opis, cena, zdjęcie oraz przycisk DODAJ, rozmieszczone zgodnie z ilustracją 2.
Każde pole ma <label for> wskazujące jego id - kliknięcie w napis ustawia kursor w polu. Atrybuty pól niosą wiedzę o danych:
maxlength="20" i required przy nazwie - kolumna nazwa ma varchar(20), a skrypt 4 bez nazwy i tak nic nie zapisze,
type="number" z step="0.01" przy cenie - przeglądarka nie przepuści liter, a na telefonie pokaże klawiaturę numeryczną.
To podpowiedzi dla użytkownika, nie zabezpieczenie: każdy może wysłać żądanie z pominięciem formularza, dlatego PHP i tak sprawdzi dane. Między polami nie ma <br> - ułożenie „etykieta nad polem” z ilustracji 2 zrobi CSS (display: block), bo to sprawa wyglądu, nie treści.
Typowe błędy
Atrybut name jest kluczem w $_POST - pole bez name nie zostanie wysłane, nawet jeśli ma id.
Krok 19: Stopka: formularz z polem i przyciskiem „Pokaż opis”
Kryteria:
R.2.8 W stopce umieszczony formularz z jednym polem edycyjnym i przyciskiem "Pokaż opis". Zastosowany znacznik <form>
ok. 2 min
Polecenie z arkusza
Zawartość stopki: formularz wysyłający dane do tego samego pliku metodą bezpieczną. Formularz zawiera pole edycyjne i przycisk o treści „Pokaż opis”. Efekt działania skryptu 3.
Drugi formularz, też method="post". Na ilustracji pole nie ma podpisu, więc zamiast widocznej etykiety jest aria-label - czytnik ekranu przeczyta „Identyfikator gry”, a wygląd się nie zmienia. type="number" z min="1", bo identyfikatory gier to dodatnie liczby całkowite. Wynik skryptu 3 pojawi się pod formularzem.
Kroki 20-31 · po tym etapie masz 29 z 41 kryteriów (pkt)
Krok 20: 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. Indigo pojawi się w pięciu regułach - wpisany raz w --color-primary, w regułach jest używany jako var(--color-primary). Zmiana koloru całej strony to wtedy jedna linia. W inspektorze przeglądarki egzaminator i tak widzi obliczony kolor (Indigo = rgb(75, 0, 130)).
Nazwy zmiennych opisują rolę koloru (główny, punkty), a nie sam kolor - --indigo: Indigo niczego by nie ułatwiło.
Domyślne formatowanie wszystkich selektorów: krój czcionki Garamond, biały kolor czcionki.
Selektor uniwersalny * (wymagany przez R.4.1). Po Garamond stoi rodzina zastępcza serif: gdy na komputerze nie ma Garamonda, przeglądarka weźmie inny krój szeryfowy zamiast domyślnego. Biały tekst chwilowo znika na białym tle - tła dojdą za moment.
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 3 w arkuszu dla ekranów o szerokości większej niż 800 px. Blok środkowy może być pusty. (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)
ok. 4 min
Polecenie z arkusza
Dla wszystkich rodzajów ekranu o szerokości większej niż 800 px szerokość sekcji lewej i prawej wynosi 20%, środkowej 60% (są wyświetlane obok siebie).
<body> staje się siatką (display: grid), a jego dzieci - kolejne bloki - jej elementami. Na szerokim ekranie siatka ma trzy kolumny: grid-template-columns: 20% 60% 20%. Elementy wpadają w komórki w kolejności z HTML; nagłówek i stopka dostają grid-column: 1 / -1, czyli „od pierwszej do ostatniej linii siatki” - zajmują cały wiersz. Trzy sekcje same lądują obok siebie w drugim wierszu, a stopka pod nimi - bez float i bez clear.
Warunek to zapis z poziomu 4 specyfikacji zapytań medialnych: (width > 800px) znaczy dokładnie „większej niż 800 px”, tak jak w arkuszu. Starszy zapis (min-width: 801px) robi to samo, ale wymaga przeliczenia granicy w głowie. Składnię zakresów rozumieją wszystkie współczesne przeglądarki (Chrome i Edge od 2022, Firefox od 2018, Safari od 2023).
Typowe błędy
Procenty w grid-template-columns liczą się od szerokości siatki. Dodanie gap rozepchnęłoby kolumny ponad 100% i prawa sekcja wyjechałaby poza ekran - przy procentach bez odstępów, przy fr (np. 1fr 3fr 1fr) odstęp byłby bezpieczny.
R.4.5 Sekcje lewa, środkowa i prawa są wyświetlane obok siebie jeśli ekran jest szerszy niż 800px - odpowiednio szerokości 20%, 60%, 20%, a w przeciwnym wypadku są wyświetlane jedna pod drugą (zastosowany np. zapis @media all and (max-width: 800px) - sprawdzić w kodzie i poprzez zmianę szerokości okna przeglądarki)
ok. 2 min
Polecenie z arkusza
Dla wszystkich rodzajów ekranu o pozostałej szerokości sekcje lewa, środkowa i prawa są wyświetlane jedna pod drugą.
To podejście „od małego ekranu” (mobile first): reguła bez warunku opisuje wąski ekran - siatka z jedną kolumną na całą szerokość, więc wszystkie bloki stoją jeden pod drugim. Zapytanie @media (width > 800px) z poprzedniego kroku tylko rozszerza ten układ dla szerszych ekranów. Nic nie trzeba odwoływać (float: none, width: 100%), jak przy podejściu odwrotnym.
Klucz R.4.5 podaje przykład @media all and (max-width: 800px) - to „np.”, ocenia się działanie: zwęź okno do 800 px i mniej, a sekcje ustawią się jedna pod drugą.
R.4.2 Ustawiony kolor tła Indigo dla bloków: bloku nagłówkowego, sekcji lewej, prawej i stopki oraz czarny kolor tła dla sekcji środkowej
R.4.4 Ustawiona wysokość sekcji lewej, środkowej i prawej 600px oraz stopki 150px
ok. 1 min
Polecenie z arkusza
Dla stopki: kolor tła Indigo, wysokość 150 px.
Tło i wysokość. clear: both nie jest potrzebne - w siatce nic nie pływa, stopka ma po prostu własny wiersz.
Teraz wszystkie bloki mają tła i wysokości: Indigo w nagłówku, sekcjach bocznych i stopce, czerń w środku (R.4.2), 600 px sekcji i 150 px stopki (R.4.4). Każdy kolor pochodzi z jednej z dwóch zmiennych - w inspektorze (Obliczone) widać rgb(75, 0, 130) albo rgb(0, 0, 0).
R.4.9 Ustawiony styl jedynie dla oznaczenia liczby punktów zgodnie z ilustracją 4: kolor tła Tomato, zaokrąglenie rogów 50%, marginesy wewnętrzne (padding) 5px (np. klasą, zastosowanie id jest błędem)
ok. 1 min
Polecenie z arkusza
Dla oznaczenia liczby punktów widocznych na ilustracji 4: kolor tła Tomato, zaokrąglenie rogów 50%, marginesy wewnętrzne 5 px.
Klasa .points - takich elementów będzie pięć, a identyfikator musi być unikalny (klucz R.4.9: id to błąd). border-radius: 50% robi z tła kółko jak na ilustracji 4.
R.4.10 Ustawiony styl dla bloku gry: bloki są umieszczone jeden obok drugiego, wyrównanie tekstu do środka, marginesy wewnętrzne (padding) 3px (np. klasą, selektorem typu, potomka, zastosowanie id jest błędem)
ok. 2 min
Polecenie z arkusza
Dla bloków gier znajdujących się w sekcji środkowej: bloki są umieszczone jeden obok drugiego, wyrównanie tekstu do środka, marginesy wewnętrzne 3 px.
Tu zostaje display: inline-block, choć wersja „siatkowa” prosiłaby się o display: flex; flex-wrap: wrap na sekcji środkowej. Powód: kryterium R.4.10 ocenia styl samego bloku gry („ustawiony styl dla bloku gry: bloki są umieszczone jeden obok drugiego”), a przy flexie właściwość układająca bloki stałaby w regule rodzica. Efekt byłby ten sam, ale reguła .game nie miałaby czym się wykazać. Punkt z klucza wygrywa z elegancją.
R.4.3 Ustawiony kolor czcionki Indigo dla pola edycyjnego
R.4.7 Ustawione marginesy wewnętrzne (padding) dla bloku nagłówkowego 2px, dla elementu listy 5px oraz marginesy zewnętrzne (margin) dla pola edycyjnego 10px
ok. 2 min
Polecenie z arkusza
Dla pola edycyjnego: marginesy zewnętrzne 10 px, kolor czcionki Indigo.
input z margin (zewnętrzny) i kolorem ze zmiennej - bez tego wpisywany tekst byłby biały na białym polu. Druga reguła, label { display: block; }, stawia etykietę w osobnej linii nad polem, jak na ilustracji 2 - to zastępuje znaczniki <br>, których nie ma w HTML.
Kroki 32-42 · po tym etapie masz 41 z 41 kryteriów (pkt)
Krok 32: Dane połączenia w pliku config.php
ok. 3 min
Polecenie z arkusza
Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie gry.
Adres serwera, nazwa bazy, użytkownik i hasło trafiają do osobnego pliku config.php, który tylko zwraca tablicę (return [...]). Powody:
dane logowania są w jednym miejscu - zmiana hasła nie wymaga szukania po plikach stron,
plik konfiguracji można wyłączyć z repozytorium kodu, a stronę udostępnić bez hasła do bazy.
dsn (nazwa źródła danych) to napis PDO: sterownik mysql, serwer localhost, baza gry i od razu charset=utf8mb4 - kodowanie połączenia ustawione przy łączeniu, więc polskie znaki z bazy przychodzą poprawnie. Arkusz pozwala na dodatkowe pliki („ewentualnie inne przygotowane pliki”), więc config.php trafia na płytę razem z resztą.
config.phpPHPnowy plik
<?php// Dane połączenia z bazą - w jednym miejscu, poza plikiem stronyreturn ['dsn' => 'mysql:host=localhost;dbname=gry;charset=utf8mb4','user' => 'root','password' => '',];
R.5.1 Skrypt zawiera działające połączenie z serwerem bazy danych i wybór bazy gry
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. 4 min
Polecenie z arkusza
Skrypty napisane w języku PHP. Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie gry.
Na samej górze gry.php - przed <!DOCTYPE html>, bo za chwilę dojdzie tu przekierowanie, a nagłówki HTTP wysyła się przed treścią strony. require 'config.php' wczytuje tablicę z danymi, a new PDO(...) otwiera połączenie z dwiema opcjami:
PDO::ERRMODE_EXCEPTION - błąd SQL rzuca wyjątek, zamiast po cichu zwrócić false,
PDO::FETCH_ASSOC - wiersze przychodzą jako tablice z nazwami kolumn ($game['nazwa'] zamiast $row[0]), więc kod nie zależy od kolejności kolumn w SELECT.
Blok try/catch (PDOException ...) łapie nieudane połączenie i pokazuje ogólny komunikat. Szczegóły błędu ($exception->getMessage()) zawierają nazwę serwera i użytkownika - nie pokazuje się ich odwiedzającym, tylko zapisuje w dzienniku serwera.
Klucz dopuszcza PDO wprost („Kryteria ... sprawdzane tylko w przypadku użycia bibliotek MySQLi lub PDO”). Podgląd od tego kroku to wynik prawdziwego uruchomienia strony w PHP 8.2.
Typowe błędy
PDO nie ma w tabeli 1 arkusza - nazwy opcji trzeba znać na pamięć. Literówka w PDO::ATTR_ERRMODE to błąd krytyczny i biała strona; kto nie jest pewny składni, pisze funkcjami mysqli_* z tabeli 1 (wersja podstawowa).
gry.phpPHPzmienione: 13 linii
<?php$config = require'config.php';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"> </section> <sectionid="right"> <h3>Dodaj nową grę</h3> <formmethod="post"> <labelfor="name">nazwa</label> <inputtype="text"id="name"name="name"maxlength="20"required> <labelfor="description">opis</label> <inputtype="text"id="description"name="description"> <labelfor="price">cena</label> <inputtype="number"id="price"name="price"min="0"step="0.01"> <labelfor="image">zdjęcie</label> <inputtype="text"id="image"name="image"> <buttontype="submit">DODAJ</button> </form> </section> <footer> <formmethod="post"> <inputtype="number"name="game_id"min="1"aria-label="Identyfikator gry"required> <buttontype="submit">Pokaż opis</button> </form> </footer></body></html>
Podgląd strony po tym kroku (gry.php)
Krok 34: Funkcja e() do bezpiecznego wypisywania
ok. 2 min
Polecenie z arkusza
Zwrócone wiersze są wyświetlane w elementach listy z sekcji lewej według wzoru: <nazwa> <punkty>, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych.
Zanim cokolwiek z bazy trafi na stronę, powstaje krótka funkcja e() - skrót od escape. Do bazy każdy może dopisać grę formularzem, więc nazwa <script>…</script> wypisana wprost wykonałaby się w przeglądarce każdego gościa (atak XSS). htmlspecialchars zamienia <, >, & i - dzięki ENT_QUOTES - oba cudzysłowy na encje, więc tekst zawsze wyświetla się dosłownie, także wewnątrz atrybutu alt="...".
Typy w nagłówku funkcji (string|int|float|null, wynik : string) dokumentują, co funkcja przyjmuje - z bazy przychodzą napisy, liczby albo NULL.
gry.phpPHPzmienione: 5 linii
<?php$config = require'config.php';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"> </section> <sectionid="right"> <h3>Dodaj nową grę</h3> <formmethod="post"> <labelfor="name">nazwa</label> <inputtype="text"id="name"name="name"maxlength="20"required> <labelfor="description">opis</label> <inputtype="text"id="description"name="description"> <labelfor="price">cena</label> <inputtype="number"id="price"name="price"min="0"step="0.01"> <labelfor="image">zdjęcie</label> <inputtype="text"id="image"name="image"> <buttontype="submit">DODAJ</button> </form> </section> <footer> <formmethod="post"> <inputtype="number"name="game_id"min="1"aria-label="Identyfikator gry"required> <buttontype="submit">Pokaż opis</button> </form> </footer></body></html>
R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 3 lub skrypt 2 wysyła zapytanie 1, lub skrypt 3 wysyła zapytanie 2 zmodyfikowane w ten sposób, że wybierany jest wiersz o id podanym w polu edycyjnym
ok. 2 min
Polecenie z arkusza
Skrypt 1: wysyła do bazy danych zapytanie 3.
W tej wersji każdy skrypt ma dwie części: pobranie danych na górze pliku i wypisanie w HTML. $pdo->query(...) wysyła zapytanie 3, a fetchAll() od razu odbiera wszystkie pięć wierszy do tablicy $topGames.
Zwykłe query (bez prepare) jest tu na miejscu: w zapytaniu nie ma ani jednej wartości od użytkownika, więc nie ma czego wstrzyknąć.
gry.phpPHPzmienione: 3 linie
<?php$config = require'config.php';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}// Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)$topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"> </section> <sectionid="right"> <h3>Dodaj nową grę</h3> <formmethod="post"> <labelfor="name">nazwa</label> <inputtype="text"id="name"name="name"maxlength="20"required> <labelfor="description">opis</label> <inputtype="text"id="description"name="description"> <labelfor="price">cena</label> <inputtype="number"id="price"name="price"min="0"step="0.01"> <labelfor="image">zdjęcie</label> <inputtype="text"id="image"name="image"> <buttontype="submit">DODAJ</button> </form> </section> <footer> <formmethod="post"> <inputtype="number"name="game_id"min="1"aria-label="Identyfikator gry"required> <buttontype="submit">Pokaż opis</button> </form> </footer></body></html>
Krok 36: Skrypt 1: elementy listy z nazwą i punktami
Kryteria: ,
R.5.6 Skrypt 1 wyświetla nazwę i punkty zwrócone zapytaniem jako elementy listy
R.2.9 Liczba punktów z sekcji lewej zapisana w dodatkowym elemencie, np. znacznik <span> (w skrypcie lub HTML)
ok. 3 min
Polecenie z arkusza
Zwrócone wiersze są wyświetlane w elementach listy z sekcji lewej według wzoru: <nazwa> <punkty>, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych. Liczba punktów jest dodatkowo formatowana stylem, którego efekt jest widoczny na ilustracji 4.
W HTML zostaje sam szablon: pętla foreach ($topGames as $game): ... endforeach; - składnia alternatywna PHP, pomyślana właśnie do przeplatania z HTML (dwukropek i endforeach zamiast nawiasów klamrowych, które łatwo zgubić między znacznikami). <?= ... ?> to skrót od <?php echo ... ?>.
Liczba punktów jest w osobnym <span class="points"> (R.2.9) - na niego czeka reguła .points. Atrybut HTML w zwykłych cudzysłowach, bo kod PHP nie składa już napisów z HTML-em.
gry.phpPHPzmienione: 3 linie
<?php$config = require'config.php';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}// Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)$topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul><?phpforeach ($topGamesas$game): ?> <li><?= e($game['nazwa']) ?> <spanclass="points"><?= e($game['punkty']) ?></span></li><?phpendforeach; ?> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"> </section> <sectionid="right"> <h3>Dodaj nową grę</h3> <formmethod="post"> <labelfor="name">nazwa</label> <inputtype="text"id="name"name="name"maxlength="20"required> <labelfor="description">opis</label> <inputtype="text"id="description"name="description"> <labelfor="price">cena</label> <inputtype="number"id="price"name="price"min="0"step="0.01"> <labelfor="image">zdjęcie</label> <inputtype="text"id="image"name="image"> <buttontype="submit">DODAJ</button> </form> </section> <footer> <formmethod="post"> <inputtype="number"name="game_id"min="1"aria-label="Identyfikator gry"required> <buttontype="submit">Pokaż opis</button> </form> </footer></body></html>
Podgląd strony po tym kroku (gry.php)
Krok 37: Skrypt 2: zapytanie 1 i obrazy gier
Kryteria: ,
R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 3 lub skrypt 2 wysyła zapytanie 1, lub skrypt 3 wysyła zapytanie 2 zmodyfikowane w ten sposób, że wybierany jest wiersz o id podanym w polu edycyjnym
R.2.10 W sekcji środkowej umieszczony przynajmniej jeden obraz z tekstem alternatywnym i atrybutem title (w skrypcie lub HTML)
ok. 4 min
Polecenie z arkusza
Skrypt 2: wysyła do bazy danych zapytanie 1. Zwrócone zapytaniem wiersze są wyświetlone w bloku, który składa się z: obrazu, którego źródłem jest pole zdjecie, tekstem alternatywnym jest pole nazwa, a podpowiedzią (dymek widoczny na ilustracji 2 dla pierwszej grafiki) jest pole id.
Na górze: zapytanie 1 do tablicy $games. W sekcji środkowej: pętla, która dla każdej gry tworzy blok <article class="game"> - article, bo każdy blok to samodzielna całość (obraz i podpis gry), którą dałoby się przenieść w inne miejsce. W obrazie src = zdjecie, alt = nazwa, title = id - ten ostatni to „dymek” z ilustracji 2 (R.2.10).
Wartości trafiają do atrybutów przez e(): apostrof albo cudzysłów w nazwie gry nie rozerwie atrybutu alt.
Typowe błędy
Zamiana alt z title kosztuje R.2.10 i R.5.7 - arkusz mówi wprost, które pole gdzie.
gry.phpPHPzmienione: 8 linii
<?php$config = require'config.php';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}// Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)$topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();// Skrypt 2: wszystkie gry do galerii (zapytanie 1)$games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul><?phpforeach ($topGamesas$game): ?> <li><?= e($game['nazwa']) ?> <spanclass="points"><?= e($game['punkty']) ?></span></li><?phpendforeach; ?> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"><?phpforeach ($gamesas$game): ?> <articleclass="game"> <imgsrc="<?= e($game['zdjecie']) ?>"alt="<?= e($game['nazwa']) ?>"title="<?= e($game['id']) ?>"> </article><?phpendforeach; ?> </section> <sectionid="right"> <h3>Dodaj nową grę</h3> <formmethod="post"> <labelfor="name">nazwa</label> <inputtype="text"id="name"name="name"maxlength="20"required> <labelfor="description">opis</label> <inputtype="text"id="description"name="description"> <labelfor="price">cena</label> <inputtype="number"id="price"name="price"min="0"step="0.01"> <labelfor="image">zdjęcie</label> <inputtype="text"id="image"name="image"> <buttontype="submit">DODAJ</button> </form> </section> <footer> <formmethod="post"> <inputtype="number"name="game_id"min="1"aria-label="Identyfikator gry"required> <buttontype="submit">Pokaż opis</button> </form> </footer></body></html>
Podgląd strony po tym kroku (gry.php)
Krok 38: Skrypt 2: paragraf z nazwą
Kryteria:
R.5.7 Skrypt 2 wyświetla obrazy oraz ich nazwy zwrócone zapytaniem
ok. 1 min
Polecenie z arkusza
Paragrafu z nazwą zdjęcia.
<p> z nazwą pod obrazem, w tym samym bloku. Sekcja środkowa wygląda jak na ilustracji 2: dziewięć bloków (z Zamczyskiem dodanym zapytaniem 4) i paski przewijania.
gry.phpPHPzmienione: 1 linia
<?php$config = require'config.php';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}// Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)$topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();// Skrypt 2: wszystkie gry do galerii (zapytanie 1)$games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul><?phpforeach ($topGamesas$game): ?> <li><?= e($game['nazwa']) ?> <spanclass="points"><?= e($game['punkty']) ?></span></li><?phpendforeach; ?> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"><?phpforeach ($gamesas$game): ?> <articleclass="game"> <imgsrc="<?= e($game['zdjecie']) ?>"alt="<?= e($game['nazwa']) ?>"title="<?= e($game['id']) ?>"> <p><?= e($game['nazwa']) ?></p> </article><?phpendforeach; ?> </section> <sectionid="right"> <h3>Dodaj nową grę</h3> <formmethod="post"> <labelfor="name">nazwa</label> <inputtype="text"id="name"name="name"maxlength="20"required> <labelfor="description">opis</label> <inputtype="text"id="description"name="description"> <labelfor="price">cena</label> <inputtype="number"id="price"name="price"min="0"step="0.01"> <labelfor="image">zdjęcie</label> <inputtype="text"id="image"name="image"> <buttontype="submit">DODAJ</button> </form> </section> <footer> <formmethod="post"> <inputtype="number"name="game_id"min="1"aria-label="Identyfikator gry"required> <buttontype="submit">Pokaż opis</button> </form> </footer></body></html>
Podgląd strony po tym kroku (gry.php)
Krok 39: Skrypt 3: id z formularza i zapytanie przygotowane
Kryteria: ,
R.5.4 W przynajmniej jednym skrypcie związanym z formularzem działania następują tylko wtedy, gdy wypełnione pole nazwa (skrypt 4) lub id (skrypt 3)
R.5.5 Skrypt zawiera instrukcję pobierającą dane z tablicy globalnej odpowiadającej zastosowanej metodzie formularza ($_POST, $_GET, $_REQUEST)
ok. 4 min
Polecenie z arkusza
Skrypt 3 związany z formularzem w stopce: jeżeli wpisano id do pola edycyjnego, skrypt wysyła do bazy danych zapytanie 2 zmodyfikowane tak, że wybierany jest wiersz o id podanym w polu edycyjnym.
Dwie warstwy ochrony:
Walidacja wejścia.filter_var(..., FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]) zwraca liczbę całkowitą albo false - dla pustego pola, liter, 1 OR 1=1 i zera. Warunek $gameId !== false to „jeżeli wpisano id” z arkusza (R.5.4); operator ?? podstawia pusty napis, gdy formularz nie został wysłany, więc nie trzeba osobnego isset. Dane czytane są z $_POST (R.5.5) - filter_input(INPUT_POST, ...) też by zadziałał, ale nie zawiera $_POST, a tego dotyczy kryterium.
Zapytanie przygotowane. W treści zapytania stoi parametr nazwany :id, a wartość idzie osobno w execute(['id' => $gameId]). Baza zna strukturę zapytania, zanim zobaczy dane, więc wpisany tekst nigdy nie stanie się częścią SQL.
fetch() zwraca jeden wiersz albo false, gdy gry o takim id nie ma. Pole w stopce zachowuje wpisaną wartość (value), jak na ilustracji 2.
Typowe błędy
Porównanie if ($gameId) zamiast !== false też odrzuci zero, ale przy filter_var zawsze porównuj ściśle z false - przy innych filtrach poprawna wartość 0 zostałaby wzięta za błąd.
gry.phpPHPzmienione: 11 linii
<?php$config = require'config.php';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}// Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)$topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();// Skrypt 2: wszystkie gry do galerii (zapytanie 1)$games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();// Skrypt 3: opis gry o id z formularza w stopce (zapytanie 2)$selectedGame = false;$gameId = filter_var($_POST['game_id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);if ($gameId !== false) {$statement = $pdo->prepare('SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cena FROM gry WHERE id = :id');$statement->execute(['id' => $gameId]);$selectedGame = $statement->fetch();}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul><?phpforeach ($topGamesas$game): ?> <li><?= e($game['nazwa']) ?> <spanclass="points"><?= e($game['punkty']) ?></span></li><?phpendforeach; ?> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"><?phpforeach ($gamesas$game): ?> <articleclass="game"> <imgsrc="<?= e($game['zdjecie']) ?>"alt="<?= e($game['nazwa']) ?>"title="<?= e($game['id']) ?>"> <p><?= e($game['nazwa']) ?></p> </article><?phpendforeach; ?> </section> <sectionid="right"> <h3>Dodaj nową grę</h3> <formmethod="post"> <labelfor="name">nazwa</label> <inputtype="text"id="name"name="name"maxlength="20"required> <labelfor="description">opis</label> <inputtype="text"id="description"name="description"> <labelfor="price">cena</label> <inputtype="number"id="price"name="price"min="0"step="0.01"> <labelfor="image">zdjęcie</label> <inputtype="text"id="image"name="image"> <buttontype="submit">DODAJ</button> </form> </section> <footer> <formmethod="post"> <inputtype="number"name="game_id"min="1"aria-label="Identyfikator gry"requiredvalue="<?= $gameId !== false ? e($gameId) : ''?>"> <buttontype="submit">Pokaż opis</button> </form> </footer></body></html>
Podgląd strony po tym kroku (gry.php)
Krok 40: Skrypt 3: nagłówek i opis pod formularzem
Kryteria:
R.5.8 Skrypt 3 wyświetla napis: „<nazwa>, <punkty> punktów, <cena> zł", gdzie nawiasy <> oznaczają wartość pobraną z bazy danych oraz wyświetla opis
ok. 3 min
Polecenie z arkusza
Zwrócony zapytaniem wiersz jest wyświetlany pod formularzem według wzoru: w nagłówku drugiego stopnia: <nazwa>, <punkty> punktów, <cena> zł. Opis w paragrafie.
Pod formularzem if ($selectedGame): - nagłówek <h2> według wzoru z arkusza i paragraf z opisem skróconym do 100 znaków (kolumna wyniku nazywa się opis dzięki aliasowi AS opis). Gałąź elseif obsługuje id spoza bazy krótkim komunikatem - zamiast pustego miejsca albo ostrzeżenia PHP o odczycie z false. Klucz tego nie ocenia, ale egzaminator wpisujący np. 99 zobaczy sensowną odpowiedź.
Podgląd pokazuje stan z ilustracji 2: w polu 6, pod formularzem „Mag, 357 punktów, 150 zł”.
gry.phpPHPzmienione: 6 linii
<?php$config = require'config.php';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}// Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)$topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();// Skrypt 2: wszystkie gry do galerii (zapytanie 1)$games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();// Skrypt 3: opis gry o id z formularza w stopce (zapytanie 2)$selectedGame = false;$gameId = filter_var($_POST['game_id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);if ($gameId !== false) {$statement = $pdo->prepare('SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cena FROM gry WHERE id = :id');$statement->execute(['id' => $gameId]);$selectedGame = $statement->fetch();}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul><?phpforeach ($topGamesas$game): ?> <li><?= e($game['nazwa']) ?> <spanclass="points"><?= e($game['punkty']) ?></span></li><?phpendforeach; ?> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"><?phpforeach ($gamesas$game): ?> <articleclass="game"> <imgsrc="<?= e($game['zdjecie']) ?>"alt="<?= e($game['nazwa']) ?>"title="<?= e($game['id']) ?>"> <p><?= e($game['nazwa']) ?></p> </article><?phpendforeach; ?> </section> <sectionid="right"> <h3>Dodaj nową grę</h3> <formmethod="post"> <labelfor="name">nazwa</label> <inputtype="text"id="name"name="name"maxlength="20"required> <labelfor="description">opis</label> <inputtype="text"id="description"name="description"> <labelfor="price">cena</label> <inputtype="number"id="price"name="price"min="0"step="0.01"> <labelfor="image">zdjęcie</label> <inputtype="text"id="image"name="image"> <buttontype="submit">DODAJ</button> </form> </section> <footer> <formmethod="post"> <inputtype="number"name="game_id"min="1"aria-label="Identyfikator gry"requiredvalue="<?= $gameId !== false ? e($gameId) : ''?>"> <buttontype="submit">Pokaż opis</button> </form><?phpif ($selectedGame): ?> <h2><?= e($selectedGame['nazwa']) ?>, <?= e($selectedGame['punkty']) ?> punktów, <?= e($selectedGame['cena']) ?> zł</h2> <p><?= e($selectedGame['opis']) ?></p><?phpelseif ($gameId !== false): ?> <p>Nie ma gry o identyfikatorze <?= e($gameId) ?>.</p><?phpendif; ?> </footer></body></html>
Podgląd strony po tym kroku (gry.php)
Krok 41: Skrypt 4: wstawienie gry z zerem punktów i przekierowanie
Kryteria:
R.5.9 Skrypt 4 wstawia do bazy danych rekord z danymi pobranymi z formularza, liczba punktów =0 (sprawdzić w phpMyAdmin)
ok. 5 min
Polecenie z arkusza
Skrypt 4 związany z formularzem z sekcji prawej: jeżeli wypełniono pole nazwa, skrypt wysyła do bazy zmodyfikowane zapytanie 4 w ten sposób, że wstawione są wartości pobrane z formularza, a liczba punktów wynosi 0.
Skrypt 4 stoi przed odczytami, żeby nowa gra była już w bazie, gdy skrypty 1 i 2 pobierają listę i galerię.
Wejście: trim usuwa spacje z brzegów, warunek $name !== '' to „jeżeli wypełniono pole nazwa”, a mb_strlen($name) <= 20 pilnuje długości kolumny varchar(20) (mb_, bo „ż” to dla zwykłego strlen dwa bajty). Cena przechodzi przez FILTER_VALIDATE_FLOAT; gdy jej nie ma albo jest błędna, do bazy idzie NULL (kolumna to dopuszcza), czyli „brak ceny”. Pusty napis w kolumnie liczbowej MariaDB w XAMPP po cichu zamienia na 0 (gra „za darmo”), a serwer w trybie ścisłym odrzuciłby całe zapytanie.
Zapytanie przygotowane z parametrami nazwanymi :name, :description, :price, :image; zero punktów jest stałą w treści zapytania, bo nie pochodzi od użytkownika.
PRG (Post/Redirect/Get): po zapisie header('Location: gry.php') i exit. Przeglądarka pobiera stronę od nowa zwykłym GET-em, więc odświeżenie (F5) nie wyśle formularza drugi raz i nie doda tej samej gry podwójnie. exit kończy skrypt - PHP sam zamyka wtedy połączenie.
Podgląd się nie zmienia: skrypt działa dopiero po kliknięciu DODAJ, a efekt sprawdza się w phpMyAdmin (R.5.9) i na liście gier.
Typowe błędy
header() musi stać przed jakimkolwiek wypisanym znakiem - nawet przed spacją albo pustą linią przed <?php. Dlatego cała logika jest na górze pliku, nad <!DOCTYPE html>.
gry.phpPHPzmienione: 17 linii
<?php$config = require'config.php';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}// Skrypt 4: nowa gra z formularza w sekcji prawej (zapytanie 4) - przed odczytami$name = trim($_POST['name'] ?? '');if ($name !== '' && mb_strlen($name) <= 20) {$price = filter_var($_POST['price'] ?? '', FILTER_VALIDATE_FLOAT);$statement = $pdo->prepare('INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie) VALUES (:name, :description, 0, :price, :image)' );$statement->execute(['name' => $name,'description' => trim($_POST['description'] ?? ''),'price' => $price === false ? null : $price,'image' => trim($_POST['image'] ?? ''), ]);header('Location: gry.php');exit;}// Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)$topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();// Skrypt 2: wszystkie gry do galerii (zapytanie 1)$games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();// Skrypt 3: opis gry o id z formularza w stopce (zapytanie 2)$selectedGame = false;$gameId = filter_var($_POST['game_id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);if ($gameId !== false) {$statement = $pdo->prepare('SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cena FROM gry WHERE id = :id');$statement->execute(['id' => $gameId]);$selectedGame = $statement->fetch();}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul><?phpforeach ($topGamesas$game): ?> <li><?= e($game['nazwa']) ?> <spanclass="points"><?= e($game['punkty']) ?></span></li><?phpendforeach; ?> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"><?phpforeach ($gamesas$game): ?> <articleclass="game"> <imgsrc="<?= e($game['zdjecie']) ?>"alt="<?= e($game['nazwa']) ?>"title="<?= e($game['id']) ?>"> <p><?= e($game['nazwa']) ?></p> </article><?phpendforeach; ?> </section> <sectionid="right"> <h3>Dodaj nową grę</h3> <formmethod="post"> <labelfor="name">nazwa</label> <inputtype="text"id="name"name="name"maxlength="20"required> <labelfor="description">opis</label> <inputtype="text"id="description"name="description"> <labelfor="price">cena</label> <inputtype="number"id="price"name="price"min="0"step="0.01"> <labelfor="image">zdjęcie</label> <inputtype="text"id="image"name="image"> <buttontype="submit">DODAJ</button> </form> </section> <footer> <formmethod="post"> <inputtype="number"name="game_id"min="1"aria-label="Identyfikator gry"requiredvalue="<?= $gameId !== false ? e($gameId) : ''?>"> <buttontype="submit">Pokaż opis</button> </form><?phpif ($selectedGame): ?> <h2><?= e($selectedGame['nazwa']) ?>, <?= e($selectedGame['punkty']) ?> punktów, <?= e($selectedGame['cena']) ?> zł</h2> <p><?= e($selectedGame['opis']) ?></p><?phpelseif ($gameId !== false): ?> <p>Nie ma gry o identyfikatorze <?= e($gameId) ?>.</p><?phpendif; ?> </footer></body></html>
R.5.2 Ostatnią operacją na bazie jest jej zamknięcie
ok. 1 min
Polecenie z arkusza
Na końcu jest zamykane połączenie z serwerem.
W PDO połączenie zamyka się, usuwając obiekt: $pdo = null;. Ponieważ wszystkie zapytania są na górze pliku, zamknięcie stoi zaraz po ostatnim zapytaniu, jeszcze przed HTML - połączenie z bazą nie wisi otwarte, gdy serwer wysyła stronę. To wciąż ostatnia operacja na bazie (R.5.2).
Szablon niżej korzysta już tylko z tablic $topGames, $games i $selectedGame, które zostają w pamięci po zamknięciu połączenia.
Typowe błędy
$pdo = null działa tylko wtedy, gdy nic innego nie trzyma referencji do połączenia. Niezamknięte $statement też ją trzyma - przy jednym zapytaniu nie ma to znaczenia, ale w dłuższym kodzie warto zerować także obiekty zapytań.
gry.phpPHPzmienione: 3 linie
<?php$config = require'config.php';try {$pdo = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);} catch (PDOException $exception) {http_response_code(500);exit('Nie udało się połączyć z bazą danych. Spróbuj ponownie za chwilę.');}functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');}// Skrypt 4: nowa gra z formularza w sekcji prawej (zapytanie 4) - przed odczytami$name = trim($_POST['name'] ?? '');if ($name !== '' && mb_strlen($name) <= 20) {$price = filter_var($_POST['price'] ?? '', FILTER_VALIDATE_FLOAT);$statement = $pdo->prepare('INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie) VALUES (:name, :description, 0, :price, :image)' );$statement->execute(['name' => $name,'description' => trim($_POST['description'] ?? ''),'price' => $price === false ? null : $price,'image' => trim($_POST['image'] ?? ''), ]);header('Location: gry.php');exit;}// Skrypt 1: pięć gier z najwyższą punktacją (zapytanie 3)$topGames = $pdo->query('SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5')->fetchAll();// Skrypt 2: wszystkie gry do galerii (zapytanie 1)$games = $pdo->query('SELECT id, nazwa, zdjecie FROM gry')->fetchAll();// Skrypt 3: opis gry o id z formularza w stopce (zapytanie 2)$selectedGame = false;$gameId = filter_var($_POST['game_id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);if ($gameId !== false) {$statement = $pdo->prepare('SELECT nazwa, LEFT(opis, 100) AS opis, punkty, cena FROM gry WHERE id = :id');$statement->execute(['id' => $gameId]);$selectedGame = $statement->fetch();}// Wszystkie dane pobrane - zamknięcie połączenia$pdo = null;?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Gry komputerowe</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Ranking gier komputerowych</h1> </header> <sectionid="left"> <h3>Top 5 gier w tym miesiącu</h3> <ul><?phpforeach ($topGamesas$game): ?> <li><?= e($game['nazwa']) ?> <spanclass="points"><?= e($game['punkty']) ?></span></li><?phpendforeach; ?> </ul> <h3>Nasz sklep</h3> <ahref="http://sklep.gry.pl">Tu kupisz gry</a> <h3>Stronę wykonał</h3> <p>Bartosz Bryniarski</p> </section> <sectionid="middle"><?phpforeach ($gamesas$game): ?> <articleclass="game"> <imgsrc="<?= e($game['zdjecie']) ?>"alt="<?= e($game['nazwa']) ?>"title="<?= e($game['id']) ?>"> <p><?= e($game['nazwa']) ?></p> </article><?phpendforeach; ?> </section> <sectionid="right"> <h3>Dodaj nową grę</h3> <formmethod="post"> <labelfor="name">nazwa</label> <inputtype="text"id="name"name="name"maxlength="20"required> <labelfor="description">opis</label> <inputtype="text"id="description"name="description"> <labelfor="price">cena</label> <inputtype="number"id="price"name="price"min="0"step="0.01"> <labelfor="image">zdjęcie</label> <inputtype="text"id="image"name="image"> <buttontype="submit">DODAJ</button> </form> </section> <footer> <formmethod="post"> <inputtype="number"name="game_id"min="1"aria-label="Identyfikator gry"requiredvalue="<?= $gameId !== false ? e($gameId) : ''?>"> <buttontype="submit">Pokaż opis</button> </form><?phpif ($selectedGame): ?> <h2><?= e($selectedGame['nazwa']) ?>, <?= e($selectedGame['punkty']) ?> punktów, <?= e($selectedGame['cena']) ?> zł</h2> <p><?= e($selectedGame['opis']) ?></p><?phpelseif ($gameId !== false): ?> <p>Nie ma gry o identyfikatorze <?= e($gameId) ?>.</p><?phpendif; ?> </footer></body></html>
Kroki 43-44 · po tym etapie masz 41 z 41 kryteriów (pkt)
Krok 43: Co jest inaczej i dlaczego
ok. 3 min
Polecenie z arkusza
Ocenie będzie podlegać 5 rezultatów: operacje na bazie danych, zawartość witryny internetowej, działanie witryny internetowej, styl CSS witryny internetowej, skrypt połączenia z bazą.
Obie wersje dają te same punkty. Różnice:
wersja podstawowa
ta wersja
układ
float + clear, @media (max-width: 800px) odwołuje układ
grid, mobile first, @media (width > 800px) dokłada kolumny
kolory
wpisane w każdej regule
zmienne w :root
baza
mysqli_* z tabeli 1
PDO, wyjątki, FETCH_ASSOC
dane w zapytaniach
sklejane z napisem
parametry nazwane :id, :name
wypisywanie
echo w pętli while
logika na górze, szablon z foreach/endforeach i e()
po dodaniu gry
strona z formularzem
przekierowanie (PRG)
Czego tu jeszcze nie ma, a w prawdziwym serwisie by było: token CSRF w formularzach (ukryte pole z losową wartością z sesji, sprawdzane przy zapisie - blokuje wysłanie formularza z cudzej strony) i ograniczenie dostępu do dodawania gier. Klucz tego nie wymaga.
Czas przeznaczony na wykonanie zadania wynosi 150 minut.
Wersja podstawowa, gdy liczy się czas albo PDO nie jest jeszcze opanowane: funkcje mysqli_* są podpowiedziane w tabeli 1 arkusza, a każdy fragment PHP stoi tam, gdzie widać jego efekt.
Ta wersja, gdy PDO i siatka CSS są pisane z pamięci. Kodu jest podobnie dużo, a błąd łatwiej znaleźć: wyjątek PDO wskazuje złe zapytanie, zamiast zostawiać pustą listę.
Mieszanie też jest dopuszczalne: siatka CSS z tej wersji i mysqli_* z podstawowej dadzą pełne punkty. Najważniejsze, żeby pisać to, co się umie bez sprawdzania składni - 150 minut nie zostawia czasu na szukanie literówki w PDO::ATTR_ERRMODE.
Krok 45 · po tym etapie masz 41 z 41 kryteriów (pkt)
Krok 45: 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/….
Na płycie, w folderze z numerem zdającego: Czarodziejka.jpg, gry.php, import.png, kalipso.jpg, katedra.jpg, krokodyl.jpg, kw1.jpg - kw4.jpg, kwerendy.txt, Mag.jpg, NieznanyLad.jpg, Persja.jpg, przeglądarka.txt, styl.css, WyprawaNaKsiezyc.jpg, zamczysko.jpg oraz config.php - bez niego strona się nie połączy z bazą. Po nagraniu sprawdź odczyt płyty.
Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.
To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.
Autor rozwiązania: Bartosz Bryniarski. Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.