Przejdź do głównej treści
INF.03-04-23.01-SGStyczeń 2023 · 33 kroki · ok. 64 min · 32 kryteria z klucza

Rozwiązanie krok po kroku: Portal biura podróży (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza biuro z czterema zapytaniami i strona wycieczki.php z listą wolnych wycieczek, tabelą bestsellerów, galerią zdjęć z bazy i kontaktem - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • siatka CSS zamiast float: cały układ opisuje jedna reguła dla <body> - kolumny 25%, 50% i 25% oraz mapa obszarów (grid-template-areas) z blokiem danych na całą szerokość, bez clear przy stopce,
  • znaczniki opisujące treść (<main>, <aside>, <section>) i kolory z arkusza w zmiennych CSS,
  • zapytania pisane czytelnie: klauzule w osobnych liniach, aliasy tabel, stała kolejność wierszy (ORDER BY),
  • PHP na PDO: oba zapytania strona wykonuje na górze pliku wycieczki.php, przed HTML-em, w jednym bloku try, a w HTML-u zostają tylko pętle foreach i bezpieczne wypisywanie (htmlspecialchars w funkcji e()),
  • komunikaty zamiast pustej listy, gdy nie ma wolnych miejsc albo bazy nie da się odczytać.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i zdjęcia pochodzą z prawdziwego archiwum zad4, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 na bazie z arkusza; najechanie kursorem na zdjęcie podświetla jego tło, jak na obrazie 2.

Jak liczą się punkty. Klucz ma 32 kryteria (R.1.1 - R.5.5), każde za 1 punkt. Próg zdania to 75%, czyli 24 punkty. 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 32 kryteriów (pkt)

    Krok 1: Baza danych biuro

    ok. 2 min
    Polecenie z arkusza

    Utwórz bazę danych o nazwie biuro

    Rozpakuj zad4.zip z pulpitu (hasło &poDrozE$ - z ampersandem na początku i dolarem na końcu) do folderu z numerem zdającego: egzamin3.sql i zdjęcia 1.jpg - 9.jpg. W phpMyAdmin Nowa → biuro albo zapytaniem:

    CREATE DATABASE biuro CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;
    

    Arkusz nie podaje porównywania. Tabele z egzamin3.sql mają zestaw znaków zapisany we własnej definicji (wycieczki i zdjecia - latin1), więc ustawienie bazy ich nie zmienia.

    Typowe błędy

    Nazwa bazy musi brzmieć dokładnie biuro (nie egzamin3) - skrypt łączy się z bazą po nazwie (R.5.1).

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

    ok. 2 min
    Polecenie z arkusza

    Do bazy biuro zaimportuj tabele z pliku egzamin3.sql z rozpakowanego archiwum

    Zaznaczona baza biuro, Import → egzamin3.sql → Wykonaj. Plik ma osiem tabel z różnych zadań; tu potrzebne są dwie z obrazu 1: wycieczki (9 wycieczek z polem dostepna równym 1 albo 0) i zdjecia (9 plików z podpisami), połączone polem zdjecia_id.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonano import tabel do bazy danych biuro czynność udokumentowano plikiem o nazwie import w formacie PNG

    ok. 2 min
    Polecenie z arkusza

    Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie PNG pod nazwą import. Nie kadruj zrzutu. Zrzut powinien 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 z paskiem zadań, w PNG, pod nazwą import. Zrzuty zapytań kw1 - kw4 będą już w JPEG.

    Typowe błędy

    Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany (uwaga w kluczu przy R.1).

  4. Krok 4: Zapytanie 1: dostępne wycieczki

    Kryteria: ,

    R.1.2 Zapisano plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania

    R.1.3 Utworzono zapytanie 1 wybierające jedynie pola id, dataWyjazdu, cel i cena z tabeli wycieczki dla dostępnych wycieczek. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE; (lub =1) oraz wynik działania udokumentowano zrzutem, na którym widocznych jest dokładnie 6 rekordów o id = 1, 2, 3, 4, 6, 9, jedynie pola id, dataWyjazdu, cel i cena

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pola id, dataWyjazdu, cel i cena z tabeli wycieczki dla dostępnych wycieczek

    WHERE dostepna = TRUE - w MariaDB TRUE to 1 (klucz uznaje oba zapisy). ORDER BY id ustala kolejność wierszy: bez sortowania serwer może je oddać w dowolnej kolejności, a lista na stronie ma iść od pierwszej wycieczki. Wynik: 6 wycieczek o id 1, 2, 3, 4, 6 i 9 - jak w kluczu. Zapytanie do kwerendy.txt, zrzut kw1.jpg.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT id, dataWyjazdu, cel, cena
    FROM wycieczki
    WHERE dostepna = TRUE
    ORDER BY id;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: zdjęcia malejąco po podpisie

    Kryteria:

    R.1.4 Utworzono zapytanie 2 wybierające jedynie pola nazwaPliku i podpis z tabeli zdjecia , sortując je malejąco według kolumny podpis. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwaPliku, podpis FROM zdjecia ORDER BY podpis DESC; oraz wynik działania udokumentowano zrzutem, na którym widocznych jest dokładnie 9 rekordów dla kolejnych nazw plików 2, 4, 6, 7, 9, 1, 5, 3, 8, jedynie pola nazwaPliku i podpis

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pola nazwaPliku i podpis z tabeli zdjecia, sortując je malejąco według kolumny podpis

    ORDER BY podpis DESC - od Z do A. Wynik: pliki w kolejności 2, 4, 6, 7, 9, 1, 5, 3, 8 (od Wenecji do Barcelony) - jak w kluczu i w galerii na obrazie 2. Zrzut: kw2.jpg.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT id, dataWyjazdu, cel, cena
    FROM wycieczki
    WHERE dostepna = TRUE
    ORDER BY id;
    
    -- Zapytanie 2
    SELECT nazwaPliku, podpis
    FROM zdjecia
    ORDER BY podpis DESC;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: wycieczki droższe niż 1200 zł z podpisem zdjęcia

    Kryteria:

    R.1.5 Utworzono zapytanie 3 wybierające jedynie cel i cenę wycieczki z tabeli wycieczki oraz odpowiadający im podpis z tabeli zdjecia dla wycieczek, których cena jest wyższa niż 1200 zł. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT cel, cena, podpis FROM zdjecia JOIN wycieczki ON zdjecia.id = wycieczki.zdjecia_id WHERE cena > 1200; (możliwe też INNER JOIN lub porównanie kluczy po WHERE) oraz wynik działania udokumentowano zrzutem, na którym widocznych jest dokładnie 5 rekordów: Francja, Paryz 1300 Paryz; Francja, Paryz 1350 Paryz; Hiszpania, Barcelona 1500 Barcelona; Hiszpania, Barcelona 1500 Barcelona; Hiszpania, Barcelona 1400 Barcelona

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie cel i cenę wycieczki z tabeli wycieczki oraz odpowiadający im podpis z tabeli zdjecia dla wycieczek, których cena jest wyższa niż 1200 zł. Zapytanie wykorzystuje relację

    • aliasy tabel w i z - obie mają pole id, a przedrostek mówi, o które chodzi,
    • JOIN ... ON z.id = w.zdjecia_id - relacja z obrazu 1, zapisana przy złączeniu,
    • WHERE w.cena > 1200 - „wyższa niż”, więc wycieczki za dokładnie 1200 zł odpadają; ORDER BY w.id - kolejność jak w tabeli.

    Wynik: 5 wierszy - Paryż 1300 i 1350, Barcelona 1500, 1500 i 1400 - jak w kluczu. Zrzut: kw3.jpg.

    kwerendy.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT id, dataWyjazdu, cel, cena
    FROM wycieczki
    WHERE dostepna = TRUE
    ORDER BY id;
    
    -- Zapytanie 2
    SELECT nazwaPliku, podpis
    FROM zdjecia
    ORDER BY podpis DESC;
    
    -- Zapytanie 3
    SELECT w.cel, w.cena, z.podpis
    FROM wycieczki AS w
    JOIN zdjecia AS z ON z.id = w.zdjecia_id
    WHERE w.cena > 1200
    ORDER BY w.id;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: pole liczbaDni po dataWyjazdu

    Kryteria:

    R.1.6 Utworzono zapytanie 4 dodające do tabeli wycieczki pole liczbaDni przyjmujące tylko liczby naturalne, pole powinno być wstawione po polu dataWyjazdu. W pliku z kwerendami lub na zrzucie istnieje zapis np.: ALTER TABLE wycieczki ADD liczbaDni int UNSIGNED AFTER dataWyjazdu; (lub ADD COLUMN, dowolny typ liczbowy całkowity, UNSIGNED obowiązkowe) oraz wynik działania udokumentowano zrzutem, na którym widnieje komunikat o poprawnym dodaniu lub struktura tabeli z dodaną kolumną

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: dodające do tabeli wycieczki pole liczbaDni przyjmujące tylko liczby naturalne, pole powinno być wstawione po polu dataWyjazdu

    • ADD COLUMN liczbaDni - nowe pole,
    • TINYINT UNSIGNED - najmniejszy typ całkowity, bez znaku: 0 - 255 dni, a wycieczka nie trwa dłużej; klucz przyjmuje dowolny typ całkowity, byle z UNSIGNED,
    • AFTER dataWyjazdu - miejsce pola w tabeli; bez tego trafiłoby na koniec.

    Struktura po zmianie ma liczbaDni zaraz po dataWyjazdu; zapytanie 1 daje dalej ten sam wynik, bo wybiera pola po nazwach (sprawdzone). Zrzut kw4.jpg: komunikat i struktura tabeli.

    Typowe błędy

    Bez UNSIGNED klucz nie uzna zapytania - samo INT albo TINYINT przyjmuje liczby ujemne.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT id, dataWyjazdu, cel, cena
    FROM wycieczki
    WHERE dostepna = TRUE
    ORDER BY id;
    
    -- Zapytanie 2
    SELECT nazwaPliku, podpis
    FROM zdjecia
    ORDER BY podpis DESC;
    
    -- Zapytanie 3
    SELECT w.cel, w.cena, z.podpis
    FROM wycieczki AS w
    JOIN zdjecia AS z ON z.id = w.zdjecia_id
    WHERE w.cena > 1200
    ORDER BY w.id;
    
    -- Zapytanie 4
    ALTER TABLE wycieczki
    ADD COLUMN liczbaDni TINYINT UNSIGNED AFTER dataWyjazdu;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Strona HTML

    Kroki 8-17 · po tym etapie masz 14 z 32 kryteriów (pkt)

    Krok 8: Plik wycieczki.php: HTML5, język, kodowanie

    Kryteria: ,

    R.2.1 Witrynę internetową zapisano w pliku PHP o nazwie wycieczki

    R.2.2 Zapisano jawnie standard kodowania polskich znaków i deklarację HTML5 <!DOCTYPE HTML> i zadeklarowano język witryny: polski np. <html lang="pl">

    ok. 2 min
    Polecenie z arkusza

    Składa się ze strony o nazwie wycieczki.php. Zapisana w języku HTML 5. Jawnie zastosowany właściwy standard kodowania polskich znaków. Zadeklarowany język witryny: polski.

    <!DOCTYPE html>, <html lang="pl"> i <meta charset="UTF-8"> jako pierwszy element <head> (R.2.1, R.2.2). Rozszerzenie .php, bo dojdą skrypty - strona działa przez http://localhost/..., a zdjęcia 1.jpg - 9.jpg leżą w tym samym folderze.

    wycieczki.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  9. Krok 9: Tytuł strony

    Kryteria:

    R.2.3 Nadano tytuł stronie: "Wycieczki po Europie"

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Wycieczki po Europie”

    <title> (R.2.3) i viewport - telefon nie pomniejsza strony do szerokości ekranu komputera.

    wycieczki.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>Wycieczki po Europie</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  10. Krok 10: Podpięcie arkusza stylów

    Kryteria:

    R.3.3 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl4.css , formatowanie pochodzi jedynie z tego arkusza Uwaga: kryterium to nie jest spełnione również, gdy zdający źle przypisał co najmniej jeden selektor do bloku (np. kropka zamiast #)

    ok. 1 min
    Polecenie z arkusza

    Arkusz stylów w pliku o nazwie styl4.css prawidłowo połączony z kodem strony

    Nazwa z cyfrą 4: styl4.css. Całe formatowanie pochodzi z tego pliku (R.3.3).

    Typowe błędy

    Klucz odbiera R.3.3 także wtedy, gdy choć jeden selektor nie trafia w blok (np. kropka zamiast #).

    wycieczki.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>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  11. Krok 11: Bloki: baner, dane, trzy bloki, stopka

    Kryteria:

    R.2.4 Układ strony zdefiniowano dzieląc ją na bloki: baner, dane, lewy, środkowy, prawy i stopka. Zastosowano znaczniki sekcji

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: na górze baner, poniżej blok z danymi, poniżej obok siebie trzy bloki: lewy, środkowy i prawy, poniżej stopka. Podział zrealizowany za pomocą znaczników sekcji tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z Obrazem 2

    Każdy blok w znaczniku, który mówi, co zawiera (R.2.4):

    • <header> - baner, <footer> - stopka,
    • <main id="trips"> - blok z danymi: lista wolnych wycieczek, główna treść strony,
    • <aside id="bestsellers"> i <aside id="contact"> - bloki boczne z informacją dodatkową,
    • <section id="gallery"> - galeria zdjęć z własnym nagłówkiem.

    Identyfikatory nazywają treść, nie położenie - dwa <aside> trzeba w CSS odróżnić, a nazwa #contact pasuje także wtedy, gdy blok zmieni miejsce.

    wycieczki.php PHP zmienione: 12 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
        </header>
        <main id="trips">
        </main>
        <aside id="bestsellers">
        </aside>
        <section id="gallery">
        </section>
        <aside id="contact">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  12. Krok 12: Baner: nagłówek pierwszego stopnia

    ok. 1 min
    Polecenie z arkusza

    Zawartość banera: nagłówek pierwszego stopnia o treści: „BIURO TURYSTYCZNE”

    <h1> w <header>, wielkimi literami jak w arkuszu.

    wycieczki.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>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
        </main>
        <aside id="bestsellers">
        </aside>
        <section id="gallery">
        </section>
        <aside id="contact">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  13. Krok 13: Blok z danymi: nagłówek i pusta lista

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku z danymi: Nagłówek trzeciego stopnia o treści „Wycieczki, na które są wolne miejsca”. Lista punktowana (nieuporządkowana) wypełniona skryptem 1

    <h3> - arkusz tak ustawia stopnie nagłówków - i pusta lista <ul>; elementy <li> wypisze Skrypt 1.

    wycieczki.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
            <h3>Wycieczki, na które są wolne miejsca</h3>
            <ul>
            </ul>
        </main>
        <aside id="bestsellers">
        </aside>
        <section id="gallery">
        </section>
        <aside id="contact">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  14. Krok 14: Blok lewy: nagłówek i tabela bestsellerów

    Kryteria:

    R.2.7 W bloku lewym zapisano tabelę o dwóch kolumnach i trzech wierszach, zastosowano znaczniki <table>, <tr>, <td>

    ok. 2 min
    Polecenie z arkusza

    Zawartość lewego bloku: Nagłówek drugiego stopnia o treści: „Bestselery”. Tabela o dwóch kolumnach i trzech wierszach wypełniona danymi: Wenecja - kwiecień, Londyn - lipiec, Rzym - wrzesień

    <h2> z napisem dokładnie jak w arkuszu („Bestselery” przez jedno „l”) i tabela: trzy wiersze <tr> po dwie komórki <td>, każda w osobnej linii kodu - łatwo policzyć kolumny (R.2.7).

    wycieczki.php PHP zmienione: 15 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
            <h3>Wycieczki, na które są wolne miejsca</h3>
            <ul>
            </ul>
        </main>
        <aside id="bestsellers">
            <h2>Bestselery</h2>
            <table>
                <tr>
                    <td>Wenecja</td>
                    <td>kwiecień</td>
                </tr>
                <tr>
                    <td>Londyn</td>
                    <td>lipiec</td>
                </tr>
                <tr>
                    <td>Rzym</td>
                    <td>wrzesień</td>
                </tr>
            </table>
        </aside>
        <section id="gallery">
        </section>
        <aside id="contact">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  15. Krok 15: Blok środkowy: nagłówek galerii

    ok. 1 min
    Polecenie z arkusza

    Zawartość środkowego bloku: Nagłówek drugiego stopnia o treści: „Nasze zdjęcia”. Efekt działania skryptu 2

    Pod nagłówkiem stanie galeria wypisana przez Skrypt 2 - pętla dojdzie w etapie skryptów.

    wycieczki.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>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
            <h3>Wycieczki, na które są wolne miejsca</h3>
            <ul>
            </ul>
        </main>
        <aside id="bestsellers">
            <h2>Bestselery</h2>
            <table>
                <tr>
                    <td>Wenecja</td>
                    <td>kwiecień</td>
                </tr>
                <tr>
                    <td>Londyn</td>
                    <td>lipiec</td>
                </tr>
                <tr>
                    <td>Rzym</td>
                    <td>wrzesień</td>
                </tr>
            </table>
        </aside>
        <section id="gallery">
            <h2>Nasze zdjęcia</h2>
        </section>
        <aside id="contact">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  16. Krok 16: Blok prawy: e-mail i telefon

    Kryteria: ,

    R.2.5 W banerze zapisano nagłówek h1: "BIURO TURYSTYCZNE", w bloku dane h3: "Wycieczki, na które są wolne miejsca", w blokach lewym, środkowym i prawym przynajmniej jeden h2 z: "Bestselery", "Nasze zdjęcia", "Skontaktuj się" oraz przynajmniej jeden paragraf

    R.3.2 Na stronie umieszczono odnośnik "napisz do nas" łączący z adresem [email protected] (href="mailto:[email protected]")

    ok. 2 min
    Polecenie z arkusza

    Zawartość prawego bloku: Nagłówek drugiego stopnia o treści: „Skontaktuj się”. Odnośnik do adresu e-mail [email protected] o treści: „napisz do nas”. Paragraf (akapit) o treści: „telefon: 111222333”

    Odnośnik mailto: otwiera program pocztowy z wpisanym odbiorcą (R.3.2). W akapicie numer jest odnośnikiem tel: z numerem kierunkowym kraju - na telefonie dotknięcie od razu dzwoni, a tekst akapitu dalej brzmi „telefon: 111222333”. Strona ma <h1>, <h3>, <h2> i akapit (R.2.5).

    wycieczki.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
            <h3>Wycieczki, na które są wolne miejsca</h3>
            <ul>
            </ul>
        </main>
        <aside id="bestsellers">
            <h2>Bestselery</h2>
            <table>
                <tr>
                    <td>Wenecja</td>
                    <td>kwiecień</td>
                </tr>
                <tr>
                    <td>Londyn</td>
                    <td>lipiec</td>
                </tr>
                <tr>
                    <td>Rzym</td>
                    <td>wrzesień</td>
                </tr>
            </table>
        </aside>
        <section id="gallery">
            <h2>Nasze zdjęcia</h2>
        </section>
        <aside id="contact">
            <h2>Skontaktuj się</h2>
            <a href="mailto:[email protected]">napisz do nas</a>
            <p>telefon: <a href="tel:+48111222333">111222333</a></p>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  17. Krok 17: Stopka

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: akapit (paragraf) o treści: „Stronę wykonał: ”, dalej wstawiony numer zdającego

    Akapit w <footer>. Na egzaminie wpisujesz swój numer zdającego (PESEL), tu stoi autor rozwiązania.

    wycieczki.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>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
            <h3>Wycieczki, na które są wolne miejsca</h3>
            <ul>
            </ul>
        </main>
        <aside id="bestsellers">
            <h2>Bestselery</h2>
            <table>
                <tr>
                    <td>Wenecja</td>
                    <td>kwiecień</td>
                </tr>
                <tr>
                    <td>Londyn</td>
                    <td>lipiec</td>
                </tr>
                <tr>
                    <td>Rzym</td>
                    <td>wrzesień</td>
                </tr>
            </table>
        </aside>
        <section id="gallery">
            <h2>Nasze zdjęcia</h2>
        </section>
        <aside id="contact">
            <h2>Skontaktuj się</h2>
            <a href="mailto:[email protected]">napisz do nas</a>
            <p>telefon: <a href="tel:+48111222333">111222333</a></p>
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  18. Etap 3: Styl CSS

    Kroki 18-25 · po tym etapie masz 25 z 32 kryteriów (pkt)

    Krok 18: Plik styl4.css: zmienne i krój czcionki

    Kryteria:

    R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (*): krój czcionki Helvetica

    ok. 2 min
    Polecenie z arkusza

    Ustawione domyślne wartości dla wszystkich selektorów stylu CSS: krój czcionki Helvetica

    Trzy kolory tła z arkusza w zmiennych - #FFFFCE ma i blok z danymi, i blok środkowy, #B3BC6D baner, stopka i tło obrazu pod kursorem. Selektor * z Helveticą; w Windows jej zwykle nie ma, więc dalej stoją Arial i rodzina sans-serif (R.4.1).

    styl4.css CSS nowy plik
    :root {
        --banner: #B3BC6D;
        --light: #FFFFCE;
        --side: #E6EE9C;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    Podgląd strony po tym kroku (wycieczki.php)
  19. Krok 19: Układ: siatka z nazwanymi obszarami

    Kryteria:

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu (prawidłowo zastosowano właściwości CSS układające bloki na stronie, np. float i clear albo display flex albo grid)

    ok. 3 min
    Polecenie z arkusza

    Podział strony na bloki: na górze baner, poniżej blok z danymi, poniżej obok siebie trzy bloki: lewy, środkowy i prawy, poniżej stopka. Podział zrealizowany za pomocą znaczników sekcji tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z Obrazem 2

    display: grid na <body> i mapa strony w grid-template-areas - cztery rzędy: baner, blok z danymi i stopka zajmują wszystkie trzy kolumny, a w trzecim rzędzie stoją trzy bloki. grid-template-columns: 25% 50% 25% to szerokości z arkusza (klucz przy R.4.4 przyjmuje je zapisane w ten sposób). grid-area przypisuje każdy blok do obszaru.

    W wersji z float trzeba było uważać, żeby nie dać float blokowi z danymi - tu każdy blok stoi tam, gdzie wskazuje mapa (R.3.1).

    Typowe błędy

    Każdy obszar w mapie musi tworzyć prostokąt, a każdy wiersz mapy mieć tyle nazw, ile jest kolumn - inaczej przeglądarka odrzuca całą deklarację grid-template-areas i bloki układają się jeden pod drugim.

    styl4.css CSS zmienione: 34 linie
    :root {
        --banner: #B3BC6D;
        --light: #FFFFCE;
        --side: #E6EE9C;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "header      header  header"
            "trips       trips   trips"
            "bestsellers gallery contact"
            "footer      footer  footer";
    }
    
    header {
        grid-area: header;
    }
    
    #trips {
        grid-area: trips;
    }
    
    #bestsellers {
        grid-area: bestsellers;
    }
    
    #gallery {
        grid-area: gallery;
    }
    
    #contact {
        grid-area: contact;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (wycieczki.php)
  20. Krok 20: Baner i stopka

    ok. 1 min
    Polecenie z arkusza

    Wspólne dla banera i stopki: kolor tła #B3BC6D, biały kolor czcionki, wyrównanie tekstu do środka, marginesy wewnętrzne 5 px

    Jedna reguła dla dwóch selektorów, kolor ze zmiennej.

    styl4.css CSS zmienione: 8 linii
    :root {
        --banner: #B3BC6D;
        --light: #FFFFCE;
        --side: #E6EE9C;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "header      header  header"
            "trips       trips   trips"
            "bestsellers gallery contact"
            "footer      footer  footer";
    }
    
    header {
        grid-area: header;
    }
    
    #trips {
        grid-area: trips;
    }
    
    #bestsellers {
        grid-area: bestsellers;
    }
    
    #gallery {
        grid-area: gallery;
    }
    
    #contact {
        grid-area: contact;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--banner);
        color: white;
        text-align: center;
        padding: 5px;
    }
    Podgląd strony po tym kroku (wycieczki.php)
  21. Krok 21: Blok z danymi

    Kryteria:

    R.4.3 Ustawiono biały kolor czcionki dla banera i stopki oraz Olive dla bloku z danymi

    ok. 1 min
    Polecenie z arkusza

    Dla bloku z danymi: kolor tła #FFFFCE, kolor czcionki Olive, marginesy wewnętrzne: górny i dolny 10 px, prawy i lewy 150 px

    padding: 10px 150px - pierwsza wartość to górny i dolny, druga lewy i prawy. Kolory czcionek z arkusza są komplet (R.4.3).

    styl4.css CSS zmienione: 6 linii
    :root {
        --banner: #B3BC6D;
        --light: #FFFFCE;
        --side: #E6EE9C;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "header      header  header"
            "trips       trips   trips"
            "bestsellers gallery contact"
            "footer      footer  footer";
    }
    
    header {
        grid-area: header;
    }
    
    #trips {
        grid-area: trips;
    }
    
    #bestsellers {
        grid-area: bestsellers;
    }
    
    #gallery {
        grid-area: gallery;
    }
    
    #contact {
        grid-area: contact;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--banner);
        color: white;
        text-align: center;
        padding: 5px;
    }
    
    #trips {
        background-color: var(--light);
        color: Olive;
        padding: 10px 150px;
    }
    Podgląd strony po tym kroku (wycieczki.php)
  22. Krok 22: Bloki lewy, środkowy i prawy

    Kryteria:

    R.4.2 Ustawiono kolor tła #B3BC6D dla banera i stopki, #FFFFCE dla bloku z danymi i środkowego, #E6EE9C dla bloku lewego i prawego

    ok. 2 min
    Polecenie z arkusza

    Dla bloku lewego i prawego: kolor tła #E6EE9C, szerokość 25%, wysokość 450 px, wyrównanie tekstu do środka. Dla bloku środkowego: kolor tła #FFFFCE, szerokość 50%, wysokość 450 px

    Szerokości 25%, 50% i 25% są już w kolumnach siatki. Tu kolory i wysokości - komplet kolorów tła (R.4.2).

    Typowe błędy

    width: 50% dopisane jeszcze na #gallery liczy się w siatce od kolumny - galeria miałaby 50% z 50% strony.

    styl4.css CSS zmienione: 12 linii
    :root {
        --banner: #B3BC6D;
        --light: #FFFFCE;
        --side: #E6EE9C;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "header      header  header"
            "trips       trips   trips"
            "bestsellers gallery contact"
            "footer      footer  footer";
    }
    
    header {
        grid-area: header;
    }
    
    #trips {
        grid-area: trips;
    }
    
    #bestsellers {
        grid-area: bestsellers;
    }
    
    #gallery {
        grid-area: gallery;
    }
    
    #contact {
        grid-area: contact;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--banner);
        color: white;
        text-align: center;
        padding: 5px;
    }
    
    #trips {
        background-color: var(--light);
        color: Olive;
        padding: 10px 150px;
    }
    
    #bestsellers,
    #contact {
        background-color: var(--side);
        height: 450px;
        text-align: center;
    }
    
    #gallery {
        background-color: var(--light);
        height: 450px;
    }
    Podgląd strony po tym kroku (wycieczki.php)
  23. Krok 23: Tabela: obramowanie, wyśrodkowanie, szerokość

    Kryteria: ,

    R.4.4 Ustawiono szerokość 25% dla bloków lewego i prawego, 50% dla środkowego (width lub grid-template-columns lub flex), 70% dla selektora table

    R.4.8 Ustawiono obramowanie dla tabeli: 1px solid Olive

    ok. 1 min
    Polecenie z arkusza

    Dla selektora tabeli: obramowanie linią ciągłą o szerokości 1 px i kolorze Olive, marginesy zewnętrzne automatycznie wyliczane przez przeglądarkę, szerokość 70%

    Selektor znacznika table, jak każe uwaga arkusza. margin: auto wyśrodkowuje tabelę w bloku; ramkę ma sama tabela, komórki nie - jak na obrazie 2. Szerokości z arkusza są komplet (R.4.4, R.4.8).

    styl4.css CSS zmienione: 6 linii
    :root {
        --banner: #B3BC6D;
        --light: #FFFFCE;
        --side: #E6EE9C;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "header      header  header"
            "trips       trips   trips"
            "bestsellers gallery contact"
            "footer      footer  footer";
    }
    
    header {
        grid-area: header;
    }
    
    #trips {
        grid-area: trips;
    }
    
    #bestsellers {
        grid-area: bestsellers;
    }
    
    #gallery {
        grid-area: gallery;
    }
    
    #contact {
        grid-area: contact;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--banner);
        color: white;
        text-align: center;
        padding: 5px;
    }
    
    #trips {
        background-color: var(--light);
        color: Olive;
        padding: 10px 150px;
    }
    
    #bestsellers,
    #contact {
        background-color: var(--side);
        height: 450px;
        text-align: center;
    }
    
    #gallery {
        background-color: var(--light);
        height: 450px;
    }
    
    table {
        border: 1px solid Olive;
        margin: auto;
        width: 70%;
    }
    Podgląd strony po tym kroku (wycieczki.php)
  24. Krok 24: Nagłówki drugiego stopnia i ich pierwsza litera

    Kryteria: ,

    R.4.7 Ustawiono wyrównanie tekstu do środka dla banera, stopki, bloku lewego, prawego, selektora h2

    R.4.9 Ustawiono styl pseudoelementu first-letter ustawionego dla h2: rozmiar czcionki 150%, kolor czcionki Olive

    ok. 1 min
    Polecenie z arkusza

    Dla selektora nagłówka drugiego stopnia: wyrównanie tekstu do środka. Dla pierwszej litery nagłówka drugiego stopnia: rozmiar czcionki 150%, kolor czcionki Olive

    Selektor znacznika h2 wyśrodkowuje trzy nagłówki (R.4.7). Pseudoelement ::first-letter to sama pierwsza litera - „B”, „N” i „S” są większe i oliwkowe (R.4.9).

    styl4.css CSS zmienione: 9 linii
    :root {
        --banner: #B3BC6D;
        --light: #FFFFCE;
        --side: #E6EE9C;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "header      header  header"
            "trips       trips   trips"
            "bestsellers gallery contact"
            "footer      footer  footer";
    }
    
    header {
        grid-area: header;
    }
    
    #trips {
        grid-area: trips;
    }
    
    #bestsellers {
        grid-area: bestsellers;
    }
    
    #gallery {
        grid-area: gallery;
    }
    
    #contact {
        grid-area: contact;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--banner);
        color: white;
        text-align: center;
        padding: 5px;
    }
    
    #trips {
        background-color: var(--light);
        color: Olive;
        padding: 10px 150px;
    }
    
    #bestsellers,
    #contact {
        background-color: var(--side);
        height: 450px;
        text-align: center;
    }
    
    #gallery {
        background-color: var(--light);
        height: 450px;
    }
    
    table {
        border: 1px solid Olive;
        margin: auto;
        width: 70%;
    }
    
    h2 {
        text-align: center;
    }
    
    h2::first-letter {
        font-size: 150%;
        color: Olive;
    }
    Podgląd strony po tym kroku (wycieczki.php)
  25. Krok 25: Obraz i obraz pod kursorem

    Kryteria: , ,

    R.4.5 Ustawiono wysokość 450px dla bloków lewego, środkowego i prawego i 100px dla selektora img

    R.4.6 Ustawiono marginesy wewnętrzne (padding) 5px dla banera i stopki, 7px dla obrazu, 10px górny i dolny i 150px prawy i lewy dla bloku z danymi oraz marginesy zewnętrzne (margin) auto dla tabeli

    R.4.10 Ustawiono styl pseudoklasy hover dla obrazu: kolor tła #B3BC6D

    ok. 1 min
    Polecenie z arkusza

    Dla selektora obrazu: wysokość 100 px, marginesy wewnętrzne 7 px. W momencie, gdy kursor znajduje się na obrazie, styl obrazu zmienia się na: kolor tła #B3BC6D

    padding: 7px to pas wokół zdjęcia wewnątrz elementu - pod kursorem (:hover) zabarwia się kolorem banera, dlatego na obrazie 2 środkowe zdjęcie ma zieloną ramkę (R.4.5, R.4.6, R.4.10).

    styl4.css CSS zmienione: 9 linii
    :root {
        --banner: #B3BC6D;
        --light: #FFFFCE;
        --side: #E6EE9C;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 50% 25%;
        grid-template-areas:
            "header      header  header"
            "trips       trips   trips"
            "bestsellers gallery contact"
            "footer      footer  footer";
    }
    
    header {
        grid-area: header;
    }
    
    #trips {
        grid-area: trips;
    }
    
    #bestsellers {
        grid-area: bestsellers;
    }
    
    #gallery {
        grid-area: gallery;
    }
    
    #contact {
        grid-area: contact;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--banner);
        color: white;
        text-align: center;
        padding: 5px;
    }
    
    #trips {
        background-color: var(--light);
        color: Olive;
        padding: 10px 150px;
    }
    
    #bestsellers,
    #contact {
        background-color: var(--side);
        height: 450px;
        text-align: center;
    }
    
    #gallery {
        background-color: var(--light);
        height: 450px;
    }
    
    table {
        border: 1px solid Olive;
        margin: auto;
        width: 70%;
    }
    
    h2 {
        text-align: center;
    }
    
    h2::first-letter {
        font-size: 150%;
        color: Olive;
    }
    
    img {
        height: 100px;
        padding: 7px;
    }
    
    img:hover {
        background-color: var(--banner);
    }
    Podgląd strony po tym kroku (wycieczki.php)
  26. Etap 4: Skrypty PHP

    Kroki 26-31 · po tym etapie masz 32 z 32 kryteriów (pkt)

    Krok 26: Połączenie z bazą na górze pliku i zamknięcie

    Kryteria: ,

    R.5.1 Skrypt zawiera działające połączenie z serwerem bazy danych i wybór bazy biuro

    R.5.2 Ostatnią operacją na bazie jest jej zamknięcie (zgodne ze składnią)

    ok. 4 min
    Polecenie z arkusza

    Łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie biuro. Na końcu działania skrypt zamyka połączenie z serwerem.

    Oba skrypty z arkusza pobierają dane w jednym bloku PHP na górze wycieczki.php, przed HTML-em - połączenie otwiera się raz, oba zapytania wykonują się jedno po drugim, a połączenie jest zamknięte, zanim przeglądarka dostanie pierwszy znacznik. W HTML-u zostaną tylko pętle wypisujące wynik.

    • new PDO(...) - serwer localhost, baza biuro, użytkownik root bez hasła (R.5.1); ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze z nazwami kolumn,
    • $pdo = null na końcu bloku - PDO zamyka połączenie, gdy znika odwołanie do obiektu; zostanie ostatnią operacją na bazie (R.5.2),
    • try / catch (PDOException ...) - przy awarii bazy uczeń zobaczy zwykłe zdanie, a nie szczegóły serwera.

    PDO zamiast mysqli z tabeli 1 - klucz przyjmuje obie biblioteki (uwaga przy R.5). Od tego kroku podgląd to wynik prawdziwego uruchomienia strony.

    wycieczki.php PHP zmienione: 12 linii
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biuro;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
            <h3>Wycieczki, na które są wolne miejsca</h3>
            <ul>
            </ul>
        </main>
        <aside id="bestsellers">
            <h2>Bestselery</h2>
            <table>
                <tr>
                    <td>Wenecja</td>
                    <td>kwiecień</td>
                </tr>
                <tr>
                    <td>Londyn</td>
                    <td>lipiec</td>
                </tr>
                <tr>
                    <td>Rzym</td>
                    <td>wrzesień</td>
                </tr>
            </table>
        </aside>
        <section id="gallery">
            <h2>Nasze zdjęcia</h2>
        </section>
        <aside id="contact">
            <h2>Skontaktuj się</h2>
            <a href="mailto:[email protected]">napisz do nas</a>
            <p>telefon: <a href="tel:+48111222333">111222333</a></p>
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  27. Krok 27: Skrypt 1: zapytanie 1

    Kryteria:

    R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 1 lub skrypt 2 wysyła zapytanie 2

    ok. 2 min
    Polecenie z arkusza

    Skrypt 1: Wysyła do bazy danych zapytanie 1

    Zapytanie bez danych od użytkownika, więc wystarczy query; fetchAll() zwraca od razu listę sześciu wycieczek z kluczami id, dataWyjazdu, cel, cena. W catch lista dostaje wartość pustą, żeby HTML niżej nie trafił na niezdefiniowaną zmienną (R.5.3). Na stronie jeszcze nic nie widać.

    wycieczki.php PHP zmienione: 4 linie
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biuro;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - wycieczki z wolnymi miejscami
        $trips = $pdo->query('SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE ORDER BY id')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $trips = [];
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
            <h3>Wycieczki, na które są wolne miejsca</h3>
            <ul>
            </ul>
        </main>
        <aside id="bestsellers">
            <h2>Bestselery</h2>
            <table>
                <tr>
                    <td>Wenecja</td>
                    <td>kwiecień</td>
                </tr>
                <tr>
                    <td>Londyn</td>
                    <td>lipiec</td>
                </tr>
                <tr>
                    <td>Rzym</td>
                    <td>wrzesień</td>
                </tr>
            </table>
        </aside>
        <section id="gallery">
            <h2>Nasze zdjęcia</h2>
        </section>
        <aside id="contact">
            <h2>Skontaktuj się</h2>
            <a href="mailto:[email protected]">napisz do nas</a>
            <p>telefon: <a href="tel:+48111222333">111222333</a></p>
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  28. Krok 28: Skrypt 1: wycieczka w elemencie listy według wzoru

    Kryteria: ,

    R.5.4 Skrypt 1 wyświetla każdy zwrócony zapytaniem wiersz w formacie: „<id>. dnia <dataWyjazdu> jedziemy do <cel>, cena: <cena>”, gdzie < > oznacza pole zwrócone zapytaniem

    R.2.6 W bloku z danymi umieszczono listę punktowaną, zastosowano znaczniki <ul>, <li> (w skrypcie lub HTML)

    ok. 3 min
    Polecenie z arkusza

    Każdy zwrócony zapytaniem wiersz jest wyświetlany w elemencie listy, według wzoru: „<id>. dnia <dataWyjazdu> jedziemy do <cel>, cena: <cena>”, gdzie w znakach < > zapisano pola zwrócone zapytaniem

    • foreach z dwukropkiem i endforeach - jeden obieg pętli to jeden element <li>; kropka, „dnia”, „jedziemy do” i przecinek to zwykły tekst wzoru między wstawkami,
    • pola po nazwach ($trip['cel']), a funkcja e() przepuszcza każdy tekst z bazy przez htmlspecialchars,
    • data w znaczniku <time datetime="..."> - na stronie wygląda tak samo, a wyszukiwarki i czytniki wiedzą, że to data.

    Podgląd: sześć wycieczek, od „1. dnia 2019-09-08 jedziemy do Wlochy, Wenecja, cena: 1200” - jak na obrazie 2 (R.5.4, R.2.6).

    wycieczki.php PHP zmienione: 8 linii
    <?php
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biuro;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - wycieczki z wolnymi miejscami
        $trips = $pdo->query('SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE ORDER BY id')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $trips = [];
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
            <h3>Wycieczki, na które są wolne miejsca</h3>
            <ul>
    <?php foreach ($trips as $trip): ?>
                <li><?= e($trip['id']) ?>. dnia <time datetime="<?= e($trip['dataWyjazdu']) ?>"><?= e($trip['dataWyjazdu']) ?></time> jedziemy do <?= e($trip['cel']) ?>, cena: <?= e($trip['cena']) ?></li>
    <?php endforeach; ?>
            </ul>
        </main>
        <aside id="bestsellers">
            <h2>Bestselery</h2>
            <table>
                <tr>
                    <td>Wenecja</td>
                    <td>kwiecień</td>
                </tr>
                <tr>
                    <td>Londyn</td>
                    <td>lipiec</td>
                </tr>
                <tr>
                    <td>Rzym</td>
                    <td>wrzesień</td>
                </tr>
            </table>
        </aside>
        <section id="gallery">
            <h2>Nasze zdjęcia</h2>
        </section>
        <aside id="contact">
            <h2>Skontaktuj się</h2>
            <a href="mailto:[email protected]">napisz do nas</a>
            <p>telefon: <a href="tel:+48111222333">111222333</a></p>
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  29. Krok 29: Skrypt 2: zapytanie 2

    ok. 1 min
    Polecenie z arkusza

    Skrypt 2: Wysyła do bazy danych zapytanie 2

    Drugie zapytanie na tym samym połączeniu, przed $pdo = null - zamknięcie zostaje ostatnią operacją. Każdy wynik ma własną zmienną o nazwie mówiącej, co zawiera ($trips, $photos), więc nic nie jest nadpisywane.

    wycieczki.php PHP zmienione: 4 linie
    <?php
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biuro;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - wycieczki z wolnymi miejscami
        $trips = $pdo->query('SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE ORDER BY id')->fetchAll();
    
        // Skrypt 2: zapytanie 2 - zdjęcia do galerii
        $photos = $pdo->query('SELECT nazwaPliku, podpis FROM zdjecia ORDER BY podpis DESC')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $trips = [];
        $photos = [];
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
            <h3>Wycieczki, na które są wolne miejsca</h3>
            <ul>
    <?php foreach ($trips as $trip): ?>
                <li><?= e($trip['id']) ?>. dnia <time datetime="<?= e($trip['dataWyjazdu']) ?>"><?= e($trip['dataWyjazdu']) ?></time> jedziemy do <?= e($trip['cel']) ?>, cena: <?= e($trip['cena']) ?></li>
    <?php endforeach; ?>
            </ul>
        </main>
        <aside id="bestsellers">
            <h2>Bestselery</h2>
            <table>
                <tr>
                    <td>Wenecja</td>
                    <td>kwiecień</td>
                </tr>
                <tr>
                    <td>Londyn</td>
                    <td>lipiec</td>
                </tr>
                <tr>
                    <td>Rzym</td>
                    <td>wrzesień</td>
                </tr>
            </table>
        </aside>
        <section id="gallery">
            <h2>Nasze zdjęcia</h2>
        </section>
        <aside id="contact">
            <h2>Skontaktuj się</h2>
            <a href="mailto:[email protected]">napisz do nas</a>
            <p>telefon: <a href="tel:+48111222333">111222333</a></p>
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  30. Krok 30: Skrypt 2: zdjęcia z bazy

    Kryteria: ,

    R.5.5 Skrypt 2 wyświetla obrazy na podstawie danych zwróconych zapytaniem, w ten sposób, że pole nazwaPliku jest źródłem obrazu, a pole podpis jest tekstem alternatywnym obrazu

    R.2.8 W bloku środkowym umieszczono przynajmniej jeden obraz (w HTML lub skrypcie)

    ok. 3 min
    Polecenie z arkusza

    Dane z każdego zwróconego zapytaniem wiersza są wykorzystane do wyświetlenia kolejnych obrazów, w ten sposób, że pole nazwaPliku jest źródłem obrazu, a pole podpis jest tekstem alternatywnym obrazu

    Jeden <img> na wiersz: nazwaPliku w src, podpis w alt (R.5.5, R.2.8). Wartości z bazy stoją w atrybutach w cudzysłowach, więc e() jest tu szczególnie potrzebne - ENT_QUOTES zamienia cudzysłów z bazy na encję i podpis typu Rzym "Wieczne Miasto" nie zamknie atrybutu w połowie. Atrybut title z tym samym podpisem pokazuje dymek z nazwą miasta po najechaniu kursorem.

    Podgląd: galeria z obrazu 2, od Wenecji do Barcelony; najedź na zdjęcie - tło wokół niego zmieni kolor.

    wycieczki.php PHP zmienione: 3 linie
    <?php
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biuro;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - wycieczki z wolnymi miejscami
        $trips = $pdo->query('SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE ORDER BY id')->fetchAll();
    
        // Skrypt 2: zapytanie 2 - zdjęcia do galerii
        $photos = $pdo->query('SELECT nazwaPliku, podpis FROM zdjecia ORDER BY podpis DESC')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $trips = [];
        $photos = [];
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
            <h3>Wycieczki, na które są wolne miejsca</h3>
            <ul>
    <?php foreach ($trips as $trip): ?>
                <li><?= e($trip['id']) ?>. dnia <time datetime="<?= e($trip['dataWyjazdu']) ?>"><?= e($trip['dataWyjazdu']) ?></time> jedziemy do <?= e($trip['cel']) ?>, cena: <?= e($trip['cena']) ?></li>
    <?php endforeach; ?>
            </ul>
        </main>
        <aside id="bestsellers">
            <h2>Bestselery</h2>
            <table>
                <tr>
                    <td>Wenecja</td>
                    <td>kwiecień</td>
                </tr>
                <tr>
                    <td>Londyn</td>
                    <td>lipiec</td>
                </tr>
                <tr>
                    <td>Rzym</td>
                    <td>wrzesień</td>
                </tr>
            </table>
        </aside>
        <section id="gallery">
            <h2>Nasze zdjęcia</h2>
    <?php foreach ($photos as $photo): ?>
            <img src="<?= e($photo['nazwaPliku']) ?>" alt="<?= e($photo['podpis']) ?>" title="<?= e($photo['podpis']) ?>">
    <?php endforeach; ?>
        </section>
        <aside id="contact">
            <h2>Skontaktuj się</h2>
            <a href="mailto:[email protected]">napisz do nas</a>
            <p>telefon: <a href="tel:+48111222333">111222333</a></p>
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (wycieczki.php)
  31. Krok 31: Brak wolnych miejsc i awaria bazy: komunikaty

    ok. 3 min

    Dodatek spoza arkusza - klucz go nie ocenia. Nad listą pojawia się zdanie w dwóch sytuacjach, których obraz 2 nie pokazuje:

    • żadna wycieczka nie ma wolnych miejsc (sprawdzone na bazie po UPDATE wycieczki SET dostepna = 0) - zamiast pustej listy pod nagłówkiem „Wycieczki, na które są wolne miejsca”,
    • awaria bazy - komunikat z role="alert"; tabela bestsellerów i kontakt są zwykłym HTML-em, więc działają dalej.

    Sprawdzone na prawdziwym PHP, bez ostrzeżeń. Podgląd: strona na bazie z arkusza.

    wycieczki.php PHP zmienione: 5 linii
    <?php
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biuro;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - wycieczki z wolnymi miejscami
        $trips = $pdo->query('SELECT id, dataWyjazdu, cel, cena FROM wycieczki WHERE dostepna = TRUE ORDER BY id')->fetchAll();
    
        // Skrypt 2: zapytanie 2 - zdjęcia do galerii
        $photos = $pdo->query('SELECT nazwaPliku, podpis FROM zdjecia ORDER BY podpis DESC')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $trips = [];
        $photos = [];
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wycieczki po Europie</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>BIURO TURYSTYCZNE</h1>
        </header>
        <main id="trips">
            <h3>Wycieczki, na które są wolne miejsca</h3>
    <?php if (isset($databaseError)): ?>
            <p role="alert"><?= e($databaseError) ?></p>
    <?php elseif ($trips === []): ?>
            <p>Na razie nie ma wolnych miejsc - zajrzyj tu za kilka dni.</p>
    <?php endif; ?>
            <ul>
    <?php foreach ($trips as $trip): ?>
                <li><?= e($trip['id']) ?>. dnia <time datetime="<?= e($trip['dataWyjazdu']) ?>"><?= e($trip['dataWyjazdu']) ?></time> jedziemy do <?= e($trip['cel']) ?>, cena: <?= e($trip['cena']) ?></li>
    <?php endforeach; ?>
            </ul>
        </main>
        <aside id="bestsellers">
            <h2>Bestselery</h2>
            <table>
                <tr>
                    <td>Wenecja</td>
                    <td>kwiecień</td>
                </tr>
                <tr>
                    <td>Londyn</td>
                    <td>lipiec</td>
                </tr>
                <tr>
                    <td>Rzym</td>
                    <td>wrzesień</td>
                </tr>
            </table>
        </aside>
        <section id="gallery">
            <h2>Nasze zdjęcia</h2>
    <?php foreach ($photos as $photo): ?>
            <img src="<?= e($photo['nazwaPliku']) ?>" alt="<?= e($photo['podpis']) ?>" title="<?= e($photo['podpis']) ?>">
    <?php endforeach; ?>
        </section>
        <aside id="contact">
            <h2>Skontaktuj się</h2>
            <a href="mailto:[email protected]">napisz do nas</a>
            <p>telefon: <a href="tel:+48111222333">111222333</a></p>
        </aside>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

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

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

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

    ok. 3 min

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

    wersja podstawowawersja nowoczesna
    układfloat trzech bloków (bez bloku danych) i clearmapa grid-template-areas na <body>
    szerokościwidth na blokachgrid-template-columns: 25% 50% 25%
    bloki<section> z id<main>, <aside>, <section> z id po treści
    kolorywpisane w regułyzmienne CSS
    zapytaniabez sortowania i aliasów, INT UNSIGNEDORDER BY, aliasy, TINYINT UNSIGNED
    bibliotekamysqli (tabela 1 arkusza)PDO z wyjątkami
    gdzie są zapytaniaSkrypt 1 w liście, Skrypt 2 w galeriioba na górze wycieczki.php
    wypisywanie$row[0], dane z bazy wprostpola po nazwach, e(), <time>
    brak wycieczek, awaria bazypusta lista, ostrzeżenia PHPkomunikaty

    Dane logowania do bazy w prawdziwym projekcie trzyma się zwykle w osobnym pliku (np. config.php), ale klucz sprawdza skrypt w kodzie strony - tu cały kod PHP zostaje w wycieczki.php.

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i łatwiej ją napisać bez dokumentacji. Nowoczesna rozdziela pobieranie danych od HTML-u, więc łatwiej ją czytać i poprawiać. Obie biblioteki klucz przyjmuje.

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

  33. Etap 6: Oddanie pracy

    Krok 33 · po tym etapie masz 32 z 32 kryteriów (pkt)

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

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz plik tekstowy. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Nazwij plik przeglądarka.txt i zapisz go w folderze z numerem zdającego. Nagraj płytę z rezultatami pracy. W folderze z numerem zdającego powinny się znajdować następujące pliki: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 5.jpg, 6.jpg, 7.jpg, 8.jpg, 9.jpg, import.png, kw1.jpg, kw2.jpg, kw3.jpg, kw4.jpg, kwerendy.txt, przeglądarka.txt, styl4.css, wycieczki.php ewentualnie inne przygotowane pliki.

    W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była strona przez http://localhost/.... Nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.