INF.03-07-25.06-SGCzerwiec 2025 · 56 kroków · ok. 98 min · 42 kryteria z klucza
Rozwiązanie krok po kroku: Portal wycieczek turystycznych (Wersja podstawowa)
Rozwiązanie buduje portal biura turystycznego: baza wyprawy z pięcioma operacjami (sortowanie, LIKE, ROUND(AVG()) z GROUP BY, CREATE TABLE), grafika przeskalowana i odbita w pionie, strona index.php z trzema podstronami, formularzem symulacji ceny i galerią ofert, style w styl.css (przezroczyste tło rgba, zaokrąglenia, float) oraz trzy skrypty PHP: lista miejsc, obliczenie kosztu wycieczki z danych formularza i bloki ofert z obrazami.
Kroki idą zdanie po zdaniu za treścią arkusza - każdy zaczyna się poleceniem z arkusza, a kod pokazuje dokładnie te linie, które je spełniają. Kod PHP jest najpierw pisany funkcjami z tabeli 1 arkusza (proceduralne mysqli), a na końcu zostaje przerobiony na wersję z prawdziwych projektów: zapytanie przygotowane z bindowaniem, escapowanie danych, walidacja liczb i formatowanie kwoty. W tym arkuszu nie ma JavaScriptu.
To jest wersja podstawowa. W zakładce „Nowoczesny kod (grid, PDO)” jest drugi sposób rozwiązania tego samego arkusza: układ na siatce CSS i PHP na PDO z zapytaniem przygotowanym od pierwszej linii - obie wersje są zgodne z kluczem.
Zdjęcia i baza pochodzą z prawdziwego archiwum pliki10 (załącznik do pobrania na stronie arkusza, hasło w treści zadania). Zrzuty wyników zapytań są odtworzone z tej bazy - na egzaminie robisz je sam, z całego ekranu.
Jak liczą się punkty. Klucz ma 42 kryteria (R.1.1 - R.5.10), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 32 punkty - można stracić 10 kryteriów i nadal zdać. 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-8 · po tym etapie masz 7 z 42 kryteriów (pkt)
Krok 1: Baza danych wyprawy
Kryteria:
R.1.1 Wykonany import tabel do bazy danych wyprawy , czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie wyprawy, z zestawem polskich znaków (np. utf8_polish_ci).
W przeglądarce otwierasz http://localhost/phpmyadmin, klikasz Nowa w lewym panelu, wpisujesz nazwę wyprawy i z listy metod porównywania wybierasz utf8_polish_ci. O kolejności w zapytaniu 1 to ustawienie jednak nie decyduje: tabela miejsca z pliku wyprawy.sql ma porównywanie utf8_polish_ci zapisane we własnej definicji i to ono sortuje nazwy. Porównywanie bazy dziedziczą tylko tabele zakładane później bez własnego ustawienia - tu tabela klienci z zapytania 5.
Typowe błędy
Nazwa bazy musi być dokładnie wyprawy - skrypty PHP łączą się z bazą po nazwie, więc literówka odetnie punkty także za skrypty (R.5.1).
Krok 2: Import tabel z pliku wyprawy.sql
Kryteria:
R.1.1 Wykonany import tabel do bazy danych wyprawy , czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Z rozpakowanego archiwum zaimportuj tabele z pliku wyprawy.sql do utworzonej bazy.
Wejdź do bazy wyprawy (musi być zaznaczona w lewym panelu), karta Import, Wybierz plik → wyprawy.sql z rozpakowanego archiwum, Wykonaj.
Po imporcie są dwie tabele z ilustracji 1: miejsca (id_miejsca, nazwa, link_obraz, cena - 21 wierszy; link_obraz to nazwa pliku zdjęcia z archiwum, np. 01.jpg) i wycieczki (id_wcieczki, id_miejsca, liczba_doroslych, liczba_dzieci - 52 wiersze). Kolumna id_miejsca w wycieczkach to klucz obcy do miejsc - jedno miejsce ma wiele wycieczek.
Typowe błędy
Import bez wcześniejszego wejścia do bazy wyprawy tworzy tabele w innej bazie (np. test) - skrypty nie znajdą wtedy tabeli miejsca.
Krok 3: Zrzut ekranu import.png
Kryteria:
R.1.1 Wykonany import tabel do bazy danych wyprawy , czynność udokumentowana w pliku import.png
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie PNG pod nazwą import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań.
Klawisz Print Screen (cały ekran, nie Alt+PrtScr, który łapie tylko aktywne okno), wklejasz do edytora grafiki i zapisujesz jako import.png w folderze z numerem zdającego. Na zrzucie ma być widać komunikat phpMyAdmin o pomyślnym imporcie albo obie tabele w bazie. W tym arkuszu wszystkie zrzuty (import i pięć kwerend) są w PNG.
Typowe błędy
Wykadrowany zrzut (samo okno przeglądarki, bez paska zadań) nie jest oceniany - klucz mówi to wprost.
Krok 4: Zapytanie 1: nazwy miejscowości rosnąco
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 nazwy wszystkich miejscowości posortowane rosnąco. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa FROM miejsca ORDER BY nazwa; oraz wynik działania zapytania udokumentowany zrzutem, na którym widocznych jest dokładnie 21 rekordów: pierwszy Brno, ostatni Wiedeń
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie nazwy wszystkich miejscowości posortowane rosnąco.
Zakładka SQL w phpMyAdmin (przy zaznaczonej bazie wyprawy). Klauzula po klauzuli:
SELECT nazwa - jedna kolumna; „jedynie nazwy” wyklucza SELECT *.
FROM miejsca - tabela miejscowości.
ORDER BY nazwa - sortowanie alfabetyczne, rosnąco (domyślne ASC). Bez WHERE, bo „wszystkich”.
Wynik: dokładnie 21 wierszy, od Brna do Wiednia. Zapytanie kopiujesz do pliku kwerendy.txt i robisz zrzut: kw1.png. To samo zapytanie wyśle skrypt 1 do listy rozwijanej.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT nazwa FROM miejsca ORDER BY nazwa;
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: cena wycieczki do Malborka
Kryteria:
R.1.4 Wykonane zapytanie 2 wybierające jedynie cenę wycieczki do Malborka. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT cena FROM miejsca WHERE nazwa = "Malbork"; oraz wynik działania zapytania udokumentowany zrzutem, na którym widoczna jest wartość 2000.00
ok. 1 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie cenę wycieczki do Malborka.
SELECT cena FROM miejsca WHERE nazwa = "Malbork" - jedna kolumna, jeden wiersz wybrany po nazwie (napis w cudzysłowie). Wynik: 2000.00. Zrzut: kw2.png. Skrypt 2 wyśle to samo zapytanie z nazwą miejsca wybraną w formularzu zamiast „Malbork”.
kwerendy.txtTekstzmienione: 3 linie
-- Zapytanie 1SELECT nazwa FROM miejsca ORDER BY nazwa;-- Zapytanie 2SELECT cena FROM miejsca WHERE nazwa = "Malbork";
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: miejsca ze zdjęciem o nazwie od zera
Kryteria:
R.1.5 Wykonane zapytanie 3 wybierające jedynie nazwę, cenę i link obrazu z tabeli miejsca dla wierszy, w których link obrazu rozpoczyna się od 0 (zero). W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%"; oraz wynik działania zapytania udokumentowany zrzutem, na którym widocznych jest dokładnie 9 rekordów, pierwszy Malbork 2000, 01.jpg ; ostatni Olsztyn 660 09.jpg
ok. 2 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie nazwę, cenę i link obrazu z tabeli miejsca dla wierszy, w których link obrazu rozpoczyna się od 0 (zero).
SELECT nazwa, cena, link_obraz FROM miejsca - trzy kolumny.
WHERE link_obraz LIKE "0%" - LIKE porównuje ze wzorcem: "0%" to „zero, a potem cokolwiek” (% zastępuje dowolny ciąg znaków). „Rozpoczyna się od” = % tylko na końcu wzorca.
Wynik: dokładnie 9 wierszy, od Malborka (01.jpg) do Olsztyna (09.jpg) - zdjęcia 10.jpg i dalej zaczynają się od jedynki lub dwójki. Zrzut: kw3.png. To zapytanie wyśle skrypt 3 do galerii ofert.
Typowe błędy
LIKE "%0%" znajdzie też 10.jpg i 20.jpg (zero w środku) - 11 wierszy zamiast 9. Wzorzec „zaczyna się od” ma % tylko po prawej.
kwerendy.txtTekstzmienione: 3 linie
-- Zapytanie 1SELECT nazwa FROM miejsca ORDER BY nazwa;-- Zapytanie 2SELECT cena FROM miejsca WHERE nazwa = "Malbork";-- Zapytanie 3SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: średnia liczba dorosłych na miejsce
Kryteria:
R.1.6 Wykonane zapytanie 4 wybierające jedynie nazwy wszystkich miejsc i odpowiadające im obliczone średnie liczby osób dorosłych, zgrupowane według nazw miejsc. Średnia liczba osób jest zaokrąglona do liczby całkowitej. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, ROUND(AVG(liczba_doroslych)) FROM miejsca JOIN wycieczki USING(id_miejsca) GROUP BY nazwa; (lub porównanie kluczy po ON lub WHERE, dowolna metoda zaokrąglenia do liczby całkowitej) oraz wynik działania zapytania udokumentowany zrzutem, na którym widocznych jest dokładnie 16 rekordów dla Brna, Gdańska, ..., Warszawy, Wenecji
ok. 4 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie nazwy wszystkich miejsc i odpowiadające im obliczone średnie liczby osób dorosłych, zgrupowane według nazw miejsc. Średnia liczba osób jest zaokrąglona do liczby całkowitej. Należy posłużyć się relacją.
Nazwa jest w miejsca, liczba dorosłych w wycieczki - „posłużyć się relacją” znaczy złączyć tabele po id_miejsca.
SELECT nazwa, ROUND(AVG(liczba_doroslych)) - funkcja grupująca AVG liczy średnią, a ROUND zaokrągla ją do liczby całkowitej (funkcja w funkcji - najpierw średnia, potem zaokrąglenie).
FROM miejsca JOIN wycieczki ON miejsca.id_miejsca = wycieczki.id_miejsca - do każdej wycieczki dokładasz jej miejsce. Kolumna nazywa się tak samo w obu tabelach, więc trzeba ją poprzedzić nazwą tabeli; klucz CKE pokazuje też skrót USING(id_miejsca).
GROUP BY nazwa - wiersze o tej samej nazwie zlewają się w jedną grupę, AVG liczy osobno dla każdej.
Wynik: dokładnie 16 wierszy (miejsca bez żadnej wycieczki nie mają pary w złączeniu). Zrzut: kw4.png.
Typowe błędy
AVG(ROUND(liczba_doroslych)) zaokrągla każdą liczbę osobno (liczby całkowite - bez efektu), a średnia zostaje ułamkiem. Zaokrąglij wynik średniej: ROUND(AVG(...)).
kwerendy.txtTekstzmienione: 3 linie
-- Zapytanie 1SELECT nazwa FROM miejsca ORDER BY nazwa;-- Zapytanie 2SELECT cena FROM miejsca WHERE nazwa = "Malbork";-- Zapytanie 3SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";-- Zapytanie 4SELECT nazwa, ROUND(AVG(liczba_doroslych)) FROM miejsca JOIN wycieczki ON miejsca.id_miejsca = wycieczki.id_miejsca GROUP BY nazwa;
Podgląd strony po tym kroku
Krok 8: Zapytanie 5: nowa tabela klienci
Kryteria:
R.1.7 Wykonane zapytanie 5 tworzące tabelę klienci z kolumnami: Klucz podstawowy: id_klienta jako liczba całkowita dodatnia, autoinkrementowany, nie może przyjmować wartości NULL, imie typu tekstowego o długości 20 znaków, nazwisko typu tekstowego o długości 40 znaków, data_ur typu daty. W pliku z kwerendami lub na zrzucie istnieje zapis np.: CREATE TABLE klienci ( id_klienta INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, imie VARCHAR(20), nazwisko VARCHAR(40), data_ur DATE ); oraz wynik działania zapytania udokumentowany zrzutem, na którym jest komunikat o poprawnym wykonaniu zapytania lub struktura tabeli klienci
ok. 3 min
Polecenie z arkusza
Zapytanie 5: tworzące tabelę klienci z kolumnami: id_klienta jako liczba całkowita dodatnia, klucz podstawowy, autoinkrementowany, nie może przyjmować wartości NULL; imie typu tekstowego o długości do 20 znaków; nazwisko typu tekstowego o długości do 40 znaków; data_ur typu data.
CREATE TABLE klienci (...) z listą kolumn po przecinku; każde słowo z arkusza ma swój odpowiednik w SQL:
„liczba całkowita dodatnia” → INT UNSIGNED, „nie może przyjmować NULL” → NOT NULL, „autoinkrementowany” → AUTO_INCREMENT, „klucz podstawowy” → PRIMARY KEY,
„tekstowy o długości do 20 znaków” → VARCHAR(20) (do - czyli zmienna długość, nie CHAR),
„typu data” → DATE.
Zrzut kw5.png ma pokazać komunikat o wykonaniu albo strukturę nowej tabeli. Plik kwerendy.txt ma komplet pięciu zapytań.
Typowe błędy
AUTO_INCREMENT bez PRIMARY KEY (albo innego klucza) to błąd MySQL - kolumna autoinkrementowana musi być kluczem.
kwerendy.txtTekstzmienione: 8 linii
-- Zapytanie 1SELECT nazwa FROM miejsca ORDER BY nazwa;-- Zapytanie 2SELECT cena FROM miejsca WHERE nazwa = "Malbork";-- Zapytanie 3SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";-- Zapytanie 4SELECT nazwa, ROUND(AVG(liczba_doroslych)) FROM miejsca JOIN wycieczki ON miejsca.id_miejsca = wycieczki.id_miejsca GROUP BY nazwa;-- Zapytanie 5CREATE TABLE klienci ( id_klienta INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, imie VARCHAR(20), nazwisko VARCHAR(40), data_ur DATE);
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 9-27 · po tym etapie masz 19 z 42 kryteriów (pkt)
Krok 9: Grafika 02.jpg: szerokość 200 px i odbicie pionowe
Kryteria:
R.2.1 Grafika 02.jpg przeskalowana z zachowaniem proporcji do szerokości 200 px, odpowiadająca temu wysokość wynosi 134 px (± 5 px)
ok. 3 min
Polecenie z arkusza
Cechy grafiki 02.jpg: grafika przeskalowana z zachowaniem proporcji do szerokości 200 px. Grafika odbita pionowo.
Dwadzieścia zdjęć z archiwum ma już 200 × 134 px, tylko 02.jpg jest większe: 800 × 536 px. W GIMP-ie: Obraz → Skaluj obraz, szerokość 200 px z zamkniętym łańcuchem proporcji - wysokość wychodzi 134 px (klucz: 134 ±5). „Odbita pionowo” to Obraz → Przekształć → Odbij pionowo - zdjęcie do góry nogami, jak w podglądzie. Eksport pod tą samą nazwą 02.jpg.
Typowe błędy
„Odbij poziomo” (lustro) to nie to samo co „odbij pionowo” (góra-dół). Arkusz mówi „pionowo”. Przy skalowaniu bez proporcji wysokość nie wyjdzie 134 px.
Składa się ze strony głównej index.php oraz podstron wczasy.html, wycieczki.html, allinclusive.html. Należy utworzyć trzy pliki podstron i zapisać w nich „Strona w trakcie budowy”.
Trzy najprostsze pliki z samym napisem „Strona w trakcie budowy” - arkusz wymaga tylko tego. Istnieją po to, żeby odnośniki z nawigacji miały dokąd prowadzić (klucz R.3.3 sprawdza, czy każdy się otwiera). Nazwy dokładnie z arkusza: allinclusive.html jednym słowem, bez myślnika i spacji.
allinclusive.htmlHTMLnowy plik
Strona w trakcie budowy
wczasy.htmlHTMLnowy plik
Strona w trakcie budowy
wycieczki.htmlHTMLnowy plik
Strona w trakcie budowy
Podgląd strony po tym kroku (allinclusive.html)
Krok 11: Plik index.php w HTML5
Kryteria:
R.2.2 W pliku index.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
Cechy strony index.php: zapisana w języku HTML5.
W edytorze zaznaczającym składnię tworzysz plik index.php w folderze zdającego, obok zdjęć i podstron. Rozszerzenie .php, bo dojdą skrypty PHP - na razie to czysty HTML.
Pierwsza linia <!DOCTYPE html> mówi przeglądarce, że dokument jest w HTML5. Dalej szkielet każdej strony: <html>, a w nim <head> (informacje o stronie) i <body> (to, co widać w oknie).
R.2.2 W pliku index.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków
ok. 1 min
Polecenie z arkusza
Zadeklarowany polski język zawartości witryny.
Atrybut lang="pl" w znaczniku <html>. Czytnik ekranu czyta wtedy tekst polską wymową, a przeglądarka dobiera słownik. Na stronie nic się nie zmienia - to informacja dla programów.
R.2.2 W pliku index.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków
ok. 1 min
Polecenie z arkusza
Jawnie zastosowany właściwy standard kodowania polskich znaków.
<meta charset="UTF-8"> jako pierwsza rzecz w <head>. „Jawnie” znaczy: wpisane w kodzie, nie zdane na domysł przeglądarki. Bez tej linii „Gdańsk” i „Wiedeń” z bazy mogłyby wyświetlić się jako krzaki. Plik trzeba też zapisać w UTF-8.
R.3.2 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.css formatowanie CSS pochodzi jedynie z tego arkusza
ok. 1 min
Polecenie z arkusza
Arkusz stylów w pliku o nazwie styl.css prawidłowo połączony z kodem strony.
<link rel="stylesheet" href="styl.css"> w <head>. W tym arkuszu nazwa to styl.css - sprawdź ją w treści zadania, bo w innych arkuszach bywa style.css albo styles.css. Plik napiszesz po HTML.
Typowe błędy
Zła nazwa pliku w href to brak stylów i cały rezultat R.4 przepada, bo „formatowanie CSS pochodzi jedynie z tego arkusza”.
R.2.4 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML 5: nav, main->aside+section, footer
ok. 2 min
Polecenie z arkusza
Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 3. Zawartość bloku głównego: blok boczny i sekcja.
Z ilustracji 3: blok nawigacyjny na górze, pod nim blok główny z blokiem bocznym i sekcją obok siebie, na dole stopka. Klucz R.2.4 wymienia znaczniki wprost: <nav>, <main> z <aside> i <section> w środku, <footer>. Tu nie ma <header> - arkusz go nie wymaga.
Blok boczny i sekcja nie potrzebują identyfikatorów: każdy znacznik występuje raz, więc CSS sięgnie po nie selektorami znaczników. Nazwy identyfikatorów, klas i zmiennych piszesz w tym rozwiązaniu po angielsku (arkusz dopuszcza polski lub angielski, wymaga konsekwencji); wyjątkiem są nazwy narzucone przez arkusz (index.php, styl.css, klasa wycieczka, kolumny bazy).
Typowe błędy
<aside> i <section> poza <main> nie spełniają R.2.4 - klucz mówi „main → aside + section”.
Zawartość bloku nawigacyjnego: lista punktowana zawierająca trzy elementy: odnośnik do podstrony wczasy.html o treści „Wczasy”, odnośnik do podstrony wycieczki.html o treści „Wycieczki”, odnośnik do podstrony allinclusive.html o treści „All inclusive”.
Lista <ul> z trzema <li>, w każdym odnośnik <a> do podstrony z folderu (ścieżka względna - sama nazwa pliku) z tekstem z arkusza. Klucz R.2.6 liczy trzy elementy, R.3.3 sprawdza, czy każdy odnośnik otwiera właściwą podstronę. W podglądzie kliknięcia są zablokowane. Punktory zdejmiesz w CSS.
R.2.5 Umieszczony w bloku bocznym <h3>: „Twój cel wyprawy” i <h4>: „Koszt wycieczki", w sekcji <h3>: „Wycieczki” oraz paragraf z numerem zdającego w stopce
ok. 1 min
Polecenie z arkusza
Zawartość bloku bocznego: nagłówek trzeciego stopnia o treści: „Twój cel wyprawy”.
Trzeciego stopnia to <h3>, w <aside>. Klucz R.2.5 wymienia też <h4> niżej w tym bloku i <h3> w sekcji - nie mieszaj stopni.
R.2.7 W bloku bocznym umieszczony formularz z listą rozwijaną, 3 polami edycyjnymi i przyciskiem „Symulacja ceny”
R.3.4 Formularz wysyła dane metodą POST do tego samego pliku (poprawne również bez atrybutu action)
ok. 1 min
Polecenie z arkusza
Formularz zgodny z ilustracją 2, wysyłający dane do tego samego pliku metodą bezpieczną, z zawartością: etykiety, lista rozwijana, pola edycyjne i przycisk.
„Metoda bezpieczna” w rozumieniu arkusza to POST - dane nie pojawiają się w adresie strony. W specyfikacji HTTP „bezpieczne” są co prawda metody, które niczego nie zmieniają na serwerze (GET i HEAD), ale arkusz używa tego słowa w innym sensie, a klucz wymaga POST. „Do tego samego pliku” to brak atrybutu action (przeglądarka wysyła formularz na adres bieżącej strony) albo action="index.php" - klucz R.3.4 przyjmuje oba warianty.
Typowe błędy
method="get" (albo brak method) wyśle dane w adresie - arkusz i klucz wymagają tu POST, a skrypt czytający $_POST nic nie dostanie.
R.2.7 W bloku bocznym umieszczony formularz z listą rozwijaną, 3 polami edycyjnymi i przyciskiem „Symulacja ceny”
R.3.6 Przynajmniej jedna etykieta jest powiązana z kontrolką, dwa pola edycyjne są typu number, jedno typu date
ok. 2 min
Polecenie z arkusza
Etykieta „Miejsce wycieczki” powiązana z listą rozwijaną. Lista rozwijana wypełniona przez skrypt 1.
Etykieta <label> jest „powiązana” z listą przez atrybut for równy id listy (klucz R.3.6 sprawdza przynajmniej jedno takie powiązanie). <select> ma id="place" dla etykiety i name="place" dla PHP - pod tą nazwą wybrane miejsce trafi do $_POST. Opcje wstawi skrypt 1. Na ilustracji 4 etykieta stoi nad kontrolką - stąd <br> po etykiecie i po liście.
R.2.7 W bloku bocznym umieszczony formularz z listą rozwijaną, 3 polami edycyjnymi i przyciskiem „Symulacja ceny”
R.3.6 Przynajmniej jedna etykieta jest powiązana z kontrolką, dwa pola edycyjne są typu number, jedno typu date
ok. 1 min
Polecenie z arkusza
Etykieta „Ile dorosłych?” powiązana z pierwszym polem edycyjnym. Pole edycyjne typu liczbowego.
Ten sam wzorzec: <label for="adults"> i pole z id="adults". „Typ liczbowy” to type="number" - przeglądarka nie pozwoli wpisać liter (klucz R.3.6: dwa pola number). Atrybut name="adults" dla PHP.
R.2.7 W bloku bocznym umieszczony formularz z listą rozwijaną, 3 polami edycyjnymi i przyciskiem „Symulacja ceny”
R.3.6 Przynajmniej jedna etykieta jest powiązana z kontrolką, dwa pola edycyjne są typu number, jedno typu date
ok. 1 min
Polecenie z arkusza
Etykieta „Termin” powiązana z trzecim polem edycyjnym. Pole edycyjne typu data.
„Typ data” to type="date" - przeglądarka pokazuje kalendarz i wysyła datę w formacie RRRR-MM-DD (tak wygląda też w ilustracji 4: „W dniu: 2024-04-25”). Klucz R.3.6 sprawdza jedno pole date.
R.2.7 W bloku bocznym umieszczony formularz z listą rozwijaną, 3 polami edycyjnymi i przyciskiem „Symulacja ceny”
R.3.5 Przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu)
ok. 1 min
Polecenie z arkusza
Przycisk „Symulacja ceny” wysyłający dane z formularza do skryptu 2.
<button type="submit">Symulacja ceny</button> wewnątrz formularza wysyła go po kliknięciu. Klucz R.3.5 przyjmuje też <input type="submit" value="Symulacja ceny">.
R.2.5 Umieszczony w bloku bocznym <h3>: „Twój cel wyprawy” i <h4>: „Koszt wycieczki", w sekcji <h3>: „Wycieczki” oraz paragraf z numerem zdającego w stopce
ok. 1 min
Polecenie z arkusza
Nagłówek czwartego stopnia o treści „Koszt wycieczki”. Efekt działania skryptu 2.
Czwartego stopnia to <h4>, pod formularzem. „Efekt działania skryptu 2” (dwa paragrafy z terminem i kwotą) pojawi się pod tym nagłówkiem - dopisze go PHP.
R.2.5 Umieszczony w bloku bocznym <h3>: „Twój cel wyprawy” i <h4>: „Koszt wycieczki", w sekcji <h3>: „Wycieczki” oraz paragraf z numerem zdającego w stopce
ok. 1 min
Polecenie z arkusza
Zawartość sekcji: nagłówek trzeciego stopnia o treści: „Wycieczki”. Efekt działania skryptu 3.
<h3> w <section>. Pod nim skrypt 3 wygeneruje bloki ofert z obrazami - w HTML sekcja ma tylko nagłówek.
R.2.5 Umieszczony w bloku bocznym <h3>: „Twój cel wyprawy” i <h4>: „Koszt wycieczki", w sekcji <h3>: „Wycieczki” oraz paragraf z numerem zdającego w stopce
ok. 1 min
Polecenie z arkusza
Zawartość stopki: paragraf o treści „Autor: ”, dalej wstawiony numer zdającego.
Paragraf <p> w <footer>. W tym rozwiązaniu po dwukropku stoi nazwisko autora - na egzaminie wpisujesz swój numer zdającego (PESEL), bo tego sprawdza klucz R.2.5. Struktura HTML jest kompletna - dalej style.
Kroki 28-38 · po tym etapie masz 30 z 42 kryteriów (pkt)
Krok 28: Plik styl.css: krój czcionki i wyrównanie
Kryteria:
R.4.1 Ustawione domyślnie dla wszystkich selektorów (*): krój czcionki "Comic Sans MS", sans- serif (tylko taka kolejność ) oraz wyrównanie tekstu do środka
ok. 1 min
Polecenie z arkusza
Domyślnie dla wszystkich selektorów: krój czcionki Comic Sans MS, w przypadku braku sans-serif, wyrównanie tekstu do środka.
Utwórz plik styl.css obok index.php. Selektor uniwersalny * obejmuje każdy element. font-family to lista: nazwa z trzech słów w cudzysłowie ("Comic Sans MS"), potem rodzina zapasowa sans-serif - klucz R.4.1 mówi: tylko taka kolejność. text-align: center wyśrodkowuje cały tekst strony.
styl.cssCSSnowy plik
* {font-family: "Comic Sans MS", sans-serif;text-align: center;}
Podgląd strony po tym kroku (index.php)
Krok 29: Nawigacja i stopka: tło, kolor tekstu, margines wewnętrzny
Kryteria: , ,
R.4.2 Ustawiony kolor tła dla bloku nawigacyjnego i stopki #00838F, dla bloku bocznego #B2DFDB
R.4.3 Ustawiony kolor czcionki dla bloku nawigacyjnego, stopki i selektora a #B2DFDB
R.4.6 Ustawione marginesy wewnętrzne (padding) dla bloku nawigacyjnego i stopki 10px oraz marginesy zewnętrzne (margin): 7px dla selektora img i jedynie lewy 50px dla odnośnika
ok. 1 min
Polecenie z arkusza
Dla bloku nawigacyjnego i stopki: kolor tła #00838F, kolor czcionki #B2DFDB, marginesy wewnętrzne 10 px.
Wspólne cechy dwóch bloków piszesz raz - nav, footer. Kolory szesnastkowe przepisujesz z arkusza (ciemny morski i jasny miętowy). Margines wewnętrzny to padding.
Krok 30: Blok boczny i sekcja: wspólne wymiary i paski przewijania
Kryteria: ,
R.4.4 Ustawione dla bloku bocznego i sekcji wysokość 600px oraz szerokość 50% (width lub grid-template-columns lub flex)
R.4.5 Ustawiona dla bloku bocznego i sekcji właściwość overflow: auto;
ok. 1 min
Polecenie z arkusza
Wspólne dla bloku bocznego i sekcji: szerokość 50%, wysokość 600 px, paski przewijania pojawiające się tylko w przypadku przepełnienia bloku.
Selektory znaczników aside, section. „Paski tylko przy przepełnieniu” to overflow: auto (klucz R.4.5). Galeria ofert w sekcji będzie dłuższa niż 600 px - pojawi się pasek jak na ilustracji 2. Obok siebie ustawisz je za dwa kroki.
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 3 w arkuszu egzaminacyjnym (prawidłowo zastosowane właściwości CSS decydujące o układzie bloków na stronie, np. float i clear albo display flex albo grid albo position)
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.
Dopisz float: left do wspólnej reguły aside, section - oba bloki pływają w lewo i ustawiają się obok siebie (50% + 50%). Stopka dostaje osobną regułę clear: both, żeby wróciła pod pływające bloki (wspólna reguła nav, footer zostaje). Układ zgadza się z ilustracją 3; klucz R.3.1 przyjmuje też flex i grid.
Typowe błędy
Bez clear: both stopka wjeżdża obok pływających bloków, a <main> ma zerową wysokość.
Krok 33: Klasa wycieczka: tło z przezroczystością, wymiary, zaokrąglenie
Kryteria:
R.4.9 Ustawiony styl klasy dla wycieczki: kolor tła #00838F z przezroczystością 0.7 lub RGBA(0,131,143,0.7) lub #00838fb3, wysokość 150px, marginesy zewnętrzne: górny i dolny 10px, boczne 100px, zaokrąglenie rogów 30px
ok. 3 min
Polecenie z arkusza
Dla klasy wycieczka, przypisanej do bloków tworzonych przez skrypt 3: kolor tła #00838F z przezroczystością 0.7, wysokość 150 px, marginesy zewnętrzne: górny i dolny 10 px, boczne 100 px, zaokrąglenie rogów 30 px.
Nazwę klasy narzuca arkusz: .wycieczka (kropka = klasa). „Kolor #00838F z przezroczystością 0.7” zapisujesz jako rgba(0, 131, 143, 0.7) - trzy składowe koloru w systemie dziesiętnym (00 → 0, 83 → 131, 8F → 143) i czwarta: krycie 0.7. Klucz R.4.9 przyjmuje też zapis #00838fb3. Skrót margin: 10px 100px to góra i dół 10 px, lewa i prawa 100 px. Bloków jeszcze nie ma - wygeneruje je skrypt 3.
Typowe błędy
opacity: 0.7 na bloku przyciemniłby też obraz i tekst wewnątrz - arkusz mówi o przezroczystości koloru tła, więc rgba.
R.4.10 Gdy kursor znajdzie się na odnośniku, jego kolor czcionki zmienia się na biały i dodawane jest podkreślenie lub gdy kursor znajdzie się na bloku ostylowanym klasą wycieczka, przezroczystość wynosi 1
ok. 1 min
Polecenie z arkusza
Gdy kursor znajdzie się na bloku z przypisaną klasą wycieczka, przezroczystość wynosi 1.
.wycieczka:hover z pełnym kryciem - ten sam kolor bez kanału alfa, #00838F (równoważnie rgba(0, 131, 143, 1)). Blok pod kursorem „nabiera koloru”, jak trzeci blok na ilustracji 2 (podpis: kursor na trzecim bloku).
R.4.6 Ustawione marginesy wewnętrzne (padding) dla bloku nawigacyjnego i stopki 10px oraz marginesy zewnętrzne (margin): 7px dla selektora img i jedynie lewy 50px dla odnośnika
R.4.7 Ustawione dla obrazu opływanie po jego lewej stronie (float: right;) oraz zaokrąglenie rogów 30px
ok. 1 min
Polecenie z arkusza
Dla selektora obrazu: opływanie po jego lewej stronie (obraz jest po prawej stronie), zaokrąglenie rogów 30 px, marginesy zewnętrzne 7 px.
Selektor znacznika img (arkusz tego wymaga). „Opływanie po lewej stronie obrazu, obraz po prawej” to float: right - obraz odsuwa się na prawą krawędź bloku, a nagłówek i cena stają po jego lewej stronie, jak na ilustracji 2. Klucz R.4.7 podaje float: right wprost. Zaokrąglone rogi i margines 7 px z każdej strony.
Typowe błędy
float: left ustawia obraz po lewej - arkusz opisuje stronę opływania („po jego lewej”), ale w nawiasie wyjaśnia: obraz po prawej. Klucz R.4.7: float: right.
Krok 36: Element listy: liniowo-blokowy, bez punktorów
Kryteria:
R.4.8 Ustawione dla selektora li: display: inline-block; punktory nie są widoczne oraz dla odnośnika styl bez podkreślenia text-decoration: none;
ok. 1 min
Polecenie z arkusza
Dla selektora elementu listy: sposób wyświetlania liniowo-blokowy, bez punktorów.
Selektor znacznika li. display: inline-block (tabela 2 arkusza) ustawia elementy listy obok siebie w jednej linii, zachowując wymiary bloku; list-style-type: none zdejmuje punktory. Trzy odnośniki stają w rzędzie na pasku nawigacji.
Krok 37: Odnośnik: kolor, bez podkreślenia, lewy margines
Kryteria: , ,
R.4.3 Ustawiony kolor czcionki dla bloku nawigacyjnego, stopki i selektora a #B2DFDB
R.4.6 Ustawione marginesy wewnętrzne (padding) dla bloku nawigacyjnego i stopki 10px oraz marginesy zewnętrzne (margin): 7px dla selektora img i jedynie lewy 50px dla odnośnika
R.4.8 Ustawione dla selektora li: display: inline-block; punktory nie są widoczne oraz dla odnośnika styl bez podkreślenia text-decoration: none;
ok. 1 min
Polecenie z arkusza
Dla selektora odnośnika: kolor czcionki #B2DFDB, bez podkreślenia, jedynie margines zewnętrzny lewy 50 px.
Selektor znacznika a. „Bez podkreślenia” to text-decoration: none (klucz R.4.8). margin-left: 50px - margines tylko z lewej, więc odnośniki rozsuwają się od siebie.
R.4.10 Gdy kursor znajdzie się na odnośniku, jego kolor czcionki zmienia się na biały i dodawane jest podkreślenie lub gdy kursor znajdzie się na bloku ostylowanym klasą wycieczka, przezroczystość wynosi 1
ok. 1 min
Polecenie z arkusza
Gdy kursor znajdzie się na odnośniku, jego kolor czcionki zmienia się na biały i dodawane jest podkreślenie.
Pseudoklasa :hover na a: biały kolor i text-decoration: underline - podkreślenie wraca tylko pod kursorem. Najedź w podglądzie na „Wczasy”. Arkusz stylów jest kompletny.
Kroki 39-52 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 39: Połączenie z bazą danych
Kryteria: ,
R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy wyprawy oraz ostatnią operacją na bazie jest jej zamknięcie
R.5.10 W skryptach zastosowana przynajmniej jedna zmienna oraz nazwy wszystkich zmiennych są w języku polskim lub angielskim i odzwierciedlają cel zastosowania
ok. 3 min
Polecenie z arkusza
Napisane w języku PHP. Łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie wyprawy.
Kod PHP piszesz między <?php a ?> - serwer wykonuje go, zanim strona trafi do przeglądarki. Połączenie otwierasz raz, na samym początku pliku, i trzymasz w zmiennej $connection (znacząca, angielska nazwa - R.5.10), z której skorzystają trzy skrypty. mysqli_connect z tabeli 1 arkusza przyjmuje kolejno: serwer, użytkownika, hasło (puste) i nazwę bazy.
Od tego kroku podgląd to wynik działania strony po stronie serwera (PHP nie wykonuje się w przeglądarce) - na razie wygląda jak wcześniej.
Typowe błędy
Kolejność argumentów ma znaczenie: mysqli_connect('localhost', 'root', 'wyprawy') podaje „wyprawy” jako HASŁO. Nazwa bazy jest czwarta. Plik sprawdzaj przez http://localhost/…, nie otwierając go z dysku.
R.5.2 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1 lub Skrypt 3 zawiera instrukcję wysyłającą do bazy danych zapytanie 3
ok. 2 min
Polecenie z arkusza
Skrypt 1: wysyła do bazy danych zapytanie 1.
Skrypt 1 piszesz wewnątrz<select>, bo tam mają trafić opcje. mysqli_query($connection, ...) wysyła zapytanie i zwraca wynik, który zapisujesz w $places. Treść to dokładnie zapytanie 1 z kwerendy.txt - posortowane nazwy wszystkich miejsc.
R.5.3 Skrypt 1 wyświetla kolejne miejsca wycieczki jako elementy listy rozwijanej
ok. 2 min
Polecenie z arkusza
Każde zwrócone zapytaniem miejsce wycieczki wypisuje jako element listy rozwijanej.
„Każde zwrócone miejsce” to pętla. mysqli_fetch_row($places) (tabela 1) oddaje kolejny wiersz jako tablicę od zera, a gdy wierszy zabraknie - false, co kończy while. Zapytanie ma jedną kolumnę, więc nazwa jest w $row[0]. echo wypisuje <option> - bez atrybutu value, więc formularz wyśle nazwę miejsca jako wartość; to wygodne, bo skrypt 2 szuka ceny właśnie po nazwie. Podgląd: lista ma 21 miejsc, jak na ilustracji 4.
Typowe błędy
echo '<option>$row[0]</option>' w apostrofach wypisze dosłownie „$row[0]” - PHP nie podstawia zmiennych w apostrofach.
R.5.4 Skrypt 2 zawiera instrukcję wysyłającą do bazy danych zmodyfikowane zapytanie 2, w ten sposób, że sprawdzane jest miejsce wycieczki wysłane z formularza
ok. 2 min
Polecenie z arkusza
Skrypt 2: sprawdza, czy z formularza zostały wysłane dane, jeżeli tak, to: wysyła zapytanie 2, wylicza wartość wycieczki, wyświetla dwa paragrafy.
Skrypt 2 piszesz pod nagłówkiem „Koszt wycieczki” - tam ma się pojawić wynik. isset($_POST['place']) (tabela 1) jest prawdziwe tylko wtedy, gdy formularz został wysłany - przy pierwszym wejściu na stronę tablica $_POST jest pusta i cały blok if jest pomijany. Dlatego pod nagłówkiem nic jeszcze nie ma.
Dwa dodatkowe warunki is_numeric(...) sprawdzają, że w polach liczby dorosłych i dzieci jest liczba. Bez nich formularz wysłany z pustym polem przerwałby stronę: PHP 8 nie mnoży pustego napisu przez cenę i zgłasza błąd TypeError.
Typowe błędy
Samo isset($_POST['place']) przepuszcza formularz z pustym polem „Ile dzieci?” - rachunek '' * $price kończy się w PHP 8 błędem Unsupported operand types zamiast kwoty.
Krok 43: Skrypt 2: zapytanie 2 z miejscem z formularza
Kryteria:
R.5.4 Skrypt 2 zawiera instrukcję wysyłającą do bazy danych zmodyfikowane zapytanie 2, w ten sposób, że sprawdzane jest miejsce wycieczki wysłane z formularza
ok. 3 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie 2, zmodyfikowane w ten sposób, że sprawdzane jest miejsce wycieczki wysłane z formularza.
Nazwa miejsca z $_POST['place'] do $place. Zapytanie 2 z kwerendy.txt dostaje ją w miejsce „Malbork” - cały napis w cudzysłowie (PHP podstawi zmienną), wartość w SQL w apostrofach. Wynik to jeden wiersz, więc bez pętli: mysqli_fetch_row do $row, cena w $row[0] - zapisujesz ją w $price, bo zaraz będzie potrzebna do rachunku.
index.phpPHPzmienione: 4 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];$result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$row = mysqli_fetch_row($result);$price = $row[0];}?> </aside> <section> <h3>Wycieczki</h3> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer></body></html>
R.5.5 Skrypt 2 wylicza wartość wycieczki na podstawie wysłanej z formularza liczby dorosłych i dzieci oraz ceny zwróconej zapytaniem, przy czym dzieci płacą połowę ceny wycieczki
ok. 2 min
Polecenie z arkusza
Wylicza wartość wycieczki na podstawie wysłanej z formularza liczby dorosłych i dzieci oraz ceny zwróconej zapytaniem, przy czym dzieci płacą połowę ceny wycieczki.
Liczby z pól formularza (adults, children) do zmiennych, a potem jedno działanie: dorośli płacą pełną cenę, dzieci połowę - $adults * $price + $children * $price / 2. Mnożenie i dzielenie mają pierwszeństwo przed dodawaniem, więc nawiasy nie są potrzebne. Dla danych z ilustracji 4 (Tokio 22 530 zł, 2 dorosłych, 4 dzieci): 45 060 + 45 060 = 90 120.
Typowe błędy
($adults + $children / 2) * $price daje ten sam wynik, ale $adults + $children * $price / 2 (bez mnożenia dorosłych przez cenę) - nie. Sprawdź rachunek na przykładzie z ilustracji 4: ma wyjść 90 120.
index.phpPHPzmienione: 3 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];$result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$row = mysqli_fetch_row($result);$price = $row[0];$adults = $_POST['adults'];$children = $_POST['children'];$cost = $adults * $price + $children * $price / 2;}?> </aside> <section> <h3>Wycieczki</h3> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer></body></html>
R.5.6 Skrypt 2 wyświetla tekst „W dniu: <termin>”, gdzie pole <> zostało wysłane z formularza
ok. 2 min
Polecenie z arkusza
Wyświetla paragraf o treści W dniu: <termin>, gdzie pole <> zostało wysłane z formularza.
echo z paragrafem: tekst „W dniu: ” i data z pola date. Element tablicy $_POST wewnątrz cudzysłowu trzeba objąć klamrami: {$_POST['date']}. Pole type="date" wysyła datę w formacie RRRR-MM-DD, więc paragraf wygląda jak na ilustracji 4: „W dniu: 2024-04-25”. Podgląd pokazuje stan po wysłaniu formularza z danymi z ilustracji.
Typowe błędy
"W dniu: $_POST['date']" bez klamer to błąd składni PHP w cudzysłowie - albo klamry, albo najpierw przepisz wartość do zwykłej zmiennej.
index.phpPHPzmienione: 1 linia
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];$result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$row = mysqli_fetch_row($result);$price = $row[0];$adults = $_POST['adults'];$children = $_POST['children'];$cost = $adults * $price + $children * $price / 2;echo"<p>W dniu: {$_POST['date']}</p>";}?> </aside> <section> <h3>Wycieczki</h3> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 46: Skrypt 2: paragraf z kwotą
Kryteria:
R.5.7 Skrypt 2 wyświetla tekst „<wartość> złotych”, gdzie pole <> jest wyliczoną wartością kosztu wycieczki
ok. 1 min
Polecenie z arkusza
Wyświetla paragraf o treści <wartość> złotych, gdzie pole <> jest wyliczoną wartością wycieczki.
Drugi paragraf: wyliczona kwota i słowo „złotych”. Podgląd: „90120 złotych”, jak na ilustracji 4. Skrypt 2 jest kompletny.
index.phpPHPzmienione: 1 linia
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];$result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$row = mysqli_fetch_row($result);$price = $row[0];$adults = $_POST['adults'];$children = $_POST['children'];$cost = $adults * $price + $children * $price / 2;echo"<p>W dniu: {$_POST['date']}</p>";echo"<p>$cost złotych</p>";}?> </aside> <section> <h3>Wycieczki</h3> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 47: Skrypt 3: zapytanie 3 do bazy
Kryteria:
R.5.2 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1 lub Skrypt 3 zawiera instrukcję wysyłającą do bazy danych zapytanie 3
ok. 2 min
Polecenie z arkusza
Skrypt 3: wysyła do bazy danych zapytanie 3.
Skrypt 3 piszesz w sekcji pod nagłówkiem „Wycieczki”. Zapytanie 3 z kwerendy.txt (dziewięć miejsc ze zdjęciami od 01.jpg do 09.jpg) trafia do $offers. Kolejność kolumn: $row[0] nazwa, $row[1] cena, $row[2] nazwa pliku obrazu. Wzorzec "0%" jest w cudzysłowie, więc cały napis zapytania w PHP bierzesz w apostrofy.
index.phpPHPzmienione: 3 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];$result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$row = mysqli_fetch_row($result);$price = $row[0];$adults = $_POST['adults'];$children = $_POST['children'];$cost = $adults * $price + $children * $price / 2;echo"<p>W dniu: {$_POST['date']}</p>";echo"<p>$cost złotych</p>";}?> </aside> <section> <h3>Wycieczki</h3><?php$offers = mysqli_query($connection, 'SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";');?> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer></body></html>
Krok 48: Skrypt 3: blok z klasą wycieczka dla każdego wiersza
Kryteria: ,
R.2.8 W sekcji umieszczony przynajmniej jeden blok z przypisaną klasą wycieczka (w skrypcie lub HTML)
R.5.8 Skrypt 3 dla każdego zwróconego zapytaniem wiersza tworzy blok. W bloku wyświetlane są miejsce i cena wycieczki
ok. 2 min
Polecenie z arkusza
Dla każdego zwróconego zapytaniem wiersza tworzy blok z przypisaną klasą CSS wycieczka zawierający: obraz, nagłówek drugiego stopnia i paragraf.
Pętla while jak w skrypcie 1; w każdym obiegu echo otwiera <div class='wycieczka'> i zamyka </div> - klasa z CSS, więc blok od razu dostaje morskie tło, zaokrąglenie i marginesy. Podgląd: dziewięć pustych, kolorowych bloków, pod kursorem ciemniejszych (R.4.10). Zawartość dokładasz w trzech następnych krokach.
index.phpPHPzmienione: 4 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];$result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$row = mysqli_fetch_row($result);$price = $row[0];$adults = $_POST['adults'];$children = $_POST['children'];$cost = $adults * $price + $children * $price / 2;echo"<p>W dniu: {$_POST['date']}</p>";echo"<p>$cost złotych</p>";}?> </aside> <section> <h3>Wycieczki</h3><?php$offers = mysqli_query($connection, 'SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";');while ($row = mysqli_fetch_row($offers)) {echo"<div class='wycieczka'>";echo'</div>';}?> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 49: Skrypt 3: obraz w bloku
Kryteria: ,
R.2.9 W sekcji umieszczona przynajmniej jedna grafika z tekstem alternatywnym "zdjęcie z wycieczki" (w skrypcie lub HTML)
R.5.9 Skrypt 3 wyświetla obrazy których nazwy zostały zwrócone zapytaniem
ok. 2 min
Polecenie z arkusza
Obraz o nazwie zwróconej zapytaniem oraz tekście alternatywnym „zdjęcie z wycieczki”.
Wewnątrz bloku <img> ze src równym nazwie pliku z bazy ($row[2] - np. 01.jpg, plik leży obok index.php) i tekstem alternatywnym z arkusza. Dzięki float: right z CSS obraz staje przy prawej krawędzi bloku. Podgląd: dziewięć zdjęć, w tym odbite do góry nogami 02.jpg Gdańska.
index.phpPHPzmienione: 1 linia
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];$result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$row = mysqli_fetch_row($result);$price = $row[0];$adults = $_POST['adults'];$children = $_POST['children'];$cost = $adults * $price + $children * $price / 2;echo"<p>W dniu: {$_POST['date']}</p>";echo"<p>$cost złotych</p>";}?> </aside> <section> <h3>Wycieczki</h3><?php$offers = mysqli_query($connection, 'SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";');while ($row = mysqli_fetch_row($offers)) {echo"<div class='wycieczka'>";echo"<img src='$row[2]' alt='zdjęcie z wycieczki'>";echo'</div>';}?> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 50: Skrypt 3: nagłówek z nazwą miejsca
Kryteria:
R.5.8 Skrypt 3 dla każdego zwróconego zapytaniem wiersza tworzy blok. W bloku wyświetlane są miejsce i cena wycieczki
ok. 1 min
Polecenie z arkusza
Nagłówek drugiego stopnia z nazwą miejsca zwróconą zapytaniem.
<h2> z $row[0] - nazwa miejsca po lewej od obrazu, wyśrodkowana w pozostałej szerokości dzięki text-align: center z *.
index.phpPHPzmienione: 1 linia
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];$result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$row = mysqli_fetch_row($result);$price = $row[0];$adults = $_POST['adults'];$children = $_POST['children'];$cost = $adults * $price + $children * $price / 2;echo"<p>W dniu: {$_POST['date']}</p>";echo"<p>$cost złotych</p>";}?> </aside> <section> <h3>Wycieczki</h3><?php$offers = mysqli_query($connection, 'SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";');while ($row = mysqli_fetch_row($offers)) {echo"<div class='wycieczka'>";echo"<img src='$row[2]' alt='zdjęcie z wycieczki'>";echo"<h2>$row[0]</h2>";echo'</div>';}?> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 51: Skrypt 3: paragraf z ceną
Kryteria:
R.5.8 Skrypt 3 dla każdego zwróconego zapytaniem wiersza tworzy blok. W bloku wyświetlane są miejsce i cena wycieczki
ok. 1 min
Polecenie z arkusza
Paragraf z ceną zwróconą zapytaniem.
<p> z $row[1] - cena pod nazwą. Blok oferty wygląda jak na ilustracji 2: zdjęcie po prawej, nazwa i cena po lewej. Dziewięć bloków po 150 px plus marginesy przekracza 600 px sekcji - pojawia się pasek przewijania z overflow: auto.
index.phpPHPzmienione: 1 linia
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];$result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$row = mysqli_fetch_row($result);$price = $row[0];$adults = $_POST['adults'];$children = $_POST['children'];$cost = $adults * $price + $children * $price / 2;echo"<p>W dniu: {$_POST['date']}</p>";echo"<p>$cost złotych</p>";}?> </aside> <section> <h3>Wycieczki</h3><?php$offers = mysqli_query($connection, 'SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";');while ($row = mysqli_fetch_row($offers)) {echo"<div class='wycieczka'>";echo"<img src='$row[2]' alt='zdjęcie z wycieczki'>";echo"<h2>$row[0]</h2>";echo"<p>$row[1]</p>";echo'</div>';}?> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 52: Zamknięcie połączenia z bazą
Kryteria:
R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy wyprawy oraz ostatnią operacją na bazie jest jej zamknięcie
ok. 1 min
Polecenie z arkusza
Na końcu skrypt zamyka połączenie z bazą.
mysqli_close($connection) na końcu pliku, po wszystkich skryptach. Klucz R.5.1 sprawdza, że zamknięcie jest ostatnią operacją na bazie. Strona spełnia wszystkie kryteria arkusza - dalej trzy kroki „jak w prawdziwym projekcie”.
Typowe błędy
mysqli_close po skrypcie 1 sprawia, że skrypty 2 i 3 idą po zamkniętym połączeniu - galeria zostaje pusta.
index.phpPHPzmienione: 3 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];$result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$row = mysqli_fetch_row($result);$price = $row[0];$adults = $_POST['adults'];$children = $_POST['children'];$cost = $adults * $price + $children * $price / 2;echo"<p>W dniu: {$_POST['date']}</p>";echo"<p>$cost złotych</p>";}?> </aside> <section> <h3>Wycieczki</h3><?php$offers = mysqli_query($connection, 'SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";');while ($row = mysqli_fetch_row($offers)) {echo"<div class='wycieczka'>";echo"<img src='$row[2]' alt='zdjęcie z wycieczki'>";echo"<h2>$row[0]</h2>";echo"<p>$row[1]</p>";echo'</div>';}?> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer><?phpmysqli_close($connection);?></body></html>
Kroki 53-55 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 53: Refaktor: zapytanie przygotowane w skrypcie 2
Kryteria:
R.5.4 Skrypt 2 zawiera instrukcję wysyłającą do bazy danych zmodyfikowane zapytanie 2, w ten sposób, że sprawdzane jest miejsce wycieczki wysłane z formularza
ok. 4 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie 2, zmodyfikowane w ten sposób, że sprawdzane jest miejsce wycieczki wysłane z formularza.
Nazwa miejsca z formularza szła prosto do zapytania - to SQL injection: lista rozwijana to tylko podpowiedź, w żądaniu POST można wysłać dowolny napis, np. x' OR '1'='1. W prawdziwych projektach dane trafiają do zapytania przez zapytanie przygotowane: mysqli_prepare z ? w miejscu wartości, mysqli_stmt_bind_param z typem 's' (napis), mysqli_stmt_execute i mysqli_stmt_get_result, z którego czytasz jak dotąd. Stara linia zostaje w komentarzu. Podgląd bez zmian.
Typowe błędy
mysqli_stmt_bind_param przyjmuje zmienne, nie wartości - mysqli_stmt_bind_param($statement, 's', $_POST['place']) działa, ale 's', 'Malbork' (napis wprost) kończy się błędem.
index.phpPHPzmienione: 5 linii
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];// $result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$statement = mysqli_prepare($connection, 'SELECT cena FROM miejsca WHERE nazwa = ?'); mysqli_stmt_bind_param($statement, 's', $place); mysqli_stmt_execute($statement);$row = mysqli_fetch_row(mysqli_stmt_get_result($statement));$price = $row[0];$adults = $_POST['adults'];$children = $_POST['children'];$cost = $adults * $price + $children * $price / 2;echo"<p>W dniu: {$_POST['date']}</p>";echo"<p>$cost złotych</p>";}?> </aside> <section> <h3>Wycieczki</h3><?php$offers = mysqli_query($connection, 'SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";');while ($row = mysqli_fetch_row($offers)) {echo"<div class='wycieczka'>";echo"<img src='$row[2]' alt='zdjęcie z wycieczki'>";echo"<h2>$row[0]</h2>";echo"<p>$row[1]</p>";echo'</div>';}?> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer><?phpmysqli_close($connection);?></body></html>
Krok 54: Refaktor: escapowanie danych przed wypisaniem
Kryteria: , ,
R.5.6 Skrypt 2 wyświetla tekst „W dniu: <termin>”, gdzie pole <> zostało wysłane z formularza
R.5.8 Skrypt 3 dla każdego zwróconego zapytaniem wiersza tworzy blok. W bloku wyświetlane są miejsce i cena wycieczki
R.5.9 Skrypt 3 wyświetla obrazy których nazwy zostały zwrócone zapytaniem
ok. 3 min
Polecenie z arkusza
Wyświetla paragraf o treści W dniu: <termin>, gdzie pole <> zostało wysłane z formularza.
Skrypt 2 wypisuje do HTML datę wprost z formularza - a formularz można wysłać z dowolną treścią, także <script>…</script> (XSS). Skrypt 3 wypisuje nazwy i pliki z bazy, które kiedyś też ktoś wpisał. htmlspecialchars() zamienia <, >, ", & na encje, więc tekst wyświetla się dosłownie i nie staje się kodem. Wartości przechodzą przez funkcję do zmiennych o angielskich nazwach, a echo używa już tylko ich. Podgląd bez zmian.
Typowe błędy
htmlspecialchars przy zapisie do bazy zamiast przy wypisywaniu to częsty błąd - baza dostaje zniekształcone dane. Escapujesz na wyjściu.
index.phpPHPzmienione: 6 linii
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];// $result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$statement = mysqli_prepare($connection, 'SELECT cena FROM miejsca WHERE nazwa = ?'); mysqli_stmt_bind_param($statement, 's', $place); mysqli_stmt_execute($statement);$row = mysqli_fetch_row(mysqli_stmt_get_result($statement));$price = $row[0];$adults = $_POST['adults'];$children = $_POST['children'];$cost = $adults * $price + $children * $price / 2;$date = htmlspecialchars($_POST['date']);echo"<p>W dniu: $date</p>";echo"<p>$cost złotych</p>";}?> </aside> <section> <h3>Wycieczki</h3><?php$offers = mysqli_query($connection, 'SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";');while ($row = mysqli_fetch_row($offers)) {echo"<div class='wycieczka'>";$name = htmlspecialchars($row[0]);$image = htmlspecialchars($row[2]);echo"<img src='$image' alt='zdjęcie z wycieczki'>";echo"<h2>$name</h2>";echo"<p>$row[1]</p>";echo'</div>';}?> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer><?phpmysqli_close($connection);?></body></html>
Krok 55: Refaktor: walidacja liczb i formatowanie kwoty
ok. 3 min
Polecenie z arkusza
Wylicza wartość wycieczki na podstawie wysłanej z formularza liczby dorosłych i dzieci oraz ceny zwróconej zapytaniem.
Dwie rzeczy, które w prawdziwym kodzie robią różnicę:
walidacja: (int) rzutuje wartość z formularza na liczbę całkowitą (puste pole albo litery dają 0, nie błąd), a max(0, …) odcina liczby ujemne - bez tego „-2 dorosłych” dawało ujemny koszt,
formatowanie: number_format($cost, 2, ',', ' ') zapisuje kwotę po polsku, z dwoma miejscami po przecinku i spacją między tysiącami: „90 120,00 złotych” zamiast „90120 złotych”.
Na egzaminie wystarczy goła liczba - klucz R.5.7 sprawdza <wartość> złotych. To już wersja gotowa do prawdziwego użycia.
Typowe błędy
number_format zwraca napis - formatuj dopiero wynik, nie wartości przed rachunkiem.
index.phpPHPzmienione: 4 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'wyprawy');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Biuro turystyczne</title> <linkrel="stylesheet"href="styl.css"></head><body> <nav> <ul> <li><ahref="wczasy.html">Wczasy</a></li> <li><ahref="wycieczki.html">Wycieczki</a></li> <li><ahref="allinclusive.html">All inclusive</a></li> </ul> </nav> <main> <aside> <h3>Twój cel wyprawy</h3> <formmethod="post"> <labelfor="place">Miejsce wycieczki</label><br> <selectid="place"name="place"><?php$places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');while ($row = mysqli_fetch_row($places)) {echo"<option>$row[0]</option>";}?> </select><br> <labelfor="adults">Ile dorosłych?</label><br> <inputtype="number"id="adults"name="adults"><br> <labelfor="children">Ile dzieci?</label><br> <inputtype="number"id="children"name="children"><br> <labelfor="date">Termin</label><br> <inputtype="date"id="date"name="date"><br> <buttontype="submit">Symulacja ceny</button> </form> <h4>Koszt wycieczki</h4><?phpif (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {$place = $_POST['place'];// $result = mysqli_query($connection, "SELECT cena FROM miejsca WHERE nazwa = '$place';");$statement = mysqli_prepare($connection, 'SELECT cena FROM miejsca WHERE nazwa = ?'); mysqli_stmt_bind_param($statement, 's', $place); mysqli_stmt_execute($statement);$row = mysqli_fetch_row(mysqli_stmt_get_result($statement));$price = $row[0];$adults = max(0, (int) $_POST['adults']);$children = max(0, (int) $_POST['children']);$cost = $adults * $price + $children * $price / 2;$date = htmlspecialchars($_POST['date']);echo"<p>W dniu: $date</p>";$formatted = number_format($cost, 2, ',', ' ');echo"<p>$formatted złotych</p>";}?> </aside> <section> <h3>Wycieczki</h3><?php$offers = mysqli_query($connection, 'SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";');while ($row = mysqli_fetch_row($offers)) {echo"<div class='wycieczka'>";$name = htmlspecialchars($row[0]);$image = htmlspecialchars($row[2]);echo"<img src='$image' alt='zdjęcie z wycieczki'>";echo"<h2>$name</h2>";echo"<p>$row[1]</p>";echo'</div>';}?> </section> </main> <footer> <p>Autor: Bartosz Bryniarski</p> </footer><?phpmysqli_close($connection);?></body></html>
Podgląd strony po tym kroku (index.php)
Etap 6: Oddanie pracy
Krok 56 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 56: 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 wpisujesz nazwę przeglądarki, w której sprawdzasz stronę przez http://localhost/….
W folderze z numerem zdającego muszą być: zdjęcia 01.jpg … 21.jpg, allinclusive.html, import.png, index.php, kw1.png … kw5.png, kwerendy.txt, przeglądarka.txt, styl.css, wczasy.html, wycieczki.html. Nagraj płytę, sprawdź jej odczyt i opisz numerem zdającego. Pusta lub nieczytelna płyta to zero punktów za wszystkie pięć rezultatów.
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.