Przejdź do głównej treści
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.

  1. 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).

  2. 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.

  3. 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.

  4. 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.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT nazwa FROM miejsca ORDER BY nazwa;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. 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.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT nazwa FROM miejsca ORDER BY nazwa;
    
    -- Zapytanie 2
    SELECT cena FROM miejsca WHERE nazwa = "Malbork";
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. 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.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT nazwa FROM miejsca ORDER BY nazwa;
    
    -- Zapytanie 2
    SELECT cena FROM miejsca WHERE nazwa = "Malbork";
    
    -- Zapytanie 3
    SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. 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.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT nazwa FROM miejsca ORDER BY nazwa;
    
    -- Zapytanie 2
    SELECT cena FROM miejsca WHERE nazwa = "Malbork";
    
    -- Zapytanie 3
    SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";
    
    -- Zapytanie 4
    SELECT 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
    Podgląd strony po kroku 7
  8. 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.txt Tekst zmienione: 8 linii
    -- Zapytanie 1
    SELECT nazwa FROM miejsca ORDER BY nazwa;
    
    -- Zapytanie 2
    SELECT cena FROM miejsca WHERE nazwa = "Malbork";
    
    -- Zapytanie 3
    SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE "0%";
    
    -- Zapytanie 4
    SELECT nazwa, ROUND(AVG(liczba_doroslych)) FROM miejsca JOIN wycieczki ON miejsca.id_miejsca = wycieczki.id_miejsca GROUP BY nazwa;
    
    -- Zapytanie 5
    CREATE 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
    Podgląd strony po kroku 8
  9. 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.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 9
  10. Krok 10: Podstrony wczasy.html, wycieczki.html, allinclusive.html

    Kryteria:

    R.3.3 Utworzone 3 odnośniki : Kliknięcie odnośnika „Wczasy” otwiera podstronę wczasy.html, kliknięcie odnośnika „Wycieczki” otwiera podstronę wycieczki.html, kliknięcie odnośnika „All inclusive” otwiera podstronę allinclusive.html

    ok. 1 min
    Polecenie z arkusza

    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.html HTML nowy plik
    Strona w trakcie budowy
    wczasy.html HTML nowy plik
    Strona w trakcie budowy
    wycieczki.html HTML nowy plik
    Strona w trakcie budowy
    Podgląd strony po tym kroku (allinclusive.html)
  11. 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).

    index.php PHP nowy plik
    <!DOCTYPE html>
    <html>
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  12. Krok 12: Język strony

    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. 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.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  13. Krok 13: Kodowanie polskich znaków

    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. 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.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  14. Krok 14: Tytuł strony

    Kryteria:

    R.2.3 Nadany stronie tytuł: "Biuro turystyczne"

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony „Biuro turystyczne”.

    <title> w <head> - napis na karcie przeglądarki, nie nagłówek na stronie. Klucz R.2.3 sprawdza tekst „Biuro turystyczne”.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Podpięcie arkusza stylów

    Kryteria:

    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”.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Bloki semantyczne

    Kryteria:

    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”.

    index.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
        </nav>
        <main>
            <aside>
            </aside>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  17. Krok 17: Blok nawigacyjny: lista odnośników

    Kryteria: ,

    R.2.6 W bloku nawigacyjnym umieszczona jedna lista punktowana z trzema elementami, zastosowane znaczniki <ul>, <li>

    R.3.3 Utworzone 3 odnośniki : Kliknięcie odnośnika „Wczasy” otwiera podstronę wczasy.html, kliknięcie odnośnika „Wycieczki” otwiera podstronę wycieczki.html, kliknięcie odnośnika „All inclusive” otwiera podstronę allinclusive.html

    ok. 2 min
    Polecenie z arkusza

    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.

    index.php PHP zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
            </aside>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Blok boczny: nagłówek „Twój cel wyprawy”

    Kryteria:

    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.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
            </aside>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  19. Krok 19: Blok boczny: formularz

    Kryteria: ,

    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.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                </form>
            </aside>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  20. Krok 20: Formularz: etykieta i lista miejsc

    Kryteria: ,

    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.

    index.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="place" name="place">
                    </select><br>
                </form>
            </aside>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  21. Krok 21: Formularz: liczba dorosłych

    Kryteria: ,

    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.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="place" name="place">
                    </select><br>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                </form>
            </aside>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Formularz: liczba dzieci

    Kryteria: ,

    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 dzieci?” powiązana z drugim polem edycyjnym. Pole edycyjne typu liczbowego.

    Drugie pole liczbowe, id i name równe children. Dwa pola number - tyle wymaga klucz R.3.6.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="place" name="place">
                    </select><br>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                </form>
            </aside>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  23. Krok 23: Formularz: termin

    Kryteria: ,

    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.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="place" name="place">
                    </select><br>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                </form>
            </aside>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Formularz: przycisk „Symulacja ceny”

    Kryteria: ,

    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">.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="place" name="place">
                    </select><br>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
            </aside>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  25. Krok 25: Blok boczny: nagłówek „Koszt wycieczki”

    Kryteria:

    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.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="place" name="place">
                    </select><br>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
            </aside>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  26. Krok 26: Sekcja: nagłówek „Wycieczki”

    Kryteria:

    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.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="place" name="place">
                    </select><br>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
            </aside>
            <section>
                <h3>Wycieczki</h3>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  27. Krok 27: Stopka z numerem zdającego

    Kryteria:

    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.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="place" name="place">
                    </select><br>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
            </aside>
            <section>
                <h3>Wycieczki</h3>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  28. Etap 3: Styl CSS

    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.css CSS nowy plik
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  29. 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.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    nav, footer {
        background-color: #00838F;
        color: #B2DFDB;
        padding: 10px;
    }
    Podgląd strony po tym kroku (index.php)
  30. 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.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    nav, footer {
        background-color: #00838F;
        color: #B2DFDB;
        padding: 10px;
    }
    aside, section {
        width: 50%;
        height: 600px;
        overflow: auto;
    }
    Podgląd strony po tym kroku (index.php)
  31. Krok 31: Blok boczny: kolor tła

    Kryteria:

    R.4.2 Ustawiony kolor tła dla bloku nawigacyjnego i stopki #00838F, dla bloku bocznego #B2DFDB

    ok. 1 min
    Polecenie z arkusza

    Dodatkowo dla bloku bocznego kolor tła: #B2DFDB.

    Osobna reguła aside dokłada tło do wspólnych wymiarów z poprzedniego kroku - nie kasuje ich. Blok boczny robi się miętowy, sekcja zostaje biała.

    styl.css CSS zmienione: 3 linie
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    nav, footer {
        background-color: #00838F;
        color: #B2DFDB;
        padding: 10px;
    }
    aside, section {
        width: 50%;
        height: 600px;
        overflow: auto;
    }
    aside {
        background-color: #B2DFDB;
    }
    Podgląd strony po tym kroku (index.php)
  32. Krok 32: Układ bloków: blok boczny obok sekcji

    Kryteria:

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 3 w arkuszu egzaminacyjnym (prawidłowo zastosowane właściwości CSS decydujące o układzie bloków na stronie, np. float i clear albo display flex albo grid albo position)

    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ść.

    styl.css CSS zmienione: 4 linie
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    nav, footer {
        background-color: #00838F;
        color: #B2DFDB;
        padding: 10px;
    }
    aside, section {
        width: 50%;
        height: 600px;
        overflow: auto;
        float: left;
    }
    aside {
        background-color: #B2DFDB;
    }
    footer {
        clear: both;
    }
    Podgląd strony po tym kroku (index.php)
  33. 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.

    styl.css CSS zmienione: 6 linii
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    nav, footer {
        background-color: #00838F;
        color: #B2DFDB;
        padding: 10px;
    }
    aside, section {
        width: 50%;
        height: 600px;
        overflow: auto;
        float: left;
    }
    aside {
        background-color: #B2DFDB;
    }
    footer {
        clear: both;
    }
    .wycieczka {
        background-color: rgba(0, 131, 143, 0.7);
        height: 150px;
        margin: 10px 100px;
        border-radius: 30px;
    }
    Podgląd strony po tym kroku (index.php)
  34. Krok 34: Klasa wycieczka pod kursorem

    Kryteria:

    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).

    styl.css CSS zmienione: 3 linie
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    nav, footer {
        background-color: #00838F;
        color: #B2DFDB;
        padding: 10px;
    }
    aside, section {
        width: 50%;
        height: 600px;
        overflow: auto;
        float: left;
    }
    aside {
        background-color: #B2DFDB;
    }
    footer {
        clear: both;
    }
    .wycieczka {
        background-color: rgba(0, 131, 143, 0.7);
        height: 150px;
        margin: 10px 100px;
        border-radius: 30px;
    }
    .wycieczka:hover {
        background-color: #00838F;
    }
    Podgląd strony po tym kroku (index.php)
  35. Krok 35: Obraz: opływanie, zaokrąglenie, marginesy

    Kryteria: ,

    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.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    nav, footer {
        background-color: #00838F;
        color: #B2DFDB;
        padding: 10px;
    }
    aside, section {
        width: 50%;
        height: 600px;
        overflow: auto;
        float: left;
    }
    aside {
        background-color: #B2DFDB;
    }
    footer {
        clear: both;
    }
    .wycieczka {
        background-color: rgba(0, 131, 143, 0.7);
        height: 150px;
        margin: 10px 100px;
        border-radius: 30px;
    }
    .wycieczka:hover {
        background-color: #00838F;
    }
    img {
        float: right;
        border-radius: 30px;
        margin: 7px;
    }
    Podgląd strony po tym kroku (index.php)
  36. 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.

    styl.css CSS zmienione: 4 linie
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    nav, footer {
        background-color: #00838F;
        color: #B2DFDB;
        padding: 10px;
    }
    aside, section {
        width: 50%;
        height: 600px;
        overflow: auto;
        float: left;
    }
    aside {
        background-color: #B2DFDB;
    }
    footer {
        clear: both;
    }
    .wycieczka {
        background-color: rgba(0, 131, 143, 0.7);
        height: 150px;
        margin: 10px 100px;
        border-radius: 30px;
    }
    .wycieczka:hover {
        background-color: #00838F;
    }
    img {
        float: right;
        border-radius: 30px;
        margin: 7px;
    }
    li {
        display: inline-block;
        list-style-type: none;
    }
    Podgląd strony po tym kroku (index.php)
  37. 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.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    nav, footer {
        background-color: #00838F;
        color: #B2DFDB;
        padding: 10px;
    }
    aside, section {
        width: 50%;
        height: 600px;
        overflow: auto;
        float: left;
    }
    aside {
        background-color: #B2DFDB;
    }
    footer {
        clear: both;
    }
    .wycieczka {
        background-color: rgba(0, 131, 143, 0.7);
        height: 150px;
        margin: 10px 100px;
        border-radius: 30px;
    }
    .wycieczka:hover {
        background-color: #00838F;
    }
    img {
        float: right;
        border-radius: 30px;
        margin: 7px;
    }
    li {
        display: inline-block;
        list-style-type: none;
    }
    a {
        color: #B2DFDB;
        text-decoration: none;
        margin-left: 50px;
    }
    Podgląd strony po tym kroku (index.php)
  38. Krok 38: Odnośnik pod kursorem

    Kryteria:

    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.

    styl.css CSS zmienione: 4 linie
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    nav, footer {
        background-color: #00838F;
        color: #B2DFDB;
        padding: 10px;
    }
    aside, section {
        width: 50%;
        height: 600px;
        overflow: auto;
        float: left;
    }
    aside {
        background-color: #B2DFDB;
    }
    footer {
        clear: both;
    }
    .wycieczka {
        background-color: rgba(0, 131, 143, 0.7);
        height: 150px;
        margin: 10px 100px;
        border-radius: 30px;
    }
    .wycieczka:hover {
        background-color: #00838F;
    }
    img {
        float: right;
        border-radius: 30px;
        margin: 7px;
    }
    li {
        display: inline-block;
        list-style-type: none;
    }
    a {
        color: #B2DFDB;
        text-decoration: none;
        margin-left: 50px;
    }
    a:hover {
        color: white;
        text-decoration: underline;
    }
    Podgląd strony po tym kroku (index.php)
  39. Etap 4: Skrypty PHP

    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.

    index.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="place" name="place">
                    </select><br>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
            </aside>
            <section>
                <h3>Wycieczki</h3>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  40. Krok 40: Skrypt 1: zapytanie 1 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 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.

    index.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="place" name="place">
    <?php
    $places = mysqli_query($connection, 'SELECT nazwa FROM miejsca ORDER BY nazwa;');
    ?>
                    </select><br>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
            </aside>
            <section>
                <h3>Wycieczki</h3>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

    Podgląd strony bez zmian - taki jak po kroku 39.

  41. Krok 41: Skrypt 1: miejsca jako opcje listy

    Kryteria:

    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.

    index.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
            </aside>
            <section>
                <h3>Wycieczki</h3>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  42. Krok 42: Skrypt 2: tylko po wysłaniu 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. 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.

    index.php PHP zmienione: 4 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (isset($_POST['place']) && is_numeric($_POST['adults']) && is_numeric($_POST['children'])) {
    }
    ?>
            </aside>
            <section>
                <h3>Wycieczki</h3>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

    Podgląd strony bez zmian - taki jak po kroku 41.

  43. 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.php PHP zmienione: 4 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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>

    Podgląd strony bez zmian - taki jak po kroku 41.

  44. Krok 44: Skrypt 2: wartość wycieczki

    Kryteria:

    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.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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>

    Podgląd strony bez zmian - taki jak po kroku 41.

  45. Krok 45: Skrypt 2: paragraf z terminem

    Kryteria:

    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.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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)
  46. 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.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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)
  47. 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.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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>

    Podgląd strony bez zmian - taki jak po kroku 46.

  48. 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.php PHP zmienione: 4 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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)
  49. 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.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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)
  50. 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.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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)
  51. 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.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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)
  52. 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.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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>
    <?php
    mysqli_close($connection);
    ?>
    </body>
    </html>

    Podgląd strony bez zmian - taki jak po kroku 51.

  53. Etap 5: Dobre praktyki (poza kluczem)

    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.php PHP zmienione: 5 linii
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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>
    <?php
    mysqli_close($connection);
    ?>
    </body>
    </html>

    Podgląd strony bez zmian - taki jak po kroku 51.

  54. 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.php PHP zmienione: 6 linii
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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>
    <?php
    mysqli_close($connection);
    ?>
    </body>
    </html>

    Podgląd strony bez zmian - taki jak po kroku 51.

  55. 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.php PHP zmienione: 4 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'wyprawy');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Biuro turystyczne</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <nav>
            <ul>
                <li><a href="wczasy.html">Wczasy</a></li>
                <li><a href="wycieczki.html">Wycieczki</a></li>
                <li><a href="allinclusive.html">All inclusive</a></li>
            </ul>
        </nav>
        <main>
            <aside>
                <h3>Twój cel wyprawy</h3>
                <form method="post">
                    <label for="place">Miejsce wycieczki</label><br>
                    <select id="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>
                    <label for="adults">Ile dorosłych?</label><br>
                    <input type="number" id="adults" name="adults"><br>
                    <label for="children">Ile dzieci?</label><br>
                    <input type="number" id="children" name="children"><br>
                    <label for="date">Termin</label><br>
                    <input type="date" id="date" name="date"><br>
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php
    if (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>
    <?php
    mysqli_close($connection);
    ?>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  56. 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.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

    Podgląd strony bez zmian - taki jak po kroku 55.

Podgląd

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: . 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.