Przejdź do głównej treści
INF.03-04-23.06-SGCzerwiec 2023 · 43 kroki · ok. 87 min · 37 kryteriów z klucza

Rozwiązanie krok po kroku: Portal biblioteki publicznej (Nowoczesny kod (flex, PDO))

To drugi sposób rozwiązania tego samego arkusza (wydanego też pod numerami 06 i 10). Wynik jest ten sam - baza biblioteka z czterema zapytaniami, grafika ze zdublowanym stosem książek i strona biblioteka.php z listą autorów, adresem, formularzem dodawania czytelnika i potwierdzeniem - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • flexbox w kolumnach zamiast float: cztery panele stoją w znaczniku <main>, który układa je w kolumny wysokie na 500 px - panel lewy i środkowy wypełniają po jednej, a dwa prawe dzielą trzecią. Szerokości 30%, 30% i 40% zostają na samych panelach - tak ocenia je klucz,
  • zmienne CSS dla kolorów z arkusza, wiersze formularza w blokach zamiast <br>, pola z required i podpowiedziami autouzupełniania,
  • PHP na PDO: wszystkie dane strona obsługuje na górze pliku biblioteka.php, przed HTML-em, a w HTML-u zostają tylko pętla i wypisywanie,
  • sprawdzanie danych z formularza (trim, symbol od 1 do 6 cyfr) i zapytanie przygotowane do INSERT,
  • wzorzec PRG (Post/Redirect/Get): po zapisie strona przekierowuje sama na siebie, więc odświeżenie nie doda tego samego czytelnika drugi raz,
  • bezpieczne wypisywanie (htmlspecialchars w funkcji e()) i komunikaty dla niepełnych danych i awarii bazy.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafika pochodzą z prawdziwego archiwum materialy4, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 na bazie z arkusza - od Skryptu 2 po wysłaniu formularza z danymi czytelniczki z obrazu 2, Anny Nowakowskiej (obraz nie pokazuje jej symbolu, więc w podglądzie stoi przykładowy 145322).

