Przejdź do głównej treści
INF.03-05-26.06-SGCzerwiec 2026 · 53 kroki · ok. 99 min · 40 kryteriów z klucza

Rozwiązanie krok po kroku: Witryna sklepu z antykami (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza antyki z czterema zapytaniami, kadr czerwonego fotela, strona index.php z trzema kategoriami mebli, przyciskiem „KUP” i koszykiem - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • siatka CSS: blok sekcji i blok boczny w grid-template-columns: 70% 30%, kolor LightCoral w zmiennej CSS,
  • PHP na PDO, logika przed HTML-em: na początku pliku obsługa zakupu i wszystkie odczyty z bazy przez małe funkcje z zapytaniami przygotowanymi, a niżej szablon strony, który tylko wypisuje gotowe tablice,
  • jeden szablon mebla w funkcji showFurniture() wołanej pod każdym nagłówkiem zamiast trzech kopii Skryptu 1,
  • bezpieczeństwo: numer mebla sprawdzony przez filter_input i w bazie, przekierowanie po zakupie (odświeżenie strony nie kupuje drugi raz), htmlspecialchars przy każdym wypisaniu.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i zdjęcia pochodzą z prawdziwego archiwum pliki5, a podgląd kroków z PHP to wynik prawdziwego uruchomienia strony w PHP 8.2 na bazie z arkusza - z pustym koszykiem jak na ilustracji 2, a od kroków z koszykiem w stanie z ilustracji 6.

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

    Krok 1: Baza danych antyki

    ok. 2 min
    Polecenie z arkusza

    Utwórz bazę danych o nazwie antyki, z zestawem polskich znaków (np. utf8_polish_ci)

    Archiwum pliki5.7z rozpakuj hasłem Twoje@ntyki&; foldery sofy, fotele i komody przenieś w całości - arkusz zaznacza, że obrazy mają zostać w folderach. W phpMyAdmin Nowa → antyki, porównywanie utf8_polish_ci:

    CREATE DATABASE antyki CHARACTER SET utf8 COLLATE utf8_polish_ci;
    
  2. Krok 2: Import tabel z pliku antyki.sql

    ok. 2 min
    Polecenie z arkusza

    Do utworzonej bazy zaimportuj tabele z pliku antyki.sql

    Baza antyki → Import → antyki.sql. Trzy tabele z ilustracji 1: klienci, meble (11 mebli w trzech kategoriach) i pusta zakupy - tabela pośrednicząca relacji m:n.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych antyki - plik import.png

    ok. 2 min
    Polecenie z arkusza

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

    Cały ekran z paskiem zadań, zapis jako import.png.

    Typowe błędy

    Wykadrowany zrzut nie jest oceniany.

  4. Krok 4: Zapytanie 1: sofy

    Kryteria: ,

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

    R.1.3 Wykonane zapytanie 1 wybierające jedynie pola idMeble, nazwa, plik, styl, cena i opis z tabeli meble dla kategorii 1. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = '1'; oraz na zrzucie widoczne są dokładnie 3 rekordy z id 1, 2, 3

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pola idMeble, nazwa, plik, styl, cena i opis z tabeli meble dla kategorii 1

    Sześć pól i warunek kategoria = 1, każda klauzula w osobnej linii. Wynik: sofy o identyfikatorach 1, 2, 3. W skrypcie to samo zapytanie dostanie zamiast 1 parametr - i obsłuży wszystkie trzy kategorie. Zapytanie do kwerendy.txt, zrzut kw1.png.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT idMeble, nazwa, plik, styl, cena, opis
    FROM meble
    WHERE kategoria = 1;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: zakup barokowej komody

    Kryteria:

    R.1.4 Wykonane zapytanie 2 wstawiające rekord do tabeli zakupy o wartościach identyfikator klienta: 1, identyfikator mebla: 6, jedna sztuka. Klucz główny nadawany automatycznie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO `zakupy`(`idKlienci`, `idMeble`, `sztuk`) VALUES ('1','6', '1'); (klucz główny pominięty lub NULL) oraz na zrzucie widoczny jest komunikat o pomyślnym dodaniu rekordu lub rekord

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: wstawiające rekord do tabeli zakupy o wartościach identyfikator klienta: 1, identyfikator mebla: 6, jedna sztuka. Klucz główny nadawany automatycznie

    Lista kolumn pomija klucz id - nada go AUTO_INCREMENT. Liczby bez apostrofów, bo kolumny są liczbowe. Zrzut z komunikatem albo z tabelą: kw2.png.

    Typowe błędy

    Klucz wpisany ręcznie przeczy wymaganiu „klucz główny nadawany automatycznie”.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT idMeble, nazwa, plik, styl, cena, opis
    FROM meble
    WHERE kategoria = 1;
    
    -- Zapytanie 2
    INSERT INTO zakupy (idKlienci, idMeble, sztuk)
    VALUES (1, 6, 1);
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: zakupy klienta 1

    Kryteria:

    R.1.5 Wykonane zapytanie 3 wybierające jedynie nazwę i cenę mebli, które zostały zakupione przez klienta o identyfikatorze równym 1. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, cena FROM zakupy JOIN meble USING(idMeble) WHERE idKlienci=1; (lub porównanie kluczy po ON lub WHERE ) oraz na zrzucie widoczne są tylko rekordy dla idKlienci = 1, liczba rekordów zależna od zawartości tabeli zakupy

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie nazwę i cenę mebli, które zostały zakupione przez klienta o identyfikatorze równym 1. Należy posłużyć się relacją

    Aliasy tabel (z, m) i warunek złączenia w ON: klucz obcy z.idMeble równa się kluczowi głównemu m.idMeble. Przedrostki są tu konieczne, bo kolumna idMeble jest w obu tabelach - USING(idMeble) z klucza robi to samo krócej, ON działa także wtedy, gdy kolumny nazywają się różnie. Wynik po zapytaniu 2: Barokowa komoda, 4000. Zrzut kw3.png.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT idMeble, nazwa, plik, styl, cena, opis
    FROM meble
    WHERE kategoria = 1;
    
    -- Zapytanie 2
    INSERT INTO zakupy (idKlienci, idMeble, sztuk)
    VALUES (1, 6, 1);
    
    -- Zapytanie 3
    SELECT m.nazwa, m.cena
    FROM zakupy AS z
    JOIN meble AS m ON m.idMeble = z.idMeble
    WHERE z.idKlienci = 1;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: mebel i kupujący (relacja m:n)

    Kryteria:

    R.1.6 Wykonane zapytanie 4 wybierające jedynie nazwę mebla oraz odpowiadające mu imię i nazwisko kupującego klienta. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, imie, nazwisko FROM zakupy JOIN meble USING(idMeble) JOIN klienci USING(idKlienci); (lub porównanie kluczy po ON lub WHERE ) oraz na zrzucie widoczne są rekordy w zależności od zawartości tabeli zakupy

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie nazwę mebla oraz odpowiadające mu imię i nazwisko kupującego klienta. Należy posłużyć się relacją m:n

    Tabela pośrednicząca zakupy w środku i dwa złączenia: z meblami i z klientami. Wynik: Barokowa komoda, Anna Kowalska. Zrzut kw4.png.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT idMeble, nazwa, plik, styl, cena, opis
    FROM meble
    WHERE kategoria = 1;
    
    -- Zapytanie 2
    INSERT INTO zakupy (idKlienci, idMeble, sztuk)
    VALUES (1, 6, 1);
    
    -- Zapytanie 3
    SELECT m.nazwa, m.cena
    FROM zakupy AS z
    JOIN meble AS m ON m.idMeble = z.idMeble
    WHERE z.idKlienci = 1;
    
    -- Zapytanie 4
    SELECT m.nazwa, k.imie, k.nazwisko
    FROM zakupy AS z
    JOIN meble AS m ON m.idMeble = z.idMeble
    JOIN klienci AS k ON k.idKlienci = z.idKlienci;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

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

    Krok 8: Grafika f3.png: kadr czerwonego fotela

    ok. 3 min
    Polecenie z arkusza

    Cechy grafiki f3.png: Grafikę należy skadrować tak, aby zawierała tylko czerwony fotel – trzeci od lewej strony

    W GIMP-ie zaznaczenie prostokątne wokół trzeciego, czerwonego fotela, Obraz → Przytnij do zaznaczenia, potem Obraz → Przytnij do zawartości - odcina przezroczysty margines.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Grafika f3.png: wysokość 640 px i płótno 524 px

    Kryteria:

    R.2.1 Grafika f3.png ma szerokość 524 px i wysokość 640 px i zawiera jedynie czerwony fotel

    ok. 4 min
    Polecenie z arkusza

    Po skadrowaniu należy zmienić wymiary grafiki na 524 px szerokości i 640 px wysokości. Proporcje muszą być zachowane, fotel musi znajdować się na środku grafiki (np. poprzez przeskalowanie grafiki z zachowaniem proporcji do wysokości 640 px, a następnie zmianę wymiarów płótna do szerokości 524 px z wyśrodkowaniem)

    Skaluj obraz do wysokości 640 px przy zamkniętym łańcuchu, potem Rozmiar płótna - szerokość 524 px i Wyśrodkuj. Warstwa do rozmiaru obrazu i eksport z nadpisaniem fotele/f3.png.

    Typowe błędy

    Skalowanie od razu do 524 × 640 przy rozpiętym łańcuchu rozciąga fotel.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 9
  10. Krok 10: Plik index.php w HTML5

    ok. 1 min
    Polecenie z arkusza

    Składa się ze strony o nazwie index.php. Zapisana w języku HTML5

    Plik index.php w folderze z rezultatami, obok folderów z obrazami. Na razie sam szkielet HTML5; kod PHP stanie później przed <!DOCTYPE html>.

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

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Zadeklarowany polski język zawartości witryny

    lang="pl" w <html>.

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

    ok. 1 min
    Polecenie z arkusza

    Jawnie zastosowany właściwy standard kodowania polskich znaków

    <meta charset="UTF-8">. To samo kodowanie ustawi połączenie z bazą (charset=utf8mb4), więc polskie litery przejdą bez zmian: baza → PHP → przeglądarka.

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

    Kryteria:

    R.2.3 W sekcji head znajduje się jawnie podany standard kodowania polskich znaków i tytuł strony "Sprzedaż antyków"

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony „Sprzedaż antyków”

    <title> w <head>; obok <meta name="viewport"> dla telefonów - arkusz tego nie wymaga.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  14. Krok 14: Podpięcie arkusza stylów

    Kryteria:

    R.3.2 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.css formatowanie CSS pochodzi jedynie z tego arkusza

    ok. 1 min
    Polecenie z arkusza

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

    <link rel="stylesheet" href="styl.css">.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Bloki semantyczne

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

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

    header, main z section i aside, footer - lista z klucza R.2.4. Obok siebie ustawi je siatka CSS.

    index.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
        </header>
        <main>
            <section>
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Nagłówek strony

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego: Nagłówek pierwszego stopnia o treści: „Najlepsze antyki w mieście”

    <h1> w <header>.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  17. Krok 17: Sekcja: nagłówki trzech kategorii

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku sekcji: Nagłówek drugiego stopnia o treści: „- Sofy -”. Nagłówek drugiego stopnia o treści: „- Fotele -”. Nagłówek drugiego stopnia o treści: „- Komody -”

    Nagłówki zostają w HTML-u, a nie w tablicy PHP: są stałym tekstem strony i egzaminator znajdzie je w kodzie nawet wtedy, gdy PHP nie zadziała.

    index.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
                <h2>- Fotele -</h2>
                <h2>- Komody -</h2>
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Blok boczny: koszyk

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku bocznego: Nagłówek drugiego stopnia o treści: „Koszyk”. Paragraf o treści „Zalogowano: Anna Kowalska”

    <h2> i akapit; pod nimi stanie lista zakupów.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
                <h2>- Fotele -</h2>
                <h2>- Komody -</h2>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  19. Krok 19: Stopka

    Kryteria:

    R.2.5 W bloku nagłówkowym znajduje się <h1>: "Najlepsze antyki w mieście", w bloku sekcji <h2>: "- Sofy -", "- Fotele -" i "- Komody -", w bloku bocznym <h2>: "Koszyk" i <p> "Zalogowano: Anna Kowalska" oraz <p> z numerem zdającego w stopce

    ok. 1 min
    Polecenie z arkusza

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

    Na egzaminie po dwukropku wpisujesz swój numer zdającego (PESEL); tutaj stoi imię i nazwisko autora rozwiązania.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
                <h2>- Fotele -</h2>
                <h2>- Komody -</h2>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  20. Etap 3: Styl CSS

    Kroki 20-33 · po tym etapie masz 23 z 40 kryteriów (pkt)

    Krok 20: Zmienna z kolorem i krój czcionki

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Domyślnie, dla wszystkich selektorów: krój czcionki Georgia

    LightCoral występuje w arkuszu dwa razy (blok mebla, przycisk), więc trafia do zmiennej CSS --coral w :root; odwołanie var(--coral). Potem selektor * z krojem.

    styl.css CSS nowy plik
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    Podgląd strony po tym kroku (index.php)
  21. Krok 21: Ciało strony

    Kryteria:

    R.4.2 kolor tła: selektor body - MistyRose

    ok. 1 min
    Polecenie z arkusza

    Dla selektora ciała strony: kolor tła MistyRose

    Selektor znacznika body - tak każe uwaga arkusza.

    styl.css CSS zmienione: 4 linie
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Nagłówek i stopka

    ok. 1 min
    Polecenie z arkusza

    Dla bloku nagłówkowego i stopki: wyrównanie tekstu do środka

    Jedna reguła dla dwóch selektorów.

    styl.css CSS zmienione: 5 linii
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    
    header,
    footer {
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  23. Krok 23: Siatka: sekcja 70%, blok boczny 30%

    Kryteria:

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 4 w arkuszu egzaminacyjnym (prawidłowo zastosowane właściwości CSS decydujące o układzie bloków na stronie, np. float i clear albo display flex albo grid albo position; żaden blok nie zachodzi na inne bloki - sprawdzić w inspektorze) nie należy brać pod uwagę bloków wewnątrz sekcji

    ok. 2 min
    Polecenie z arkusza

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

    Blok główny jako siatka z dwiema kolumnami 70% i 30% - to szerokości bloku sekcji i bloku bocznego z arkusza zapisane w jednym miejscu. Klucz R.4.4 dopuszcza je w grid-template-columns, a R.3.1 układ na grid. Sekcja i blok boczny same trafiają do kolejnych kolumn, a stopka zostaje pod blokiem głównym bez clear.

    styl.css CSS zmienione: 5 linii
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    
    header,
    footer {
        text-align: center;
    }
    
    main {
        display: grid;
        grid-template-columns: 70% 30%;
    }
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Blok sekcji: wysokość i suwaki zawsze widoczne

    Kryteria:

    R.4.5 paski przewijania: sekcja - scroll

    ok. 2 min
    Polecenie z arkusza

    Dla bloku sekcji: szerokość 70%, wysokość 700 px, suwaki do przewijania zawartości bloku zawsze widoczne

    Szerokość 70% daje już kolumna siatki. „Zawsze widoczne” to overflow: scroll - suwaki są nawet przy treści, która się mieści (auto pokazałby je tylko w razie potrzeby).

    styl.css CSS zmienione: 5 linii
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    
    header,
    footer {
        text-align: center;
    }
    
    main {
        display: grid;
        grid-template-columns: 70% 30%;
    }
    
    section {
        height: 700px;
        overflow: scroll;
    }
    Podgląd strony po tym kroku (index.php)
  25. Krok 25: Blok boczny

    ok. 1 min
    Polecenie z arkusza

    Dla bloku bocznego: szerokość 30%

    Bez nowej reguły: 30% to druga kolumna siatki z kroku o układzie. Szerokość zapisana raz nie może się rozjechać z sumą 100%.

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

  26. Krok 26: Obrazy

    Kryteria:

    R.4.4 wysokość: blok sekcji 700px szerokość: blok sekcji 70%, blok boczny 30% (width lub grid-template-columns lub flex ), selektor img 90%

    ok. 1 min
    Polecenie z arkusza

    Dla selektora obrazu: szerokość 90%, wyrównanie tekstu do środka

    Selektor znacznika img. Szerokość liczy się od bloku obrazu. text-align na obrazie niczego nie przesuwa (obraz nie ma treści), ale arkusz go wymienia, więc zostaje.

    styl.css CSS zmienione: 5 linii
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    
    header,
    footer {
        text-align: center;
    }
    
    main {
        display: grid;
        grid-template-columns: 70% 30%;
    }
    
    section {
        height: 700px;
        overflow: scroll;
    }
    
    img {
        width: 90%;
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  27. Krok 27: Klasa mebel

    Kryteria:

    R.4.8 styl klasy .mebel : margin: 5px, padding: 10px, kolor tła LightCoral, zaokrąglenie rogów 20px

    ok. 1 min
    Polecenie z arkusza

    Dla klasy mebel (skrypt 1): marginesy zewnętrzne 5 px, wewnętrzne 10 px, kolor tła LightCoral, zaokrąglenie rogów 20 px

    Styl bloków, które wypisze PHP.

    styl.css CSS zmienione: 7 linii
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    
    header,
    footer {
        text-align: center;
    }
    
    main {
        display: grid;
        grid-template-columns: 70% 30%;
    }
    
    section {
        height: 700px;
        overflow: scroll;
    }
    
    img {
        width: 90%;
        text-align: center;
    }
    
    .mebel {
        margin: 5px;
        padding: 10px;
        background-color: var(--coral);
        border-radius: 20px;
    }
    Podgląd strony po tym kroku (index.php)
  28. Krok 28: Klasa obraz: opływanie z prawej

    ok. 1 min
    Polecenie z arkusza

    Dla klasy obraz (skrypt 1): szerokość 30%, opływanie z prawej strony

    Tekst ma płynąć po prawej stronie zdjęcia, czyli blok pływa w lewo: float: left. Tu siatka nie zastąpi float: klucz R.4.9 podaje tę właściwość wprost, a „opływanie” to dosłownie zadanie dla float - informacje obok zdjęcia, opis pod spodem.

    Typowe błędy

    float: right stawia zdjęcie po prawej - odwrotnie niż na ilustracji 5.

    styl.css CSS zmienione: 5 linii
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    
    header,
    footer {
        text-align: center;
    }
    
    main {
        display: grid;
        grid-template-columns: 70% 30%;
    }
    
    section {
        height: 700px;
        overflow: scroll;
    }
    
    img {
        width: 90%;
        text-align: center;
    }
    
    .mebel {
        margin: 5px;
        padding: 10px;
        background-color: var(--coral);
        border-radius: 20px;
    }
    
    .obraz {
        width: 30%;
        float: left;
    }
    Podgląd strony po tym kroku (index.php)
  29. Krok 29: Klasa opis: pod obrazem i informacjami

    Kryteria:

    R.4.9 styl klasy .obraz: szerokość 30%, opływanie po prawej stronie - float: left styl klasy .opis: tekst znajduje się pod blokami obrazu i informacji, na całej szerokości bloku nadrzędnego (np. clear: both; lub width: 100%)

    ok. 1 min
    Polecenie z arkusza

    Dla klasy opis (skrypt 1): znajduje się pod blokami obrazu i informacji, na całej szerokości bloku nadrzędnego

    clear: both - opis zaczyna się pod pływającym obrazem, na całej szerokości bloku mebla, a blok mebla obejmuje zdjęcie w całości.

    styl.css CSS zmienione: 4 linie
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    
    header,
    footer {
        text-align: center;
    }
    
    main {
        display: grid;
        grid-template-columns: 70% 30%;
    }
    
    section {
        height: 700px;
        overflow: scroll;
    }
    
    img {
        width: 90%;
        text-align: center;
    }
    
    .mebel {
        margin: 5px;
        padding: 10px;
        background-color: var(--coral);
        border-radius: 20px;
    }
    
    .obraz {
        width: 30%;
        float: left;
    }
    
    .opis {
        clear: both;
    }
    Podgląd strony po tym kroku (index.php)
  30. Krok 30: Przycisk

    Kryteria:

    R.4.6 styl przycisku: margin-left: 30px, padding: 20px, kolor tła LightCoral, obramowanie 2px dotted Maroon, zaokrąglenie rogów 10px

    ok. 2 min
    Polecenie z arkusza

    Dla przycisku: jedynie margines zewnętrzny lewy 30 px, marginesy wewnętrzne 20 px, kolor tła LightCoral, obramowanie linią kropkowaną o szerokości 2 px i kolorze Maroon, zaokrąglenie rogów 10 px

    „Jedynie margines lewy” to margin-left, nie margin. Obramowanie skrótem: grubość, styl dotted, kolor.

    styl.css CSS zmienione: 8 linii
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    
    header,
    footer {
        text-align: center;
    }
    
    main {
        display: grid;
        grid-template-columns: 70% 30%;
    }
    
    section {
        height: 700px;
        overflow: scroll;
    }
    
    img {
        width: 90%;
        text-align: center;
    }
    
    .mebel {
        margin: 5px;
        padding: 10px;
        background-color: var(--coral);
        border-radius: 20px;
    }
    
    .obraz {
        width: 30%;
        float: left;
    }
    
    .opis {
        clear: both;
    }
    
    button {
        margin-left: 30px;
        padding: 20px;
        background-color: var(--coral);
        border: 2px dotted Maroon;
        border-radius: 10px;
    }
    Podgląd strony po tym kroku (index.php)
  31. Krok 31: Nagłówki drugiego i czwartego stopnia: kursywa

    Kryteria:

    R.4.7 styl czcionki: nagłówki drugiego i czwartego stopnia - italic lub oblique

    ok. 1 min
    Polecenie z arkusza

    Dla selektora nagłówka drugiego i czwartego stopnia: czcionka pochylona

    font-style: italic dla dwóch selektorów znaczników.

    styl.css CSS zmienione: 5 linii
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    
    header,
    footer {
        text-align: center;
    }
    
    main {
        display: grid;
        grid-template-columns: 70% 30%;
    }
    
    section {
        height: 700px;
        overflow: scroll;
    }
    
    img {
        width: 90%;
        text-align: center;
    }
    
    .mebel {
        margin: 5px;
        padding: 10px;
        background-color: var(--coral);
        border-radius: 20px;
    }
    
    .obraz {
        width: 30%;
        float: left;
    }
    
    .opis {
        clear: both;
    }
    
    button {
        margin-left: 30px;
        padding: 20px;
        background-color: var(--coral);
        border: 2px dotted Maroon;
        border-radius: 10px;
    }
    
    h2,
    h4 {
        font-style: italic;
    }
    Podgląd strony po tym kroku (index.php)
  32. Krok 32: Nagłówki drugiego stopnia: do środka

    Kryteria:

    R.4.3 wyrównanie tekstu do środka: blok nagłówkowy, stopka i selektor h2

    ok. 1 min
    Polecenie z arkusza

    Dodatkowo dla selektora nagłówka drugiego stopnia: wyrównanie tekstu do środka

    Osobna reguła h2.

    styl.css CSS zmienione: 4 linie
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    
    header,
    footer {
        text-align: center;
    }
    
    main {
        display: grid;
        grid-template-columns: 70% 30%;
    }
    
    section {
        height: 700px;
        overflow: scroll;
    }
    
    img {
        width: 90%;
        text-align: center;
    }
    
    .mebel {
        margin: 5px;
        padding: 10px;
        background-color: var(--coral);
        border-radius: 20px;
    }
    
    .obraz {
        width: 30%;
        float: left;
    }
    
    .opis {
        clear: both;
    }
    
    button {
        margin-left: 30px;
        padding: 20px;
        background-color: var(--coral);
        border: 2px dotted Maroon;
        border-radius: 10px;
    }
    
    h2,
    h4 {
        font-style: italic;
    }
    
    h2 {
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  33. Krok 33: Blok mebla pod kursorem

    Kryteria:

    R.4.10 gdy kursor znajdzie się na elemencie z przypisaną klasą .mebel , jego kolor tła zmienia się na Coral

    ok. 1 min
    Polecenie z arkusza

    Gdy kursor znajdzie się na elemencie z przypisaną klasą mebel, jego kolor tła zmienia się na Coral

    Pseudoklasa :hover.

    styl.css CSS zmienione: 4 linie
    :root {
        --coral: LightCoral;
    }
    
    * {
        font-family: Georgia;
    }
    
    body {
        background-color: MistyRose;
    }
    
    header,
    footer {
        text-align: center;
    }
    
    main {
        display: grid;
        grid-template-columns: 70% 30%;
    }
    
    section {
        height: 700px;
        overflow: scroll;
    }
    
    img {
        width: 90%;
        text-align: center;
    }
    
    .mebel {
        margin: 5px;
        padding: 10px;
        background-color: var(--coral);
        border-radius: 20px;
    }
    
    .obraz {
        width: 30%;
        float: left;
    }
    
    .opis {
        clear: both;
    }
    
    button {
        margin-left: 30px;
        padding: 20px;
        background-color: var(--coral);
        border: 2px dotted Maroon;
        border-radius: 10px;
    }
    
    h2,
    h4 {
        font-style: italic;
    }
    
    h2 {
        text-align: center;
    }
    
    .mebel:hover {
        background-color: Coral;
    }
    Podgląd strony po tym kroku (index.php)
  34. Etap 4: Skrypty PHP

    Kroki 34-51 · po tym etapie masz 40 z 40 kryteriów (pkt)

    Krok 34: Połączenie PDO na początku pliku

    Kryteria:

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

    ok. 3 min
    Polecenie z arkusza

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

    Kod PHP stoi na samym początku index.php, przed <!DOCTYPE html>: najpierw cała praca z bazą, potem szablon, który tylko wypisuje gotowe dane. Klucz R.5 przyjmuje PDO tak samo jak MySQLi.

    • new PDO(...) - połączenie i wybór bazy antyki: pierwszy argument to DSN, czyli adres bazy dla PDO w jednym napisie (mysql:host=localhost;dbname=antyki;charset=utf8mb4 - serwer, nazwa bazy i kodowanie połączenia), dalej użytkownik root i puste hasło,
    • ERRMODE_EXCEPTION zamienia każdy błąd SQL w wyjątek, FETCH_ASSOC daje wiersze jako tablice z nazwami kolumn ($item['nazwa']),
    • try … catch (PDOException $error) - gdy baza nie działa, kupujący widzi zwykły komunikat zamiast treści błędu z nazwą serwera i ścieżkami.

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

    Typowe błędy

    Klucz sprawdza połączenie i wybór bazy antyki w kodzie źródłowym strony (R.5.1, razem z zamknięciem na końcu), więc DSN z nazwą bazy stoi w index.php, a nie w dołączanym pliku.

    index.php PHP zmienione: 11 linii
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
                <h2>- Fotele -</h2>
                <h2>- Komody -</h2>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  35. Krok 35: Skrypt 1a: zapytanie 1 z parametrem i blok mebla dla każdego wiersza

    Kryteria:

    R.5.3 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1 lub Skrypt 2 - zapytanie 2, lub Skrypt 3 - zapytanie 3

    ok. 5 min
    Polecenie z arkusza

    Pierwszy wariant skryptu – 1a – wysyła do bazy danych zapytanie 1. Każdy zwrócony zapytaniem wiersz jest wypisywany w osobnym bloku formatowanym klasą mebel, według schematu (ilustracja 5)

    Trzy warianty Skryptu 1 różnią się tylko numerem kategorii, więc zamiast trzech kopii są dwie funkcje:

    • fetchFurniture($db, $category) wysyła zapytanie 1 jako zapytanie przygotowane: w miejscu 1 stoi parametr :category, a wartość idzie osobno w execute. Typy przy parametrach (PDO, int) i wyniku (array) mówią, czego funkcja oczekuje. Zmienna $query żyje tylko w funkcji - to ważne przy zamykaniu połączenia,
    • stała FOLDERS łączy numer kategorii z folderem obrazów, a pętla foreach pobiera meble wszystkich trzech kategorii do tablicy $furniture,
    • showFurniture($items, $folder) to szablon: między ?> i <?php stoi zwykły HTML bloku mebla, powtórzony dla każdego wiersza (foreach … endforeach).

    Pod nagłówkiem „- Sofy -” jedno wywołanie. W podglądzie trzy puste bloki mebli - styl .mebel czekał już w CSS.

    index.php PHP zmienione: 23 linie
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
                <h2>- Komody -</h2>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  36. Krok 36: Skrypt 1a: blok obrazu z folderu kategorii

    ok. 3 min
    Polecenie z arkusza

    Blok obrazu: Formatowany klasą obraz. Wyświetla obraz z pliku o nazwie pobranej z bazy danych i ścieżce dostępu odpowiednio do kategorii: sofy/, fotele/, komody/ oraz tekście alternatywnym „mebel”

    W bazie jest sama nazwa pliku, a ścieżkę składa folder kategorii i nazwa: sofy/s1.png. Zapis <?= ... ?> to skrót od <?php echo ... ?>.

    Każdy napis z bazy przechodzi przez e() (od escape): htmlspecialchars z ENT_QUOTES zamienia <, >, & i oba cudzysłowy na encje. Napis z bazy, w którym ktoś zapisał "><script>, wyświetli się jako tekst, a nie wykona (ochrona przed XSS). Escapuje się na wyjściu, tuż przed wypisaniem - w bazie zostają dane surowe.

    index.php PHP zmienione: 6 linii
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
                <h2>- Komody -</h2>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  37. Krok 37: Skrypt 1a: blok informacji z nazwą

    ok. 1 min
    Polecenie z arkusza

    Blok informacji wyświetla: W nagłówku trzeciego stopnia nazwę pobraną z bazy danych

    Drugi blok (bez klasy) obok pływającego zdjęcia, nazwa w <h3>. W szablonie HTML jest HTML-em, bez sklejania napisów i ukośników przed cudzysłowami.

    index.php PHP zmienione: 3 linie
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                    </div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
                <h2>- Komody -</h2>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  38. Krok 38: Skrypt 1a: styl mebla

    ok. 1 min
    Polecenie z arkusza

    W nagłówku czwartego stopnia styl w postaci „styl <styl>”

    Stały tekst „styl ” i wartość z bazy.

    index.php PHP zmienione: 1 linia
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                    </div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
                <h2>- Komody -</h2>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  39. Krok 39: Skrypt 1a: cena

    Kryteria:

    R.2.10 Na stronie znajdują się znaczniki <h3> i <h4> (w skrypcie lub HTML )

    ok. 1 min
    Polecenie z arkusza

    W nagłówku trzeciego stopnia cenę w postaci „CENA: <cena> zł”. Patrz ilustracje 2, 5

    PDO zwraca wartości z MariaDB jako napisy, więc e() przyjmuje też cenę („1000”). Kolumna jest typu decimal(10,0) - bez miejsc po przecinku, jak na ilustracji 2.

    index.php PHP zmienione: 1 linia
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                    </div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
                <h2>- Komody -</h2>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  40. Krok 40: Skrypt 1a: formularz z przyciskiem „KUP”

    Kryteria: , ,

    R.2.7 W bloku sekcji znajduje się przynajmniej jeden formularz z przyciskiem "KUP". Domknięcie </form> zaraz za przyciskiem (w HTML lub skrypcie )

    R.3.3 Przynajmniej jeden formularz wysyła dane metodą POST do tego samego pliku (poprawne również bez atrybutu action )

    R.3.4 Przynajmniej jeden przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu ) oraz ma przypisany atrybut value

    ok. 2 min
    Polecenie z arkusza

    Formularz wysyłający dane do tego samego pliku metodą bezpieczną z jednym przyciskiem „KUP”. Przycisk wysyła dane, ma przypisany atrybut wartości równy polu idMeble z zapytania. Patrz tabela 2.

    Formularz method="post" bez action - dane wracają do tego samego pliku. Przycisk bez typu wysyła formularz; name="buy" to klucz w $_POST, a value - identyfikator mebla. </form> zaraz za przyciskiem, jak wymaga klucz.

    index.php PHP zmienione: 1 linia
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
                <h2>- Komody -</h2>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  41. Krok 41: Skrypt 1a: blok opisu

    Kryteria: ,

    R.2.6 W bloku sekcji znajduje się przynajmniej jeden blok z przypisaną klasą mebel , zawierający trzy bloki, z czego pierwszy z klasą obraz, ostatni z klasą opis (w HTML lub skrypcie)

    R.5.5 Na stronie wyświetlany jest przynajmniej jeden rekord zwrócony zapytaniem: nazwa, styl - "styl <styl>", cena - "CENA: <cena> zł" i opis

    ok. 1 min
    Polecenie z arkusza

    Blok opisu: Jest formatowany klasą opis. Wyświetla zwrócony z bazy danych opis

    Trzeci blok z klasą opis - clear: both stawia go pod zdjęciem. Podgląd: trzy sofy jak na ilustracji 2.

    index.php PHP zmienione: 1 linia
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                    <div class="opis"><?= e($item['opis']) ?></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
                <h2>- Komody -</h2>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  42. Krok 42: Skrypt 1b: fotele

    ok. 2 min
    Polecenie z arkusza

    Drugi wariant skryptu – 1b – wysyła do bazy danych zapytanie 1 zmodyfikowane tak, że sprawdzana jest kategoria równa 2

    Zapytanie dla kategorii 2 wykonała już pętla na początku pliku (to samo zapytanie przygotowane z :category = 2), więc pod „- Fotele -” wystarczy jedno wywołanie szablonu z folderem fotele.

    index.php PHP zmienione: 1 linia
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                    <div class="opis"><?= e($item['opis']) ?></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
    <?php showFurniture($furniture[2], FOLDERS[2]); ?>
                <h2>- Komody -</h2>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  43. Krok 43: Skrypt 1c: komody

    Kryteria: ,

    R.5.4 Skrypt 1 wyświetla przynajmniej po jednym obrazie z każdego z podfolderów: sofy/, fotele/, komody/ z tekstem alternatywnym "mebel"

    R.5.6 Na stronie wyświetlane są informacje ze wszystkich rekordów tabeli meble , podzielone na trzy kategorie (wymagana liczba zwróconych rekordów > 0)

    ok. 1 min
    Polecenie z arkusza

    Trzeci wariant skryptu – 1c – wysyła do bazy danych zapytanie 1 zmodyfikowane tak, że sprawdzana jest kategoria równa 3

    Trzecie wywołanie. Na stronie jest teraz wszystkie 11 mebli z bazy, każdy z obrazem z właściwego folderu. Poprawka w szablonie bloku mebla zmienia od razu wszystkie trzy kategorie.

    index.php PHP zmienione: 1 linia
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                    <div class="opis"><?= e($item['opis']) ?></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
    <?php showFurniture($furniture[2], FOLDERS[2]); ?>
                <h2>- Komody -</h2>
    <?php showFurniture($furniture[3], FOLDERS[3]); ?>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  44. Krok 44: Skrypt 2: tylko po kliknięciu „KUP”, numer sprawdzony

    ok. 3 min
    Polecenie z arkusza

    Skrypt 2 obsługujący dane z formularza: Jeżeli zostały wysłane dane z formularza

    Obsługa zakupu stoi przed odczytem mebli i koszyka: koszyk pokaże już nowy zakup. Warunek $_SERVER['REQUEST_METHOD'] === 'POST' sprawdza, czy strona przyszła z formularza.

    Wartość przycisku jest w kodzie strony, ale żądanie da się wysłać z dowolną wartością. filter_input(INPUT_POST, 'buy', FILTER_VALIDATE_INT, ...) zwraca liczbę całkowitą tylko wtedy, gdy wysłano liczbę od 1 w górę - dla abc, 0, -5 albo 6 OR 1=1 daje false, a gdy pola brak - null. Sprawdzanie wejścia to pierwsza warstwa ochrony.

    index.php PHP zmienione: 5 linii
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $furnitureId = filter_input(INPUT_POST, 'buy', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    }
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                    <div class="opis"><?= e($item['opis']) ?></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
    <?php showFurniture($furniture[2], FOLDERS[2]); ?>
                <h2>- Komody -</h2>
    <?php showFurniture($furniture[3], FOLDERS[3]); ?>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  45. Krok 45: Skrypt 2: zapis zakupu i przekierowanie

    Kryteria:

    R.5.7 Skrypt 2 wstawia rekord do bazy danych dla idMeble odpowiadającego meblowi, dla którego wybrano przycisk (sprawdzić w phpMyAdmin przynajmniej dla dwóch mebli z różnych kategorii)

    ok. 5 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 2, zmodyfikowane w ten sposób, że w miejscu identyfikatora mebla zostanie umieszczona wartość atrybutu value z przycisku „KUP” (wciśnięty przycisk przy niebieskim fotelu – identyfikator 9), zostanie przesłana wartość 9. Zakupy są uproszczone – kupuje zawsze klient o identyfikatorze 1, kupowana jest zawsze 1 sztuka

    Dwie nowe funkcje z zapytaniami przygotowanymi:

    • furnitureExists() - druga warstwa ochrony: liczba może być poprawna, a mebla o takim numerze może nie być (99). Zapytanie COUNT(*) z parametrem, fetchColumn() zwraca pierwszą kolumnę pierwszego wiersza,
    • addPurchase() - zapytanie 2 z parametrami :client i :furniture w miejscu 1 i 6. Klient to stała CLIENT_ID = 1, liczba sztuk zostaje 1.

    Po zapisie header('Location: index.php', true, 303) i exit - wzorzec PRG (Post/Redirect/Get): przeglądarka dostaje polecenie wczytania strony od nowa zwykłym GET. Bez tego odświeżenie strony (F5) wysłałoby formularz drugi raz i kupiło mebel podwójnie. Przekierowanie działa, bo przed nim nic nie zostało wypisane - cały kod PHP stoi przed HTML-em.

    Podgląd: stan po kliknięciu „KUP” przy niebieskim fotelu - w tabeli zakupy przybył rekord z idMeble = 9 (sprawdzone zapytaniem po uruchomieniu).

    Typowe błędy

    header() po jakimkolwiek wypisanym znaku (nawet spacji przed <?php) kończy się ostrzeżeniem „headers already sent” i przekierowanie nie działa.

    index.php PHP zmienione: 19 linii
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    const CLIENT_ID = 1;
    
    function furnitureExists(PDO $db, int $id): bool
    {
        $query = $db->prepare('SELECT COUNT(*) FROM meble WHERE idMeble = :id');
        $query->execute(['id' => $id]);
        return $query->fetchColumn() > 0;
    }
    
    function addPurchase(PDO $db, int $clientId, int $furnitureId): void
    {
        $query = $db->prepare('INSERT INTO zakupy (idKlienci, idMeble, sztuk) VALUES (:client, :furniture, 1)');
        $query->execute(['client' => $clientId, 'furniture' => $furnitureId]);
    }
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $furnitureId = filter_input(INPUT_POST, 'buy', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($furnitureId && furnitureExists($db, $furnitureId)) {
            addPurchase($db, CLIENT_ID, $furnitureId);
            header('Location: index.php', true, 303);
            exit;
        }
    }
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                    <div class="opis"><?= e($item['opis']) ?></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
    <?php showFurniture($furniture[2], FOLDERS[2]); ?>
                <h2>- Komody -</h2>
    <?php showFurniture($furniture[3], FOLDERS[3]); ?>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  46. Krok 46: Skrypt 2: komunikat zamiast błędu

    ok. 2 min
    Polecenie z arkusza

    Efekt działania skryptu 2

    Gdy numer nie przeszedł sprawdzenia, zakup się nie odbywa, a pod komodami pojawia się akapit z komunikatem - w miejscu, które arkusz przeznacza na „efekt działania skryptu 2”. Po udanym zakupie nie ma czego wyświetlać: zakup widać w koszyku.

    Sprawdzone na serwerze: buy równe abc, 0, -5, 99, 6 OR 1=1, puste pole i <script> - za każdym razem komunikat, zero wierszy w tabeli zakupy i żadnego ostrzeżenia PHP. W wersji bez sprawdzania abc i 99 przerywały stronę błędem bazy.

    index.php PHP zmienione: 4 linie
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    const CLIENT_ID = 1;
    
    function furnitureExists(PDO $db, int $id): bool
    {
        $query = $db->prepare('SELECT COUNT(*) FROM meble WHERE idMeble = :id');
        $query->execute(['id' => $id]);
        return $query->fetchColumn() > 0;
    }
    
    function addPurchase(PDO $db, int $clientId, int $furnitureId): void
    {
        $query = $db->prepare('INSERT INTO zakupy (idKlienci, idMeble, sztuk) VALUES (:client, :furniture, 1)');
        $query->execute(['client' => $clientId, 'furniture' => $furnitureId]);
    }
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $furnitureId = filter_input(INPUT_POST, 'buy', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($furnitureId && furnitureExists($db, $furnitureId)) {
            addPurchase($db, CLIENT_ID, $furnitureId);
            header('Location: index.php', true, 303);
            exit;
        }
        $message = 'Nie ma takiego mebla w ofercie.';
    }
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                    <div class="opis"><?= e($item['opis']) ?></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
    <?php showFurniture($furniture[2], FOLDERS[2]); ?>
                <h2>- Komody -</h2>
    <?php showFurniture($furniture[3], FOLDERS[3]); ?>
    <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  47. Krok 47: Skrypt 3: lista numerowana

    Kryteria:

    R.2.8 W bloku bocznym znajduje się jedna lista numerowana (znacznik <ol>; w skrypcie lub HTML )

    ok. 1 min
    Polecenie z arkusza

    Skrypt 3: Tworzy listę numerowaną (uporządkowaną)

    <ol> stoi w szablonie jako zwykły HTML - PHP wypisze tylko elementy. Od tego kroku podgląd pokazuje stan z ilustracji 6: wcześniej kupiono niebieski fotel i sofę 2-osobową, teraz kliknięto „KUP” przy barokowej komodzie.

    index.php PHP zmienione: 2 linie
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    const CLIENT_ID = 1;
    
    function furnitureExists(PDO $db, int $id): bool
    {
        $query = $db->prepare('SELECT COUNT(*) FROM meble WHERE idMeble = :id');
        $query->execute(['id' => $id]);
        return $query->fetchColumn() > 0;
    }
    
    function addPurchase(PDO $db, int $clientId, int $furnitureId): void
    {
        $query = $db->prepare('INSERT INTO zakupy (idKlienci, idMeble, sztuk) VALUES (:client, :furniture, 1)');
        $query->execute(['client' => $clientId, 'furniture' => $furnitureId]);
    }
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $furnitureId = filter_input(INPUT_POST, 'buy', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($furnitureId && furnitureExists($db, $furnitureId)) {
            addPurchase($db, CLIENT_ID, $furnitureId);
            header('Location: index.php', true, 303);
            exit;
        }
        $message = 'Nie ma takiego mebla w ofercie.';
    }
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                    <div class="opis"><?= e($item['opis']) ?></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
    <?php showFurniture($furniture[2], FOLDERS[2]); ?>
                <h2>- Komody -</h2>
    <?php showFurniture($furniture[3], FOLDERS[3]); ?>
    <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
                <ol>
                </ol>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  48. Krok 48: Skrypt 3: zapytanie 3

    ok. 2 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 3

    Funkcja fetchCart() z zapytaniem 3, klient jako parametr :client. Wynik trafia do tablicy $cart - jeszcze nic nie jest wypisane.

    index.php PHP zmienione: 9 linii
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    const CLIENT_ID = 1;
    
    function fetchCart(PDO $db, int $clientId): array
    {
        $query = $db->prepare('SELECT nazwa, cena FROM zakupy JOIN meble USING(idMeble) WHERE idKlienci = :client');
        $query->execute(['client' => $clientId]);
        return $query->fetchAll();
    }
    
    function furnitureExists(PDO $db, int $id): bool
    {
        $query = $db->prepare('SELECT COUNT(*) FROM meble WHERE idMeble = :id');
        $query->execute(['id' => $id]);
        return $query->fetchColumn() > 0;
    }
    
    function addPurchase(PDO $db, int $clientId, int $furnitureId): void
    {
        $query = $db->prepare('INSERT INTO zakupy (idKlienci, idMeble, sztuk) VALUES (:client, :furniture, 1)');
        $query->execute(['client' => $clientId, 'furniture' => $furnitureId]);
    }
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $furnitureId = filter_input(INPUT_POST, 'buy', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($furnitureId && furnitureExists($db, $furnitureId)) {
            addPurchase($db, CLIENT_ID, $furnitureId);
            header('Location: index.php', true, 303);
            exit;
        }
        $message = 'Nie ma takiego mebla w ofercie.';
    }
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    $cart = fetchCart($db, CLIENT_ID);
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                    <div class="opis"><?= e($item['opis']) ?></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
    <?php showFurniture($furniture[2], FOLDERS[2]); ?>
                <h2>- Komody -</h2>
    <?php showFurniture($furniture[3], FOLDERS[3]); ?>
    <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
                <ol>
                </ol>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  49. Krok 49: Skrypt 3: elementy listy z nazwą i ceną

    Kryteria:

    R.5.8 Elementy listy w koszyku są pobrane z bazy danych: nazwy i ceny mebli. Jeżeli lista jest pusta sprawdzić w kodzie PHP

    ok. 2 min
    Polecenie z arkusza

    Każdy zwrócony zapytaniem wiersz wypisuje jako element listy o treści „<nazwa>, cena: <cena>”, gdzie <> oznacza zwrócone dane.

    Pętla w szablonie, jeden <li> na zakup: „Fotel niebieski, cena: 800”, „Sofa 2-osobowa, cena: 1200”, „Barokowa komoda, cena: 4000” - jak na ilustracji 6. Pusty koszyk to pusta tablica: pętla nie wypisze nic, jak na ilustracji 2.

    index.php PHP zmienione: 3 linie
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    const CLIENT_ID = 1;
    
    function fetchCart(PDO $db, int $clientId): array
    {
        $query = $db->prepare('SELECT nazwa, cena FROM zakupy JOIN meble USING(idMeble) WHERE idKlienci = :client');
        $query->execute(['client' => $clientId]);
        return $query->fetchAll();
    }
    
    function furnitureExists(PDO $db, int $id): bool
    {
        $query = $db->prepare('SELECT COUNT(*) FROM meble WHERE idMeble = :id');
        $query->execute(['id' => $id]);
        return $query->fetchColumn() > 0;
    }
    
    function addPurchase(PDO $db, int $clientId, int $furnitureId): void
    {
        $query = $db->prepare('INSERT INTO zakupy (idKlienci, idMeble, sztuk) VALUES (:client, :furniture, 1)');
        $query->execute(['client' => $clientId, 'furniture' => $furnitureId]);
    }
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $furnitureId = filter_input(INPUT_POST, 'buy', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($furnitureId && furnitureExists($db, $furnitureId)) {
            addPurchase($db, CLIENT_ID, $furnitureId);
            header('Location: index.php', true, 303);
            exit;
        }
        $message = 'Nie ma takiego mebla w ofercie.';
    }
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    $cart = fetchCart($db, CLIENT_ID);
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                    <div class="opis"><?= e($item['opis']) ?></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
    <?php showFurniture($furniture[2], FOLDERS[2]); ?>
                <h2>- Komody -</h2>
    <?php showFurniture($furniture[3], FOLDERS[3]); ?>
    <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
                <ol>
    <?php foreach ($cart as $item): ?>
                    <li><?= e($item['nazwa']) ?>, cena: <?= e($item['cena']) ?></li>
    <?php endforeach; ?>
                </ol>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  50. Krok 50: Skrypt 3: koszt całkowity pod listą

    Kryteria: , ,

    R.2.9 W bloku bocznym pod listą znajduje się napis "Koszt całkowity: " (w skrypcie lub HTML )

    R.5.9 Skrypt 3 zawiera instrukcje obliczające całkowity koszt poprzez zsumowanie wszystkich cen zwróconych zapytaniem

    R.5.10 Obliczony koszt całkowity jest wyświetlony na stronie

    ok. 2 min
    Polecenie z arkusza

    Skrypt na podstawie zwróconych cen oblicza koszt całkowity koszyka. Wartość jest wyświetlona pod listą, w postaci „Koszt całkowity: <koszt>” (ilustracja 6). Gdy koszyk jest pusty to koszt całkowity wynosi 0 (ilustracja 2).

    array_column($cart, 'cena') wyciąga z koszyka same ceny, a array_sum je dodaje: 800 + 1200 + 4000 = 6000. Dla pustego koszyka suma pustej tablicy to 0 - bez osobnej zmiennej startowej i bez ostrzeżenia. Wynik wypisuje akapit pod listą. Liczba wyliczona w PHP nie pochodzi od użytkownika, więc nie potrzebuje e().

    index.php PHP zmienione: 2 linie
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    const CLIENT_ID = 1;
    
    function fetchCart(PDO $db, int $clientId): array
    {
        $query = $db->prepare('SELECT nazwa, cena FROM zakupy JOIN meble USING(idMeble) WHERE idKlienci = :client');
        $query->execute(['client' => $clientId]);
        return $query->fetchAll();
    }
    
    function furnitureExists(PDO $db, int $id): bool
    {
        $query = $db->prepare('SELECT COUNT(*) FROM meble WHERE idMeble = :id');
        $query->execute(['id' => $id]);
        return $query->fetchColumn() > 0;
    }
    
    function addPurchase(PDO $db, int $clientId, int $furnitureId): void
    {
        $query = $db->prepare('INSERT INTO zakupy (idKlienci, idMeble, sztuk) VALUES (:client, :furniture, 1)');
        $query->execute(['client' => $clientId, 'furniture' => $furnitureId]);
    }
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $furnitureId = filter_input(INPUT_POST, 'buy', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($furnitureId && furnitureExists($db, $furnitureId)) {
            addPurchase($db, CLIENT_ID, $furnitureId);
            header('Location: index.php', true, 303);
            exit;
        }
        $message = 'Nie ma takiego mebla w ofercie.';
    }
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    $cart = fetchCart($db, CLIENT_ID);
    $total = array_sum(array_column($cart, 'cena'));
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                    <div class="opis"><?= e($item['opis']) ?></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
    <?php showFurniture($furniture[2], FOLDERS[2]); ?>
                <h2>- Komody -</h2>
    <?php showFurniture($furniture[3], FOLDERS[3]); ?>
    <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
                <ol>
    <?php foreach ($cart as $item): ?>
                    <li><?= e($item['nazwa']) ?>, cena: <?= e($item['cena']) ?></li>
    <?php endforeach; ?>
                </ol>
                <p>Koszt całkowity: <?= $total ?></p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  51. Krok 51: Zamknięcie połączenia z bazą

    Kryteria:

    R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy antyki oraz ostatnią operacją na bazie jest jej zamknięcie

    ok. 2 min
    Polecenie z arkusza

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

    $db = null; zaraz po ostatnim zapytaniu zamyka połączenie PDO - to ostatnia operacja na bazie (R.5.1), a szablon używa już tylko gotowych tablic. Połączenie zamyka się naprawdę dopiero, gdy nie zostaje żaden obiekt zapytania, który z niego korzysta - dlatego zapytania żyją w funkcjach ($query znika po return), a nie w zmiennych globalnych.

    Typowe błędy

    $db->close() nie istnieje w PDO - skończy się błędem „Call to undefined method”.

    index.php PHP zmienione: 2 linie
    <?php
    try {
        $db = new PDO('mysql:host=localhost;dbname=antyki;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Sklep jest chwilowo niedostępny. Spróbuj ponownie później.');
    }
    
    function fetchFurniture(PDO $db, int $category): array
    {
        $query = $db->prepare('SELECT idMeble, nazwa, plik, styl, cena, opis FROM meble WHERE kategoria = :category');
        $query->execute(['category' => $category]);
        return $query->fetchAll();
    }
    
    const FOLDERS = [1 => 'sofy', 2 => 'fotele', 3 => 'komody'];
    const CLIENT_ID = 1;
    
    function fetchCart(PDO $db, int $clientId): array
    {
        $query = $db->prepare('SELECT nazwa, cena FROM zakupy JOIN meble USING(idMeble) WHERE idKlienci = :client');
        $query->execute(['client' => $clientId]);
        return $query->fetchAll();
    }
    
    function furnitureExists(PDO $db, int $id): bool
    {
        $query = $db->prepare('SELECT COUNT(*) FROM meble WHERE idMeble = :id');
        $query->execute(['id' => $id]);
        return $query->fetchColumn() > 0;
    }
    
    function addPurchase(PDO $db, int $clientId, int $furnitureId): void
    {
        $query = $db->prepare('INSERT INTO zakupy (idKlienci, idMeble, sztuk) VALUES (:client, :furniture, 1)');
        $query->execute(['client' => $clientId, 'furniture' => $furnitureId]);
    }
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $furnitureId = filter_input(INPUT_POST, 'buy', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
        if ($furnitureId && furnitureExists($db, $furnitureId)) {
            addPurchase($db, CLIENT_ID, $furnitureId);
            header('Location: index.php', true, 303);
            exit;
        }
        $message = 'Nie ma takiego mebla w ofercie.';
    }
    
    $furniture = [];
    foreach (array_keys(FOLDERS) as $category) {
        $furniture[$category] = fetchFurniture($db, $category);
    }
    
    $cart = fetchCart($db, CLIENT_ID);
    $total = array_sum(array_column($cart, 'cena'));
    
    $db = null;
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function showFurniture(array $items, string $folder): void
    {
        foreach ($items as $item): ?>
                <div class="mebel">
                    <div class="obraz"><img src="<?= $folder ?>/<?= e($item['plik']) ?>" alt="mebel"></div>
                    <div>
                        <h3><?= e($item['nazwa']) ?></h3>
                        <h4>styl <?= e($item['styl']) ?></h4>
                        <h3>CENA: <?= e($item['cena']) ?> zł</h3>
                        <form method="post"><button name="buy" value="<?= e($item['idMeble']) ?>">KUP</button></form>
                    </div>
                    <div class="opis"><?= e($item['opis']) ?></div>
                </div>
    <?php endforeach;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sprzedaż antyków</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Najlepsze antyki w mieście</h1>
        </header>
        <main>
            <section>
                <h2>- Sofy -</h2>
    <?php showFurniture($furniture[1], FOLDERS[1]); ?>
                <h2>- Fotele -</h2>
    <?php showFurniture($furniture[2], FOLDERS[2]); ?>
                <h2>- Komody -</h2>
    <?php showFurniture($furniture[3], FOLDERS[3]); ?>
    <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <aside>
                <h2>Koszyk</h2>
                <p>Zalogowano: Anna Kowalska</p>
                <ol>
    <?php foreach ($cart as $item): ?>
                    <li><?= e($item['nazwa']) ?>, cena: <?= e($item['cena']) ?></li>
    <?php endforeach; ?>
                </ol>
                <p>Koszt całkowity: <?= $total ?></p>
            </aside>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

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

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

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

    ok. 2 min

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

    wersja podstawowawersja nowoczesna
    układdisplay: flex na <main>, szerokości w widthgrid-template-columns: 70% 30%
    połączeniemysqli_connect w treści stronyPDO w treści strony, przed HTML-em
    Skrypt 1trzy kopie pętli while z echojedno zapytanie przygotowane w funkcji, szablon w funkcji
    wypisywanieecho "<div class=\"mebel\">"HTML w szablonie, <?= e(...) ?>
    Skrypt 2wartość z $_POST sklejona z SQLfilter_input, sprawdzenie w bazie, parametr, PRG
    sumapętla z $total = $total + ...array_sum(array_column(...))

    Klucz wymusza float: left dla bloku obrazu (R.4.9) - obie wersje go mają. Na egzaminie wersja podstawowa wymaga mniej pisania i każdy wariant Skryptu 1 stoi dokładnie tam, gdzie mówi arkusz. Nowoczesna ma więcej kodu na starcie, ale poprawka bloku mebla to jedna zmiana zamiast trzech, a złośliwe dane z formularza nie wywracają strony. Poza egzaminem dane logowania do bazy trzyma się zwykle w osobnym pliku (np. config.php), żeby hasło nie trafiało do repozytorium razem z kodem - tu stoją w index.php, bo klucz sprawdza połączenie w kodzie strony. Kolejne warstwy, których klucz nie wymaga: token CSRF w formularzu (zakup tylko ze strony sklepu, nie z obcej strony podsuwającej formularz) i zalogowany klient zamiast stałej CLIENT_ID.

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

  53. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

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

    W folderze z numerem zdającego: foldery sofy, fotele (z przerobionym f3.png) i komody, a także import.png, index.php, kw1.png … kw4.png, kwerendy.txt, przeglądarka.txt, styl.css. Płytę sprawdź po nagraniu.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.