Przejdź do głównej treści
INF.03-01-26.06-SGCzerwiec 2026 · 52 kroki · ok. 91 min · 37 kryteriów z klucza

Rozwiązanie krok po kroku: Portal turystyczny Islandii (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza islandia, cztery zapytania, dwie strony PHP (galeria islandia.php i opis miejsca obiekty.php?id=…), style w styl.css - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • układ na siatce CSS: grid-template-areas rysuje ilustrację 4 tekstem, więc bloki w kodzie stoją w kolejności czytania (nagłówek, treść, blok boczny, stopka), a nie w kolejności wymaganej przez float,
  • PHP na PDO z logiką na górze każdej strony i szablonem pod nią; tekst z bazy wychodzi przez funkcję e(),
  • jedno zapytanie przygotowane dla obu list bloku bocznego (wodospady i siedliska różnią się tylko numerem rodzaju),
  • parametr z adresu jest sprawdzany i nigdy nie trafia do treści SQL: filter_var przepuszcza tylko liczbę całkowitą, a zapytanie dostaje ją jako parametr; nieznane miejsce kończy się komunikatem i kodem 404 zamiast błędu PHP.

Każda strona jest samodzielna - połączenie (z nazwą bazy) i wszystkie skrypty są w jej pliku, tam gdzie egzaminator ich szuka. Wersja z kodem wspólnym wyniesionym do osobnych plików jest w trzeciej zakładce. Na końcu jest krótkie porównanie wersji i podpowiedź, którą wybrać na egzaminie.

Baza i zdjęcia pochodzą z prawdziwego archiwum pliki1 (załącznik na stronie arkusza, hasło w treści zadania), a podgląd każdego kroku z PHP to wynik prawdziwego uruchomienia stron na serwerze z tą bazą. Podgląd obiekty.php pokazuje obiekt z ilustracji 3 (Godafoss, obiekty.php?id=53).

Jak liczą się punkty. Klucz ma 37 kryteriów (R.1.1 - R.5.7), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 28 punktów. Przy każdym kroku jest napisane, które kryteria spełnia.

  1. Etap 1: Baza danych

    Kroki 1-7 · po tym etapie masz 6 z 37 kryteriów (pkt)

    Krok 1: Baza danych islandia

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych islandia - plik import w formacie JPEG

    ok. 3 min
    Polecenie z arkusza

    Utwórz bazę danych o nazwie islandia, z zestawem znaków utf8_unicode_ci.

    Najpierw rozpakuj archiwum pliki1.7z z pulpitu (hasło isLandi@ z treści zadania) - są w nim baza1.sql i dziewięć zdjęć. W phpMyAdmin kliknij Nowa, wpisz islandia i wybierz utf8_unicode_ci. To samo w SQL:

    CREATE DATABASE islandia CHARACTER SET utf8 COLLATE utf8_unicode_ci;
    

    utf8_unicode_ci to ogólne porównywanie Unicode: sortuje polskie („gorące źródło”) i islandzkie litery („Fuglaskoðunarhús”) według zasad Unicode, a przy porównaniach nie rozróżnia wielkości liter (ci - case insensitive). Dziedziczą je jednak tylko tabele zakładane później bez własnego ustawienia - tabele z baza1.sql mają w definicji utf8_polish_ci i to ono działa w zapytaniach.

    Typowe błędy

    Nazwa bazy musi być dokładnie islandia - skrypty łączą się z bazą po nazwie (R.5.1).

  2. Krok 2: Import tabel z pliku baza1.sql

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych islandia - plik import w formacie JPEG

    ok. 2 min
    Polecenie z arkusza

    Do utworzonej bazy zaimportuj tabele z pliku baza1.sql.

    Zaznacz bazę islandia, karta Import, plik baza1.sql, Wykonaj. Plik tworzy pięć tabel, ale zadanie korzysta tylko z trzech z ilustracji 1: kontynenty, rodzaje (rodzaje obiektów: wodospad, szczyt, siedlisko zwierząt…) i obiekty (25 obiektów z kluczami obcymi idRodzaj i idKontynent). Nazwy kolumn mają wielkie litery w środku (idObiekt, nazwaCechy, wartoscCechy) - w zapytaniach pisz je dokładnie tak.

  3. Krok 3: Zrzut ekranu import.jpg

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych islandia - plik import w formacie JPEG

    ok. 2 min
    Polecenie z arkusza

    Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie JPEG pod nazwą import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań. Na zrzucie powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel.

    Cały ekran (Print Screen) z komunikatem o udanym imporcie albo listą tabel, zapis jako import.jpg - w tym arkuszu import jest w JPEG, a zrzuty zapytań w PNG.

    Typowe błędy

    import.png zamiast import.jpg albo wykadrowany zrzut to strata punktu R.1.1.

  4. Krok 4: Zapytanie 1: obiekty z Islandii

    Kryteria: ,

    R.1.2 W pliku kwerendy.txt zapisane co najmniej jedno zapytanie SQL, wynikające z treści zadania

    R.1.3 Wykonane zapytanie 1 wybierające jedynie pola idObiekt, plik i nazwa z tabeli obiekty dla Islandii. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT idObiekt, plik, nazwa FROM obiekty WHERE panstwo = "Islandia"; oraz na zrzucie widocznych jest dokładnie 9 rekordów o identyfikatorach 46 ÷ 54

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pola idObiekt, plik i nazwa z tabeli obiekty dla Islandii.

    Każda klauzula w osobnej linii: co (trzy kolumny w kolejności z arkusza), skąd (obiekty), które wiersze (WHERE panstwo = 'Islandia'). Tekst w SQL w apostrofach - to zapis zgodny ze standardem, działający w każdej bazie (MySQL przyjmuje też cudzysłów, jak w kluczu).

    Wynik: 9 wierszy o identyfikatorach 46-54 (R.1.3). Zapytanie do kwerendy.txt, zrzut kw1.png.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT idObiekt, plik, nazwa
    FROM obiekty
    WHERE panstwo = 'Islandia';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: dane obiektu 46 z rodzajem

    Kryteria:

    R.1.4 Wykonane zapytanie 2 wybierające jedynie pola plik, nazwa, nazwaCechy, wartoscCechy, opis z tabeli obiekty oraz odpowiadające im pole rodzaj z tabeli rodzaje dla obiektu o identyfikatorze 46. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT plik, nazwa, nazwaCechy, wartoscCechy, opis, rodzaj FROM obiekty JOIN rodzaje USING(idRodzaj) WHERE idObiekt = 46; (lub INNER JOIN lub porównanie kluczy po ON lub WHERE ) oraz na zrzucie widoczny jest dokładnie 1 rekord dla nazwy Strokkur

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pola plik, nazwa, nazwaCechy, wartoscCechy, opis z tabeli obiekty oraz odpowiadające im pole rodzaj z tabeli rodzaje dla obiektu o identyfikatorze 46. Należy posłużyć się relacją.

    Złączenie z aliasami tabel (obiekty AS o, rodzaje AS r): każda kolumna mówi, z której tabeli pochodzi, a warunek złączenia r.idRodzaj = o.idRodzaj nie jest dwuznaczny, choć kolumna idRodzaj jest w obu tabelach.

    Wynik: jeden wiersz - gejzer Strokkur (R.1.4). Zrzut kw2.png. Strona obiekty.php wyśle to samo zapytanie z identyfikatorem z adresu w miejscu 46.

    Typowe błędy

    ON idRodzaj = idRodzaj bez nazw tabel to błąd „Column idRodzaj in on clause is ambiguous”.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT idObiekt, plik, nazwa
    FROM obiekty
    WHERE panstwo = 'Islandia';
    
    -- Zapytanie 2
    SELECT o.plik, o.nazwa, o.nazwaCechy, o.wartoscCechy, o.opis, r.rodzaj
    FROM obiekty AS o
    JOIN rodzaje AS r ON r.idRodzaj = o.idRodzaj
    WHERE o.idObiekt = 46;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: wodospady Islandii

    Kryteria:

    R.1.5 Wykonane zapytanie 3 wybierające jedynie nazwy obiektów z Islandii, których identyfikator rodzaju to 10. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa FROM obiekty WHERE idRodzaj = 10 AND Panstwo = "Islandia"; oraz na zrzucie widoczne są dokładnie 4 rekordy: Skogafoss, Seljalandsfoss, Godafoss, Dettifoss

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie nazwy obiektów z Islandii, których identyfikator rodzaju to 10.

    Dwa warunki naraz - AND. Identyfikator rodzaju jest w samej tabeli obiekty, więc złączenie nie jest potrzebne. Wynik: Skogafoss, Seljalandsfoss, Godafoss, Dettifoss (R.1.5), zrzut kw3.png.

    Typowe błędy

    OR zamiast AND zwróci wszystkie obiekty z Islandii i wszystkie wodospady z bazy.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT idObiekt, plik, nazwa
    FROM obiekty
    WHERE panstwo = 'Islandia';
    
    -- Zapytanie 2
    SELECT o.plik, o.nazwa, o.nazwaCechy, o.wartoscCechy, o.opis, r.rodzaj
    FROM obiekty AS o
    JOIN rodzaje AS r ON r.idRodzaj = o.idRodzaj
    WHERE o.idObiekt = 46;
    
    -- Zapytanie 3
    SELECT nazwa
    FROM obiekty
    WHERE panstwo = 'Islandia'
      AND idRodzaj = 10;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: liczba obiektów każdego rodzaju

    Kryteria:

    R.1.6 Wykonane zapytanie 4 wybierające jedynie rodzaj z tabeli rodzaje oraz zliczające liczbę obiektów danego rodzaju. Kolumna z liczbą obiektów powinna mieć nadaną nazwę (alias) „Liczba obiektów”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT rodzaj, COUNT(obiekty.idObiekt) AS "Liczba obiektów" FROM Rodzaje JOIN obiekty USING(idRodzaj) GROUP BY rodzaj; (Po COUNT dowolna nazwa pola tabeli obiekty, AS opcjonalnie, lub porównanie kluczy po ON lub WHERE ) oraz na zrzucie widocznych jest dokładnie 8 rekordów: gorące źródło 1, jaskinia 2, jezioro 1, park narodowy 1, siedlisko zwierząt 2, szczyt 4, wodospad 12, wulkan 2

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie rodzaj z tabeli rodzaje oraz zliczające liczbę obiektów (wodospadów, szczytów itd.) danego rodzaju. Kolumna z liczbą obiektów powinna mieć nadaną nazwę (alias) „Liczba obiektów”. Należy posłużyć się grupowaniem i relacją.

    • JOIN obiekty AS o ON o.idRodzaj = r.idRodzaj - relacja: do każdego rodzaju jego obiekty,
    • GROUP BY r.idRodzaj, r.rodzaj - grupa to jeden rodzaj wskazany kluczem; dwa różne rodzaje o tej samej nazwie nie zleją się w jeden wiersz,
    • COUNT(o.idObiekt) AS "Liczba obiektów" - alias ze spacją i polską literą musi być w cudzysłowie (tu cudzysłów oznacza nazwę, nie tekst),
    • ORDER BY r.rodzaj - kolejność alfabetyczna podana wprost; samo grupowanie jej nie gwarantuje.

    Wynik: 8 wierszy - gorące źródło 1, jaskinia 2, jezioro 1, park narodowy 1, siedlisko zwierząt 2, szczyt 4, wodospad 12, wulkan 2 (R.1.6). Zrzut kw4.png.

    Typowe błędy

    Bez GROUP BY COUNT policzy wszystkie 25 obiektów w jednym wierszu.

    kwerendy.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT idObiekt, plik, nazwa
    FROM obiekty
    WHERE panstwo = 'Islandia';
    
    -- Zapytanie 2
    SELECT o.plik, o.nazwa, o.nazwaCechy, o.wartoscCechy, o.opis, r.rodzaj
    FROM obiekty AS o
    JOIN rodzaje AS r ON r.idRodzaj = o.idRodzaj
    WHERE o.idObiekt = 46;
    
    -- Zapytanie 3
    SELECT nazwa
    FROM obiekty
    WHERE panstwo = 'Islandia'
      AND idRodzaj = 10;
    
    -- Zapytanie 4
    SELECT r.rodzaj, COUNT(o.idObiekt) AS "Liczba obiektów"
    FROM rodzaje AS r
    JOIN obiekty AS o ON o.idRodzaj = r.idRodzaj
    GROUP BY r.idRodzaj, r.rodzaj
    ORDER BY r.rodzaj;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Strona HTML

    Kroki 8-21 · po tym etapie masz 16 z 37 kryteriów (pkt)

    Krok 8: Zdjęcia w folderze zdającego

    ok. 1 min
    Polecenie z arkusza

    Witryna składa się z podstron: islandia.php i obiekty.php.

    Dziewięć zdjęć z rozpakowanego archiwum kopiujesz do folderu zdającego, obok stron. Skrypty będą je wskazywać samą nazwą pliku z kolumny plik, więc ścieżka względna wystarcza. Arkusz nie wymaga ich obróbki.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Plik islandia.php w HTML5

    Kryteria:

    R.2.1 W pliku islandia.php lub obiekty.php znajduje się deklaracja HTML5 <!DOCTYPE HTML> i deklaracja języka zawartości: polski np. <html lang="pl">

    ok. 1 min
    Polecenie z arkusza

    Cechy wspólne obu stron: Zapisane w języku HTML5.

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

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

    Kryteria:

    R.2.1 W pliku islandia.php lub obiekty.php znajduje się deklaracja HTML5 <!DOCTYPE HTML> i deklaracja języka zawartości: polski np. <html lang="pl">

    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.

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

    Kryteria:

    R.2.2 W sekcji head znajduje się jawnie podany standard kodowania polskich znaków i tytuł strony "Islandia”

    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.

    islandia.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 (islandia.php)
  12. Krok 12: Tytuł strony

    Kryteria:

    R.2.2 W sekcji head znajduje się jawnie podany standard kodowania polskich znaków i tytuł strony "Islandia”

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony „Islandia”.

    <title>Islandia</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.

    islandia.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>Islandia</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  13. Krok 13: 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.

    islandia.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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  14. Krok 14: Bloki semantyczne

    Kryteria:

    R.2.3 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML5: header, aside, main -> section, footer (dopuszcza się stosowanie bloków pomocniczych)

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: nagłówkowy, boczny, główny, stopki. Blok główny zawiera blok sekcji. Podział zrealizowany za pomocą semantycznych znaczników języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków był zgodny z ilustracją 4.

    Znaczniki z tabeli 4 arkusza i klucza R.2.3: header, main z section w środku, aside, footer.

    Kolejność w kodzie to kolejność czytania: nagłówek, treść główna, dodatki z boku, stopka - tak stronę przeczyta czytnik ekranu i tak zobaczy ją telefon bez stylów. O tym, że blok boczny stanie po prawej od samej góry, zdecyduje siatka CSS, a nie miejsce w kodzie. (Przy układzie na float blok boczny musiałby stać w kodzie przed blokiem głównym - tak jest w wersji podstawowej.)

    islandia.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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
        </header>
        <main>
            <section>
            </section>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  15. Krok 15: Nagłówek pierwszego stopnia będący odnośnikiem

    Kryteria: ,

    R.2.4 W bloku nagłówkowym znajduje się <h1>: "Zwiedzaj Islandię", w bloku bocznym <h3>: "Do zwiedzania" w bloku głównym <h2>: "Galeria" lub "Opis miejsca" oraz <p> z numerem zdającego w stopce

    R.3.3 Przynajmniej na jednej stronie kliknięcie odnośnika z bloku nagłówkowego prowadzi do strony islandia.php

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści „Zwiedzaj Islandię”. Treść jest odnośnikiem do strony islandia.php.

    Odnośnik wewnątrz nagłówka (<h1><a>…</a></h1>) - nagłówek zostaje nagłówkiem, a jego treść da się kliknąć. Ze strony obiekty.php zaprowadzi z powrotem do galerii (R.2.4, R.3.3).

    islandia.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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <section>
            </section>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  16. Krok 16: Blok główny: nagłówek „Galeria” i blok sekcji

    Kryteria:

    R.2.4 W bloku nagłówkowym znajduje się <h1>: "Zwiedzaj Islandię", w bloku bocznym <h3>: "Do zwiedzania" w bloku głównym <h2>: "Galeria" lub "Opis miejsca" oraz <p> z numerem zdającego w stopce

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku głównego dla strony islandia.php: nagłówek drugiego stopnia o treści: „Galeria”. Blok sekcji z efektem wykonania skryptu 3.

    <h2> przed <section> - nagłówek stoi nad ramką sekcji, jak na ilustracji 2. Sekcję wypełni galeria ze skryptu 3.

    islandia.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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
            </section>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  17. Krok 17: Blok boczny: nagłówek trzeciego stopnia

    Kryteria:

    R.2.4 W bloku nagłówkowym znajduje się <h1>: "Zwiedzaj Islandię", w bloku bocznym <h3>: "Do zwiedzania" w bloku głównym <h2>: "Galeria" lub "Opis miejsca" oraz <p> z numerem zdającego w stopce

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku bocznego: nagłówek trzeciego stopnia o treści „Do zwiedzania”.

    <h3> - stopień nagłówka narzuca arkusz (R.2.4).

    islandia.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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  18. Krok 18: Lista punktowana z dwoma elementami

    Kryteria:

    R.2.5 W bloku bocznym znajduje się jedna lista punktowana z dwoma elementami „Wodospady:” i „Siedliska zwierząt:” (znaczniki <ul>, <li> )

    ok. 1 min
    Polecenie z arkusza

    Lista punktowana (nieuporządkowana) z dwoma elementami: „Wodospady:” i „Siedliska zwierząt:”.

    <ul> z dwoma <li> (R.2.5). Znacznik </li> w osobnej linii, bo w środku każdego elementu stanie zaraz druga lista.

    islandia.php PHP zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                </li>
                <li>Siedliska zwierząt:
                </li>
            </ul>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  19. Krok 19: Zagnieżdżone listy numerowane

    Kryteria:

    R.2.6 Przynajmniej jeden element listy zawiera zagnieżdżoną jedną listę numerowaną (w skrypcie lub HTML, znaczniki <ol> i <li>, znacznik <ol> zawarto wewnątrz <li> listy z R.2.5)

    ok. 2 min
    Polecenie z arkusza

    Element „Wodospady:” zawiera listę numerowaną (uporządkowaną) wypełnioną skryptem 1. Element „Siedliska zwierząt:” zawiera listę numerowaną (uporządkowaną) wypełnioną skryptem 2.

    Lista zagnieżdżona to <ol> wewnątrz <li>, między tekstem a </li> (R.2.6). Elementy dopiszą skrypty 1 i 2.

    Typowe błędy

    <ol> między </li> a następnym <li> nie jest zagnieżdżona w elemencie - to niepoprawny HTML i brak R.2.6.

    islandia.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  20. Krok 20: Stopka: linia pozioma i autor

    Kryteria: ,

    R.2.4 W bloku nagłówkowym znajduje się <h1>: "Zwiedzaj Islandię", w bloku bocznym <h3>: "Do zwiedzania" w bloku głównym <h2>: "Galeria" lub "Opis miejsca" oraz <p> z numerem zdającego w stopce

    R.2.9 Stopka zawiera linię poziomą (<hr> lub za pomocą obramowania w CSS )

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku stopki: linia pozioma. Paragraf o treści „Autor: ”, dalej wstawiony numer zdającego.

    <hr> (element pusty, bez ukośnika w HTML5) i paragraf (R.2.4, R.2.9). Tu stoi nazwisko autora rozwiązania - na egzaminie wpisujesz swój numer zdającego (PESEL).

    islandia.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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  21. Krok 21: Strona obiekty.php z kopii

    Kryteria:

    R.2.8 Na stronie obiekty.php w bloku głównym lub bloku sekcji znajdują się elementy <h2>, <h3> i przynajmniej jeden <p> (w skrypcie lub HTML )

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku głównego dla strony obiekty.php: nagłówek drugiego stopnia o treści: „Opis miejsca”. Blok sekcji z efektem działania skryptu 4.

    Cechy wspólne są już w islandia.php, więc Zapisz jako obiekty.php i jedna zmiana: nagłówek bloku głównego „Opis miejsca”. Sekcję wypełni skrypt 4 (R.2.8 - nagłówki i paragrafy w sekcji).

    obiekty.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (obiekty.php)
  22. Etap 3: Styl CSS

    Kroki 22-31 · po tym etapie masz 27 z 37 kryteriów (pkt)

    Krok 22: Zmienne CSS z wartościami arkusza

    ok. 2 min
    Polecenie z arkusza

    Styl CSS zdefiniowany jest w całości w zewnętrznym pliku o nazwie styl.css.

    Plik styl.css zaczyna się od zmiennych w :root: kolor PaleTurquoise i zaokrąglenie 20px dotyczą dwóch bloków, więc wpisane raz występują w regułach jako var(--accent) i var(--radius). W inspektorze widać obliczone wartości - kryteria sprawdza się tak samo.

    styl.css CSS nowy plik
    :root {
        --accent: PaleTurquoise;
        --frame: DimGray;
        --radius: 20px;
    }
    Podgląd strony po tym kroku (islandia.php)
  23. Krok 23: Krój czcionki dla wszystkich selektorów

    Kryteria:

    R.4.1 domyślne formatowanie selektorów (*): krój czcionki "Comic Sans MS" - jako pierwszy, sans-serif - jako ostatni

    ok. 1 min
    Polecenie z arkusza

    Domyślnie, dla wszystkich selektorów: krój czcionki Comic Sans MS, w przypadku braku sans-serif.

    Selektor *, nazwa ze spacjami w cudzysłowie, na końcu rodzina zastępcza sans-serif (R.4.1).

    styl.css CSS zmienione: 4 linie
    :root {
        --accent: PaleTurquoise;
        --frame: DimGray;
        --radius: 20px;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
    }
    Podgląd strony po tym kroku (islandia.php)
  24. Krok 24: Układ bloków zgodny z ilustracją 4: siatka

    Kryteria: ,

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 4 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; żaden blok nie zachodzi na inne bloki - sprawdzić w inspektorze)

    R.4.3 szerokość: blok nagłówkowy, główny - 70%, boczny - 25% (width lub grid-template- columns lub flex ) wysokość: blok nagłówkowy - 100px, boczny - 700px, sekcja - 400px

    ok. 4 min
    Polecenie z arkusza

    Podział zrealizowany za pomocą semantycznych znaczników języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków był zgodny z ilustracją 4.

    Ciało strony staje się siatką, a grid-template-areas rysuje ilustrację 4 tekstem: w pierwszym wierszu nagłówek i blok boczny, w drugim blok główny i dalej blok boczny (to samo słowo w dwóch wierszach = blok na dwa wiersze), w trzecim stopka na całą szerokość. Cztery krótkie reguły przypisują bloki do obszarów (R.3.1).

    • grid-template-columns: 70% 25% - szerokości z arkusza: nagłówek i blok główny 70%, blok boczny 25% (klucz R.4.3 przyjmuje je w siatce), column-gap: 5% - reszta szerokości to odstęp,
    • grid-template-rows: auto 1fr - pierwszy wiersz ma wysokość nagłówka, a drugi bierze resztę wysokości bloku bocznego; bez tego siatka rozdzieliłaby nadmiar po równo i pod nagłówkiem powstałaby dziura,
    • align-items: start - bloki nie rozciągają się do wysokości wiersza, więc nagłówek ma swoje 100 px.

    Bloki nie nachodzą na siebie, a stopka zawsze jest pod najwyższym z nich - bez clear.

    Typowe błędy

    width: 70% na nagłówku w siatce liczy się od szerokości kolumny, nie strony - szerokości trzymaj w grid-template-columns.

    styl.css CSS zmienione: 28 linii
    :root {
        --accent: PaleTurquoise;
        --frame: DimGray;
        --radius: 20px;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 25%;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header aside"
            "main   aside"
            "footer footer";
        column-gap: 5%;
        align-items: start;
    }
    
    header {
        grid-area: header;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (islandia.php)
  25. Krok 25: Wyśrodkowanie, tło i zaokrąglenie

    Kryteria: , ,

    R.4.2 kolor tła: blok nagłówkowy i boczny - PaleTurquoise

    R.4.5 wyrównanie tekstu: blok nagłówkowy, główny - do środka

    R.4.7 zaokrąglenie rogów (border-radius) : blok nagłówkowy, boczny - 20px

    ok. 1 min
    Polecenie z arkusza

    Dla bloku nagłówkowego i głównego: szerokość 70%, wyrównanie tekstu do środka. Dla bloku nagłówkowego i bocznego: kolor tła PaleTurquoise, zaokrąglenie rogów 20 px.

    Szerokość 70% jest już w siatce. Dochodzą wspólne reguły par bloków: wyśrodkowany tekst (R.4.5), tło i zaokrąglenie ze zmiennych (R.4.2, R.4.7).

    styl.css CSS zmienione: 11 linii
    :root {
        --accent: PaleTurquoise;
        --frame: DimGray;
        --radius: 20px;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 25%;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header aside"
            "main   aside"
            "footer footer";
        column-gap: 5%;
        align-items: start;
    }
    
    header {
        grid-area: header;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    main {
        text-align: center;
    }
    
    header,
    aside {
        border-radius: var(--radius);
        background-color: var(--accent);
    }
    Podgląd strony po tym kroku (islandia.php)
  26. Krok 26: Nagłówek: wysokość i marginesy wewnętrzne

    Kryteria: ,

    R.4.3 szerokość: blok nagłówkowy, główny - 70%, boczny - 25% (width lub grid-template- columns lub flex ) wysokość: blok nagłówkowy - 100px, boczny - 700px, sekcja - 400px

    R.4.4 marginesy wewnętrzne (padding ): blok nagłówkowy - 2%, stopka jedynie padding-top: 10px; marginesy zewnętrzne (margin ): sekcja - górny i dolny - 10px, pozostałe - 50px

    ok. 1 min
    Polecenie z arkusza

    Dodatkowo dla bloku nagłówkowego: wysokość 100 px, marginesy wewnętrzne 2%.

    Do reguły header, która już przypisuje obszar siatki (R.4.3, R.4.4). Procentowy padding liczy się od szerokości obszaru - także w pionie.

    styl.css CSS zmienione: 2 linie
    :root {
        --accent: PaleTurquoise;
        --frame: DimGray;
        --radius: 20px;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 25%;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header aside"
            "main   aside"
            "footer footer";
        column-gap: 5%;
        align-items: start;
    }
    
    header {
        grid-area: header;
        height: 100px;
        padding: 2%;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    main {
        text-align: center;
    }
    
    header,
    aside {
        border-radius: var(--radius);
        background-color: var(--accent);
    }
    Podgląd strony po tym kroku (islandia.php)
  27. Krok 27: Blok boczny: wysokość i interlinia

    Kryteria: ,

    R.4.3 szerokość: blok nagłówkowy, główny - 70%, boczny - 25% (width lub grid-template- columns lub flex ) wysokość: blok nagłówkowy - 100px, boczny - 700px, sekcja - 400px

    R.4.8 wysokość linii (line-hight) : blok boczny - 2

    ok. 1 min
    Polecenie z arkusza

    Dodatkowo dla bloku bocznego: szerokość 25%, wysokość 700 px, wysokość linii (interlinia) 2.

    Szerokość 25% to druga kolumna siatki. Wysokość 700 px i line-height: 2 - liczba bez jednostki to dwukrotność rozmiaru czcionki, więc interlinia rośnie razem z tekstem (R.4.3, R.4.8).

    styl.css CSS zmienione: 2 linie
    :root {
        --accent: PaleTurquoise;
        --frame: DimGray;
        --radius: 20px;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 25%;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header aside"
            "main   aside"
            "footer footer";
        column-gap: 5%;
        align-items: start;
    }
    
    header {
        grid-area: header;
        height: 100px;
        padding: 2%;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
        height: 700px;
        line-height: 2;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    main {
        text-align: center;
    }
    
    header,
    aside {
        border-radius: var(--radius);
        background-color: var(--accent);
    }
    Podgląd strony po tym kroku (islandia.php)
  28. Krok 28: Stopka: margines wewnętrzny górny

    Kryteria:

    R.4.4 marginesy wewnętrzne (padding ): blok nagłówkowy - 2%, stopka jedynie padding-top: 10px; marginesy zewnętrzne (margin ): sekcja - górny i dolny - 10px, pozostałe - 50px

    ok. 1 min
    Polecenie z arkusza

    Dla bloku stopki: jedynie margines wewnętrzny górny 10 px.

    „Jedynie” dotyczy wyglądu: padding-top: 10px (R.4.4). Przypisanie do obszaru siatki to układ, nie formatowanie stopki.

    styl.css CSS zmienione: 1 linia
    :root {
        --accent: PaleTurquoise;
        --frame: DimGray;
        --radius: 20px;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 25%;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header aside"
            "main   aside"
            "footer footer";
        column-gap: 5%;
        align-items: start;
    }
    
    header {
        grid-area: header;
        height: 100px;
        padding: 2%;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
        height: 700px;
        line-height: 2;
    }
    
    footer {
        grid-area: footer;
        padding-top: 10px;
    }
    
    header,
    main {
        text-align: center;
    }
    
    header,
    aside {
        border-radius: var(--radius);
        background-color: var(--accent);
    }
    Podgląd strony po tym kroku (islandia.php)
  29. Krok 29: Blok sekcji: ramka, marginesy, wysokość, przewijanie

    Kryteria: , ,

    R.4.3 szerokość: blok nagłówkowy, główny - 70%, boczny - 25% (width lub grid-template- columns lub flex ) wysokość: blok nagłówkowy - 100px, boczny - 700px, sekcja - 400px

    R.4.4 marginesy wewnętrzne (padding ): blok nagłówkowy - 2%, stopka jedynie padding-top: 10px; marginesy zewnętrzne (margin ): sekcja - górny i dolny - 10px, pozostałe - 50px

    R.4.6 cechy sekcji: obramowanie 1px dashed DimGray, paski przewijania (overflow) auto

    ok. 2 min
    Polecenie z arkusza

    Dla bloku sekcji: obramowanie linią kreskową o szerokości 1 px i kolorze DimGray, marginesy zewnętrzne górny i dolny 10 px, pozostałe 50 px, wysokość 400 px, paski przewijania pojawiające się tylko w przypadku przepełnienia bloku.

    margin: 10px 50px - pierwsza wartość góra i dół, druga lewa i prawa; overflow: auto - paski tylko przy przepełnieniu; dashed - linia kreskowa (R.4.3, R.4.4, R.4.6).

    styl.css CSS zmienione: 7 linii
    :root {
        --accent: PaleTurquoise;
        --frame: DimGray;
        --radius: 20px;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 25%;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header aside"
            "main   aside"
            "footer footer";
        column-gap: 5%;
        align-items: start;
    }
    
    header {
        grid-area: header;
        height: 100px;
        padding: 2%;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
        height: 700px;
        line-height: 2;
    }
    
    footer {
        grid-area: footer;
        padding-top: 10px;
    }
    
    header,
    main {
        text-align: center;
    }
    
    header,
    aside {
        border-radius: var(--radius);
        background-color: var(--accent);
    }
    
    section {
        height: 400px;
        margin: 10px 50px;
        overflow: auto;
        border: 1px dashed var(--frame);
    }
    Podgląd strony po tym kroku (islandia.php)
  30. Krok 30: Klasa miniatury: wysokość, przezroczystość, tranzycja

    Kryteria:

    R.4.9 styl klasy .miniatury : wysokość - 200px, padding: 10px, przezroczystość (opacity) - 0.8, tranzycja trwająca 1s (transition lub transition-duration)

    ok. 2 min
    Polecenie z arkusza

    Dla klasy miniatury przypisanej do obrazów ze strony islandia.php, generowanych skryptem 3: wysokość 200 px, marginesy wewnętrzne 10 px, przezroczystość 0.8, dodana tranzycja trwająca 1 sekundę.

    Nazwa klasy z arkusza. transition: opacity 1s podaje, którą właściwość animować i jak długo (tabela 3 arkusza) - przeglądarka nie musi śledzić zmian wszystkich właściwości, a przypadkowa zmiana innej (np. rozmiaru) nie zacznie się nagle animować (R.4.9).

    styl.css CSS zmienione: 7 linii
    :root {
        --accent: PaleTurquoise;
        --frame: DimGray;
        --radius: 20px;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 25%;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header aside"
            "main   aside"
            "footer footer";
        column-gap: 5%;
        align-items: start;
    }
    
    header {
        grid-area: header;
        height: 100px;
        padding: 2%;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
        height: 700px;
        line-height: 2;
    }
    
    footer {
        grid-area: footer;
        padding-top: 10px;
    }
    
    header,
    main {
        text-align: center;
    }
    
    header,
    aside {
        border-radius: var(--radius);
        background-color: var(--accent);
    }
    
    section {
        height: 400px;
        margin: 10px 50px;
        overflow: auto;
        border: 1px dashed var(--frame);
    }
    
    .miniatury {
        height: 200px;
        padding: 10px;
        opacity: 0.8;
        transition: opacity 1s;
    }
    Podgląd strony po tym kroku (islandia.php)
  31. Krok 31: Miniatura pod kursorem: pełne krycie

    Kryteria:

    R.4.10 gdy kursor znajdzie się na elemencie z przypisaną klasą .miniatury , jego przezroczystość (opacity) zmienia się na wartość 1

    ok. 1 min
    Polecenie z arkusza

    W przypadku gdy kursor znajdzie się na elemencie z przypisaną klasą miniatury, jego przezroczystość zmienia się na wartość 1.

    :hover zmienia tylko to, co się zmienia - opacity: 1. Tranzycja działa w obie strony (R.4.10).

    styl.css CSS zmienione: 4 linie
    :root {
        --accent: PaleTurquoise;
        --frame: DimGray;
        --radius: 20px;
    }
    
    * {
        font-family: "Comic Sans MS", sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 70% 25%;
        grid-template-rows: auto 1fr;
        grid-template-areas:
            "header aside"
            "main   aside"
            "footer footer";
        column-gap: 5%;
        align-items: start;
    }
    
    header {
        grid-area: header;
        height: 100px;
        padding: 2%;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
        height: 700px;
        line-height: 2;
    }
    
    footer {
        grid-area: footer;
        padding-top: 10px;
    }
    
    header,
    main {
        text-align: center;
    }
    
    header,
    aside {
        border-radius: var(--radius);
        background-color: var(--accent);
    }
    
    section {
        height: 400px;
        margin: 10px 50px;
        overflow: auto;
        border: 1px dashed var(--frame);
    }
    
    .miniatury {
        height: 200px;
        padding: 10px;
        opacity: 0.8;
        transition: opacity 1s;
    }
    
    .miniatury:hover {
        opacity: 1;
    }
    Podgląd strony po tym kroku (islandia.php)
  32. Etap 4: Skrypty PHP

    Kroki 32-50 · po tym etapie masz 37 z 37 kryteriów (pkt)

    Krok 32: Połączenie z bazą przez PDO na obu stronach

    Kryteria: ,

    R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy islandia oraz ostatnią operacją na bazie jest jej zamknięcie (wystarczy na jednej stronie )

    R.5.2 Zastosowana przynajmniej jedna zmienna oraz nazewnictwo jest w języku polskim lub angielskim i odzwierciedla cel zastosowania

    ok. 3 min
    Polecenie z arkusza

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

    Kod PHP stoi na samym początku obu stron, przed <!DOCTYPE html>: najpierw wszystkie operacje na bazie, potem szablon, który tylko wypisuje gotowe dane. Każda strona to osobne żądanie do serwera, więc każda łączy się sama (R.5.1, R.5.2).

    • new PDO(...) - połączenie i wybór bazy islandia: pierwszy argument to DSN, czyli adres bazy dla PDO w jednym napisie (mysql:host=localhost;dbname=islandia;charset=utf8mb4), dalej użytkownik root i puste hasło,
    • ERRMODE_EXCEPTION zamienia błędy SQL w wyjątki, FETCH_ASSOC daje wiersze z nazwami kolumn,
    • try … catch (PDOException $error) - przy awarii bazy użytkownik widzi ogólny komunikat i kod 500, a nie treść błędu z nazwą serwera.

    Od tego kroku podgląd to prawdziwy wynik strony z serwera.

    Typowe błędy

    Klucz sprawdza połączenie i wybór bazy islandia w kodzie źródłowym strony (R.5.1), więc DSN z nazwą bazy stoi w islandia.php i obiekty.php, a nie w dołączanym pliku. Wypisanie $error->getMessage() na ekran zdradza szczegóły serwera - komunikat dla użytkownika ma być ogólny.

    islandia.php PHP zmienione: 11 linii
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    obiekty.php PHP zmienione: 11 linii
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  33. Krok 33: Funkcja e(): bezpieczne wypisywanie tekstu

    ok. 2 min
    Polecenie z arkusza

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

    Każdy tekst z bazy trafi do HTML-u przez e(): htmlspecialchars z ENT_QUOTES zamienia <, >, & i oba cudzysłowy na encje. Nazwa obiektu z cudzysłowem nie rozbije atrybutu alt, a <script> w opisie pokaże się jako tekst (ochrona przed XSS).

    Typ parametru ?string (ze znakiem zapytania) przyjmuje też null: w tej bazie kolumny nazwaCechy, wartoscCechy i opis mogą być puste (DEFAULT NULL), a funkcja z samym string zatrzymałaby stronę błędem typu. $text ?? '' zamienia null na pusty napis.

    islandia.php PHP zmienione: 5 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    obiekty.php PHP zmienione: 5 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  34. Krok 34: Skrypt 1: zapytanie 3 do bazy

    Kryteria:

    R.5.3 Skrypty zawierają przynajmniej jedną instrukcję wysyłającą zapytanie do bazy danych: skrypt 2 zmodyfikowane zapytanie 3 - idRodzaj = 14 skrypt 1 zapytanie 3 skrypt 3 - zapytanie 1 skrypt 4 - zmodyfikowane zapytanie 2 - id pobrane z tablicy $_GET

    ok. 3 min
    Polecenie z arkusza

    Skrypt 1 na obu stronach: wysyła do bazy danych zapytanie 3.

    Zapytanie 3 jako zapytanie przygotowane: w miejscu numeru rodzaju stoi parametr :typeId, a wartość 10 idzie osobno w execute([...]) (R.5.3). Ten sam szablon zapytania obsłuży za chwilę skrypt 2 z numerem 14 - baza przygotowuje go raz.

    fetchAll(PDO::FETCH_COLUMN) zwraca zamiast wierszy od razu listę wartości jedynej kolumny: $waterfalls to ['Skogafoss', 'Seljalandsfoss', 'Godafoss', 'Dettifoss']. Napis z zapytaniem jest w cudzysłowie, bo w środku stoją apostrofy wokół 'Islandia'.

    islandia.php PHP zmienione: 6 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  35. Krok 35: Skrypt 1: nazwy jako elementy listy

    Kryteria:

    R.5.4 Skrypt 1 lub skrypt 2 wyświetla wszystkie zwrócone zapytaniem nazwy jako elementy listy

    ok. 2 min
    Polecenie z arkusza

    Każda zwrócona zapytaniem nazwa jest wyświetlana jako element listy.

    W <ol> przy „Wodospady:” pętla w składni szablonu: foreach (...): i endforeach; zamiast nawiasów klamrowych - HTML elementu listy zostaje zwykłym HTML-em z wcięciem, a wartość wstawia <?= ... ?> (skrót od <?php echo ... ?>) przez e(). Podgląd: cztery wodospady (R.5.4).

    islandia.php PHP zmienione: 3 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  36. Krok 36: Skrypt 2: siedliska zwierząt

    Kryteria: ,

    R.5.3 Skrypty zawierają przynajmniej jedną instrukcję wysyłającą zapytanie do bazy danych: skrypt 2 zmodyfikowane zapytanie 3 - idRodzaj = 14 skrypt 1 zapytanie 3 skrypt 3 - zapytanie 1 skrypt 4 - zmodyfikowane zapytanie 2 - id pobrane z tablicy $_GET

    R.5.4 Skrypt 1 lub skrypt 2 wyświetla wszystkie zwrócone zapytaniem nazwy jako elementy listy

    ok. 2 min
    Polecenie z arkusza

    Skrypt 2 na obu stronach: wysyła do bazy danych zapytanie 3, zmodyfikowane tak, że w warunku sprawdzane jest pole idRodzaj równe 14. Każda zwrócona zapytaniem nazwa jest wyświetlana jako element listy.

    „Zmodyfikowane zapytanie” to tu to samo zapytanie przygotowane z inną wartością parametru: execute(['typeId' => 14]) - dwie linie zamiast kopii całego skryptu. Wynik trafia do $habitats, a w drugiej <ol> stoi ta sama pętla co przy wodospadach (R.5.3, R.5.4). Podgląd: dwa siedliska, w tym „Fuglaskoðunarhús” z islandzką literą ð.

    islandia.php PHP zmienione: 5 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  37. Krok 37: Skrypty 1 i 2 również na stronie obiekty.php

    Kryteria:

    R.5.4 Skrypt 1 lub skrypt 2 wyświetla wszystkie zwrócone zapytaniem nazwy jako elementy listy

    ok. 2 min
    Polecenie z arkusza

    Skrypt 1 na obu stronach. Skrypt 2 na obu stronach.

    Ten sam kod w obiekty.php: zapytanie przygotowane wykonane dwa razy na górze pliku i dwie pętle w bloku bocznym (R.5.4). Blok boczny wygląda identycznie na obu stronach.

    To jedyne miejsce, w którym ta wersja powtarza kod - w zamian każda strona jest kompletna w jednym pliku. Jak uniknąć tej kopii, pokazuje trzecia zakładka („Wspólne pliki”).

    obiekty.php PHP zmienione: 14 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (obiekty.php)
  38. Krok 38: Skrypt 3: zapytanie 1 do bazy

    Kryteria:

    R.5.3 Skrypty zawierają przynajmniej jedną instrukcję wysyłającą zapytanie do bazy danych: skrypt 2 zmodyfikowane zapytanie 3 - idRodzaj = 14 skrypt 1 zapytanie 3 skrypt 3 - zapytanie 1 skrypt 4 - zmodyfikowane zapytanie 2 - id pobrane z tablicy $_GET

    ok. 1 min
    Polecenie z arkusza

    Skrypt 3 na stronie islandia.php: wysyła do bazy danych zapytanie 1.

    Zapytanie 1 bez danych od użytkownika - wystarcza query, a fetchAll() odbiera dziewięć wierszy do tablicy $gallery (R.5.3).

    islandia.php PHP zmienione: 4 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $gallery = $db->query(
            "SELECT idObiekt, plik, nazwa FROM obiekty WHERE panstwo = 'Islandia'"
        )->fetchAll();
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  39. Krok 39: Skrypt 3: obraz z każdego wiersza

    Kryteria: ,

    R.5.6 Skrypt 3 dla każdego zwróconego zapytaniem wiersza wyświetla obraz, którego źródłem jest zwrócone pole plik

    R.2.7 W bloku głównym lub bloku sekcji znajduje się przynajmniej jeden obraz wraz z tekstem alternatywnym (w skrypcie lub HTML )

    ok. 2 min
    Polecenie z arkusza

    Odnośnik jest obrazem, dla którego źródło to zwrócone pole plik, tekst alternatywny to zwrócone pole nazwa.

    W sekcji pętla po galerii: <img> ze źródłem z pola plik i tekstem alternatywnym z pola nazwa (R.5.6, R.2.7). Atrybuty są w zwykłym HTML-u - nie trzeba zabezpieczać cudzysłowów ukośnikami jak w napisach dla echo. Zdjęcia mają na razie naturalny rozmiar.

    islandia.php PHP zmienione: 3 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $gallery = $db->query(
            "SELECT idObiekt, plik, nazwa FROM obiekty WHERE panstwo = 'Islandia'"
        )->fetchAll();
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
    <?php foreach ($gallery as $item): ?>
                <img src="<?= e($item['plik']) ?>" alt="<?= e($item['nazwa']) ?>">
    <?php endforeach; ?>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  40. Krok 40: Skrypt 3: dymek z nazwą i klasa miniatury

    Kryteria:

    R.3.5 Przynajmniej jeden obraz ze strony islandia.php ma przypisaną klasę miniatury oraz atrybut title

    ok. 1 min
    Polecenie z arkusza

    Dodatkowo pole nazwa jest tekstem pojawiającym się w postaci dymka, gdy kursor znajdzie się na obrazie (ilustracja 2, czwarty obraz). Obraz ma przypisaną klasę miniatury.

    Dymek to atrybut title, klasa miniatury podpina regułę z CSS: 200 px wysokości, przezroczystość i płynne przejście po najechaniu (R.3.5).

    islandia.php PHP zmienione: 1 linia
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $gallery = $db->query(
            "SELECT idObiekt, plik, nazwa FROM obiekty WHERE panstwo = 'Islandia'"
        )->fetchAll();
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
    <?php foreach ($gallery as $item): ?>
                <img src="<?= e($item['plik']) ?>" alt="<?= e($item['nazwa']) ?>" title="<?= e($item['nazwa']) ?>" class="miniatury">
    <?php endforeach; ?>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  41. Krok 41: Skrypt 3: odnośnik z parametrem GET

    Kryteria: ,

    R.3.4 Na stronie islandia.php umieszczono przynajmniej jeden odnośnik, którego kliknięcie powoduje otwarcie strony obiekty.php oraz zostaje wysłane żądanie metodą GET z jednym parametrem

    R.5.5 Skrypt 3 dla każdego zwróconego zapytaniem wiersza wstawia idObiekt jako wartość parametru przesłanego metodą GET

    ok. 2 min
    Polecenie z arkusza

    Dla każdego zwróconego zapytaniem wiersza wypisuje odnośnik prowadzący do strony obiekty.php oraz przekazujący do tej strony metodą GET parametr o wartości równej polu idObiekt.

    Obraz otacza odnośnik obiekty.php?id=… - parametr GET to część adresu po znaku zapytania (tabela 2 arkusza), a jego wartość to idObiekt z bazy (R.3.4, R.5.5). Nazwę parametru (id) wybiera się samemu; strona obiekty.php musi odczytać tę samą. Odnośnik i obraz stoją w jednej linii kodu: spacje i znaki nowej linii wewnątrz <a> byłyby częścią odnośnika i przeglądarka podkreśliłaby je jako kreskę obok zdjęcia. W podglądzie kliknięcia są zablokowane.

    islandia.php PHP zmienione: 1 linia
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $gallery = $db->query(
            "SELECT idObiekt, plik, nazwa FROM obiekty WHERE panstwo = 'Islandia'"
        )->fetchAll();
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
    <?php foreach ($gallery as $item): ?>
                <a href="obiekty.php?id=<?= e($item['idObiekt']) ?>"><img src="<?= e($item['plik']) ?>" alt="<?= e($item['nazwa']) ?>" title="<?= e($item['nazwa']) ?>" class="miniatury"></a>
    <?php endforeach; ?>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (islandia.php)
  42. Krok 42: Zamknięcie połączenia na stronie islandia.php

    Kryteria:

    R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy islandia oraz ostatnią operacją na bazie jest jej zamknięcie (wystarczy na jednej stronie )

    ok. 1 min
    Polecenie z arkusza

    Na końcu należy zamknąć połączenie z bazą.

    W PDO połączenie zamyka się, gdy znika ostatnie odwołanie do niego. Zapytanie przygotowane $namesQuery też je trzyma - dlatego najpierw $namesQuery = null, potem $db = null (R.5.1). To koniec bloku try: wszystkie dane są już w tablicach, a szablon bazy nie potrzebuje.

    Typowe błędy

    Samo $db = null przy istniejącym $namesQuery zostawia połączenie otwarte do końca skryptu.

    islandia.php PHP zmienione: 3 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $gallery = $db->query(
            "SELECT idObiekt, plik, nazwa FROM obiekty WHERE panstwo = 'Islandia'"
        )->fetchAll();
    
        $namesQuery = null;
        $db = null;
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Galeria</h2>
            <section>
    <?php foreach ($gallery as $item): ?>
                <a href="obiekty.php?id=<?= e($item['idObiekt']) ?>"><img src="<?= e($item['plik']) ?>" alt="<?= e($item['nazwa']) ?>" title="<?= e($item['nazwa']) ?>" class="miniatury"></a>
    <?php endforeach; ?>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  43. Krok 43: Skrypt 4: identyfikator z adresu, sprawdzony

    ok. 3 min
    Polecenie z arkusza

    Skrypt 4 na stronie obiekty.php: wysyła do bazy danych zapytanie 2, zmodyfikowane w ten sposób, że w warunku jest sprawdzane id obiektu przesłane jako parametr metody GET ze strony islandia.php.

    Parametr z adresu jest w tablicy $_GET (tabela 2 arkusza), ale nic, co przychodzi w adresie, nie jest pewne - każdy może wpisać obiekty.php?id=abc albo nie podać parametru wcale. Dlatego wartość przechodzi najpierw przez filter_var z filtrem FILTER_VALIDATE_INT i min_range 1:

    • ?id=53 → liczba 53,
    • ?id=abc, ?id=0 OR 1=1, ?id=-5 → false,
    • brak parametru → $_GET['id'] ?? null daje null, a filtr zwraca false, bez ostrzeżenia „Undefined array key”.

    Dalej strona pracuje już na liczbie $objectId albo wie, że jej nie ma.

    Typowe błędy

    $_GET['id'] bez ?? null przy wejściu na obiekty.php bez parametru daje ostrzeżenie „Undefined array key "id"”.

    obiekty.php PHP zmienione: 2 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $objectId = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (obiekty.php)
  44. Krok 44: Skrypt 4: zapytanie 2 z parametrem z adresu

    Kryteria:

    R.5.3 Skrypty zawierają przynajmniej jedną instrukcję wysyłającą zapytanie do bazy danych: skrypt 2 zmodyfikowane zapytanie 3 - idRodzaj = 14 skrypt 1 zapytanie 3 skrypt 3 - zapytanie 1 skrypt 4 - zmodyfikowane zapytanie 2 - id pobrane z tablicy $_GET

    ok. 3 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 2, zmodyfikowane w ten sposób, że w warunku jest sprawdzane id obiektu przesłane jako parametr metody GET ze strony islandia.php.

    Zapytanie 2 z kwerendy.txt, w którym liczbę 46 zastępuje parametr :objectId, a identyfikator z adresu idzie osobno w execute (R.5.3). Nawet gdyby filtr z poprzedniego kroku coś przepuścił, wartość nie stanie się częścią polecenia SQL - SQL injection jest niemożliwe.

    Zapytanie wykonuje się tylko dla poprawnego identyfikatora. fetch() zwraca jeden wiersz albo false, gdy miejsca o takim numerze nie ma - $place jest false także wtedy, gdy zapytania w ogóle nie było.

    Typowe błędy

    Wklejenie $_GET['id'] do napisu z zapytaniem ("… WHERE idObiekt = $id") działa, ale adres ?id=0 OR 1=1 zmienia wtedy sens warunku.

    obiekty.php PHP zmienione: 12 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $objectId = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        $place = false;
        if ($objectId !== false) {
            $placeQuery = $db->prepare(
                'SELECT o.plik, o.nazwa, o.nazwaCechy, o.wartoscCechy, o.opis, r.rodzaj
                 FROM obiekty AS o
                 JOIN rodzaje AS r ON r.idRodzaj = o.idRodzaj
                 WHERE o.idObiekt = :objectId'
            );
            $placeQuery->execute(['objectId' => $objectId]);
            $place = $placeQuery->fetch();
            $placeQuery = null;
        }
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  45. Krok 45: Skrypt 4: obraz obiektu

    Kryteria:

    R.5.7 Skrypt 4 wyświetla obraz oraz dane których id zostało przesłane metodą GET

    ok. 1 min
    Polecenie z arkusza

    Na podstawie zwróconych zapytaniem danych: wyświetla obraz, którego źródłem jest pole plik, a tekstem alternatywnym pole nazwa.

    Treść sekcji tylko wtedy, gdy miejsce znaleziono: <?php if ($place): ?> … <?php endif; ?>. Obraz z pól plik i nazwa (R.5.7). W podglądzie Godafoss - obiekty.php?id=53 z ilustracji 3.

    obiekty.php PHP zmienione: 3 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $objectId = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        $place = false;
        if ($objectId !== false) {
            $placeQuery = $db->prepare(
                'SELECT o.plik, o.nazwa, o.nazwaCechy, o.wartoscCechy, o.opis, r.rodzaj
                 FROM obiekty AS o
                 JOIN rodzaje AS r ON r.idRodzaj = o.idRodzaj
                 WHERE o.idObiekt = :objectId'
            );
            $placeQuery->execute(['objectId' => $objectId]);
            $place = $placeQuery->fetch();
            $placeQuery = null;
        }
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
    <?php if ($place): ?>
                <img src="<?= e($place['plik']) ?>" alt="<?= e($place['nazwa']) ?>">
    <?php endif; ?>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (obiekty.php)
  46. Krok 46: Skrypt 4: nazwa w nagłówku drugiego stopnia

    Kryteria:

    R.2.8 Na stronie obiekty.php w bloku głównym lub bloku sekcji znajdują się elementy <h2>, <h3> i przynajmniej jeden <p> (w skrypcie lub HTML )

    ok. 1 min
    Polecenie z arkusza

    W nagłówku drugiego stopnia wyświetla wartość pola nazwa.

    <h2> z nazwą miejsca (R.2.8).

    obiekty.php PHP zmienione: 1 linia
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $objectId = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        $place = false;
        if ($objectId !== false) {
            $placeQuery = $db->prepare(
                'SELECT o.plik, o.nazwa, o.nazwaCechy, o.wartoscCechy, o.opis, r.rodzaj
                 FROM obiekty AS o
                 JOIN rodzaje AS r ON r.idRodzaj = o.idRodzaj
                 WHERE o.idObiekt = :objectId'
            );
            $placeQuery->execute(['objectId' => $objectId]);
            $place = $placeQuery->fetch();
            $placeQuery = null;
        }
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
    <?php if ($place): ?>
                <img src="<?= e($place['plik']) ?>" alt="<?= e($place['nazwa']) ?>">
                <h2><?= e($place['nazwa']) ?></h2>
    <?php endif; ?>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (obiekty.php)
  47. Krok 47: Skrypt 4: rodzaj w nagłówku trzeciego stopnia

    Kryteria:

    R.2.8 Na stronie obiekty.php w bloku głównym lub bloku sekcji znajdują się elementy <h2>, <h3> i przynajmniej jeden <p> (w skrypcie lub HTML )

    ok. 1 min
    Polecenie z arkusza

    W nagłówku trzeciego stopnia wyświetla wartość pola rodzaj.

    <h3> z polem rodzaj z tabeli rodzaje, dołączonej relacją.

    obiekty.php PHP zmienione: 1 linia
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $objectId = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        $place = false;
        if ($objectId !== false) {
            $placeQuery = $db->prepare(
                'SELECT o.plik, o.nazwa, o.nazwaCechy, o.wartoscCechy, o.opis, r.rodzaj
                 FROM obiekty AS o
                 JOIN rodzaje AS r ON r.idRodzaj = o.idRodzaj
                 WHERE o.idObiekt = :objectId'
            );
            $placeQuery->execute(['objectId' => $objectId]);
            $place = $placeQuery->fetch();
            $placeQuery = null;
        }
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
    <?php if ($place): ?>
                <img src="<?= e($place['plik']) ?>" alt="<?= e($place['nazwa']) ?>">
                <h2><?= e($place['nazwa']) ?></h2>
                <h3><?= e($place['rodzaj']) ?></h3>
    <?php endif; ?>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (obiekty.php)
  48. Krok 48: Skrypt 4: cecha i jej wartość w paragrafie

    Kryteria:

    R.2.8 Na stronie obiekty.php w bloku głównym lub bloku sekcji znajdują się elementy <h2>, <h3> i przynajmniej jeden <p> (w skrypcie lub HTML )

    ok. 1 min
    Polecenie z arkusza

    W paragrafie wyświetla tekst „<nazwaCechy>: <wartoscCechy>”, gdzie w <> umieszczono wartości zwrócone zapytaniem.

    Stały dwukropek w HTML-u, dwie wartości z bazy. Dla Godafossu: „wysokość: 30”. wartoscCechy to liczba - e() zamieni ją na tekst sama.

    obiekty.php PHP zmienione: 1 linia
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $objectId = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        $place = false;
        if ($objectId !== false) {
            $placeQuery = $db->prepare(
                'SELECT o.plik, o.nazwa, o.nazwaCechy, o.wartoscCechy, o.opis, r.rodzaj
                 FROM obiekty AS o
                 JOIN rodzaje AS r ON r.idRodzaj = o.idRodzaj
                 WHERE o.idObiekt = :objectId'
            );
            $placeQuery->execute(['objectId' => $objectId]);
            $place = $placeQuery->fetch();
            $placeQuery = null;
        }
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
    <?php if ($place): ?>
                <img src="<?= e($place['plik']) ?>" alt="<?= e($place['nazwa']) ?>">
                <h2><?= e($place['nazwa']) ?></h2>
                <h3><?= e($place['rodzaj']) ?></h3>
                <p><?= e($place['nazwaCechy']) ?>: <?= e($place['wartoscCechy']) ?></p>
    <?php endif; ?>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (obiekty.php)
  49. Krok 49: Skrypt 4: opis i strona dla nieznanego miejsca

    Kryteria:

    R.5.7 Skrypt 4 wyświetla obraz oraz dane których id zostało przesłane metodą GET

    ok. 2 min
    Polecenie z arkusza

    W paragrafie wyświetla pole opis.

    Ostatni paragraf to opis miejsca; razem ze zdjęciem nie mieści się w 400 px sekcji, więc pojawia się pasek przewijania (ilustracja 3) - R.5.7.

    Gałąź <?php else: ?> obsługuje adres bez poprawnego identyfikatora: zamiast pustych znaczników strona mówi, co się stało, a http_response_code(404) zgłasza przeglądarce i wyszukiwarkom „nie ma takiej strony” - tak jak dla nieistniejącego adresu. Kod odpowiedzi ustawia się w części z logiką, przed wypisaniem HTML-u.

    obiekty.php PHP zmienione: 6 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $objectId = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        $place = false;
        if ($objectId !== false) {
            $placeQuery = $db->prepare(
                'SELECT o.plik, o.nazwa, o.nazwaCechy, o.wartoscCechy, o.opis, r.rodzaj
                 FROM obiekty AS o
                 JOIN rodzaje AS r ON r.idRodzaj = o.idRodzaj
                 WHERE o.idObiekt = :objectId'
            );
            $placeQuery->execute(['objectId' => $objectId]);
            $place = $placeQuery->fetch();
            $placeQuery = null;
        }
        if ($place === false) {
            http_response_code(404);
        }
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
    <?php if ($place): ?>
                <img src="<?= e($place['plik']) ?>" alt="<?= e($place['nazwa']) ?>">
                <h2><?= e($place['nazwa']) ?></h2>
                <h3><?= e($place['rodzaj']) ?></h3>
                <p><?= e($place['nazwaCechy']) ?>: <?= e($place['wartoscCechy']) ?></p>
                <p><?= e($place['opis']) ?></p>
    <?php else: ?>
                <p>Nie znaleziono takiego miejsca - wybierz zdjęcie w galerii.</p>
    <?php endif; ?>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (obiekty.php)
  50. Krok 50: Zamknięcie połączenia na stronie obiekty.php

    Kryteria:

    R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy islandia oraz ostatnią operacją na bazie jest jej zamknięcie (wystarczy na jednej stronie )

    ok. 1 min
    Polecenie z arkusza

    Na końcu należy zamknąć połączenie z bazą.

    Tak samo jak w galerii: najpierw zapytania przygotowane ($placeQuery jest już zwolnione w bloku if), potem $db = null na końcu bloku try (R.5.1).

    obiekty.php PHP zmienione: 3 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO('mysql:host=localhost;dbname=islandia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $namesQuery = $db->prepare(
            "SELECT nazwa FROM obiekty WHERE panstwo = 'Islandia' AND idRodzaj = :typeId"
        );
        $namesQuery->execute(['typeId' => 10]);
        $waterfalls = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
        $namesQuery->execute(['typeId' => 14]);
        $habitats = $namesQuery->fetchAll(PDO::FETCH_COLUMN);
    
        $objectId = filter_var($_GET['id'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        $place = false;
        if ($objectId !== false) {
            $placeQuery = $db->prepare(
                'SELECT o.plik, o.nazwa, o.nazwaCechy, o.wartoscCechy, o.opis, r.rodzaj
                 FROM obiekty AS o
                 JOIN rodzaje AS r ON r.idRodzaj = o.idRodzaj
                 WHERE o.idObiekt = :objectId'
            );
            $placeQuery->execute(['objectId' => $objectId]);
            $place = $placeQuery->fetch();
            $placeQuery = null;
        }
        if ($place === false) {
            http_response_code(404);
        }
    
        $namesQuery = null;
        $db = null;
    } 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>Islandia</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1><a href="islandia.php">Zwiedzaj Islandię</a></h1>
        </header>
        <main>
            <h2>Opis miejsca</h2>
            <section>
    <?php if ($place): ?>
                <img src="<?= e($place['plik']) ?>" alt="<?= e($place['nazwa']) ?>">
                <h2><?= e($place['nazwa']) ?></h2>
                <h3><?= e($place['rodzaj']) ?></h3>
                <p><?= e($place['nazwaCechy']) ?>: <?= e($place['wartoscCechy']) ?></p>
                <p><?= e($place['opis']) ?></p>
    <?php else: ?>
                <p>Nie znaleziono takiego miejsca - wybierz zdjęcie w galerii.</p>
    <?php endif; ?>
            </section>
        </main>
        <aside>
            <h3>Do zwiedzania</h3>
            <ul>
                <li>Wodospady:
                    <ol>
    <?php foreach ($waterfalls as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
                <li>Siedliska zwierząt:
                    <ol>
    <?php foreach ($habitats as $name): ?>
                        <li><?= e($name) ?></li>
    <?php endforeach; ?>
                    </ol>
                </li>
            </ul>
        </aside>
        <footer>
            <hr>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

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

    Krok 51 · po tym etapie masz 37 z 37 kryteriów (pkt)

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

    ok. 2 min
    Polecenie z arkusza

    Czas przeznaczony na wykonanie zadania wynosi 150 minut.

    Wszystkie trzy wersje dają komplet 37 punktów. Różnice:

    wersja podstawowata wersjawspólne pliki
    układfloat + clear, blok boczny w kodzie przed głównymsiatka grid-template-areassiatka (ten sam CSS)
    bazafunkcje mysqli_* z tabeli 1PDO, połączenie w pliku stronymysqli obiektowe, config.php
    listy w bloku bocznymdwie kopie skryptujedno zapytanie przygotowane, dwa executefunkcja w baza.php
    ?id= z adresuwklejony do SQL (poprawiony w „Dobrych praktykach”)filter_var + parametr, 404filter_var + parametr, 404
    kod wspólny stronskopiowanyskopiowanyinclude

    Na egzaminie: wersja podstawowa używa tylko funkcji z tabeli 1 arkusza. Ta wersja ma całe PHP strony w jej pliku, razem z nazwą bazy, więc egzaminator znajdzie połączenie i zapytania tam, gdzie klucz każe ich szukać („sprawdzić w kodzie źródłowym strony”). Poza egzaminem dane logowania do bazy trzyma się zwykle w osobnym pliku (np. config.php), żeby hasło nie trafiało do repozytorium razem z kodem. Wersja z osobnymi plikami jest najbliższa prawdziwym projektom, ale na egzaminie wymaga, żeby egzaminator zajrzał do dołączonych plików.

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

  52. Etap 6: Oddanie pracy

    Krok 52 · po tym etapie masz 37 z 37 kryteriów (pkt)

    Krok 52: Plik przeglądarka.txt i nagranie płyty

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Nagraj płytę z rezultatami pracy.

    W przeglądarka.txt nazwa przeglądarki, w której strony były sprawdzane przez http://localhost/…. W folderze z numerem zdającego: dziewięć zdjęć, import.jpg, islandia.php, obiekty.php, kw1.png … kw4.png, kwerendy.txt, przeglądarka.txt, styl.css. Płytę sprawdź po nagraniu i opisz numerem zdającego.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.