Jak liczą się punkty. Klucz ma 37 kryteriów (R.1.1 - R.5.7), każde za 1 punkt. Próg zdania 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 biblioteka

    ok. 2 min
    Polecenie z arkusza

    Utwórz nową bazę danych o nazwie biblioteka

    Rozpakuj materialy4.7z z pulpitu (hasło 4rch!wum4x - cyfra 4 zamiast „a”, wykrzyknik zamiast „i”) do folderu z numerem zdającego: biblioteka.sql i biblioteka.png. W phpMyAdmin Nowa → biblioteka albo zapytaniem:

    CREATE DATABASE biblioteka CHARACTER SET utf8 COLLATE utf8_polish_ci;
    

    Arkusz nie podaje porównywania, więc wybór należy do Ciebie. Tabele z biblioteka.sql mają zestaw znaków zapisany we własnej definicji (część latin1, część utf8), więc ustawienie bazy ich nie zmienia.

    Typowe błędy

    Skrypt łączy się z bazą po nazwie - literówka w nazwie bazy zabiera też punkty za część PHP (R.5.1).

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

    ok. 2 min
    Polecenie z arkusza

    Do bazy zaimportuj tabele z pliku biblioteka.sql, z wcześniej rozpakowanego archiwum

    Zaznaczona baza biblioteka, Import → biblioteka.sql → Wykonaj. Zadanie używa pięciu tabel z obrazu 1: autorzy (10), ksiazki (21 tytułów), kategorie (epika, dramat, liryka), czytelnicy (10) i wypozyczenia.

  3. Krok 3: Zrzut ekranu import.jpg

    Kryteria:

    R.1.1 Wykonano import tabel do bazy danych o nazwie biblioteka , czynność udokumentowano plikiem o nazwie import w formacie JPEG

    ok. 2 min
    Polecenie z arkusza

    Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie JPEG i nazwij 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 z paskiem zadań, w JPEG, pod nazwą import. Zrzuty zapytań: kwerenda1 - kwerenda4, też w JPEG i z widocznym zapytaniem (phpMyAdmin wyświetla je nad wynikiem). Każde zapytanie skopiuj też do kwerendy.txt.

    Typowe błędy

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

  4. Krok 4: Zapytanie 1: autorzy alfabetycznie po nazwisku

    Kryteria: ,

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

    R.1.3 Zapisano zapytanie 1 wybierające jedynie imie i nazwisko dla wszystkich rekordów w tabeli autorzy , alfabetycznie posortowane rosnąco nazwiskami autorów. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko; (opcjonalnie ASC) oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym jest widocznych dokładnie 10 rekordów z imionami i nazwiskami wszystkich autorów w kolejności alfabetycznej

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pola imie i nazwisko dla wszystkich rekordów w tabeli autorzy, posortowane rosnąco nazwiskami autorów

    Dwa pola i ORDER BY nazwisko - rosnąco to kierunek domyślny (ASC można dopisać). Każda klauzula w osobnej linii. Wynik: 10 autorów od Jana Brzechwy do Magdaleny Witkiewicz - w tej kolejności co lista na obrazie 2. Zrzut: kwerenda1.jpg.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT imie, nazwisko
    FROM autorzy
    ORDER BY nazwisko;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: nowa czytelniczka Ewa Kowalska

    Kryteria:

    R.1.4 Zapisano zapytanie 2 wstawiające do tabeli czytelnicy rekord Ewa Kowalska z kodem 145321, wartość klucza głównego jest nadawana automatycznie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO `czytelnicy`(`imie`, `nazwisko`, `kod`) VALUES ('Ewa', 'Kowalska', '145321'); (możliwe również bez struktury tabeli lub przez przypisania SET) oraz wynik działania zapytania udokumentowano zrzutem ekranu z widocznym komunikatem o pomyślnym wykonaniu zapytania lub tabela z wstawionym wierszem

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: wstawiające do tabeli czytelnicy nowy rekord z danymi: Ewa Kowalska z kodem 145321. Wartość klucza głównego nadawana automatycznie

    Lista pól bez id - klucz główny ma AUTO_INCREMENT, więc baza sama nada kolejny numer (11). kod to tekst (varchar(6)), więc też w apostrofach. Komunikat „1 wiersz wstawiony” na zrzucie kwerenda2.jpg. To zapytanie wyśle Skrypt 2 - z parametrami w miejscu danych.

    Typowe błędy

    Własna wartość id nie spełnia warunku „nadawana automatycznie”. Zapytanie wykonane dwa razy doda dwie Ewy Kowalskie.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT imie, nazwisko
    FROM autorzy
    ORDER BY nazwisko;
    
    -- Zapytanie 2
    INSERT INTO czytelnicy (imie, nazwisko, kod)
    VALUES ('Ewa', 'Kowalska', '145321');
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: tytuły z kategorii dramat

    Kryteria:

    R.1.5 Zapisano zapytanie 3 wybierające jedynie pole tytul dla wszystkich utworów w tabeli ksiazki z kategorii dramat. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT tytul FROM ksiazki WHERE id_kategoria = 2; (lub wykorzystując połączenie tabel i nazwę kategorii 'dramat') oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są dokładnie 4 tytuły: Zemsta, Pan Jowialski, Niemcy, Odwiedziny

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pole tytul dla wszystkich utworów w tabeli ksiazki, należących do kategorii dramat

    • aliasy k (ksiazki) i kat (kategorie) - krótki przedrostek mówi, z której tabeli pochodzi pole,
    • JOIN kategorie AS kat ON kat.id = k.id_kategoria - nazwa kategorii jest w osobnej tabeli,
    • WHERE kat.nazwa = 'dramat' - warunek na nazwie, a nie na numerze 2: zadziała także wtedy, gdy kategorie dostaną inne numery (klucz uznaje oba warianty).

    Wynik: 4 tytuły - Zemsta, Pan Jowialski, Niemcy, Odwiedziny. Zrzut: kwerenda3.jpg.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT imie, nazwisko
    FROM autorzy
    ORDER BY nazwisko;
    
    -- Zapytanie 2
    INSERT INTO czytelnicy (imie, nazwisko, kod)
    VALUES ('Ewa', 'Kowalska', '145321');
    
    -- Zapytanie 3
    SELECT k.tytul
    FROM ksiazki AS k
    JOIN kategorie AS kat ON kat.id = k.id_kategoria
    WHERE kat.nazwa = 'dramat';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: książki wypożyczone przez czytelnika 2

    Kryteria:

    R.1.6 Zapisano zapytanie 4 wybierające jedynie pole tytul wszystkich utworów z tabeli ksiazki wypożyczonych przez czytelnika o identyfikatorze 2. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT tytul FROM ksiazki JOIN wypozyczenia ON ksiazki.id = wypozyczenia.id_ksiazka WHERE wypozyczenia.id_czytelnik = 2; (lub INNER JOIN, lub bez JOIN, z porównaniem kuczy w WHERE) oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym widoczne są jedynie tytuły: Zemsta, Brzechwa dzieciom

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie pole tytul wszystkich utworów w tabeli książki, wypożyczonych przez czytelnika o identyfikatorze 2. W zapytaniu 4 należy posłużyć się relacją

    JOIN wypozyczenia AS w ON w.id_ksiazka = k.id - relacja książka-wypożyczenie, warunek na czytelniku. Wynik: Zemsta i Brzechwa dzieciom. Czytelnik 2 ma trzy wypożyczenia, ale jedno wskazuje książkę numer 22, której nie ma w tabeli ksiazki - złączenie wewnętrzne takie wiersze pomija. Zrzut: kwerenda4.jpg.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT imie, nazwisko
    FROM autorzy
    ORDER BY nazwisko;
    
    -- Zapytanie 2
    INSERT INTO czytelnicy (imie, nazwisko, kod)
    VALUES ('Ewa', 'Kowalska', '145321');
    
    -- Zapytanie 3
    SELECT k.tytul
    FROM ksiazki AS k
    JOIN kategorie AS kat ON kat.id = k.id_kategoria
    WHERE kat.nazwa = 'dramat';
    
    -- Zapytanie 4
    SELECT k.tytul
    FROM ksiazki AS k
    JOIN wypozyczenia AS w ON w.id_ksiazka = k.id
    WHERE w.id_czytelnik = 2;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

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

    Krok 8: Grafika: płótno 300 px i drugi stos książek

    Kryteria:

    R.2.1 Grafikę biblioteka.png przekształcono zgodnie z obrazem 3b, są widoczne dwa stosy książek i szerokość wynosi dokładnie 300 px, a wysokość 235 px

    ok. 5 min
    Polecenie z arkusza

    Grafikę biblioteka.png należy rozszerzyć z rozmiaru poziomego 150 px do rozmiaru 300 px, a następnie w pustym obszarze zdublować stos książek zgodnie z obrazem 3b

    Grafika jest taka sama jak w wersji podstawowej. W GIMP-ie Obraz → Rozmiar płótna: szerokość 300, wysokość bez zmian (235), obraz przy lewej krawędzi, wszystkie warstwy. Potem kopia stosu (Zaznacz → Wszystko na starym fragmencie, Kopiuj, Wklej jako nową warstwę) przesunięta o 150 px w prawo i Nadpisz biblioteka.png. To rozszerzenie płótna, nie skalowanie - stos nie może się rozciągnąć.

    Typowe błędy

    Skaluj obraz do 300 px rozciąga stos w poziomie zamiast dodać puste miejsce - R.2.1 wymaga dwóch stosów i wymiarów dokładnie 300 × 235.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Plik biblioteka.php: HTML5, język, kodowanie

    Kryteria:

    R.2.2 Witrynę internetową zapisano w pliku biblioteka .php, zastosowano deklarację języka HTML5 <!DOCTYPE html>, zadeklarowano język witryny polski np. <html lang = "pl"> oraz zastosowano właściwy standard kodowania polskich znaków

    ok. 2 min
    Polecenie z arkusza

    Składa się ze strony o nazwie biblioteka.php. Zapisana w języku HTML5. Ustawiony język zawartości strony na polski. Jawnie zastosowany właściwy standard kodowania polskich znaków

    <!DOCTYPE html>, <html lang="pl"> i <meta charset="UTF-8"> jako pierwszy element <head> - razem R.2.2. Plik leży w htdocs XAMPP-a i otwierasz go przez http://localhost/..., inaczej PHP się nie wykona.

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

    Kryteria:

    R.2.3 Nadano stronie tytuł "Biblioteka publiczna”

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Biblioteka publiczna”

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

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

    Kryteria:

    R.3.6 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie style.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 style.css prawidłowo połączony z kodem strony

    Nazwa pliku to style.css (inne arkusze tej sesji mają styl.css). Całe formatowanie pochodzi z tego pliku (R.3.6).

    Typowe błędy

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

    biblioteka.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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  12. Krok 12: Bloki: baner, cztery panele w main, stopka

    Kryteria:

    R.2.4 Wygląd strony zdefiniowano dzieląc ją na baner, panele lewy, środkowy, prawy górny i dolny, oraz stopkę. Zastosowano znaczniki sekcji

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: na górze baner, poniżej bloki: lewy, środkowy i dwa prawe ułożone jeden pod drugim oraz na dole blok stopki. Podział zrealizowany za pomocą znaczników sekcji tak, aby wygląd układu bloków strony po uruchomieniu w przeglądarce, był zgodny z obrazem 2

    Baner <header>, stopka <footer>, a cztery panele - cztery <section> we wspólnym <main>, który w CSS ułoży je w kolumny (R.2.4). Identyfikatory mówią, co jest w panelu:

    • authors - polecani autorzy (lewy),
    • address - adres i kontakt (środkowy),
    • new-reader - formularz dodawania czytelnika (prawy górny),
    • status - wynik Skryptu 2 (prawy dolny).
    biblioteka.php PHP zmienione: 14 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
        </header>
        <main>
            <section id="authors">
            </section>
            <section id="address">
            </section>
            <section id="new-reader">
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  13. Krok 13: Baner: nagłówek pierwszego stopnia

    ok. 1 min
    Polecenie z arkusza

    Zawartość banera: nagłówek pierwszego stopnia o treści „Biblioteka w Książkowicach Wielkich”

    <h1> w <header>. Nagłówki paneli są w tym arkuszu trzeciego stopnia - bez <h2>, tak każe arkusz.

    biblioteka.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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
            </section>
            <section id="address">
            </section>
            <section id="new-reader">
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  14. Krok 14: Panel lewy: nagłówek i lista numerowana

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku lewego: Nagłówek trzeciego stopnia o treści „Polecamy dzieła autorów:”. Lista numerowana (uporządkowana), której elementy są generowane skryptem 1

    <h3> z dwukropkiem i pusta <ol> - elementy <li> wypisze Skrypt 1.

    biblioteka.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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
                </ol>
            </section>
            <section id="address">
            </section>
            <section id="new-reader">
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  15. Krok 15: Panel środkowy: adres ze spacją nierozdzielającą

    Kryteria:

    R.2.9 Zastosowano jedynie znak specjalny &nbsp; w danych adresowych: Książkowice&nbsp;Wielkie (bez zwykłych spacji)

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku środkowego: Nagłówek trzeciego stopnia o treści: „ul. Czytelnicza 25, Książkowice Wielkie”. Dwa człony nazwy miasta połączone są spacją nierozdzielającą

    Encja &nbsp; zamiast spacji między członami nazwy miasta: przeglądarka nie złamie tam wiersza, więc „Książkowice Wielkie” zawsze stoją w jednej linii. Pozostałe spacje zwykłe (R.2.9).

    Typowe błędy

    Książkowice &nbsp;Wielkie (spacja obok encji) to dwie spacje - R.2.9 przepada.

    biblioteka.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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
            </section>
            <section id="new-reader">
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  16. Krok 16: Panel środkowy: odsyłacz pocztowy i obraz

    Kryteria: , ,

    R.2.5 Zapisano w banerze nagłówek h1 "Biblioteka w Książkowicach Wielkich", w panelach przynajmniej jeden z nagłówków h3: "Polecamy dzieła autorów:”, "ul. Czytelnicza 25, Książkowice Wielkie", "Dodaj czytelnika” oraz zapisano przynajmniej jeden paragraf

    R.2.8 W bloku środkowym umieszczono obraz z pliku biblioteka.png z tekstem alternatywnym "książki"

    R.3.5 Napis "Napisz do nas" jest odsyłaczem pocztowym do adresu [email protected], w definicji zastosowano "mailto:"

    ok. 2 min
    Polecenie z arkusza

    Paragraf (akapit) o treści „Napisz do nas”, który jest odsyłaczem pocztowym do adresu [email protected]. Obraz biblioteka.png z tekstem alternatywnym „książki”

    Akapit z odnośnikiem mailto: - kliknięcie otwiera program pocztowy z wpisanym adresem (R.3.5). Z <h1> i nagłówkami <h3> to komplet do R.2.5. Pod spodem grafika z tekstem alternatywnym z arkusza (R.2.8).

    biblioteka.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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  17. Krok 17: Panel prawy górny: nagłówek i formularz POST

    Kryteria:

    R.3.2 Dane z formularza przesyłane są za pomocą metody POST do tego samego pliku (poprawne również bez atrybutu action)

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku prawego – górnego: Nagłówek trzeciego stopnia o treści „Dodaj czytelnika”. Formularz wysyłający dane do tego samego pliku metodą bezpieczną.

    method="post" - „metodę bezpieczną” arkusz rozumie jako POST: dane idą w treści żądania, a nie w adresie strony (w specyfikacji HTTP słowo „bezpieczna” znaczy co innego, ale klucz R.3.2 wymaga POST). Bez action formularz wysyła dane do tego samego pliku.

    Typowe błędy

    Bez method="post" formularz wysyła dane metodą GET i Skrypt 2, który czyta $_POST, niczego nie dostanie.

    biblioteka.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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                </form>
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  18. Krok 18: Pole edycyjne „imię”

    ok. 2 min
    Polecenie z arkusza

    Pole edycyjne poprzedzone napisem „imię: ”

    • <label for="name"> - napis powiązany z polem po id: kliknięcie napisu ustawia kursor w polu, a czytnik ekranu odczyta napis jako nazwę pola,
    • name="name" - pod tą nazwą wartość trafi do $_POST,
    • required - przeglądarka nie wyśle formularza z pustym polem, autocomplete="given-name" - podpowie zapisane imię,
    • pole z napisem w <div> - blok sam zaczyna nową linię, więc kolejne pole stanie pod spodem bez <br>.
    biblioteka.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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                </form>
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  19. Krok 19: Pole edycyjne „nazwisko”

    ok. 1 min
    Polecenie z arkusza

    Pole edycyjne poprzedzone napisem „nazwisko: ”

    To samo dla nazwiska: name="surname", autocomplete="family-name".

    biblioteka.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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                </form>
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  20. Krok 20: Pole numeryczne „symbol”

    Kryteria:

    R.3.4 Pole edycyjne do wpisania symbolu jest typu numerycznego

    ok. 1 min
    Polecenie z arkusza

    Pole edycyjne typu numerycznego poprzedzone napisem „symbol: ”

    type="number" (R.3.4). Symbol trafi do kolumny kod typu varchar(6), więc max="999999" - najwyżej sześć cyfr - a min="0" blokuje liczby ujemne. To wygoda dla użytkownika, nie zabezpieczenie: Skrypt 2 sprawdzi symbol jeszcze raz.

    biblioteka.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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="code">symbol: </label>
                        <input type="number" id="code" name="code" required min="0" max="999999">
                    </div>
                </form>
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  21. Krok 21: Przycisk DODAJ

    Kryteria: ,

    R.2.7 W bloku prawym - górnym umieszczono formularz z trzema polami edycyjnymi i przyciskiem DODAJ

    R.3.3 Przycisk w formularzu realizuje wysyłanie danych do skryptu (input lub button z typem submit lub button bez typu)

    ok. 1 min
    Polecenie z arkusza

    Przycisk DODAJ wysyłający dane z formularza

    <button type="submit"> wysyła formularz (R.3.3). Formularz ma trzy pola i przycisk - komplet do R.2.7.

    biblioteka.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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="code">symbol: </label>
                        <input type="number" id="code" name="code" required min="0" max="999999">
                    </div>
                    <button type="submit">DODAJ</button>
                </form>
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  22. Krok 22: Panel prawy dolny: miejsce na Skrypt 2

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku prawego – dolnego: efekt działania skryptu 2

    Panel status zostaje na razie pusty - potwierdzenie dodania czytelnika wypisze w nim Skrypt 2. Panel jest w kodzie od kroku z blokami, więc ten krok nie zmienia pliku.

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

  23. Krok 23: Stopka

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: paragraf (akapit) o treści „Projekt strony: ”, dalej wstawiony numer zdającego

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

    biblioteka.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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="code">symbol: </label>
                        <input type="number" id="code" name="code" required min="0" max="999999">
                    </div>
                    <button type="submit">DODAJ</button>
                </form>
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
            <p>Projekt strony: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  24. Etap 3: Styl CSS

    Kroki 24-33 · po tym etapie masz 29 z 37 kryteriów (pkt)

    Krok 24: Plik style.css: zmienne i krój Verdana

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Domyślne formatowanie wszystkich selektorów: krój czcionki Verdana

    Cztery kolory z arkusza w zmiennych - #004D40 pojawia się w trzech regułach (baner, stopka, ramka obrazu), #E0F2F1 w dwóch. Selektor * z Verdaną i krojem zapasowym sans-serif (R.4.1).

    style.css CSS nowy plik
    :root {
        --dark: #004D40;
        --teal: #80CBC4;
        --light: #E0F2F1;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: Verdana, sans-serif;
    }
    Podgląd strony po tym kroku (biblioteka.php)
  25. Krok 25: Baner i stopka

    ok. 1 min
    Polecenie z arkusza

    Dla banera i stopki: kolor tła #004D40, biały kolor czcionki, marginesy wewnętrzne 10 px, wyrównanie tekstu do środka

    Jedna reguła dla dwóch selektorów, kolor ze zmiennej. Marginesy wewnętrzne to padding.

    style.css CSS zmienione: 8 linii
    :root {
        --dark: #004D40;
        --teal: #80CBC4;
        --light: #E0F2F1;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: Verdana, sans-serif;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        padding: 10px;
        text-align: center;
    }
    Podgląd strony po tym kroku (biblioteka.php)
  26. Krok 26: Panel lewy

    Kryteria:

    R.4.3 Ustawiono biały kolor czcionki dla banera, stopki i panelu lewego

    ok. 1 min
    Polecenie z arkusza

    Dla panelu lewego: kolor tła #80CBC4, biały kolor czcionki, wysokość 500 px, szerokość 30%

    Biały kolor czcionki mają już baner, stopka i panel lewy - komplet do R.4.3.

    Typowe błędy

    .authors (kropka) zamiast #authors nie trafia w panel - klucz R.3.6 wprost wymienia ten błąd.

    style.css CSS zmienione: 7 linii
    :root {
        --dark: #004D40;
        --teal: #80CBC4;
        --light: #E0F2F1;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: Verdana, sans-serif;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    #authors {
        background-color: var(--teal);
        color: white;
        height: 500px;
        width: 30%;
    }
    Podgląd strony po tym kroku (biblioteka.php)
  27. Krok 27: Panel środkowy

    ok. 1 min
    Polecenie z arkusza

    Dla panelu środkowego: kolor tła #E0F2F1, wysokość 500 px, szerokość 30%, wyrównanie tekstu do środka

    text-align: center wyśrodkowuje też obraz - <img> zachowuje się jak słowo w linii tekstu.

    style.css CSS zmienione: 7 linii
    :root {
        --dark: #004D40;
        --teal: #80CBC4;
        --light: #E0F2F1;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: Verdana, sans-serif;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    #authors {
        background-color: var(--teal);
        color: white;
        height: 500px;
        width: 30%;
    }
    
    #address {
        background-color: var(--light);
        height: 500px;
        width: 30%;
        text-align: center;
    }
    Podgląd strony po tym kroku (biblioteka.php)
  28. Krok 28: Oba panele prawe: wymiary

    Kryteria:

    R.4.5 Ustawiono szerokość panelu lewego i środkowego 30% oraz obu prawych 40%

    ok. 1 min
    Polecenie z arkusza

    Dla obu paneli prawych: wysokość 250 px, szerokość 40%

    Dwa panele po 250 px mają razem 500 px - tyle co lewy i środkowy. 30% + 30% + 40% = 100% (R.4.5).

    style.css CSS zmienione: 6 linii
    :root {
        --dark: #004D40;
        --teal: #80CBC4;
        --light: #E0F2F1;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: Verdana, sans-serif;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    #authors {
        background-color: var(--teal);
        color: white;
        height: 500px;
        width: 30%;
    }
    
    #address {
        background-color: var(--light);
        height: 500px;
        width: 30%;
        text-align: center;
    }
    
    #new-reader,
    #status {
        height: 250px;
        width: 40%;
    }
    Podgląd strony po tym kroku (biblioteka.php)
  29. Krok 29: Panele prawe: kolory tła

    Kryteria:

    R.4.2 Ustawiono kolor tła dla banera i stopki na #004D40, panelu lewego #80CBC4, środkowego i prawego górnego #E0F2F1, prawego dolnego #B2DFDB

    ok. 1 min
    Polecenie z arkusza

    Kolory tła dla paneli prawych: prawego-górnego: #E0F2F1, prawego-dolnego: #B2DFDB

    Dwie osobne reguły - kolory się różnią. Wszystkie kolory tła z arkusza są już ustawione (R.4.2).

    style.css CSS zmienione: 8 linii
    :root {
        --dark: #004D40;
        --teal: #80CBC4;
        --light: #E0F2F1;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: Verdana, sans-serif;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    #authors {
        background-color: var(--teal);
        color: white;
        height: 500px;
        width: 30%;
    }
    
    #address {
        background-color: var(--light);
        height: 500px;
        width: 30%;
        text-align: center;
    }
    
    #new-reader,
    #status {
        height: 250px;
        width: 40%;
    }
    
    #new-reader {
        background-color: var(--light);
    }
    
    #status {
        background-color: var(--mint);
    }
    Podgląd strony po tym kroku (biblioteka.php)
  30. Krok 30: Układ: panele w trzech kolumnach

    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 z wartością flex lub grid albo position)

    ok. 3 min
    Polecenie z arkusza

    Podział zrealizowany za pomocą znaczników sekcji tak, aby wygląd układu bloków strony po uruchomieniu w przeglądarce, był zgodny z obrazem 2

    flex-flow: column wrap - pojemnik flex układa panele z góry na dół, a gdy kolumna się zapełni, zawija resztę do następnej. Kolumna ma wysokość <main>, czyli 500 px:

    • panel lewy (500 px) wypełnia całą pierwszą kolumnę,
    • panel środkowy (500 px) - drugą,
    • dwa prawe po 250 px mieszczą się razem w trzeciej, jeden pod drugim.

    Szerokość kolumny wyznacza szerokość panelu, a procent w width liczy się od <main> - czyli 30%, 30% i 40% strony, jak w arkuszu. Stopka stoi pod <main> sama z siebie, bez clear (R.3.1).

    Dlaczego nie siatka? W siatce procent szerokości elementu liczy się od jego kolumny: panel z width: 30% w kolumnie szerokiej na 30% strony zająłby 9% strony. Szerokości trzeba by przenieść do grid-template-columns, a klucz z tego arkusza ocenia je na panelach.

    Typowe błędy

    height: 500px na <main> jest konieczne - bez niego kolumna nie ma końca i wszystkie cztery panele stoją jeden pod drugim.

    style.css CSS zmienione: 6 linii
    :root {
        --dark: #004D40;
        --teal: #80CBC4;
        --light: #E0F2F1;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: Verdana, sans-serif;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    #authors {
        background-color: var(--teal);
        color: white;
        height: 500px;
        width: 30%;
    }
    
    #address {
        background-color: var(--light);
        height: 500px;
        width: 30%;
        text-align: center;
    }
    
    #new-reader,
    #status {
        height: 250px;
        width: 40%;
    }
    
    #new-reader {
        background-color: var(--light);
    }
    
    #status {
        background-color: var(--mint);
    }
    
    main {
        display: flex;
        flex-flow: column wrap;
        height: 500px;
    }
    Podgląd strony po tym kroku (biblioteka.php)
  31. Krok 31: Nagłówki trzeciego stopnia wyśrodkowane

    Kryteria:

    R.4.6 Ustawiono wyrównanie tekstu do środka dla banera, stopki, panelu środkowego i selektora h3

    ok. 1 min
    Polecenie z arkusza

    Dla selektora nagłówka trzeciego stopnia: wyrównanie tekstu do środka

    Selektor znacznika h3 - uwaga arkusza zabrania tu klasy i identyfikatora. Wyśrodkowane są już baner, stopka, panel środkowy i nagłówki (R.4.6).

    style.css CSS zmienione: 4 linie
    :root {
        --dark: #004D40;
        --teal: #80CBC4;
        --light: #E0F2F1;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: Verdana, sans-serif;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    #authors {
        background-color: var(--teal);
        color: white;
        height: 500px;
        width: 30%;
    }
    
    #address {
        background-color: var(--light);
        height: 500px;
        width: 30%;
        text-align: center;
    }
    
    #new-reader,
    #status {
        height: 250px;
        width: 40%;
    }
    
    #new-reader {
        background-color: var(--light);
    }
    
    #status {
        background-color: var(--mint);
    }
    
    main {
        display: flex;
        flex-flow: column wrap;
        height: 500px;
    }
    
    h3 {
        text-align: center;
    }
    Podgląd strony po tym kroku (biblioteka.php)
  32. Krok 32: Obraz: wysokość, kropkowane obramowanie i zaokrąglenie

    Kryteria: , ,

    R.4.4 Ustawiono wysokość panelu lewego i środkowego 500px, obu prawych 250px, selektora img 150px

    R.4.8 Ustawiono obramowanie obrazu 1px linią kropkowaną koloru #004D40

    R.4.9 Ustawiono zaokrąglenie rogów dla obramowania obrazu na 5px (np. border-radius)

    ok. 2 min
    Polecenie z arkusza

    Dla selektora obrazu: marginesy wewnętrzne 5 px, wysokość 150 px, obramowanie linią kropkowaną o grubości 1 px i kolorze #004D40 zaokrągloną na rogach o 5 px

    height: 150px - szerokość przeliczy się w tej samej proporcji (około 191 px). dotted to linia kropkowana, border-radius zaokrągla rogi ramki. Wszystkie wysokości z arkusza są ustawione (R.4.4).

    style.css CSS zmienione: 7 linii
    :root {
        --dark: #004D40;
        --teal: #80CBC4;
        --light: #E0F2F1;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: Verdana, sans-serif;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    #authors {
        background-color: var(--teal);
        color: white;
        height: 500px;
        width: 30%;
    }
    
    #address {
        background-color: var(--light);
        height: 500px;
        width: 30%;
        text-align: center;
    }
    
    #new-reader,
    #status {
        height: 250px;
        width: 40%;
    }
    
    #new-reader {
        background-color: var(--light);
    }
    
    #status {
        background-color: var(--mint);
    }
    
    main {
        display: flex;
        flex-flow: column wrap;
        height: 500px;
    }
    
    h3 {
        text-align: center;
    }
    
    img {
        padding: 5px;
        height: 150px;
        border: 1px dotted var(--dark);
        border-radius: 5px;
    }
    Podgląd strony po tym kroku (biblioteka.php)
  33. Krok 33: Pola edycyjne: marginesy

    Kryteria:

    R.4.7 Ustawiono marginesy wewnętrzne (padding) 10px dla banera i stopki, 5px dla obrazu oraz marginesy zewnętrzne (margin) 10px dla selektora input

    ok. 1 min
    Polecenie z arkusza

    Dla selektora pola edycyjnego: marginesy zewnętrzne 10 px

    Selektor znacznika input - odstęp 10 px wokół każdego pola. Z padding banera, stopki i obrazu to komplet odstępów z arkusza (R.4.7).

    style.css CSS zmienione: 4 linie
    :root {
        --dark: #004D40;
        --teal: #80CBC4;
        --light: #E0F2F1;
        --mint: #B2DFDB;
    }
    
    * {
        font-family: Verdana, sans-serif;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    #authors {
        background-color: var(--teal);
        color: white;
        height: 500px;
        width: 30%;
    }
    
    #address {
        background-color: var(--light);
        height: 500px;
        width: 30%;
        text-align: center;
    }
    
    #new-reader,
    #status {
        height: 250px;
        width: 40%;
    }
    
    #new-reader {
        background-color: var(--light);
    }
    
    #status {
        background-color: var(--mint);
    }
    
    main {
        display: flex;
        flex-flow: column wrap;
        height: 500px;
    }
    
    h3 {
        text-align: center;
    }
    
    img {
        padding: 5px;
        height: 150px;
        border: 1px dotted var(--dark);
        border-radius: 5px;
    }
    
    input {
        margin: 10px;
    }
    Podgląd strony po tym kroku (biblioteka.php)
  34. Etap 4: Skrypty PHP

    Kroki 34-41 · po tym etapie masz 37 z 37 kryteriów (pkt)

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

    Kryteria: ,

    R.5.1 Zastosowano funkcję realizującą połączenie z serwerem bazy danych na localhost, użytkownik root, bez hasła, baza danych biblioteka

    R.5.2 Ostatnią operacją na bazie danych jest jej zamknięcie

    ok. 4 min
    Polecenie z arkusza

    Wymagania dotyczące skryptu: Napisany w języku PHP, umieszczony w pliku biblioteka.php. Skrypt łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie biblioteka. Na końcu działania skrypt zamyka połączenie z serwerem

    Całą pracę z bazą strona wykona przed HTML-em, w jednym bloku PHP na samej górze biblioteka.php - w jednym miejscu, a połączenie zamyka się, zanim przeglądarka dostanie pierwszy znacznik. Ten układ przyda się też za chwilę: po zapisie czytelnika strona wyśle przeglądarce przekierowanie, a to da się zrobić tylko przed wypisaniem czegokolwiek.

    • new PDO(...) - serwer localhost, baza biblioteka, kodowanie utf8mb4, 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 - połączenie PDO zamyka się, gdy znika ostatnie odwołanie do obiektu. Stoi na końcu bloku, więc pozostanie ostatnią operacją na bazie (R.5.2),
    • try / catch (PDOException ...) - gdy baza nie działa, uczeń zobaczy zwykłe zdanie, a nie komunikat PHP ze szczegółami 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.

    biblioteka.php PHP zmienione: 12 linii
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biblioteka;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ę połączyć z bazą. 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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="code">symbol: </label>
                        <input type="number" id="code" name="code" required min="0" max="999999">
                    </div>
                    <button type="submit">DODAJ</button>
                </form>
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
            <p>Projekt strony: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  35. Krok 35: Skrypt 1: zapytanie 1

    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 dziesięć wierszy z kluczami imie i nazwisko. W catch lista dostaje wartość pustą, żeby HTML niżej nie trafił na niezdefiniowaną zmienną. Na stronie jeszcze nic nie widać.

    biblioteka.php PHP zmienione: 4 linie
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - autorzy po nazwisku
        $authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $authors = [];
        $databaseError = 'Nie udało się połączyć z bazą. 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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="code">symbol: </label>
                        <input type="number" id="code" name="code" required min="0" max="999999">
                    </div>
                    <button type="submit">DODAJ</button>
                </form>
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
            <p>Projekt strony: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  36. Krok 36: Skrypt 1: autorzy jako elementy listy

    Kryteria: , ,

    R.2.6 W bloku lewym umieszczono jedną listę numerowaną, w kodzie HTML lub skrypcie, zastosowano znaczniki <ol>, <li>

    R.5.3 Skrypt 1 wyświetla imiona i nazwiska autorów zwrócone zapytaniem 1

    R.5.4 Skrypt 1 generuje rekordy jako elementy listy z wykorzystaniem pętli

    ok. 3 min
    Polecenie z arkusza

    Pobrane zapytaniem imiona i nazwiska autorów wypisywane są jako elementy listy numerowanej (uporządkowanej)

    • foreach z dwukropkiem i endforeach - pętla wypisuje jeden <li> na autora (R.5.4); numery 1-10 dopisuje przeglądarka, bo elementy stoją w <ol>,
    • pola po nazwach kolumn ($author['imie']), nie po numerach,
    • funkcja e() (na górze pliku) przepuszcza każdy tekst z bazy przez htmlspecialchars - znak < albo cudzysłów w danych nie rozbije strony. <?= ... ?> to skrót od <?php echo ... ?>.

    Lista z obrazu 2, od Jana Brzechwy do Magdaleny Witkiewicz (R.5.3, R.2.6).

    biblioteka.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=biblioteka;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - autorzy po nazwisku
        $authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $authors = [];
        $databaseError = 'Nie udało się połączyć z bazą. 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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
    <?php foreach ($authors as $author): ?>
                    <li><?= e($author['imie']) ?> <?= e($author['nazwisko']) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="code">symbol: </label>
                        <input type="number" id="code" name="code" required min="0" max="999999">
                    </div>
                    <button type="submit">DODAJ</button>
                </form>
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
            <p>Projekt strony: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  37. Krok 37: Skrypt 2: dane z formularza

    Kryteria:

    R.5.6 Skrypt 2 zawiera instrukcję pobierającą dane z tablicy globalnej odpowiadającej zastosowanej metodzie formularza ($_POST, $_GET, $_REQUEST)

    ok. 4 min
    Polecenie z arkusza

    Skrypt 2: Pobiera dane z formularza

    • $_SERVER['REQUEST_METHOD'] === 'POST' - czy strona przyszła z wysłanego formularza, czy to zwykłe wejście,
    • $_POST['name'] ?? '' - wartość z tablicy globalnej $_POST (R.5.6), a gdy formularza nie wysłano - pusty napis, bez ostrzeżenia „Undefined array key”,
    • trim usuwa spacje z brzegów - sama spacja w polu to też „nic”,
    • $isValid - imię i nazwisko niepuste, a symbol to od 1 do 6 cyfr (preg_match z wyrażeniem ^[0-9]{1,6}$), bo tyle mieści kolumna kod typu varchar(6). required w formularzu tego nie gwarantuje - żądanie da się wysłać z pominięciem przeglądarki.

    Od tego kroku podgląd pokazuje stronę po wysłaniu formularza z danymi Anny Nowakowskiej; na razie nic się nie zmienia.

    biblioteka.php PHP zmienione: 7 linii
    <?php
    // Skrypt 2: dane z formularza
    $formSent = $_SERVER['REQUEST_METHOD'] === 'POST';
    $name = trim($_POST['name'] ?? '');
    $surname = trim($_POST['surname'] ?? '');
    $code = trim($_POST['code'] ?? '');
    $isValid = $name !== '' && $surname !== '' && preg_match('/^[0-9]{1,6}$/', $code) === 1;
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - autorzy po nazwisku
        $authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $authors = [];
        $databaseError = 'Nie udało się połączyć z bazą. 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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
    <?php foreach ($authors as $author): ?>
                    <li><?= e($author['imie']) ?> <?= e($author['nazwisko']) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="code">symbol: </label>
                        <input type="number" id="code" name="code" required min="0" max="999999">
                    </div>
                    <button type="submit">DODAJ</button>
                </form>
            </section>
            <section id="status">
            </section>
        </main>
        <footer>
            <p>Projekt strony: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  38. Krok 38: Skrypt 2: napis o dodanym czytelniku

    Kryteria:

    R.5.5 Skrypt 2 wyświetla napis: "Czytelnik <imie> <nazwisko> został(a) dodany do bazy danych" (gdzie pola <> są pobrane z formularza)

    ok. 2 min
    Polecenie z arkusza

    Wyświetla napis „Czytelnik <imie> <nazwisko> został(a) dodany do bazy danych” (gdzie pola <> są pobrane z formularza)

    Zmienna $addedReader - imię i nazwisko, gdy formularz wysłano z poprawnymi danymi, a w przeciwnym razie null. W panelu prawym dolnym akapit z napisem z arkusza, imię i nazwisko przez e() - tekst od użytkownika nie wstrzyknie do strony kodu HTML. Podgląd: „Czytelnik Anna Nowakowska został(a) dodany do bazy danych”, jak na obrazie 2 (R.5.5).

    biblioteka.php PHP zmienione: 4 linie
    <?php
    // Skrypt 2: dane z formularza
    $formSent = $_SERVER['REQUEST_METHOD'] === 'POST';
    $name = trim($_POST['name'] ?? '');
    $surname = trim($_POST['surname'] ?? '');
    $code = trim($_POST['code'] ?? '');
    $isValid = $name !== '' && $surname !== '' && preg_match('/^[0-9]{1,6}$/', $code) === 1;
    $addedReader = $formSent && $isValid ? "$name $surname" : null;
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - autorzy po nazwisku
        $authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $authors = [];
        $databaseError = 'Nie udało się połączyć z bazą. 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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
    <?php foreach ($authors as $author): ?>
                    <li><?= e($author['imie']) ?> <?= e($author['nazwisko']) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="code">symbol: </label>
                        <input type="number" id="code" name="code" required min="0" max="999999">
                    </div>
                    <button type="submit">DODAJ</button>
                </form>
            </section>
            <section id="status">
    <?php if ($addedReader !== null): ?>
                <p>Czytelnik <?= e($addedReader) ?> został(a) dodany do bazy danych</p>
    <?php endif; ?>
            </section>
        </main>
        <footer>
            <p>Projekt strony: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (biblioteka.php)
  39. Krok 39: Skrypt 2: zapytanie 2 z danymi z formularza

    Kryteria:

    R.5.7 Skrypt 2 dodaje do tabeli czytelnicy nowy rekord, zawierający dane pobrane z formularza. Należy sprawdzić w phpMyAdmin

    ok. 3 min
    Polecenie z arkusza

    Wysyła do bazy danych zmodyfikowane zapytanie 2 tak, że wstawia do bazy danych dane z formularza

    Zapytanie przygotowane: w miejscu danych stoją parametry :name, :surname, :code, a wartości podaje execute([...]). Baza dostaje treść zapytania i dane osobno, więc tekst z formularza nigdy nie stanie się częścią SQL - nazwisko O'Hara z apostrofem zapisuje się poprawnie (sprawdzone), zamiast przerwać zapytanie (R.5.7).

    • zapytanie wykonuje się tylko dla poprawnych danych (if ($formSent && $isValid)),
    • $addedReader dostaje imię i nazwisko dopiero po zapisie - napis o dodaniu pojawia się tylko wtedy, gdy czytelnik naprawdę jest w bazie,
    • $statement = null zwalnia zapytanie przed zamknięciem połączenia.

    Sprawdzenie, jak każe klucz: w phpMyAdmin tabela czytelnicy ma nowy wiersz Anna Nowakowska, 145322.

    Typowe błędy

    Tabela czytelnicy z pliku arkusza ma zestaw znaków latin1 - polskie litery z formularza zapisują się jako „?” (sprawdzone: „Łucja Żak” → „?ucja ?ak”), choć napis na stronie jest poprawny. Na egzaminie nie zmieniaj struktury tabel z arkusza; poza egzaminem naprawia to ALTER TABLE czytelnicy CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;.

    biblioteka.php PHP zmienione: 9 linii
    <?php
    // Skrypt 2: dane z formularza
    $formSent = $_SERVER['REQUEST_METHOD'] === 'POST';
    $name = trim($_POST['name'] ?? '');
    $surname = trim($_POST['surname'] ?? '');
    $code = trim($_POST['code'] ?? '');
    $isValid = $name !== '' && $surname !== '' && preg_match('/^[0-9]{1,6}$/', $code) === 1;
    $addedReader = null;
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - autorzy po nazwisku
        $authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();
    
        // Skrypt 2: zapytanie 2 - nowy czytelnik z formularza
        if ($formSent && $isValid) {
            $statement = $pdo->prepare('INSERT INTO czytelnicy (imie, nazwisko, kod) VALUES (:name, :surname, :code)');
            $statement->execute(['name' => $name, 'surname' => $surname, 'code' => $code]);
            $statement = null;
            $addedReader = "$name $surname";
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $authors = [];
        $databaseError = 'Nie udało się połączyć z bazą. 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>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
    <?php foreach ($authors as $author): ?>
                    <li><?= e($author['imie']) ?> <?= e($author['nazwisko']) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="code">symbol: </label>
                        <input type="number" id="code" name="code" required min="0" max="999999">
                    </div>
                    <button type="submit">DODAJ</button>
                </form>
            </section>
            <section id="status">
    <?php if ($addedReader !== null): ?>
                <p>Czytelnik <?= e($addedReader) ?> został(a) dodany do bazy danych</p>
    <?php endif; ?>
            </section>
        </main>
        <footer>
            <p>Projekt strony: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  40. Krok 40: Odświeżenie strony nie dodaje czytelnika drugi raz (PRG)

    ok. 4 min

    Dodatek spoza arkusza - klucz go nie ocenia. Po wysłaniu formularza strona jest wynikiem żądania POST: odświeżenie (F5) wysyła formularz jeszcze raz i ten sam czytelnik trafia do bazy drugi raz. Wzorzec PRG (Post/Redirect/Get) to usuwa:

    • po udanym zapisie strona zapamiętuje imię i nazwisko w sesji ($_SESSION, wymaga session_start() na samym początku pliku),
    • header('Location: biblioteka.php') i exit - przeglądarka dostaje polecenie, żeby wczytać stronę jeszcze raz, tym razem zwykłym żądaniem GET. exit jest konieczne: header tylko ustawia nagłówek odpowiedzi i nie zatrzymuje skryptu,
    • nowe wejście odczytuje napis z sesji i od razu go usuwa (unset) - pokazuje się raz.

    Połączenie jest już zamknięte, zanim padnie przekierowanie, więc $pdo = null dalej jest ostatnią operacją na bazie. Sprawdzone na prawdziwym PHP: wysłanie formularza daje odpowiedź 302, po niej napis o dodaniu Anny Nowakowskiej, a kolejne odświeżenie - stronę bez napisu i bez drugiego wiersza w tabeli czytelnicy.

    biblioteka.php PHP zmienione: 11 linii
    <?php
    session_start();
    
    // Skrypt 2: dane z formularza
    $formSent = $_SERVER['REQUEST_METHOD'] === 'POST';
    $name = trim($_POST['name'] ?? '');
    $surname = trim($_POST['surname'] ?? '');
    $code = trim($_POST['code'] ?? '');
    $isValid = $name !== '' && $surname !== '' && preg_match('/^[0-9]{1,6}$/', $code) === 1;
    $addedReader = null;
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - autorzy po nazwisku
        $authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();
    
        // Skrypt 2: zapytanie 2 - nowy czytelnik z formularza
        if ($formSent && $isValid) {
            $statement = $pdo->prepare('INSERT INTO czytelnicy (imie, nazwisko, kod) VALUES (:name, :surname, :code)');
            $statement->execute(['name' => $name, 'surname' => $surname, 'code' => $code]);
            $statement = null;
            $addedReader = "$name $surname";
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $authors = [];
        $databaseError = 'Nie udało się połączyć z bazą. Spróbuj odświeżyć stronę za chwilę.';
    }
    
    // PRG: po zapisie przekierowanie na tę samą stronę - odświeżenie nie wyśle formularza drugi raz
    if ($addedReader !== null) {
        $_SESSION['addedReader'] = $addedReader;
        header('Location: biblioteka.php');
        exit;
    }
    $addedReader = $_SESSION['addedReader'] ?? null;
    unset($_SESSION['addedReader']);
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
    <?php foreach ($authors as $author): ?>
                    <li><?= e($author['imie']) ?> <?= e($author['nazwisko']) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="code">symbol: </label>
                        <input type="number" id="code" name="code" required min="0" max="999999">
                    </div>
                    <button type="submit">DODAJ</button>
                </form>
            </section>
            <section id="status">
    <?php if ($addedReader !== null): ?>
                <p>Czytelnik <?= e($addedReader) ?> został(a) dodany do bazy danych</p>
    <?php endif; ?>
            </section>
        </main>
        <footer>
            <p>Projekt strony: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  41. Krok 41: Niepełne dane i awaria bazy: komunikaty

    ok. 3 min

    Dodatek spoza arkusza - klucz go nie ocenia. Panel prawy dolny odpowiada teraz także w sytuacjach, których obraz 2 nie pokazuje:

    • niepełne albo złe dane (sama spacja w imieniu, puste nazwisko, symbol „1234567” albo „-5”, wysłane z pominięciem formularza) - „Wpisz imię, nazwisko i symbol (od 1 do 6 cyfr).”, a do bazy nic nie trafia,
    • awaria bazy (catch) - komunikat z role="alert" (czytnik ekranu przeczyta go od razu), a reszta strony działa dalej.

    Sprawdzone na prawdziwym PHP 8.2: cztery błędne zestawy danych i złe hasło do bazy - za każdym razem cała strona, bez ostrzeżeń PHP i bez nowych wierszy w tabeli czytelnicy.

    biblioteka.php PHP zmienione: 5 linii
    <?php
    session_start();
    
    // Skrypt 2: dane z formularza
    $formSent = $_SERVER['REQUEST_METHOD'] === 'POST';
    $name = trim($_POST['name'] ?? '');
    $surname = trim($_POST['surname'] ?? '');
    $code = trim($_POST['code'] ?? '');
    $isValid = $name !== '' && $surname !== '' && preg_match('/^[0-9]{1,6}$/', $code) === 1;
    $addedReader = null;
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=biblioteka;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - autorzy po nazwisku
        $authors = $pdo->query('SELECT imie, nazwisko FROM autorzy ORDER BY nazwisko')->fetchAll();
    
        // Skrypt 2: zapytanie 2 - nowy czytelnik z formularza
        if ($formSent && $isValid) {
            $statement = $pdo->prepare('INSERT INTO czytelnicy (imie, nazwisko, kod) VALUES (:name, :surname, :code)');
            $statement->execute(['name' => $name, 'surname' => $surname, 'code' => $code]);
            $statement = null;
            $addedReader = "$name $surname";
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $authors = [];
        $databaseError = 'Nie udało się połączyć z bazą. Spróbuj odświeżyć stronę za chwilę.';
    }
    
    // PRG: po zapisie przekierowanie na tę samą stronę - odświeżenie nie wyśle formularza drugi raz
    if ($addedReader !== null) {
        $_SESSION['addedReader'] = $addedReader;
        header('Location: biblioteka.php');
        exit;
    }
    $addedReader = $_SESSION['addedReader'] ?? null;
    unset($_SESSION['addedReader']);
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Biblioteka publiczna</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <h1>Biblioteka w Książkowicach Wielkich</h1>
        </header>
        <main>
            <section id="authors">
                <h3>Polecamy dzieła autorów:</h3>
                <ol>
    <?php foreach ($authors as $author): ?>
                    <li><?= e($author['imie']) ?> <?= e($author['nazwisko']) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="address">
                <h3>ul. Czytelnicza 25, Książkowice&nbsp;Wielkie</h3>
                <p><a href="mailto:[email protected]">Napisz do nas</a></p>
                <img src="biblioteka.png" alt="książki">
            </section>
            <section id="new-reader">
                <h3>Dodaj czytelnika</h3>
                <form method="post">
                    <div>
                        <label for="name">imię: </label>
                        <input type="text" id="name" name="name" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">nazwisko: </label>
                        <input type="text" id="surname" name="surname" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="code">symbol: </label>
                        <input type="number" id="code" name="code" required min="0" max="999999">
                    </div>
                    <button type="submit">DODAJ</button>
                </form>
            </section>
            <section id="status">
    <?php if (isset($databaseError)): ?>
                <p role="alert"><?= e($databaseError) ?></p>
    <?php elseif ($formSent && !$isValid): ?>
                <p role="alert">Wpisz imię, nazwisko i symbol (od 1 do 6 cyfr).</p>
    <?php endif; ?>
    <?php if ($addedReader !== null): ?>
                <p>Czytelnik <?= e($addedReader) ?> został(a) dodany do bazy danych</p>
    <?php endif; ?>
            </section>
        </main>
        <footer>
            <p>Projekt strony: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

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

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

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

    ok. 3 min

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

    wersja podstawowawersja nowoczesna
    układfloat: left paneli, prawy dolny „opada” pod prawy górny, clear stopki<main> z flex-flow: column wrap i wysokością 500 px
    formularz<label> wokół pola, <br>label for, wiersze w <div>, required, autocomplete, max
    zapytania 3 i 4pełne nazwy tabelaliasy k, kat, w
    bibliotekamysqli (tabela 1 arkusza)PDO z wyjątkami
    gdzie są zapytaniaskrypty w liście i w panelu prawym dolnymjeden blok na górze biblioteka.php
    dane z formularza$_POST wprost do zapytania$_POST + trim + sprawdzenie symbolu
    INSERTdane wklejone w treść zapytaniaparametry :name, :surname, :code
    odświeżenie po zapisiedodaje czytelnika drugi razPRG - przekierowanie, napis z sesji
    wypisywaniedane z formularza wproste() - htmlspecialchars

    W prawdziwym projekcie przy formularzu zapisującym dane dochodzi jeszcze token CSRF - losowy napis z sesji w ukrytym polu, który potwierdza, że formularz wysłano z tej strony, a nie z cudzej. Klucz tego nie wymaga. Dane logowania do bazy trzyma się zwykle w osobnym pliku (np. config.php), ale arkusz każe umieścić skrypt w pliku biblioteka.php.

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie zapisuje pustych ani podwójnych czytelników i nie wywraca się na apostrofie. Obie biblioteki klucz przyjmuje.

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

  43. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz w folderze z numerem zdającego 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 sprawdzana była strona. W folderze z numerem zdającego: biblioteka.php, biblioteka.png, import.jpg, kwerenda1.jpg - kwerenda4.jpg, kwerendy.txt, przeglądarka.txt i style.css. 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 38.

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.