Przejdź do głównej treści
INF.03-07-25.06-SGCzerwiec 2025 · 56 kroków · ok. 100 min · 42 kryteria z klucza

Rozwiązanie krok po kroku: Portal wycieczek turystycznych (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza wyprawy z pięcioma zapytaniami, odbita grafika, strona index.php z podstronami, formularzem symulacji ceny i galerią ofert - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • siatka CSS (grid-template-columns: 50% 50%) zamiast pływających bloków i clear, kolory z arkusza w zmiennych CSS, przezroczystość w nowej składni rgb(0 131 143 / 0.7), formularz ułożony siatką bez <br>,
  • oferty jako <article class="wycieczka"> - każda oferta to samodzielna treść,
  • PHP na PDO: wszystkie operacje na bazie na początku pliku, zapytanie przygotowane, filter_input dla liczby osób, sprawdzenie daty, biała lista miejsc z bazy, kwota sformatowana number_format, funkcja e() przy każdym wypisaniu i config.php,
  • formularz pamięta wpisane dane po symulacji, jak na ilustracji 4.

Tam, gdzie klucz wymaga konkretnej formy - float: right dla obrazu, display: inline-block dla elementów listy - ta forma zostaje, a krok mówi dlaczego. Na końcu krótkie porównanie z wersją podstawową (zakładka obok).

Zdjęcia i baza pochodzą z prawdziwego archiwum pliki10 (załącznik na stronie arkusza), a podgląd każdego kroku z PHP to wynik prawdziwego uruchomienia strony na serwerze z tą bazą.

Jak liczą się punkty. Klucz ma 42 kryteria (R.1.1 - R.5.10), każde za 1 punkt. Próg zdania to 75%, czyli 32 punkty. Przy każdym kroku jest napisane, które kryteria spełnia.

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

    phpMyAdmin → Nowa → nazwa wyprawy, porównywanie utf8_polish_ci. W SQL:

    CREATE DATABASE wyprawy CHARACTER SET utf8 COLLATE utf8_polish_ci;
    

    Porównywanie bazy odziedziczy tabela klienci z zapytania 5. Tabela miejsca z pliku wyprawy.sql ma utf8_polish_ci zapisane we własnej definicji i to ono ustala kolejność w zapytaniu 1.

    Typowe błędy

    Literówka w nazwie bazy odcina też punkty 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.

    Zaznacz bazę wyprawy, karta Import, plik wyprawy.sql, Wykonaj. Tabele miejsca (21 miejsc, link_obraz = nazwa pliku zdjęcia) i wycieczki (52 wycieczki, id_miejsca to klucz obcy).

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

    Cały ekran z paskiem zadań, zapis jako import.png. W tym arkuszu wszystkie zrzuty są w PNG.

    Typowe błędy

    Wykadrowany zrzut nie jest oceniany.

  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.

    Jedna kolumna, kierunek sortowania wpisany jawnie (ASC). Wynik: 21 miejsc od Brna do Wiednia (R.1.3), zapytanie do kwerendy.txt, zrzut kw1.png. Ta lista wypełni listę rozwijaną i posłuży jako biała lista miejsc w skrypcie 2.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT nazwa
    FROM miejsca
    ORDER BY nazwa ASC;
    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.

    Napis w apostrofach - standard SQL (cudzysłów jako napis rozumie tylko MySQL). Wynik: 2000.00 (R.1.4), zrzut kw2.png.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT nazwa
    FROM miejsca
    ORDER BY nazwa ASC;
    
    -- 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).

    LIKE '0%' - zero na początku, potem cokolwiek (% tylko na końcu wzorca). Wynik: 9 miejsc, od Malborka (01.jpg) do Olsztyna (09.jpg) (R.1.5), zrzut kw3.png.

    Typowe błędy

    LIKE '%0%' znajdzie też 10.jpg i 20.jpg.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT nazwa
    FROM miejsca
    ORDER BY nazwa ASC;
    
    -- 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ą.

    Złączenie z aliasami tabel (miejsca AS m, wycieczki AS w) i alias kolumny wyniku: AS srednio_doroslych - bez niego nagłówek kolumny brzmiałby ROUND(AVG(w.liczba_doroslych)), a w PHP trudno byłoby się do niej odwołać.

    • ROUND(AVG(...)) - najpierw średnia, potem zaokrąglenie wyniku,
    • GROUP BY m.id_miejsca, m.nazwa - grupa to jedno miejsce (po kluczu; dwa miejsca o tej samej nazwie by się nie zlały), arkusz każe grupować według nazwy, więc nazwa też jest w GROUP BY,
    • ORDER BY m.nazwa - kolejność alfabetyczna wprost, nie przypadkowa.

    Wynik: 16 miejsc od Brna do Wenecji (R.1.6) - pięć miejsc bez żadnej wycieczki nie ma pary w złączeniu. Zrzut kw4.png.

    Typowe błędy

    AVG(ROUND(...)) zaokrągla liczby przed liczeniem średniej - średnia zostaje ułamkiem.

    kwerendy.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT nazwa
    FROM miejsca
    ORDER BY nazwa ASC;
    
    -- 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 m.nazwa, ROUND(AVG(w.liczba_doroslych)) AS srednio_doroslych
    FROM miejsca AS m
    JOIN wycieczki AS w ON w.id_miejsca = m.id_miejsca
    GROUP BY m.id_miejsca, m.nazwa
    ORDER BY m.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.

    Kolumny jedna pod drugą, a klucz podstawowy jako osobne ograniczenie tabeli na końcu: PRIMARY KEY (id_klienta). To ten sam klucz co dopisek przy kolumnie, ale ten zapis pozwala później zbudować klucz z dwóch kolumn i od razu widać, co jest kluczem. Typy: INT UNSIGNED NOT NULL AUTO_INCREMENT, VARCHAR(20), VARCHAR(40), DATE (R.1.7). Zrzut kw5.png.

    Typowe błędy

    AUTO_INCREMENT w kolumnie, która nie jest kluczem, to błąd MySQL.

    kwerendy.txt Tekst zmienione: 9 linii
    -- Zapytanie 1
    SELECT nazwa
    FROM miejsca
    ORDER BY nazwa ASC;
    
    -- 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 m.nazwa, ROUND(AVG(w.liczba_doroslych)) AS srednio_doroslych
    FROM miejsca AS m
    JOIN wycieczki AS w ON w.id_miejsca = m.id_miejsca
    GROUP BY m.id_miejsca, m.nazwa
    ORDER BY m.nazwa;
    
    -- Zapytanie 5
    CREATE TABLE klienci (
        id_klienta INT UNSIGNED NOT NULL AUTO_INCREMENT,
        imie VARCHAR(20),
        nazwisko VARCHAR(40),
        data_ur DATE,
        PRIMARY KEY (id_klienta)
    );
    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.

    02.jpg (800 × 536 px): Skaluj obraz do szerokości 200 px przy zamkniętym łańcuchu (134 px wysokości), potem Odbij pionowo (góra-dół), eksport pod tą samą nazwą (R.2.1).

    Typowe błędy

    „Odbij poziomo” to lustro lewo-prawo - arkusz mówi „pionowo”.

    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. 2 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 podstrony ze szkieletem HTML5 (język, kodowanie, tytuł) i napisem z arkusza w paragrafie - bez <meta charset> „Strona w trakcie budowy” mogłaby wyświetlić się z krzakami. Odnośniki z nawigacji mają dokąd prowadzić (R.3.3).

    allinclusive.html HTML nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>All inclusive</title>
    </head>
    <body>
        <p>Strona w trakcie budowy</p>
    </body>
    </html>
    wczasy.html HTML nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Wczasy</title>
    </head>
    <body>
        <p>Strona w trakcie budowy</p>
    </body>
    </html>
    wycieczki.html HTML nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Wycieczki</title>
    </head>
    <body>
        <p>Strona w trakcie budowy</p>
    </body>
    </html>
    Podgląd strony po tym kroku (wczasy.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. 1 min
    Polecenie z arkusza

    Cechy strony index.php: zapisana w języku HTML5.

    Plik index.php w folderze zdającego. <!DOCTYPE html> włącza tryb standardów HTML5, dalej szkielet: <html>, <head> i <body>. Rozszerzenie .php, bo na początku pliku stanie kod PHP.

    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.

    lang="pl" w <html> - czytnik ekranu czyta polską wymową, przeglądarka dobiera słownik i dzielenie wyrazó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 pierwszy element <head>. Ten sam standard ustawi połączenie z bazą (charset=utf8mb4 w PDO), więc polskie litery przejdą bez zmian całą drogę: baza → PHP → przeglądarka.

    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>Biuro turystyczne</title> - napis na karcie przeglądarki. Obok <meta name="viewport">: bez niego telefon pokazałby stronę pomniejszoną jak na monitorze. Arkusz tego nie wymaga, a w każdym współczesnym szablonie ta linia jest.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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"> - nazwa dokładnie z arkusza. Cały wygląd będzie w tym pliku, w HTML nie ma ani jednego atrybutu style.

    Typowe błędy

    Zła nazwa w href to brak stylów, a wtedy cały rezultat R.4 przepada.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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.

    Znaczniki z klucza R.2.4: <nav>, <main> z <aside> i <section>, <footer>. Każdy występuje raz, więc CSS sięgnie po nie selektorami znaczników, bez identyfikatorów.

    Typowe błędy

    <aside> i <section> poza <main> nie spełniają R.2.4.

    index.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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”.

    Menu jako lista - czytnik ekranu zapowie „lista, trzy elementy”, więc użytkownik wie, ile jest pozycji (R.2.6, R.3.3).

    index.php PHP zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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”.

    <h3> w <aside> (R.2.5).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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.

    method="post" bez action (R.2.7, R.3.4). Elementy ustawi jeden pod drugim siatka w CSS - bez znaczników <br>.

    Typowe błędy

    Brak method to GET - skrypt czytający dane POST nic nie dostanie.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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.

    for="place" wskazuje listę o id="place" (R.3.6), name="place" to klucz w danych POST. Opcje wstawi skrypt 1.

    index.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
                    </select>
                </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.

    type="number" z min="0" i step="1" - przeglądarka przyjmie tylko liczby całkowite od zera (R.3.6). Ten sam warunek sprawdzi PHP, bo żądanie można wysłać z pominięciem formularza.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                </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 z tym samym zakresem (R.3.6).

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                </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.

    type="date" - kalendarz w przeglądarce, a wysyłana wartość ma zawsze format RRRR-MM-DD, niezależnie od tego, jak przeglądarka pokazuje datę (R.3.6).

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                </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"> (R.2.7, R.3.5).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <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.

    <h4> pod formularzem; wynik skryptu 2 stanie pod nim (R.2.5).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <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 sekcji; oferty wygeneruje skrypt 3 (R.2.5).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <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.

    Tu nazwisko autora rozwiązania - na egzaminie wpisujesz swój numer zdającego (PESEL), bo tego sprawdza klucz R.2.5.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <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-37 · po tym etapie masz 30 z 42 kryteriów (pkt)

    Krok 28: Plik styl.css: zmienne kolorów, krój 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. 2 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.

    Na początku dwa kolory arkusza jako zmienne CSS: morski #00838F (tło nawigacji, stopki i ofert) i miętowy #B2DFDB (tekst nawigacji, odnośniki, blok boczny). Każdy występuje w kilku regułach - zmienna trzyma go w jednym miejscu. Potem * z krojem "Comic Sans MS", sans-serif i wyśrodkowaniem (R.4.1).

    Typowe błędy

    Inspektor przeglądarki pokazuje kolory obliczone ze zmiennych. Jeśli wolisz nie ryzykować sporu przy czytaniu samego pliku, wpisz kolory wprost - jak w wersji podstawowej.

    styl.css CSS nowy plik
    :root {
        --teal: #00838F;
        --mint: #B2DFDB;
    }
    
    * {
        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.

    Kolory ze zmiennych, padding: 10px (R.4.2, R.4.3, R.4.6).

    styl.css CSS zmienione: 7 linii
    :root {
        --teal: #00838F;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    
    nav,
    footer {
        background-color: var(--teal);
        color: var(--mint);
        padding: 10px;
    }
    Podgląd strony po tym kroku (index.php)
  30. Krok 30: Blok boczny i sekcja: siatka, wysokość, paski przewijania

    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)

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

    <main> to siatka o dwóch kolumnach po 50% - blok boczny i sekcja od razu stoją obok siebie, a stopka poza <main> zostaje pod nimi bez clear (R.3.1). Szerokość 50% zapisana w grid-template-columns - klucz R.4.4 wymienia tę właściwość obok width. Wspólna reguła dla obu bloków to już tylko wysokość i overflow: auto (R.4.4, R.4.5).

    Ten jeden krok zastępuje dwa z wersji podstawowej (float: left w obu blokach i clear: both w stopce).

    Typowe błędy

    width: 50% w bloku umieszczonym w kolumnie siatki liczy się od kolumny (25% strony) - szerokość daje się raz, w siatce.

    styl.css CSS zmienione: 11 linii
    :root {
        --teal: #00838F;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    
    nav,
    footer {
        background-color: var(--teal);
        color: var(--mint);
        padding: 10px;
    }
    
    main {
        display: grid;
        grid-template-columns: 50% 50%;
    }
    
    aside,
    section {
        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 (R.4.2).

    styl.css CSS zmienione: 4 linie
    :root {
        --teal: #00838F;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    
    nav,
    footer {
        background-color: var(--teal);
        color: var(--mint);
        padding: 10px;
    }
    
    main {
        display: grid;
        grid-template-columns: 50% 50%;
    }
    
    aside,
    section {
        height: 600px;
        overflow: auto;
    }
    
    aside {
        background-color: var(--mint);
    }
    Podgląd strony po tym kroku (index.php)
  32. Krok 32: Formularz: etykiety nad polami

    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.

    Formularz to siatka o jednej kolumnie: każda etykieta, pole i przycisk w osobnym wierszu, wyśrodkowane, z równym odstępem gap - jak na ilustracji 4, bez <br> w HTML. Klucz tego nie ocenia.

    styl.css CSS zmienione: 6 linii
    :root {
        --teal: #00838F;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    
    nav,
    footer {
        background-color: var(--teal);
        color: var(--mint);
        padding: 10px;
    }
    
    main {
        display: grid;
        grid-template-columns: 50% 50%;
    }
    
    aside,
    section {
        height: 600px;
        overflow: auto;
    }
    
    aside {
        background-color: var(--mint);
    }
    
    form {
        display: grid;
        justify-items: center;
        gap: 4px;
    }
    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.

    • rgb(0 131 143 / 0.7) - nowa składnia koloru: składowe oddzielone spacjami, a po ukośniku krycie. To ten sam kolor co rgba(0, 131, 143, 0.7) (inspektor tak go pokaże); 00 → 0, 83 → 131, 8F → 143 to #00838F w systemie dziesiętnym (R.4.9),
    • margin-block: 10px i margin-inline: 100px - marginesy „w kierunku bloku” (góra i dół) i „w kierunku tekstu” (lewa i prawa). Nazwy mówią wprost, czego dotyczą, zamiast skrótu 10px 100px, w którym trzeba pamiętać kolejność,
    • transition - przy najechaniu kursorem kolor zmieni się płynnie przez 0,2 s, a nie skokiem. Arkusz tego nie wymaga.
    Typowe błędy

    opacity: 0.7 przyciemniłby też obraz i tekst w bloku - przezroczyste ma być tylko tło.

    styl.css CSS zmienione: 9 linii
    :root {
        --teal: #00838F;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    
    nav,
    footer {
        background-color: var(--teal);
        color: var(--mint);
        padding: 10px;
    }
    
    main {
        display: grid;
        grid-template-columns: 50% 50%;
    }
    
    aside,
    section {
        height: 600px;
        overflow: auto;
    }
    
    aside {
        background-color: var(--mint);
    }
    
    form {
        display: grid;
        justify-items: center;
        gap: 4px;
    }
    
    .wycieczka {
        background-color: rgb(0 131 143 / 0.7);
        height: 150px;
        margin-block: 10px;
        margin-inline: 100px;
        border-radius: 30px;
        transition: background-color 0.2s;
    }
    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.

    Ten sam kolor z kryciem 1 - pełny, nieprzezroczysty (R.4.10).

    styl.css CSS zmienione: 4 linie
    :root {
        --teal: #00838F;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    
    nav,
    footer {
        background-color: var(--teal);
        color: var(--mint);
        padding: 10px;
    }
    
    main {
        display: grid;
        grid-template-columns: 50% 50%;
    }
    
    aside,
    section {
        height: 600px;
        overflow: auto;
    }
    
    aside {
        background-color: var(--mint);
    }
    
    form {
        display: grid;
        justify-items: center;
        gap: 4px;
    }
    
    .wycieczka {
        background-color: rgb(0 131 143 / 0.7);
        height: 150px;
        margin-block: 10px;
        margin-inline: 100px;
        border-radius: 30px;
        transition: background-color 0.2s;
    }
    
    .wycieczka:hover {
        background-color: rgb(0 131 143 / 1);
    }
    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.

    Tu nowoczesny układ ustępuje kluczowi: arkusz mówi o opływaniu, a klucz R.4.7 wymaga wprost float: right. Ofertę dałoby się ułożyć siatką (obraz w prawej kolumnie, tekst w lewej), ale za to punktu by nie było. float robi tu dokładnie to, do czego powstał - tekst opływa obraz (R.4.6, R.4.7).

    Typowe błędy

    float: left stawia obraz po lewej - nawias w arkuszu mówi: obraz po prawej.

    styl.css CSS zmienione: 6 linii
    :root {
        --teal: #00838F;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    
    nav,
    footer {
        background-color: var(--teal);
        color: var(--mint);
        padding: 10px;
    }
    
    main {
        display: grid;
        grid-template-columns: 50% 50%;
    }
    
    aside,
    section {
        height: 600px;
        overflow: auto;
    }
    
    aside {
        background-color: var(--mint);
    }
    
    form {
        display: grid;
        justify-items: center;
        gap: 4px;
    }
    
    .wycieczka {
        background-color: rgb(0 131 143 / 0.7);
        height: 150px;
        margin-block: 10px;
        margin-inline: 100px;
        border-radius: 30px;
        transition: background-color 0.2s;
    }
    
    .wycieczka:hover {
        background-color: rgb(0 131 143 / 1);
    }
    
    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.

    „Liniowo-blokowy” to dosłownie display: inline-block (R.4.8) - też forma narzucona przez klucz; menu w jednej linii dałoby się zrobić display: flex na liście, ale kryterium sprawdza właściwość elementu li.

    styl.css CSS zmienione: 5 linii
    :root {
        --teal: #00838F;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    
    nav,
    footer {
        background-color: var(--teal);
        color: var(--mint);
        padding: 10px;
    }
    
    main {
        display: grid;
        grid-template-columns: 50% 50%;
    }
    
    aside,
    section {
        height: 600px;
        overflow: auto;
    }
    
    aside {
        background-color: var(--mint);
    }
    
    form {
        display: grid;
        justify-items: center;
        gap: 4px;
    }
    
    .wycieczka {
        background-color: rgb(0 131 143 / 0.7);
        height: 150px;
        margin-block: 10px;
        margin-inline: 100px;
        border-radius: 30px;
        transition: background-color 0.2s;
    }
    
    .wycieczka:hover {
        background-color: rgb(0 131 143 / 1);
    }
    
    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śniki: kolor, bez podkreślenia, lewy margines, stan pod kursorem

    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;

    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. 2 min
    Polecenie z arkusza

    Dla selektora odnośnika: kolor czcionki #B2DFDB, bez podkreślenia, jedynie margines zewnętrzny lewy 50 px. Gdy kursor znajdzie się na odnośniku, jego kolor czcionki zmienia się na biały i dodawane jest podkreślenie.

    margin-left - „jedynie” lewy margines, więc nie margin-inline (to byłyby oba boki). Pod kursorem biały kolor i podkreślenie (R.4.3, R.4.6, R.4.8, R.4.10).

    styl.css CSS zmienione: 11 linii
    :root {
        --teal: #00838F;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
        text-align: center;
    }
    
    nav,
    footer {
        background-color: var(--teal);
        color: var(--mint);
        padding: 10px;
    }
    
    main {
        display: grid;
        grid-template-columns: 50% 50%;
    }
    
    aside,
    section {
        height: 600px;
        overflow: auto;
    }
    
    aside {
        background-color: var(--mint);
    }
    
    form {
        display: grid;
        justify-items: center;
        gap: 4px;
    }
    
    .wycieczka {
        background-color: rgb(0 131 143 / 0.7);
        height: 150px;
        margin-block: 10px;
        margin-inline: 100px;
        border-radius: 30px;
        transition: background-color 0.2s;
    }
    
    .wycieczka:hover {
        background-color: rgb(0 131 143 / 1);
    }
    
    img {
        float: right;
        border-radius: 30px;
        margin: 7px;
    }
    
    li {
        display: inline-block;
        list-style-type: none;
    }
    
    a {
        color: var(--mint);
        text-decoration: none;
        margin-left: 50px;
    }
    
    a:hover {
        color: white;
        text-decoration: underline;
    }
    Podgląd strony po tym kroku (index.php)
  38. Etap 4: Skrypty PHP

    Kroki 38-54 · po tym etapie masz 42 z 42 kryteriów (pkt)

    Krok 38: Dane połączenia w pliku config.php

    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. 2 min
    Polecenie z arkusza

    Łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie wyprawy.

    Dane logowania do bazy w osobnym pliku config.php, który tylko zwraca tablicę (return [...]). W prawdziwym projekcie hasło do bazy jest inne na każdym serwerze i nie trafia do repozytorium razem z kodem strony - wystarczy podmienić jeden plik. Arkusz dopuszcza dodatkowe pliki („ewentualnie inne przygotowane pliki”).

    DSN (mysql:host=localhost;dbname=wyprawy;charset=utf8mb4) to adres bazy dla PDO w jednym napisie: rodzaj serwera, host, nazwa bazy i kodowanie połączenia. Plik bez HTML-u nie ma końcowego ?> - nic po nim nie wypisze się przypadkiem.

    Typowe błędy

    Plik config.php musi trafić do folderu z rezultatami - bez niego strona się nie połączy, a egzaminator nie znajdzie nazwy bazy.

    config.php PHP nowy plik
    <?php
    // Dane połączenia z bazą - w jednym miejscu, poza plikiem strony
    return [
        'dsn' => 'mysql:host=localhost;dbname=wyprawy;charset=utf8mb4',
        'user' => 'root',
        'password' => '',
    ];

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

  39. Krok 39: Połączenie z bazą przez PDO

    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 stoi na samym początku pliku, przed <!DOCTYPE html>: najpierw wszystkie operacje na bazie, potem szablon strony, który tylko wypisuje gotowe dane. Klucz R.5 przyjmuje PDO tak samo jak MySQLi.

    • require 'config.php' - dane połączenia trafiają do $config,
    • new PDO(...) - połączenie i wybór bazy; opcja ERRMODE_EXCEPTION sprawia, że każdy błąd SQL zatrzymuje skrypt wyjątkiem, zamiast po cichu zwracać false, a FETCH_ASSOC - że wiersze przychodzą jako tablice z nazwami kolumn ($row['nazwa'] zamiast $row[1]),
    • try … catch (PDOException $error) - gdy baza nie działa, użytkownik widzi zwykły komunikat, a nie treść błędu z nazwą serwera i ścieżkami; http_response_code(500) mówi przeglądarce, że to błąd serwera.

    Od tego kroku podgląd to prawdziwy wynik strony z serwera - na razie bez zmian.

    Typowe błędy

    catch wypisujący $error->getMessage() pokazałby użytkownikowi szczegóły serwera.

    index.php PHP zmienione: 13 linii
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <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: Funkcja e(): bezpieczne wypisywanie tekstu

    ok. 2 min
    Polecenie z arkusza

    Należy stosować znaczące nazewnictwo zmiennych i funkcji w języku polskim lub angielskim.

    Każdy napis z bazy albo z formularza, który trafi do HTML-u, przejdzie przez e() (od escape). htmlspecialchars zamienia <, >, & i - dzięki ENT_QUOTES - oba cudzysłowy na encje, więc napis <script> zapisany kiedyś w bazie wyświetli się jako tekst, a nie wykona (ochrona przed XSS). Escapowanie robi się na wyjściu, tuż przed wypisaniem - w bazie zostają dane surowe.

    Krótka nazwa to świadomy wyjątek od „znaczącego nazewnictwa”: e() występuje w szablonie wiele razy i jest powszechnie znaną konwencją (tak samo nazywa się w Laravelu). Typy string przy parametrze i wyniku mówią, czego funkcja oczekuje; liczby PHP zamieni na tekst sam.

    index.php PHP zmienione: 5 linii
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <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: 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.

    Zapytanie 1 bez danych od użytkownika - query, a PDO::FETCH_COLUMN zwraca od razu listę nazw (R.5.2).

    index.php PHP zmienione: 2 linie
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <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.

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

    Pętla w składni szablonu, jeden <option> na miejsce (R.5.3). Bez value formularz wyśle nazwę miejsca - skrypt 2 szuka ceny właśnie po nazwie. Lista ma 21 miejsc.

    index.php PHP zmienione: 3 linie
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <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)
  43. Krok 43: Skrypt 2: czy wysłano poprawne dane

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

    „Wysłane dane” to tu cztery wartości, każda sprawdzona na wejściu:

    • miejsce - biała lista: in_array($place, $places, true) przepuszcza tylko nazwę, która jest w bazie (lista ze skryptu 1),
    • liczby osób - filter_input z FILTER_VALIDATE_INT i min_range 0: liczba całkowita albo false/null; puste pole czy „-2” nie przejdą, a rachunek nie skończy się błędem TypeError,
    • termin: DateTimeImmutable::createFromFormat('!Y-m-d', ...) czyta datę w formacie z pola date, a porównanie z powrotem z tekstem odrzuca daty nieistniejące - „2024-02-31” PHP zamieniłby po cichu na 2 marca.

    Dopiero gdy wszystkie cztery są poprawne, $cost przestanie być null. Przy pierwszym wejściu na stronę żaden warunek nie jest spełniony.

    index.php PHP zmienione: 10 linii
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <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 42.

  44. Krok 44: Skrypt 2: zapytanie 2 przygotowane 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.

    Zapytanie 2 z parametrem :place w miejscu „Malbork” (R.5.4). fetchColumn() zwraca samą cenę; kolumna decimal przychodzi jako napis „22530.00”, więc (float) zamienia ją jawnie na liczbę przed rachunkiem.

    index.php PHP zmienione: 3 linie
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
            $priceQuery = $db->prepare('SELECT cena FROM miejsca WHERE nazwa = :place');
            $priceQuery->execute(['place' => $place]);
            $price = (float) $priceQuery->fetchColumn();
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <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 42.

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

    Dorośli płacą pełną cenę, dzieci połowę: $adults * $price + $children * $price / 2 (R.5.5). Dla ilustracji 4 (Tokio 22 530 zł, 2 dorosłych, 4 dzieci): 45 060 + 45 060 = 90 120.

    index.php PHP zmienione: 1 linia
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
            $priceQuery = $db->prepare('SELECT cena FROM miejsca WHERE nazwa = :place');
            $priceQuery->execute(['place' => $place]);
            $price = (float) $priceQuery->fetchColumn();
            $cost = $adults * $price + $children * $price / 2;
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <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 42.

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

    if … endif w szablonie - paragraf tylko po udanej symulacji. Data wysłana z formularza przez e() (R.5.6): „W dniu: 2024-04-25”, jak na ilustracji 4.

    index.php PHP zmienione: 3 linie
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
            $priceQuery = $db->prepare('SELECT cena FROM miejsca WHERE nazwa = :place');
            $priceQuery->execute(['place' => $place]);
            $price = (float) $priceQuery->fetchColumn();
            $cost = $adults * $price + $children * $price / 2;
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php if ($cost !== null): ?>
                <p>W dniu: <?= e($dateText) ?></p>
    <?php endif; ?>
            </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 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. 2 min
    Polecenie z arkusza

    Wyświetla paragraf o treści <wartość> złotych, gdzie pole <> jest wyliczoną wartością wycieczki.

    number_format($cost, 2, ',', ' ') zapisuje kwotę po polsku: dwa miejsca po przecinku, przecinek dziesiętny, spacja między tysiącami - „90 120,00 złotych”. Ilustracja 4 pokazuje gołą liczbę „90120”; klucz R.5.7 sprawdza wartość i słowo „złotych”, więc obie postacie są poprawne, a sformatowana czyta się lepiej i nie gubi groszy przy cenach nieparzystych (połowa 1 999 zł to 999,50).

    Typowe błędy

    number_format zwraca napis - formatuj dopiero wynik, nie liczby przed rachunkiem.

    index.php PHP zmienione: 1 linia
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
            $priceQuery = $db->prepare('SELECT cena FROM miejsca WHERE nazwa = :place');
            $priceQuery->execute(['place' => $place]);
            $price = (float) $priceQuery->fetchColumn();
            $cost = $adults * $price + $children * $price / 2;
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date">
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php if ($cost !== null): ?>
                <p>W dniu: <?= e($dateText) ?></p>
                <p><?= number_format($cost, 2, ',', ' ') ?> złotych</p>
    <?php endif; ?>
            </aside>
            <section>
                <h3>Wycieczki</h3>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  48. Krok 48: Formularz pamięta wpisane dane

    ok. 2 min
    Polecenie z arkusza

    Formularz zgodny z ilustracją 2, wysyłający dane do tego samego pliku metodą bezpieczną.

    Na ilustracji 4 po symulacji formularz nadal pokazuje Tokio, 2, 4 i datę. Po wysłaniu strona ładuje się od nowa, więc pola trzeba wypełnić z powrotem: opcja równa wysłanemu miejscu dostaje selected, a pola - value z wysłaną wartością. Każda wartość przez e(): to dane od użytkownika wstawiane w atrybut, a ENT_QUOTES w e() nie pozwoli cudzysłowowi zamknąć atrybutu. Klucz tego nie ocenia - to wygoda z ilustracji.

    index.php PHP zmienione: 4 linie
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
            $priceQuery = $db->prepare('SELECT cena FROM miejsca WHERE nazwa = :place');
            $priceQuery->execute(['place' => $place]);
            $price = (float) $priceQuery->fetchColumn();
            $cost = $adults * $price + $children * $price / 2;
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option<?= $placeName === $place ? ' selected' : '' ?>><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1" value="<?= e($_POST['adults'] ?? '') ?>">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1" value="<?= e($_POST['children'] ?? '') ?>">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date" value="<?= e($dateText) ?>">
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php if ($cost !== null): ?>
                <p>W dniu: <?= e($dateText) ?></p>
                <p><?= number_format($cost, 2, ',', ' ') ?> złotych</p>
    <?php endif; ?>
            </aside>
            <section>
                <h3>Wycieczki</h3>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  49. Krok 49: 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.

    Zapytanie 3 przez query i fetchAll() do $offers (R.5.2). Napis PHP w cudzysłowie, bo wzorzec '0%' w SQL ma apostrofy.

    index.php PHP zmienione: 2 linie
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
            $priceQuery = $db->prepare('SELECT cena FROM miejsca WHERE nazwa = :place');
            $priceQuery->execute(['place' => $place]);
            $price = (float) $priceQuery->fetchColumn();
            $cost = $adults * $price + $children * $price / 2;
        }
    
        $offers = $db->query("SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE '0%'")->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option<?= $placeName === $place ? ' selected' : '' ?>><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1" value="<?= e($_POST['adults'] ?? '') ?>">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1" value="<?= e($_POST['children'] ?? '') ?>">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date" value="<?= e($dateText) ?>">
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php if ($cost !== null): ?>
                <p>W dniu: <?= e($dateText) ?></p>
                <p><?= number_format($cost, 2, ',', ' ') ?> złotych</p>
    <?php endif; ?>
            </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 48.

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

    Blok oferty to <article> - samodzielna treść, która miałaby sens także wyjęta ze strony (jak wpis czy karta produktu). Arkusz mówi „blok z klasą wycieczka”, nie narzuca znacznika; <div> też by wystarczył, ale nic by nie mówił (R.2.8, R.5.8).

    index.php PHP zmienione: 4 linie
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
            $priceQuery = $db->prepare('SELECT cena FROM miejsca WHERE nazwa = :place');
            $priceQuery->execute(['place' => $place]);
            $price = (float) $priceQuery->fetchColumn();
            $cost = $adults * $price + $children * $price / 2;
        }
    
        $offers = $db->query("SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE '0%'")->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option<?= $placeName === $place ? ' selected' : '' ?>><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1" value="<?= e($_POST['adults'] ?? '') ?>">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1" value="<?= e($_POST['children'] ?? '') ?>">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date" value="<?= e($dateText) ?>">
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php if ($cost !== null): ?>
                <p>W dniu: <?= e($dateText) ?></p>
                <p><?= number_format($cost, 2, ',', ' ') ?> złotych</p>
    <?php endif; ?>
            </aside>
            <section>
                <h3>Wycieczki</h3>
    <?php foreach ($offers as $offer): ?>
                <article class="wycieczka">
                </article>
    <?php endforeach; ?>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  51. Krok 51: 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”.

    src z kolumny link_obraz (przez e() - nazwa pliku z bazy trafia do atrybutu), tekst alternatywny z arkusza (R.2.9, R.5.9).

    index.php PHP zmienione: 1 linia
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
            $priceQuery = $db->prepare('SELECT cena FROM miejsca WHERE nazwa = :place');
            $priceQuery->execute(['place' => $place]);
            $price = (float) $priceQuery->fetchColumn();
            $cost = $adults * $price + $children * $price / 2;
        }
    
        $offers = $db->query("SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE '0%'")->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option<?= $placeName === $place ? ' selected' : '' ?>><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1" value="<?= e($_POST['adults'] ?? '') ?>">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1" value="<?= e($_POST['children'] ?? '') ?>">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date" value="<?= e($dateText) ?>">
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php if ($cost !== null): ?>
                <p>W dniu: <?= e($dateText) ?></p>
                <p><?= number_format($cost, 2, ',', ' ') ?> złotych</p>
    <?php endif; ?>
            </aside>
            <section>
                <h3>Wycieczki</h3>
    <?php foreach ($offers as $offer): ?>
                <article class="wycieczka">
                    <img src="<?= e($offer['link_obraz']) ?>" alt="zdjęcie z wycieczki">
                </article>
    <?php endforeach; ?>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  52. Krok 52: 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 nazwą - wewnątrz <article> nagłówek tytułuje właśnie tę ofertę (R.5.8).

    index.php PHP zmienione: 1 linia
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
            $priceQuery = $db->prepare('SELECT cena FROM miejsca WHERE nazwa = :place');
            $priceQuery->execute(['place' => $place]);
            $price = (float) $priceQuery->fetchColumn();
            $cost = $adults * $price + $children * $price / 2;
        }
    
        $offers = $db->query("SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE '0%'")->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option<?= $placeName === $place ? ' selected' : '' ?>><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1" value="<?= e($_POST['adults'] ?? '') ?>">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1" value="<?= e($_POST['children'] ?? '') ?>">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date" value="<?= e($dateText) ?>">
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php if ($cost !== null): ?>
                <p>W dniu: <?= e($dateText) ?></p>
                <p><?= number_format($cost, 2, ',', ' ') ?> złotych</p>
    <?php endif; ?>
            </aside>
            <section>
                <h3>Wycieczki</h3>
    <?php foreach ($offers as $offer): ?>
                <article class="wycieczka">
                    <img src="<?= e($offer['link_obraz']) ?>" alt="zdjęcie z wycieczki">
                    <h2><?= e($offer['nazwa']) ?></h2>
                </article>
    <?php endforeach; ?>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  53. Krok 53: 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.

    Cena w zapisie z bazy („2000.00”), jak na ilustracji 2 (R.5.8). Dziewięć ofert przekracza 600 px - pojawia się pasek przewijania.

    index.php PHP zmienione: 1 linia
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
            $priceQuery = $db->prepare('SELECT cena FROM miejsca WHERE nazwa = :place');
            $priceQuery->execute(['place' => $place]);
            $price = (float) $priceQuery->fetchColumn();
            $cost = $adults * $price + $children * $price / 2;
        }
    
        $offers = $db->query("SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE '0%'")->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option<?= $placeName === $place ? ' selected' : '' ?>><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1" value="<?= e($_POST['adults'] ?? '') ?>">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1" value="<?= e($_POST['children'] ?? '') ?>">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date" value="<?= e($dateText) ?>">
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php if ($cost !== null): ?>
                <p>W dniu: <?= e($dateText) ?></p>
                <p><?= number_format($cost, 2, ',', ' ') ?> złotych</p>
    <?php endif; ?>
            </aside>
            <section>
                <h3>Wycieczki</h3>
    <?php foreach ($offers as $offer): ?>
                <article class="wycieczka">
                    <img src="<?= e($offer['link_obraz']) ?>" alt="zdjęcie z wycieczki">
                    <h2><?= e($offer['nazwa']) ?></h2>
                    <p><?= e($offer['cena']) ?></p>
                </article>
    <?php endforeach; ?>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  54. Krok 54: 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ą.

    $db = null; na końcu bloku try - ostatnia operacja na bazie (R.5.1). Szablon korzysta już tylko z gotowych tablic.

    Typowe błędy

    $db->close() nie istnieje w PDO.

    index.php PHP zmienione: 2 linie
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $places = $db->query('SELECT nazwa FROM miejsca ORDER BY nazwa')->fetchAll(PDO::FETCH_COLUMN);
    
        $place = $_POST['place'] ?? '';
        $adults = filter_input(INPUT_POST, 'adults', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $children = filter_input(INPUT_POST, 'children', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);
        $dateText = $_POST['date'] ?? '';
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $dateText);
        $cost = null;
        if (in_array($place, $places, true) && is_int($adults) && is_int($children)
            && $date !== false && $date->format('Y-m-d') === $dateText) {
            $priceQuery = $db->prepare('SELECT cena FROM miejsca WHERE nazwa = :place');
            $priceQuery->execute(['place' => $place]);
            $price = (float) $priceQuery->fetchColumn();
            $cost = $adults * $price + $children * $price / 2;
        }
    
        $offers = $db->query("SELECT nazwa, cena, link_obraz FROM miejsca WHERE link_obraz LIKE '0%'")->fetchAll();
    
        $db = null;
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <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>
                    <select id="place" name="place">
    <?php foreach ($places as $placeName): ?>
                        <option<?= $placeName === $place ? ' selected' : '' ?>><?= e($placeName) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label for="adults">Ile dorosłych?</label>
                    <input type="number" id="adults" name="adults" min="0" step="1" value="<?= e($_POST['adults'] ?? '') ?>">
                    <label for="children">Ile dzieci?</label>
                    <input type="number" id="children" name="children" min="0" step="1" value="<?= e($_POST['children'] ?? '') ?>">
                    <label for="date">Termin</label>
                    <input type="date" id="date" name="date" value="<?= e($dateText) ?>">
                    <button type="submit">Symulacja ceny</button>
                </form>
                <h4>Koszt wycieczki</h4>
    <?php if ($cost !== null): ?>
                <p>W dniu: <?= e($dateText) ?></p>
                <p><?= number_format($cost, 2, ',', ' ') ?> złotych</p>
    <?php endif; ?>
            </aside>
            <section>
                <h3>Wycieczki</h3>
    <?php foreach ($offers as $offer): ?>
                <article class="wycieczka">
                    <img src="<?= e($offer['link_obraz']) ?>" alt="zdjęcie z wycieczki">
                    <h2><?= e($offer['nazwa']) ?></h2>
                    <p><?= e($offer['cena']) ?></p>
                </article>
    <?php endforeach; ?>
            </section>
        </main>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  55. Etap 5: Porównanie z wersją podstawową

    Krok 55 · po tym etapie masz 42 z 42 kryteriów (pkt)

    Krok 55: Co jest inaczej i którą wersję wybrać na egzaminie

    ok. 2 min

    Obie wersje dają komplet 42 punktów. Różnice:

    wersja podstawowawersja nowoczesna
    układfloat: left + clear: bothmain jako siatka 50% / 50%
    kolorywpisane w każdej regulezmienne CSS, rgb(0 131 143 / 0.7)
    oferta<div class="wycieczka"><article class="wycieczka">
    bazamysqli_*, miejsce sklejone z SQLPDO, zapytanie przygotowane, config.php
    dane z formularzaisset i is_numericbiała lista, filter_input, kontrola daty
    kwota„90120 złotych”„90 120,00 złotych” (number_format)
    formularz po symulacjipustypamięta dane

    Klucz wymusza starsze formy w dwóch miejscach: float: right dla obrazu (R.4.7) i display: inline-block dla elementów listy (R.4.8) - obie wersje ich używają. Na egzaminie wersja podstawowa ma mniej kodu do napisania; nowoczesna wymaga znajomości PDO i DateTimeImmutable na pamięć. Symulacja ceny niczego nie zapisuje, więc w prawdziwej aplikacji formularz poszedłby metodą GET (wynik można wtedy zapisać w zakładce) - arkusz wymaga POST.

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

  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 folderze z numerem zdającego: 01.jpg … 21.jpg, allinclusive.html, config.php, import.png, index.php, kw1.png … kw5.png, kwerendy.txt, przeglądarka.txt, styl.css, wczasy.html, wycieczki.html. Płytę sprawdź po nagraniu.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.