Przejdź do głównej treści
EE.09-02-21.06-SGCzerwiec 2021 · 30 kroków · ok. 79 min · 37 kryteriów z klucza

Rozwiązanie krok po kroku: Sklep internetowy z eko-warzywami (Nowoczesny kod (flex, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza dane2 z czterema zapytaniami, przycięta grafika arbuz.jpg oraz strona sklep.php z dwoma banerami, przewijanym blokiem produktów i formularzem w stopce - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • flexbox zamiast float: dwa banery stoją w <header> ułożonym we flexie, a produkty zawijają się w rzędy w bloku głównym <main>,
  • semantyczne znaczniki: każdy produkt w <article>, formularz z etykietami <label for> i przyciskiem <button>,
  • zmienne CSS dla dwóch zieleni z arkusza (ciemna zieleń wraca w banerach, stopce, nagłówkach produktów i ramkach),
  • PHP na PDO na górze pliku sklep.php, przed HTML-em: najpierw sprawdzenie danych z formularza, potem zapis zapytaniem z parametrami, potem odczyt - w HTML-u zostaje tylko pętla i wypisywanie przez htmlspecialchars,
  • cena z przecinkiem („4,50”) zapisuje się poprawnie, a błędne dane i błąd bazy kończą się zrozumiałym komunikatem, nie ostrzeżeniem PHP.

To arkusz EE.09 z czerwca 2021 - kwalifikacji formuły 2017, którą zastąpiła INF.03. Baza i zdjęcia pochodzą z oryginalnego archiwum CKE zad2.zip (załącznik na stronie arkusza), zapytania i zrzuty są policzone na MariaDB 10.4, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2.

Jak liczą się punkty. Klucz ma 37 pozycji (R.1.1 - R.5.6), każda za 1 punkt; pozycja R.2.10 jest w kluczu CKE pusta, więc w praktyce ocenianych jest 36 kryteriów. Próg zdania części praktycznej to 75%, czyli 28 punktów. Przy każdym kroku jest napisane, które kryteria spełnia; klucz przyjmuje zarówno mysqli, jak i PDO.

  1. Etap 1: Baza danych

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

    Krok 1: Folder zdającego i archiwum zad2.zip

    ok. 2 min
    Polecenie z arkusza

    Na pulpicie znajduje się archiwum ZIP o nazwie zad2.zip zabezpieczone hasłem: #Warzywni@czeK Należy utworzyć folder o nazwie odpowiadającej numerowi PESEL zdającego lub w przypadku jego braku innemu numerowi, którym został podpisany arkusz. Archiwum należy rozpakować do tego folderu.

    Folder na pulpicie nazwany numerem PESEL (w tym rozwiązaniu w jego miejscu stoi Bartosz Bryniarski), do niego archiwum zad2.zip rozpakowane z hasłem #Warzywni@czeK. W środku: dane.sql i dziewięć zdjęć produktów. Strona i zdjęcia muszą leżeć w tym samym folderze - skrypt poda w znaczniku obrazu samą nazwę pliku z bazy.

  2. Krok 2: XAMPP i pusta baza dane2

    ok. 2 min
    Polecenie z arkusza

    Uruchom usługi MySQL i Apache za pomocą XAMPP Control Panel. Za pomocą narzędzia phpMyAdmin wykonaj podane operacje na bazie danych: Utwórz bazę danych o nazwie dane2

    Start przy Apache i MySQL, potem http://localhost/phpmyadmin, Nowa, nazwa dane2.

  3. Krok 3: Import pliku dane.sql i zrzut egzamin.jpg

    Kryteria:

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

    ok. 3 min
    Polecenie z arkusza

    Do bazy dane2 zaimportuj plik dane.sql z rozpakowanego archiwum. Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w folderze z numerem PESEL lub w przypadku jego braku w folderze z innym numerem, którym został podpisany arkusz, w formacie JPEG i nazwij egzamin. 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

    Baza dane2, karta Import, plik dane.sql, Wykonaj. Powstaje pięć tabel - w zadaniu potrzebne są produkty, producenci i rodzaje. Pola Rodzaje_id i Producenci_id w produktach wskazują id w tamtych tabelach. Zrzut całego ekranu z paskiem zadań: egzamin.jpg - w tym arkuszu zrzut importu jest w JPEG, a zrzuty kwerend w PNG.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 3
  4. Krok 4: Zapytanie 1: owoce i warzywa

    Kryteria: ,

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

    R.1.3 Utworzono zapytanie 1 wybierające jedynie pola nazwa, ilosc, opis, cena, zdjecie z tabeli produkty , dla produktów, których pole Rodzaje_id przyjmuje wartości: 1, 2. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, ilosc, opis, cena, zdjecie FROM produkty WHERE Rodzaje_id = 1 OR Rodzaje_id = 2; oraz wynik działania zapytania udokumentowano zrzutem, na którym widocznych jest dokładnie 8 wierszy, jedynie pola nazwa, ilosc, opis, cena, zdjecie

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pola nazwa, ilosc, opis, cena, zdjecie z tabeli Produkty, dla produktów, których pole Rodzaje_id przyjmuje jedną z wartości: 1, 2

    Pięć kolumn w kolejności z arkusza i warunek na jedną z dwóch wartości (to samo da Rodzaje_id IN (1, 2)). Wynik: 8 wierszy, od gruszki do papryki. Zapytanie trafia do kwerendy.txt, zrzut - do kw1.png. Każda klauzula w osobnej linii: przy dłuższych zapytaniach łatwiej znaleźć błąd.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT nazwa, ilosc, opis, cena, zdjecie
    FROM produkty
    WHERE Rodzaje_id = 1 OR Rodzaje_id = 2;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: owoce z niskim stanem i ich producenci

    Kryteria:

    R.1.4 Utworzono zapytanie 2 wybierające jedynie nazwę produktu i odpowiadającą mu nazwę producenta dla produktów, dla których ilość jest mniejsza od 10 oraz pole Rodzaje_id przyjmuje wartość 1. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT produkty.nazwa, producenci.nazwa FROM produkty JOIN producenci ON produkty.Producenci_id = producenci.id WHERE produkty.ilosc < 10 AND Rodzaje_id = 1; możliwe też INNER JOIN lub bez JOIN (porównanie kluczy w warunku) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są dokładnie 2 rekordy: arbuz FruitFarm, papaja Owoce-rolnik

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie nazwę produktu z tabeli Produkty i odpowiadającą mu nazwę producenta z tabeli Producenci dla produktów, dla których ilość jest mniejsza od 10 oraz pole Rodzaje_id przyjmuje wartość 1

    JOIN ... ON produkty.Producenci_id = producenci.id łączy każdy produkt z jego producentem. Obie tabele mają kolumnę nazwa, więc przed nazwą kolumny stoi nazwa tabeli. Wynik: 2 wiersze - arbuz (FruitFarm) i papaja (Owoce-rolnik). Zrzut: kw2.png.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT nazwa, ilosc, opis, cena, zdjecie
    FROM produkty
    WHERE Rodzaje_id = 1 OR Rodzaje_id = 2;
    
    -- Zapytanie 2
    SELECT produkty.nazwa, producenci.nazwa
    FROM produkty
    JOIN producenci ON produkty.Producenci_id = producenci.id
    WHERE produkty.ilosc < 10 AND produkty.Rodzaje_id = 1;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: użytkownik anna

    Kryteria:

    R.1.5 Utworzono zapytanie 3 tworzące użytkownika anna z hasłem @nna23. W pliku z kwerendami lub na zrzucie istnieje zapis np.: CREATE USER 'anna'@'localhost' IDENTIFIED BY '@nna23'; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest efekt poprawnie wykonanego zapytania. Nie uznajemy za spełnione, jeśli brak widocznego hasła (np. wygenerowano z kreatora)

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: tworzące użytkownika anna z hasłem @nna23

    CREATE USER z nazwą konta, hostem localhost i hasłem w IDENTIFIED BY. Klucz wymaga widocznego hasła, więc konto zakładasz zapytaniem w karcie SQL, nie kreatorem. Zrzut: kw3.png.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT nazwa, ilosc, opis, cena, zdjecie
    FROM produkty
    WHERE Rodzaje_id = 1 OR Rodzaje_id = 2;
    
    -- Zapytanie 2
    SELECT produkty.nazwa, producenci.nazwa
    FROM produkty
    JOIN producenci ON produkty.Producenci_id = producenci.id
    WHERE produkty.ilosc < 10 AND produkty.Rodzaje_id = 1;
    
    -- Zapytanie 3
    CREATE USER 'anna'@'localhost' IDENTIFIED BY '@nna23';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: nowy produkt owoc1

    Kryteria:

    R.1.6 Utworzono zapytanie 4 wstawiające wiersz do tabeli produkty, klucz główny nadany automatycznie, dane: Rodzaje_id: 1, Producenci_id: 4, nazwa: owoc1, ilość: 10, opis pusty, cena: 9,99, zdjecie: owoce.jpg. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO produkty(`Rodzaje_id`, `Producenci_id`, `nazwa`, `ilosc`, `opis`, `cena`, `zdjecie`) VALUES (1, 4, "owoc1", 10,"",9.99,"owoce.jpg") (możliwe również bez wymieniania pól w nawiasie, a także przez SET + lista przypisań) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest komunikat o poprawnie wykonanym zapytaniu lub wstawiony rekord

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 4: wstawiające wiersz do tabeli Produkty według specyfikacji: klucz główny nadany automatycznie; Rodzaje_id – id wiersza z nazwą owoce w tabeli Rodzaje; Producenci_id – id wiersza z nazwą warzywa-rolnik w tabeli Producenci; pozostałe dane: nazwa: owoc1, ilość: 10, opis: puste pole, cena: 9,99, zdjecie: owoce.jpg

    W rodzaje owoce mają id 1, w producenci warzywa-rolnik ma id 4. Lista kolumn bez id (nada je AUTO_INCREMENT), pusty opis jako '', cena z kropką - przecinek w SQL oddziela wartości. Zrzut: kw4.png. To zapytanie skrypt powtórzy z nazwą i ceną z formularza.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT nazwa, ilosc, opis, cena, zdjecie
    FROM produkty
    WHERE Rodzaje_id = 1 OR Rodzaje_id = 2;
    
    -- Zapytanie 2
    SELECT produkty.nazwa, producenci.nazwa
    FROM produkty
    JOIN producenci ON produkty.Producenci_id = producenci.id
    WHERE produkty.ilosc < 10 AND produkty.Rodzaje_id = 1;
    
    -- Zapytanie 3
    CREATE USER 'anna'@'localhost' IDENTIFIED BY '@nna23';
    
    -- Zapytanie 4
    INSERT INTO produkty (Rodzaje_id, Producenci_id, nazwa, ilosc, opis, cena, zdjecie)
    VALUES (1, 4, 'owoc1', 10, '', 9.99, 'owoce.jpg');
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Krok 8: Grafika: arbuz.jpg w proporcji 3:2, 300 × 200 px

    Kryteria:

    R.2.1 Plik arbuz.jpg przeskalowano do rozmiaru 300 px na 200 px (± 2 px)

    ok. 4 min
    Polecenie z arkusza

    Plik arbuz.jpg, wypakowany z archiwum, należy skadrować do proporcji 3:2, a następnie przeskalować z zachowaniem proporcji do rozmiaru 300 px na 200 px. Pozostałe pliki graficzne są przygotowane i nie należy ich zmieniać

    Oryginał ma 500 × 297 px. W GIMP-ie Kadrowanie ze stałymi proporcjami 3:2 na całą wysokość (446 × 297 px, wycentrowane), potem Obraz → Skaluj obraz do szerokości 300 - wysokość sama wyjdzie 200 (R.2.1). Pozostałe zdjęcia mają już 300 × 200 px.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Etap 2: Strona HTML

    Kroki 9-14 · po tym etapie masz 18 z 37 kryteriów (pkt)

    Krok 9: Plik sklep.php i kodowanie znaków

    Kryteria: ,

    R.2.2 Witrynę internetową zapisano w pliku PHP o nazwie sklep

    R.2.3 Zastosowano właściwy standard kodowania polskich znaków dla witryny

    ok. 1 min
    Polecenie z arkusza

    Składa się ze strony o nazwie sklep.php. Zastosowany właściwy standard kodowania polskich znaków

    Szkielet HTML5 w pliku sklep.php: lang="pl", <meta charset="UTF-8"> jako pierwszy element <head> (plik zapisany w UTF-8) i <meta name="viewport"> - bez niego telefon pokazałby stronę pomniejszoną jak na monitorze (R.2.2, R.2.3).

    sklep.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  10. Krok 10: Tytuł strony i arkusz stylów

    Kryteria: ,

    R.2.4 Nadano tytuł strony: "Warzywniak"

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

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Warzywniak”. Arkusz stylów w pliku o nazwie styl2.css prawidłowo połączony z kodem strony

    <title> z napisem na karcie przeglądarki (R.2.4) i <link rel="stylesheet" href="styl2.css"> - całe formatowanie pochodzi z tego pliku, bez atrybutów style w HTML-u (R.3.5).

    sklep.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>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  11. Krok 11: Bloki strony i nagłówek banera

    Kryteria: ,

    R.2.5 Wygląd strony zdefiniowano dzieląc ją na bloki: dwa bloki banera, główny i stopka. Zastosowano znaczniki sekcji

    R.2.6 W bloku banera lewego zapisano nagłówek h1: "Internetowy sklep z eko-warzywami"

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: dwa bloki banera obok siebie, blok główny i stopka. W bloku głównym znajdują się bloki produktów generowane przez skrypt. Podział zrealizowany za pomocą znaczników sekcji tak, aby wygląd układu bloków strony po uruchomieniu w przeglądarce, był zgodny z Obrazem 2. Zawartość banera lewego: nagłówek pierwszego stopnia o treści „Internetowy sklep z eko-warzywami”

    Znaczniki mówią, czym jest każda część strony:

    • <header> - nagłówek strony, a w nim dwa bloki banera jako <section> z identyfikatorami banner-left i banner-right; lewy ma jedyne na stronie <h1> (R.2.6),
    • <main> - blok główny, do którego skrypt wstawi produkty,
    • <footer> - stopka.

    Bloki banera, główny i stopka są na miejscu (R.2.5). Znacznik <header> nie zmienia wyglądu, ale pozwoli ustawić oba banery w rzędzie jedną regułą CSS.

    sklep.php PHP zmienione: 11 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowy sklep z eko-warzywami</h1>
            </section>
            <section id="banner-right">
            </section>
        </header>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  12. Krok 12: Baner prawy: lista numerowana z odnośnikiem

    Kryteria: ,

    R.2.7 W bloku banera prawego umieszczono listę numerowaną z trzema elementami, zastosowano znaczniki <ol>, <li> oraz ich zamknięcia

    R.3.2 Umieszczono odnośnik, którego kliknięcie inicjuje otwarcie w nowym oknie strony https://terapiasokami.pl/

    ok. 2 min
    Polecenie z arkusza

    Zawartość banera prawego: Lista numerowana (uporządkowana) z elementami: warzywa, owoce, soki. Element soki jest odnośnikiem prowadzącym do strony o adresie „https://terapiasokami.pl/”, odnośnik otwiera się w osobnym oknie

    • <ol> - lista numerowana, każdy element w <li> z zamknięciem (R.2.7),
    • target="_blank" otwiera stronę w nowej karcie lub oknie (R.3.2),
    • rel="noopener" odcina otwartą stronę od karty sklepu - obca strona nie może wtedy przez window.opener podmienić adresu karty, z której przyszedł użytkownik. Nowe przeglądarki robią to same, ale atrybut zabezpiecza też starsze.
    sklep.php PHP zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowy sklep z eko-warzywami</h1>
            </section>
            <section id="banner-right">
                <ol>
                    <li>warzywa</li>
                    <li>owoce</li>
                    <li><a href="https://terapiasokami.pl/" target="_blank" rel="noopener">soki</a></li>
                </ol>
            </section>
        </header>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  13. Krok 13: Stopka: formularz z etykietami

    Kryteria: , ,

    R.3.3 Formularz wysyła dane metodą POST

    R.3.4 Wciśnięcie przycisku wysyła dane do skryptu (np. typ submit dla input lub button albo button bez typu)

    R.2.9 W stopce umieszczono formularz z dwoma polami edycyjnymi i przyciskiem, zastosowano znacznik <form> oraz jego zamknięcie

    ok. 3 min
    Polecenie z arkusza

    Zawartość stopki: Formularz wysyłający dane do tego samego pliku metodą POST. Pole edycyjne poprzedzone wpisem „Nazwa:”. Pole edycyjne poprzedzone wpisem „Cena:”. Przycisk „Dodaj produkt” wysyłający dane do skryptu 2

    • <form method="post"> bez action wysyła dane do tego samego pliku (R.3.3),
    • napisy „Nazwa:” i „Cena:” w <label for> - kliknięcie napisu ustawia kursor w polu, a czytnik ekranu odczyta, czego pole dotyczy,
    • maxlength="100" - tyle znaków mieści kolumna nazwa (varchar(100)), required - przeglądarka nie wyśle pustego pola,
    • pole ceny zostaje tekstowe, ale inputmode="decimal" pokazuje na telefonie klawiaturę z cyframi i przecinkiem,
    • <button type="submit"> wysyła formularz (R.3.4).

    Formularz ma dwa pola edycyjne i przycisk, z zamknięciem </form> (R.2.9). Atrybuty w przeglądarce to wygoda, nie zabezpieczenie - każde z nich da się ominąć, więc skrypt i tak sprawdzi dane.

    sklep.php PHP zmienione: 7 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowy sklep z eko-warzywami</h1>
            </section>
            <section id="banner-right">
                <ol>
                    <li>warzywa</li>
                    <li>owoce</li>
                    <li><a href="https://terapiasokami.pl/" target="_blank" rel="noopener">soki</a></li>
                </ol>
            </section>
        </header>
        <main>
        </main>
        <footer>
            <form method="post">
                <label for="name">Nazwa:</label>
                <input type="text" id="name" name="nazwa" maxlength="100" required>
                <label for="price">Cena:</label>
                <input type="text" id="price" name="cena" inputmode="decimal" required>
                <button type="submit">Dodaj produkt</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  14. Krok 14: Stopka: autor strony

    ok. 1 min
    Polecenie z arkusza

    Napis o treści „Stronę wykonał: ”, dalej wstawiony numer PESEL zdającego lub w przypadku jego braku inny numer, którym został podpisany arkusz

    Na egzaminie numer PESEL, tu w jego miejscu Bartosz Bryniarski. Napis w akapicie <p> - pod formularzem, jak na obrazie 2.

    sklep.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>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowy sklep z eko-warzywami</h1>
            </section>
            <section id="banner-right">
                <ol>
                    <li>warzywa</li>
                    <li>owoce</li>
                    <li><a href="https://terapiasokami.pl/" target="_blank" rel="noopener">soki</a></li>
                </ol>
            </section>
        </header>
        <main>
        </main>
        <footer>
            <form method="post">
                <label for="name">Nazwa:</label>
                <input type="text" id="name" name="nazwa" maxlength="100" required>
                <label for="price">Cena:</label>
                <input type="text" id="price" name="cena" inputmode="decimal" required>
                <button type="submit">Dodaj produkt</button>
            </form>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  15. Etap 3: Styl CSS

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

    Krok 15: Plik styl2.css: kolory w zmiennych i krój Garamond

    Kryteria:

    R.4.1 Ustawiono domyślny krój czcionki dla całej strony na Garamond (dla selektora * lub body lub html lub kontenera całej strony)

    ok. 2 min
    Polecenie z arkusza

    Domyślne dla całej strony: krój czcionki Garamond

    • zmienne CSS w :root nadają nazwy dwóm kolorom z arkusza; ciemna zieleń #00600F wróci sześć razy (banery, stopka, nagłówek produktu, ramka), więc zmiana odcienia to teraz jedna linia,
    • selektor * z krojem Garamond dla każdego elementu, także pól formularza (R.4.1); serif to krój zapasowy na komputerze bez Garamonda.
    styl2.css CSS nowy plik
    :root {
        --green: #00600F;
        --mint: #C8E6C9;
    }
    * {
        font-family: Garamond, serif;
    }
    Podgląd strony po tym kroku (sklep.php)
  16. Krok 16: Dwa banery obok siebie we flexie

    Kryteria: ,

    R.4.5 Ustawiono szerokość banera lewego: 75%, prawego 25%

    R.4.6 Ustawiono wyrównanie tekstu do środka dla banera lewego

    ok. 3 min
    Polecenie z arkusza

    Dla banera lewego: kolor tła #00600F, biały kolor czcionki, wyrównanie tekstu do środka, szerokość 75%, wysokość 90 px. Dla banera prawego: kolor tła #00600F, biały kolor czcionki, szerokość 25%, wysokość 90 px

    • display: flex na <header> ustawia oba banery w jednym rzędzie, bez float i bez clear pod spodem,
    • wspólne tło, biały tekst i wysokość w jednej regule, a szerokość i wyśrodkowanie osobno (R.4.5, R.4.6).

    Szerokości zostają na blokach, a nie w podziale kolumn rodzica: klucz z 2021 roku sprawdza szerokość ustawioną dla banera.

    styl2.css CSS zmienione: 15 linii
    :root {
        --green: #00600F;
        --mint: #C8E6C9;
    }
    * {
        font-family: Garamond, serif;
    }
    header {
        display: flex;
    }
    #banner-left, #banner-right {
        background-color: var(--green);
        color: white;
        height: 90px;
    }
    #banner-left {
        width: 75%;
        text-align: center;
    }
    #banner-right {
        width: 25%;
    }
    Podgląd strony po tym kroku (sklep.php)
  17. Krok 17: Blok główny z paskiem przewijania

    Kryteria: ,

    R.4.4 Ustawiono wysokość obu bloków banera: 90px, bloku głównego: 600px

    R.4.7 Ustawiono przynajmniej jeden pasek przewijania (przesuwania) dla bloku głównego, np. overflow: scroll; (lub auto)

    ok. 2 min
    Polecenie z arkusza

    Dla bloku głównego: kolor tła #C8E6C9, wysokość 600 px, paski przesuwania (scroll)

    Wysokość banerów i bloku głównego jest ustawiona (R.4.4). overflow: auto pokazuje pasek przewijania wtedy, gdy produkty nie mieszczą się w 600 px - czyli tu zawsze w pionie, a w poziomie wcale; scroll rysowałby oba paski na stałe, także pusty poziomy. Klucz przyjmuje obie wartości (R.4.7).

    styl2.css CSS zmienione: 5 linii
    :root {
        --green: #00600F;
        --mint: #C8E6C9;
    }
    * {
        font-family: Garamond, serif;
    }
    header {
        display: flex;
    }
    #banner-left, #banner-right {
        background-color: var(--green);
        color: white;
        height: 90px;
    }
    #banner-left {
        width: 75%;
        text-align: center;
    }
    #banner-right {
        width: 25%;
    }
    main {
        background-color: var(--mint);
        height: 600px;
        overflow: auto;
    }
    Podgląd strony po tym kroku (sklep.php)
  18. Krok 18: Stopka

    Kryteria: ,

    R.4.2 Ustawiono kolor tła #00600F dla obu bloków banera i stopki, #C8E6C9 dla bloku głównego

    R.4.3 Ustawiono biały kolor czcionki dla obu bloków banera i stopki

    ok. 1 min
    Polecenie z arkusza

    Dla stopki: kolor tła #00600F, biały kolor czcionki

    Ta sama zmienna co w banerach. Tła banerów, stopki i bloku głównego (R.4.2) oraz biały tekst w obu banerach i stopce (R.4.3) są ustawione.

    styl2.css CSS zmienione: 4 linie
    :root {
        --green: #00600F;
        --mint: #C8E6C9;
    }
    * {
        font-family: Garamond, serif;
    }
    header {
        display: flex;
    }
    #banner-left, #banner-right {
        background-color: var(--green);
        color: white;
        height: 90px;
    }
    #banner-left {
        width: 75%;
        text-align: center;
    }
    #banner-right {
        width: 25%;
    }
    main {
        background-color: var(--mint);
        height: 600px;
        overflow: auto;
    }
    footer {
        background-color: var(--green);
        color: white;
    }
    Podgląd strony po tym kroku (sklep.php)
  19. Krok 19: Nagłówki: nazwa i cena produktu

    Kryteria: ,

    R.4.8 Ustawiono styl selektora h5: kolor tła #00600F, biały kolor czcionki, rozmiar czcionki 200%, wyrównanie tekstu do środka, marginesy zewnętrzne (margin) 0px (0 również poprawne bez jednostki)

    R.4.9 Ustawiono styl selektora h2: wyrównanie tekstu do prawej

    ok. 2 min
    Polecenie z arkusza

    Dla selektora nagłówka piątego stopnia: kolor tła #00600F, biały kolor czcionki, rozmiar czcionki 200%, wyrównanie tekstu do środka, marginesy zewnętrzne 0 px. Dla selektora nagłówka drugiego stopnia: wyrównanie tekstu do prawej

    Klucz wymaga stylu selektora znacznika h5 i h2, nie klasy (R.4.8, R.4.9). margin: 0 - przy zerze jednostka nie jest potrzebna.

    styl2.css CSS zmienione: 10 linii
    :root {
        --green: #00600F;
        --mint: #C8E6C9;
    }
    * {
        font-family: Garamond, serif;
    }
    header {
        display: flex;
    }
    #banner-left, #banner-right {
        background-color: var(--green);
        color: white;
        height: 90px;
    }
    #banner-left {
        width: 75%;
        text-align: center;
    }
    #banner-right {
        width: 25%;
    }
    main {
        background-color: var(--mint);
        height: 600px;
        overflow: auto;
    }
    footer {
        background-color: var(--green);
        color: white;
    }
    h5 {
        background-color: var(--green);
        color: white;
        font-size: 200%;
        text-align: center;
        margin: 0;
    }
    h2 {
        text-align: right;
    }
    Podgląd strony po tym kroku (sklep.php)
  20. Krok 20: Blok produktu

    Kryteria:

    R.4.10 Ustawiono styl bloku produktu: marginesy zewnętrzne 10px (margin), obramowanie linia ciągła 1px w kolorze #00600F, szerokość 300px

    ok. 2 min
    Polecenie z arkusza

    Dla bloku produktu generowanego przez skrypt 1: marginesy zewnętrzne 10 px, obramowanie linia ciągła 1 px w kolorze #00600F, szerokość 300 px

    Klasa .product z marginesem, ramką i szerokością równą szerokości zdjęć (R.4.10). Dodatek: display: block dla obrazu w bloku produktu. Obraz jest domyślnie elementem w linii tekstu i pod nim zostaje kilka pikseli na „ogonki” liter - zielony pasek nazwy przylegał do zdjęcia ze szparą. Jako blok obraz styka się z nagłówkiem.

    styl2.css CSS zmienione: 8 linii
    :root {
        --green: #00600F;
        --mint: #C8E6C9;
    }
    * {
        font-family: Garamond, serif;
    }
    header {
        display: flex;
    }
    #banner-left, #banner-right {
        background-color: var(--green);
        color: white;
        height: 90px;
    }
    #banner-left {
        width: 75%;
        text-align: center;
    }
    #banner-right {
        width: 25%;
    }
    main {
        background-color: var(--mint);
        height: 600px;
        overflow: auto;
    }
    footer {
        background-color: var(--green);
        color: white;
    }
    h5 {
        background-color: var(--green);
        color: white;
        font-size: 200%;
        text-align: center;
        margin: 0;
    }
    h2 {
        text-align: right;
    }
    .product {
        margin: 10px;
        border: 1px solid var(--green);
        width: 300px;
    }
    .product img {
        display: block;
    }
    Podgląd strony po tym kroku (sklep.php)
  21. Krok 21: Produkty w rzędach: flex-wrap

    Kryteria:

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z Obrazem 2 w arkuszu egzaminacyjnym (nie uwzględnia się bloków zawierających produkty)

    ok. 2 min
    Polecenie z arkusza

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

    W bloku głównym:

    • display: flex z flex-wrap: wrap - produkty stają obok siebie, a gdy brakuje miejsca, zawijają się do nowego rzędu; na ekranie szerokości ok. 1150 px mieszczą się po trzy, jak na obrazie 2,
    • align-content: flex-start - rzędy przylegają do góry bloku, zamiast rozkładać się równo na 600 px wysokości.

    Szerokość bloku produktu zostaje na nim (300 px), więc kryterium R.4.10 nadal jest spełnione. Układ jest kompletny: banery we flexie, przewijany blok główny i stopka pod nim - bez jednego float (R.3.1).

    styl2.css CSS zmienione: 3 linie
    :root {
        --green: #00600F;
        --mint: #C8E6C9;
    }
    * {
        font-family: Garamond, serif;
    }
    header {
        display: flex;
    }
    #banner-left, #banner-right {
        background-color: var(--green);
        color: white;
        height: 90px;
    }
    #banner-left {
        width: 75%;
        text-align: center;
    }
    #banner-right {
        width: 25%;
    }
    main {
        background-color: var(--mint);
        height: 600px;
        overflow: auto;
        display: flex;
        flex-wrap: wrap;
        align-content: flex-start;
    }
    footer {
        background-color: var(--green);
        color: white;
    }
    h5 {
        background-color: var(--green);
        color: white;
        font-size: 200%;
        text-align: center;
        margin: 0;
    }
    h2 {
        text-align: right;
    }
    .product {
        margin: 10px;
        border: 1px solid var(--green);
        width: 300px;
    }
    .product img {
        display: block;
    }
    Podgląd strony po tym kroku (sklep.php)
  22. Etap 4: Skrypt PHP

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

    Krok 22: Połączenie z bazą dane2 przez PDO

    Kryteria:

    R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy dane2

    ok. 4 min
    Polecenie z arkusza

    Skrypt łączy się z serwerem bazy danych na localhost, użytkownik root bez hasła, baza danych o nazwie dane2

    Cała logika na górze pliku, przed <!DOCTYPE html> - HTML niżej tylko wypisuje gotowe dane. To ważne właśnie w tym arkuszu: formularz stoi w stopce, po liście produktów, więc zapis musi się wykonać, zanim lista zostanie odczytana.

    • funkcja e() - skrót do htmlspecialchars, przyda się przy każdym wypisaniu danych z bazy,
    • new PDO(...) - serwer localhost, baza dane2 i kodowanie w jednym napisie (DSN), użytkownik root, puste hasło (R.5.1),
    • ERRMODE_EXCEPTION - każdy błąd bazy rzuca wyjątek, który łapie catch,
    • FETCH_ASSOC - wiersze jako tablice z nazwami kolumn ($product['cena'] zamiast $row[3]).

    Od tego kroku podgląd to wynik prawdziwego uruchomienia w PHP 8.2.

    sklep.php PHP zmienione: 17 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $pdo = null;
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=dane2;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $exception) {
        $error = 'nie udało się odczytać lub zapisać danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowy sklep z eko-warzywami</h1>
            </section>
            <section id="banner-right">
                <ol>
                    <li>warzywa</li>
                    <li>owoce</li>
                    <li><a href="https://terapiasokami.pl/" target="_blank" rel="noopener">soki</a></li>
                </ol>
            </section>
        </header>
        <main>
        </main>
        <footer>
            <form method="post">
                <label for="name">Nazwa:</label>
                <input type="text" id="name" name="nazwa" maxlength="100" required>
                <label for="price">Cena:</label>
                <input type="text" id="price" name="cena" inputmode="decimal" required>
                <button type="submit">Dodaj produkt</button>
            </form>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  23. Krok 23: Skrypt 1: zapytanie 1 i blok dla każdego produktu

    Kryteria: ,

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

    R.5.5 Skrypt 1 wyświetla wszystkie zwrócone zapytaniem bloki zawierające pobrane z bazy produkty przy zastosowaniu pętli

    ok. 4 min
    Polecenie z arkusza

    Działanie skryptu 1: Wysyła do bazy zapytanie 1. Dla każdego zwróconego zapytaniem wiersza tworzy blok produktu

    Dwie części w dwóch miejscach pliku:

    • na górze $pdo->query(...)->fetchAll() - zapytanie 1 (R.5.3); wszystkie wiersze trafiają do tablicy $products. Zapytanie nie ma danych od użytkownika, więc query wystarczy,
    • w <main> pętla foreach ($products as $product): z zamknięciem endforeach; - ta składnia z dwukropkiem jest czytelniejsza niż klamry, gdy między fragmentami PHP stoi HTML (R.5.5).

    Każdy produkt dostaje <article> - znacznik na samodzielną całość, którą można by wyjąć ze strony (tu: karta produktu). Po zapytaniu 4 w bazie jest dziewięć owoców i warzyw, więc na stronie jest 9 pustych ramek.

    sklep.php PHP zmienione: 5 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $pdo = null;
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=dane2;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $products = $pdo->query('SELECT nazwa, ilosc, opis, cena, zdjecie FROM produkty WHERE Rodzaje_id = 1 OR Rodzaje_id = 2;')->fetchAll();
    } catch (PDOException $exception) {
        $error = 'nie udało się odczytać lub zapisać danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowy sklep z eko-warzywami</h1>
            </section>
            <section id="banner-right">
                <ol>
                    <li>warzywa</li>
                    <li>owoce</li>
                    <li><a href="https://terapiasokami.pl/" target="_blank" rel="noopener">soki</a></li>
                </ol>
            </section>
        </header>
        <main>
            <?php foreach ($products as $product): ?>
                <article class="product">
                </article>
            <?php endforeach; ?>
        </main>
        <footer>
            <form method="post">
                <label for="name">Nazwa:</label>
                <input type="text" id="name" name="nazwa" maxlength="100" required>
                <label for="price">Cena:</label>
                <input type="text" id="price" name="cena" inputmode="decimal" required>
                <button type="submit">Dodaj produkt</button>
            </form>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  24. Krok 24: Skrypt 1: zawartość bloku produktu

    Kryteria: ,

    R.5.4 Skrypt 1 tworzy blok, w którym znajdują się elementy: obraz, nazwa w nagłówku h5, treść "opis: <opis>" i "na stanie: <ilosc>" w osobnych paragrafach oraz "<cena> zł" w nagłówku h2. Wartości wszystkich elementów zostały pobrane z bazy

    R.2.8 Przynajmniej jeden obraz ma tekst alternatywny "warzywniak" (umieszczony skryptem lub HTML)

    ok. 4 min
    Polecenie z arkusza

    Blok produktu zawiera: Obraz o nazwie pobranej z bazy danych i tekście alternatywnym „warzywniak”. Nagłówek piątego stopnia z nazwą pobraną z bazy. Paragraf (akapit) o treści „opis: <opis>”. Paragraf o treści „na stanie: <ilosc>”. Nagłówek drugiego stopnia o treści „<cena> zł”, gdzie pola umieszczone w nawiasach < > oznaczają dane pobrane z bazy danych

    Blok jest zwykłym HTML-em, a dane wchodzą w miejsca oznaczone <?= ... ?> (skrót od <?php echo ... ?>):

    • src z kolumny zdjecie, stały tekst alternatywny warzywniak (R.2.8),
    • nazwa w <h5>, opis i stan w osobnych akapitach, cena z dopiskiem „zł” w <h2> (R.5.4).

    Nie trzeba ukośników przed cudzysłowami jak w echo, a każda wartość przechodzi przez e() - nazwa z formularza nie stanie się kodem HTML. Pierwszy blok: gruszka, „opis: Klaps”, „na stanie: 20”, „2.56 zł”, jak na obrazie 2.

    sklep.php PHP zmienione: 5 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $pdo = null;
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=dane2;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $products = $pdo->query('SELECT nazwa, ilosc, opis, cena, zdjecie FROM produkty WHERE Rodzaje_id = 1 OR Rodzaje_id = 2;')->fetchAll();
    } catch (PDOException $exception) {
        $error = 'nie udało się odczytać lub zapisać danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowy sklep z eko-warzywami</h1>
            </section>
            <section id="banner-right">
                <ol>
                    <li>warzywa</li>
                    <li>owoce</li>
                    <li><a href="https://terapiasokami.pl/" target="_blank" rel="noopener">soki</a></li>
                </ol>
            </section>
        </header>
        <main>
            <?php foreach ($products as $product): ?>
                <article class="product">
                    <img src="<?= e($product['zdjecie']) ?>" alt="warzywniak">
                    <h5><?= e($product['nazwa']) ?></h5>
                    <p>opis: <?= e($product['opis']) ?></p>
                    <p>na stanie: <?= e($product['ilosc']) ?></p>
                    <h2><?= e($product['cena']) ?> zł</h2>
                </article>
            <?php endforeach; ?>
        </main>
        <footer>
            <form method="post">
                <label for="name">Nazwa:</label>
                <input type="text" id="name" name="nazwa" maxlength="100" required>
                <label for="price">Cena:</label>
                <input type="text" id="price" name="cena" inputmode="decimal" required>
                <button type="submit">Dodaj produkt</button>
            </form>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  25. Krok 25: Skrypt 2: odczyt i sprawdzenie danych z formularza

    ok. 4 min
    Polecenie z arkusza

    Działanie skryptu 2: Jeśli wypełniono formularz, pobiera z niego dane

    Pod połączeniem, a przed odczytem produktów:

    • $_SERVER['REQUEST_METHOD'] === 'POST' - formularz wysłano; przy zwykłym wejściu na stronę ten blok się nie wykona,
    • $_POST['nazwa'] ?? '' - wartość pola, a gdy jej brak - pusty napis zamiast ostrzeżenia; trim zdejmuje spacje z brzegów,
    • cena: przecinek zamieniony na kropkę, a filter_var(..., FILTER_VALIDATE_FLOAT) zwraca liczbę albo false, gdy tekst liczbą nie jest,
    • pusta nazwa, cena niebędąca liczbą albo ujemna - komunikat w $error, który stopka pokaże nad formularzem.

    Podgląd: wysłana cena „tanio” - skrypt odrzucił dane i wyświetlił komunikat.

    sklep.php PHP zmienione: 10 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $pdo = null;
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=dane2;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $name = trim($_POST['nazwa'] ?? '');
            $price = filter_var(str_replace(',', '.', trim($_POST['cena'] ?? '')), FILTER_VALIDATE_FLOAT);
            if ($name === '' || $price === false || $price < 0) {
                $error = 'podaj nazwę i cenę produktu, np. 4,50';
            }
        }
        $products = $pdo->query('SELECT nazwa, ilosc, opis, cena, zdjecie FROM produkty WHERE Rodzaje_id = 1 OR Rodzaje_id = 2;')->fetchAll();
    } catch (PDOException $exception) {
        $error = 'nie udało się odczytać lub zapisać danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowy sklep z eko-warzywami</h1>
            </section>
            <section id="banner-right">
                <ol>
                    <li>warzywa</li>
                    <li>owoce</li>
                    <li><a href="https://terapiasokami.pl/" target="_blank" rel="noopener">soki</a></li>
                </ol>
            </section>
        </header>
        <main>
            <?php foreach ($products as $product): ?>
                <article class="product">
                    <img src="<?= e($product['zdjecie']) ?>" alt="warzywniak">
                    <h5><?= e($product['nazwa']) ?></h5>
                    <p>opis: <?= e($product['opis']) ?></p>
                    <p>na stanie: <?= e($product['ilosc']) ?></p>
                    <h2><?= e($product['cena']) ?> zł</h2>
                </article>
            <?php endforeach; ?>
        </main>
        <footer>
            <?php if (isset($error)): ?>
                <p class="error"><?= e($error) ?></p>
            <?php endif; ?>
            <form method="post">
                <label for="name">Nazwa:</label>
                <input type="text" id="name" name="nazwa" maxlength="100" required>
                <label for="price">Cena:</label>
                <input type="text" id="price" name="cena" inputmode="decimal" required>
                <button type="submit">Dodaj produkt</button>
            </form>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  26. Krok 26: Skrypt 2: zapis produktu zapytaniem 4 z parametrami

    Kryteria:

    R.5.6 Skrypt 2 wysyła do bazy zmodyfikowane zapytanie 4, pola nazwa i cena są pobrane z formularza. Należy sprawdzić w bazie danych czy wiersz został dodany do bazy (uwaga: dodany rekord może być widoczny na 2 stronie w phpMyAdmin)

    ok. 3 min
    Polecenie z arkusza

    Wysyła zmodyfikowane zapytanie 4 do bazy danych. Dane nazwa i cena są pobrane z formularza, reszta danych zapytania 4 nie ulega zmianie

    Zapytanie 4, w którym nazwę i cenę zastępują parametry :name i :price; reszta bez zmian - owoce (1), warzywa-rolnik (4), ilość 10, pusty opis, owoce.jpg:

    • $pdo->prepare(...) - baza dostaje samą treść zapytania,
    • execute([...]) - osobno przychodzą wartości; nigdy nie staną się częścią kodu SQL, więc nazwa z apostrofem zapisze się poprawnie, a próba wstrzyknięcia SQL zapisze się jako zwykły tekst.

    Zapis stoi przed odczytem produktów, więc nowy produkt widać od razu, na końcu listy. Sprawdzenie, jak każe klucz (R.5.6): po wysłaniu „ jablko ” i „4,50” w tabeli produkty jest wiersz jablko z ceną 4.5.

    Typowe błędy

    Nazwa z literami „ł” czy „ś” kończy się błędem bazy: tabele z archiwum mają kodowanie latin1. Wersja nowoczesna złapie ten wyjątek i pokaże komunikat (następny krok), ale zapisu nie będzie - klucz sprawdza import pliku bez zmian, więc tabeli nie przerabiaj.

    sklep.php PHP zmienione: 3 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $pdo = null;
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=dane2;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $name = trim($_POST['nazwa'] ?? '');
            $price = filter_var(str_replace(',', '.', trim($_POST['cena'] ?? '')), FILTER_VALIDATE_FLOAT);
            if ($name === '' || $price === false || $price < 0) {
                $error = 'podaj nazwę i cenę produktu, np. 4,50';
            } else {
                $statement = $pdo->prepare("INSERT INTO produkty (Rodzaje_id, Producenci_id, nazwa, ilosc, opis, cena, zdjecie) VALUES (1, 4, :name, 10, '', :price, 'owoce.jpg');");
                $statement->execute(['name' => $name, 'price' => $price]);
            }
        }
        $products = $pdo->query('SELECT nazwa, ilosc, opis, cena, zdjecie FROM produkty WHERE Rodzaje_id = 1 OR Rodzaje_id = 2;')->fetchAll();
    } catch (PDOException $exception) {
        $error = 'nie udało się odczytać lub zapisać danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowy sklep z eko-warzywami</h1>
            </section>
            <section id="banner-right">
                <ol>
                    <li>warzywa</li>
                    <li>owoce</li>
                    <li><a href="https://terapiasokami.pl/" target="_blank" rel="noopener">soki</a></li>
                </ol>
            </section>
        </header>
        <main>
            <?php foreach ($products as $product): ?>
                <article class="product">
                    <img src="<?= e($product['zdjecie']) ?>" alt="warzywniak">
                    <h5><?= e($product['nazwa']) ?></h5>
                    <p>opis: <?= e($product['opis']) ?></p>
                    <p>na stanie: <?= e($product['ilosc']) ?></p>
                    <h2><?= e($product['cena']) ?> zł</h2>
                </article>
            <?php endforeach; ?>
        </main>
        <footer>
            <?php if (isset($error)): ?>
                <p class="error"><?= e($error) ?></p>
            <?php endif; ?>
            <form method="post">
                <label for="name">Nazwa:</label>
                <input type="text" id="name" name="nazwa" maxlength="100" required>
                <label for="price">Cena:</label>
                <input type="text" id="price" name="cena" inputmode="decimal" required>
                <button type="submit">Dodaj produkt</button>
            </form>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  27. Krok 27: Zamknięcie połączenia i komunikat o błędzie bazy

    Kryteria:

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

    ok. 3 min
    Polecenie z arkusza

    Po wykonaniu operacji na bazie danych skrypt zamyka połączenie z serwerem.

    PDO nie ma funkcji close - połączenie zamyka się, gdy znika ostatnie odwołanie do obiektu. Dlatego w bloku finally stoją $statement = null i $pdo = null. finally wykonuje się po try zawsze - po udanym zapisie i odczycie, ale też po wyjątku - więc zamknięcie jest ostatnią operacją na bazie w każdej gałęzi (R.5.2). Produkty są już w tablicy, więc pętla w <main> działa bez połączenia.

    Gdy baza nie odpowie albo odrzuci zapis, catch zapisuje komunikat w $error, a stopka go pokazuje. Treść wyjątku zostaje na serwerze: komunikat bazy zdradzałby nazwy tabel i kolumn. Sprawdzone na prawdziwym PHP: przy złym haśle i przy nazwie „jabłko”, której tabela latin1 nie przyjmuje, strona wyświetla banery, komunikat i formularz.

    sklep.php PHP zmienione: 3 linie
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $pdo = null;
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=dane2;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $name = trim($_POST['nazwa'] ?? '');
            $price = filter_var(str_replace(',', '.', trim($_POST['cena'] ?? '')), FILTER_VALIDATE_FLOAT);
            if ($name === '' || $price === false || $price < 0) {
                $error = 'podaj nazwę i cenę produktu, np. 4,50';
            } else {
                $statement = $pdo->prepare("INSERT INTO produkty (Rodzaje_id, Producenci_id, nazwa, ilosc, opis, cena, zdjecie) VALUES (1, 4, :name, 10, '', :price, 'owoce.jpg');");
                $statement->execute(['name' => $name, 'price' => $price]);
            }
        }
        $products = $pdo->query('SELECT nazwa, ilosc, opis, cena, zdjecie FROM produkty WHERE Rodzaje_id = 1 OR Rodzaje_id = 2;')->fetchAll();
    } catch (PDOException $exception) {
        $error = 'nie udało się odczytać lub zapisać danych, spróbuj ponownie za chwilę';
    } finally {
        $statement = null;
        $pdo = null;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowy sklep z eko-warzywami</h1>
            </section>
            <section id="banner-right">
                <ol>
                    <li>warzywa</li>
                    <li>owoce</li>
                    <li><a href="https://terapiasokami.pl/" target="_blank" rel="noopener">soki</a></li>
                </ol>
            </section>
        </header>
        <main>
            <?php foreach ($products as $product): ?>
                <article class="product">
                    <img src="<?= e($product['zdjecie']) ?>" alt="warzywniak">
                    <h5><?= e($product['nazwa']) ?></h5>
                    <p>opis: <?= e($product['opis']) ?></p>
                    <p>na stanie: <?= e($product['ilosc']) ?></p>
                    <h2><?= e($product['cena']) ?> zł</h2>
                </article>
            <?php endforeach; ?>
        </main>
        <footer>
            <?php if (isset($error)): ?>
                <p class="error"><?= e($error) ?></p>
            <?php endif; ?>
            <form method="post">
                <label for="name">Nazwa:</label>
                <input type="text" id="name" name="nazwa" maxlength="100" required>
                <label for="price">Cena:</label>
                <input type="text" id="price" name="cena" inputmode="decimal" required>
                <button type="submit">Dodaj produkt</button>
            </form>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  28. Krok 28: Odświeżenie po zapisie nie dodaje produktu drugi raz

    ok. 3 min

    Dodatek spoza arkusza - klucz go nie ocenia. Strona po wysłaniu formularza jest wynikiem żądania POST, więc odświeżenie (F5) wysyła dane ponownie - i w sklepie pojawia się drugi taki sam produkt. Wzorzec PRG (Post/Redirect/Get):

    • po udanym execute skrypt zapamiętuje $saved = true,
    • po bloku finally (połączenie jest już zamknięte) header('Location: sklep.php') każe przeglądarce wczytać stronę jeszcze raz zwykłym żądaniem GET,
    • exit jest konieczne - header tylko ustawia nagłówek i nie zatrzymuje skryptu.

    Sprawdzone na prawdziwym PHP: wysłanie formularza daje odpowiedź 302, a strona wczytana po przekierowaniu pokazuje nowy produkt; w bazie jest dokładnie jeden taki wiersz.

    sklep.php PHP zmienione: 5 linii
    <?php
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $pdo = null;
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=dane2;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $name = trim($_POST['nazwa'] ?? '');
            $price = filter_var(str_replace(',', '.', trim($_POST['cena'] ?? '')), FILTER_VALIDATE_FLOAT);
            if ($name === '' || $price === false || $price < 0) {
                $error = 'podaj nazwę i cenę produktu, np. 4,50';
            } else {
                $statement = $pdo->prepare("INSERT INTO produkty (Rodzaje_id, Producenci_id, nazwa, ilosc, opis, cena, zdjecie) VALUES (1, 4, :name, 10, '', :price, 'owoce.jpg');");
                $statement->execute(['name' => $name, 'price' => $price]);
                $saved = true;
            }
        }
        $products = $pdo->query('SELECT nazwa, ilosc, opis, cena, zdjecie FROM produkty WHERE Rodzaje_id = 1 OR Rodzaje_id = 2;')->fetchAll();
    } catch (PDOException $exception) {
        $error = 'nie udało się odczytać lub zapisać danych, spróbuj ponownie za chwilę';
    } finally {
        $statement = null;
        $pdo = null;
    }
    if (isset($saved)) {
        header('Location: sklep.php');
        exit;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Warzywniak</title>
        <link rel="stylesheet" href="styl2.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowy sklep z eko-warzywami</h1>
            </section>
            <section id="banner-right">
                <ol>
                    <li>warzywa</li>
                    <li>owoce</li>
                    <li><a href="https://terapiasokami.pl/" target="_blank" rel="noopener">soki</a></li>
                </ol>
            </section>
        </header>
        <main>
            <?php foreach ($products as $product): ?>
                <article class="product">
                    <img src="<?= e($product['zdjecie']) ?>" alt="warzywniak">
                    <h5><?= e($product['nazwa']) ?></h5>
                    <p>opis: <?= e($product['opis']) ?></p>
                    <p>na stanie: <?= e($product['ilosc']) ?></p>
                    <h2><?= e($product['cena']) ?> zł</h2>
                </article>
            <?php endforeach; ?>
        </main>
        <footer>
            <?php if (isset($error)): ?>
                <p class="error"><?= e($error) ?></p>
            <?php endif; ?>
            <form method="post">
                <label for="name">Nazwa:</label>
                <input type="text" id="name" name="nazwa" maxlength="100" required>
                <label for="price">Cena:</label>
                <input type="text" id="price" name="cena" inputmode="decimal" required>
                <button type="submit">Dodaj produkt</button>
            </form>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (sklep.php)
  29. Etap 5: Porównanie z wersją podstawową

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

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

    ok. 3 min

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

    wersja podstawowawersja nowoczesna
    baneryfloat: left dwóch sekcji, clear pod nimi<header> z display: flex
    produkty<section> z float: left<article> w <main> z flex-wrap
    kolorywpisane w każdej regulezmienne --green, --mint
    formularznapisy, <input type="submit">label for, required, maxlength, <button>
    bibliotekamysqli (tabela 1 arkusza)PDO z wyjątkami
    gdzie jest skryptpołączenie i zapis na górze, zapytanie 1 w bloku głównymwszystko na górze, w HTML-u tylko foreach i <?= ?>
    cena „4,50”błąd zapytania, poprawione w dobrych praktykachprzecinek zamieniany, FILTER_VALIDATE_FLOAT
    zapis produktudane wklejone w treść INSERT, potem zapytanie przygotowaneparametry :name, :price od początku
    wypisywanie danychhtmlspecialchars dopisane na końcue() od pierwszej linii
    zamknięcie bazymysqli_close po liście produktów$pdo = null w finally
    po zapisieF5 dodaje produkt ponownieprzekierowanie (PRG)

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie wywraca się na cenie z przecinkiem ani na apostrofie i nie dubluje produktów przy odświeżeniu - a klucz przyjmuje obie.

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

  30. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz w folderze z numerem PESEL lub w przypadku jego braku w folderze z innym numerem, którym został podpisany arkusz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowałeś poprawność działania witryny. Nagraj płytę z rezultatami pracy.

    W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była strona. W folderze z numerem zdającego: egzamin.jpg, kw1.png - kw4.png, kwerendy.txt, przeglądarka.txt, sklep.php, styl2.css i dziewięć zdjęć z archiwum (z przyciętym arbuz.jpg). Nagraj płytę, sprawdź jej odczyt i opisz ją numerem PESEL.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.

Potwierdzenie

Informacja