Przejdź do głównej treści
INF.03-02-23.06-SGCzerwiec 2023 · 42 kroki · ok. 80 min · 39 kryteriów z klucza

Rozwiązanie krok po kroku: Portal hurtowni papierniczej (Nowoczesny kod (flex, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza sklep z czterema zapytaniami, przeskalowana grafika z przezroczystym tłem i strona index.php z banerem, tabelą cen, formularzem liczącym wartość zakupów, kontaktem i stopką - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • flexbox zamiast float: trzy panele stoją w znaczniku <main> ustawionym w rząd, bez clear przy stopce. Szerokości 25%, 50% i 25% zostają na samych panelach - tak ocenia je klucz,
  • zmienne CSS dla kolorów z arkusza, wiersze formularza w blokach zamiast <br>, pole liczbowe przyjmujące tylko liczby od 1 i pamiętające wpisaną wartość,
  • PHP na PDO: wszystkie dane strona pobiera na górze pliku index.php, przed HTML-em, w bloku try, a w HTML-u zostają tylko pętle i wypisywanie,
  • sprawdzanie danych z formularza: liczba sztuk przez filter_var (liczba całkowita od 1), nazwa towaru z listą w stałej PRODUCTS, a do bazy nazwa trafia zapytaniem przygotowanym,
  • bezpieczne wypisywanie (htmlspecialchars w funkcji e()) i komunikaty zamiast błędów PHP przy pustym polu, towarze spoza listy i awarii bazy.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafika pochodzą z prawdziwego archiwum materialy2, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 na bazie po zapytaniach 1-4 - od Skryptu 2 po wysłaniu formularza z obrazu 2 (Zeszyt 32 kartki, 2 sztuki).

Jak liczą się punkty. Klucz ma 39 kryteriów (R.1.1 - R.5.8), 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-8 · po tym etapie masz 6 z 39 kryteriów (pkt)

    Krok 1: Baza danych sklep

    ok. 2 min
    Polecenie z arkusza

    Utwórz nową bazę danych o nazwie sklep

    Rozpakuj materialy2.7z z pulpitu (hasło Zad@n!e#2 - wielkie Z, małpa zamiast „a”, wykrzyknik zamiast „i”) do folderu z numerem zdającego: sklep.sql i zakupy.png. W phpMyAdmin Nowa → sklep albo zapytaniem:

    CREATE DATABASE sklep CHARACTER SET utf8 COLLATE utf8_polish_ci;
    

    Arkusz nie podaje porównywania, więc wybór należy do Ciebie. Tabele z sklep.sql mają zestaw znaków zapisany we własnej definicji, więc ustawienie bazy ich nie zmienia.

    Typowe błędy

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

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

    ok. 2 min
    Polecenie z arkusza

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

    Zaznaczona baza sklep, Import → sklep.sql → Wykonaj. Dwie tabele z obrazu 1: dostawcy (3 rekordy) i towary (10 rekordów). idDostawcy wskazuje id dostawcy - to relacja z arkusza. Cena ma typ float, dlatego wyniki pokazują 4.5, a nie 4.50.

  3. Krok 3: Zrzut ekranu import.jpg

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

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

    Cały ekran z paskiem zadań, w JPEG, pod nazwą import. Na zrzucie komunikat o zakończonym imporcie albo obie tabele w drzewie bazy sklep.

    Typowe błędy

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

  4. Krok 4: Plik kwerendy.txt i zasady zrzutów

    ok. 1 min
    Polecenie z arkusza

    Wykonaj zapytania SQL działające na bazie danych sklep. Zapytania zapisz w pliku kwerendy.txt. Wykonaj zrzuty ekranu przedstawiające wyniki działania kwerend. Zrzuty zapisz w formacie JPEG i nadaj im nazwy kwerenda1, kwerenda2, kwerenda3, kwerenda4. Zrzuty powinny obejmować cały ekran monitora z widocznym paskiem zadań oraz zawierać wykonane zapytanie

    Każde zapytanie wykonaj w zakładce SQL bazy sklep i skopiuj do kwerendy.txt. phpMyAdmin pokazuje treść wykonanego zapytania nad wynikiem, więc zrzut całego ekranu od razu ją zawiera. Nazwy: kwerenda1.jpg - kwerenda4.jpg. Każda klauzula w osobnej linii - łatwiej przeczytać i poprawić.

  5. Krok 5: Zapytanie 1: cztery pierwsze towary

    Kryteria: ,

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

    R.1.3 Zapisano zapytanie 1 wybierające jedynie pola nazwa i cena z tabeli towary i wyświetlające jedynie cztery pierwsze zwrócone zapytaniem rekordy. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, cena FROM towary LIMIT 4; oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym widoczne są dokładnie 4 rekordy: Zeszyt 60 kartek, 4.5; Zeszyt 32 kartki, 1.2; Cyrkiel, 12.4; Linijka 30 cm, 7.2

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pola nazwa i cena z tabeli towary i wyświetlające jedynie cztery pierwsze zwrócone zapytaniem rekordy

    • ORDER BY id - kolejność według klucza głównego. Bez sortowania baza może zwrócić wiersze w dowolnej kolejności (zależy od planu zapytania), a wtedy „cztery pierwsze” nie są niczym ustalone,
    • LIMIT 4 - z posortowanego wyniku zostają cztery pierwsze rekordy.

    Wynik: Zeszyt 60 kartek 4.5, Zeszyt 32 kartki 1.2, Cyrkiel 12.4, Linijka 30 cm 7.2 - jak w kluczu. Zrzut: kwerenda1.jpg. To zapytanie wyśle Skrypt 1.

    Typowe błędy

    WHERE id <= 4 daje na tej bazie ten sam wynik, ale nie jest „czterema pierwszymi rekordami” - klucz wymaga zapytania uniwersalnego.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT nazwa, cena
    FROM towary
    ORDER BY id
    LIMIT 4;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 2: cena ekierki

    Kryteria:

    R.1.4 Zapisano zapytanie 2 wybierające jedynie pole cena z tabeli towary dla towaru Ekierka. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT cena FROM towary WHERE nazwa = 'Ekierka'; oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym widoczna jest jedynie cena 5.5

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pole cena z tabeli towary dla towaru Ekierka

    Jedno pole i warunek na całej nazwie. Wynik: 5.5. Zrzut: kwerenda2.jpg. Skrypt 2 wyśle to zapytanie z parametrem w miejscu nazwy.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT nazwa, cena
    FROM towary
    ORDER BY id
    LIMIT 4;
    
    -- Zapytanie 2
    SELECT cena
    FROM towary
    WHERE nazwa = 'Ekierka';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 3: nowa nazwa dostawcy

    Kryteria:

    R.1.5 Zapisano zapytanie 3 aktualizujące dane w tabeli dostawcy , rekord o id równym 2 w polu nazwa przyjmuje nową wartość „Artykuly szkolne”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: UPDATE dostawcy SET nazwa = "Artykuly szkolne" WHERE id = 2; oraz wynik działania zapytania udokumentowano zrzutem wskazującym na poprawne wykonanie zapytania

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: aktualizujące dane w tabeli dostawcy, rekord o id równym 2 w polu nazwa przyjmuje nową wartość „Artykuly szkolne”

    UPDATE ... SET ... WHERE id = 2 - nowa wartość tylko w rekordzie 2 (dotąd „Art. szkolne”), bez polskiej litery, dokładnie jak w arkuszu. Warunek po kluczu głównym trafia zawsze w jeden rekord. phpMyAdmin odpowiada „1 wiersz zmieniony” - to pokazuje zrzut kwerenda3.jpg.

    Typowe błędy

    UPDATE bez WHERE zmienia nazwę wszystkich trzech dostawców.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT nazwa, cena
    FROM towary
    ORDER BY id
    LIMIT 4;
    
    -- Zapytanie 2
    SELECT cena
    FROM towary
    WHERE nazwa = 'Ekierka';
    
    -- Zapytanie 3
    UPDATE dostawcy
    SET nazwa = 'Artykuly szkolne'
    WHERE id = 2;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Krok 8: Zapytanie 4: towary w promocji i poza nią

    Kryteria:

    R.1.6 Zapisano zapytanie 4 wybierające jedynie pole promocja z tabeli towary oraz zliczające liczbę towarów objętych i towarów nieobjętych promocją. Nazwa kolumny (alias) zliczającej podsumowanie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT promocja, COUNT(*) AS podsumowanie FROM towary GROUP BY promocja; (w COUNT możliwa również nazwa dowolnego pola tabeli) oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym widoczne są jedynie dwa rekordy z danymi: 0 i 6 oraz 1 i 4

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie pole promocja dla wszystkich rekordów z tabeli towary oraz zliczające liczbę towarów objętych i towarów nieobjętych promocją. Nazwa kolumny (alias) dla zliczenia towarów: podsumowanie.

    GROUP BY promocja - jedna grupa dla wartości 0 i jedna dla 1, a COUNT(*) liczy wiersze grupy pod aliasem podsumowanie. Wynik: 0 - 6 i 1 - 4. Zrzut: kwerenda4.jpg.

    Typowe błędy

    Bez GROUP BY wychodzi jeden wiersz z liczbą 10.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT nazwa, cena
    FROM towary
    ORDER BY id
    LIMIT 4;
    
    -- Zapytanie 2
    SELECT cena
    FROM towary
    WHERE nazwa = 'Ekierka';
    
    -- Zapytanie 3
    UPDATE dostawcy
    SET nazwa = 'Artykuly szkolne'
    WHERE id = 2;
    
    -- Zapytanie 4
    SELECT promocja, COUNT(*) AS podsumowanie
    FROM towary
    GROUP BY promocja;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Etap 2: Grafika i strona HTML

    Kroki 9-22 · po tym etapie masz 19 z 39 kryteriów (pkt)

    Krok 9: zakupy.png: 180 px szerokości z przezroczystością

    Kryteria:

    R.2.1 Obraz z pliku zakupy.png przeskalowano do szerokości 180 px, odpowiadająca temu wysokość wynosi 162 px (± 5 px) oraz obraz ma tło przezroczyste

    ok. 2 min
    Polecenie z arkusza

    Plik zakupy.png, wypakowany z archiwum, należy przeskalować z zachowaniem proporcji tak, aby jego szerokość wynosiła dokładnie 180 px. Plik należy zapisać z zachowaniem przezroczystości pod tą samą nazwą

    Grafika jest taka sama jak w wersji podstawowej. Obraz ma 377 × 340 px. W GIMP-ie Obraz → Skaluj obraz, łańcuch proporcji zamknięty, szerokość 180 - wysokość wyjdzie 162 px. Eksportuj jako zakupy.png: PNG zachowuje przezroczystość.

    Typowe błędy

    Eksport do JPEG albo spłaszczenie obrazu zamienia przezroczyste tło na białe - R.2.1 wymaga przezroczystości.

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

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

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

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

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

    Kryteria:

    R.2.3 Nadano stronie tytuł "Hurtownia szkolna”

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Hurtownia szkolna”

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

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

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

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

    Całe formatowanie pochodzi ze styl.css - bez atrybutów style w HTML-u (R.3.6).

    Typowe błędy

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

    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>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  13. Krok 13: Bloki: baner, trzy panele w main, stopka

    Kryteria:

    R.2.4 Wygląd strony zdefiniowano dzieląc ją na bloki: baner, panele lewy, środkowy, prawy oraz stopkę. Zastosowano znaczniki sekcji

    ok. 2 min
    Polecenie z arkusza

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

    Baner <header>, stopka <footer>, a trzy panele we wspólnym <main> - to główna treść strony i pojemnik, który w CSS ustawi je w rząd:

    • lewy <section id="prices"> - ceny,
    • środkowy <section id="order"> - koszt zakupów,
    • prawy <aside id="contact"> - kontakt, treść dodatkowa obok głównej.

    <section> i <aside> to znaczniki sekcji HTML5 (R.2.4). Identyfikatory mówią, co jest w panelu, a nie gdzie stoi.

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

    ok. 1 min
    Polecenie z arkusza

    Zawartość banera: nagłówek pierwszego stopnia o treści „Hurtownia z najlepszymi cenami”

    <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>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
            </section>
            <section id="order">
            </section>
            <aside id="contact">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Panel lewy: nagłówek i tabela

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku lewego: Nagłówek drugiego stopnia o treści „Nasze ceny”. Tabela składająca się z czterech wierszy i dwóch kolumn o zawartości generowanej skryptem 1

    <h2> i pusta <table> - wiersze <tr> z dwiema komórkami <td> wypisze Skrypt 1. Na obrazie 2 tabela nie ma wiersza nagłówkowego, więc nie ma też <th>.

    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>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
                </table>
            </section>
            <section id="order">
            </section>
            <aside id="contact">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Panel środkowy: nagłówek i formularz POST

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku środkowego: Nagłówek drugiego stopnia o treści „Koszt zakupów”. Formularz zgodny z obrazem 2, wysyłający dane do tego samego pliku metodą bezpieczną.

    method="post" - „metodę bezpieczną” arkusz rozumie jako POST: dane idą w treści żądania, a nie w adresie strony (w specyfikacji HTTP słowo „bezpieczna” znaczy co innego, ale klucz R.3.2 wymaga POST). Bez action formularz wysyła dane do tego samego pliku. Z <h1>, trzema <h2>, <h4> w stopce i akapitem w prawym panelu złoży się R.2.5.

    Typowe błędy

    method="get" też wysyła dane, ale klucz R.3.2 wymaga POST.

    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>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                </form>
            </section>
            <aside id="contact">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  17. Krok 17: Napis „wybierz artykuł: ” i lista rozwijalna

    Kryteria:

    R.2.7 W bloku środkowym umieszczono jedną listę rozwijaną, zawierającą nazwy produktów: Zeszyt 60 kartek, Zeszyt 32 kartki, Cyrkiel, Linijka 30 cm. Zastosowano znaczniki <select>, <option>

    ok. 2 min
    Polecenie z arkusza

    Napis „wybierz artykuł: ”. Listę rozwijalną zawierającą nazwy towarów z tabeli: Zeszyt 60 kartek, Zeszyt 32 kartki, Cyrkiel, Linijka 30 cm.

    • napis to <label for="product"> - kliknięcie napisu przenosi kursor do listy, a czytnik ekranu odczyta go jako nazwę listy,
    • <select name="product"> z czterema <option> (R.2.7) - opcja bez value wysyła swój tekst, czyli nazwę towaru,
    • napis z listą stoją we wspólnym <div> - blok sam zaczyna nową linię, więc kolejne pole stanie pod spodem bez <br>.
    Typowe błędy

    Literówka w nazwie („Linijka 30cm” bez spacji) sprawi, że zapytanie w Skrypcie 2 nic nie znajdzie dla tego towaru.

    index.php PHP zmienione: 9 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
                            <option>Zeszyt 60 kartek</option>
                            <option>Zeszyt 32 kartki</option>
                            <option>Cyrkiel</option>
                            <option>Linijka 30 cm</option>
                        </select>
                    </div>
                </form>
            </section>
            <aside id="contact">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Pole liczbowe z napisem „liczba sztuk: ”

    Kryteria:

    R.3.4 Pole edycyjne jest typu numerycznego

    ok. 2 min
    Polecenie z arkusza

    Pole edycyjne typu numerycznego poprzedzone napisem „liczba sztuk: ”

    type="number" przyjmuje tylko liczby (R.3.4). min="1" i required - przeglądarka nie wyśle formularza z pustym polem, zerem ani liczbą ujemną i sama pokaże komunikat. To wygoda dla użytkownika, nie zabezpieczenie: żądanie da się wysłać z pominięciem formularza, dlatego liczbę sprawdzi jeszcze Skrypt 2.

    index.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
                            <option>Zeszyt 60 kartek</option>
                            <option>Zeszyt 32 kartki</option>
                            <option>Cyrkiel</option>
                            <option>Linijka 30 cm</option>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required>
                    </div>
                </form>
            </section>
            <aside id="contact">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  19. Krok 19: Przycisk „OBLICZ” i blok wyniku

    Kryteria: ,

    R.2.8 W bloku środkowym umieszczono pole edycyjne oraz przycisk OBLICZ

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

    ok. 1 min
    Polecenie z arkusza

    Przycisk „OBLICZ”, którego wybranie powoduje przesłanie danych do skryptu 2

    <button type="submit"> wysyła formularz (R.2.8, R.3.3). <button>, a nie <input type="submit">: arkusz nadaje marginesy selektorowi input, a przycisk zbudowany z input dostałby je razem z polem.

    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>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
                            <option>Zeszyt 60 kartek</option>
                            <option>Zeszyt 32 kartki</option>
                            <option>Cyrkiel</option>
                            <option>Linijka 30 cm</option>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required>
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
            </section>
            <aside id="contact">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  20. Krok 20: Blok na wynik skryptu 2

    ok. 1 min
    Polecenie z arkusza

    Wyniki działania skryptu 2

    Pusty <div id="result"> pod formularzem - wypełni go Skrypt 2 napisem z kwotą.

    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>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
                            <option>Zeszyt 60 kartek</option>
                            <option>Zeszyt 32 kartki</option>
                            <option>Cyrkiel</option>
                            <option>Linijka 30 cm</option>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required>
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  21. Krok 21: Panel prawy: nagłówek, obraz i odsyłacz pocztowy

    Kryteria: ,

    R.2.9 W bloku prawym umieszczono obraz zakupy.png z tekstem alternatywnym "hurtownia"

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

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku prawego: Nagłówek drugiego stopnia o treści „Kontakt”. Obraz zakupy.png z tekstem alternatywnym „hurtownia”. Paragraf (akapit) „e-mail: [email protected]”, gdzie [email protected] jest odsyłaczem pocztowym łączącym z tym adresem

    Obraz z tekstem alternatywnym z arkusza (R.2.9), pod nim akapit z odnośnikiem mailto: - kliknięcie otwiera program pocztowy z wpisanym odbiorcą (R.3.5).

    Typowe błędy

    href="[email protected]" bez mailto: przeglądarka potraktuje jak plik w bieżącym folderze.

    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>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
                            <option>Zeszyt 60 kartek</option>
                            <option>Zeszyt 32 kartki</option>
                            <option>Cyrkiel</option>
                            <option>Linijka 30 cm</option>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required>
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <img src="zakupy.png" alt="hurtownia">
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Stopka: nagłówek czwartego stopnia

    Kryteria:

    R.2.5 Zapisano w banerze nagłówek h1 "Hurtownia z najlepszymi cenami”, w panelach przynajmniej jeden z nagłówków h2: "Nasze ceny”, "Koszt zakupów”, "Kontakt" i nagłówek h4 z numerem zdającego w stopce oraz w panelu prawym zapisano paragraf

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: nagłówek czwartego stopnia o treści „Witrynę wykonał: ”, dalej wstawiony numer zdającego

    <h4> w <footer>. Na egzaminie po dwukropku wpisujesz swój numer zdającego (PESEL), tu stoi autor rozwiązania. Strona ma już komplet nagłówków i akapit z kryterium R.2.5.

    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>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
                            <option>Zeszyt 60 kartek</option>
                            <option>Zeszyt 32 kartki</option>
                            <option>Cyrkiel</option>
                            <option>Linijka 30 cm</option>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required>
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <img src="zakupy.png" alt="hurtownia">
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
            </aside>
        </main>
        <footer>
            <h4>Witrynę wykonał: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  23. Etap 3: Styl CSS

    Kroki 23-31 · po tym etapie masz 30 z 39 kryteriów (pkt)

    Krok 23: Plik styl.css: zmienne, krój Georgia i wyśrodkowanie

    Kryteria:

    R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (*) krój czcionki Georgia i wyrównanie tekstu do środka

    ok. 2 min
    Polecenie z arkusza

    Domyślne formatowanie wszystkich selektorów: krój czcionki Georgia i wyrównanie tekstu do środka

    Trzy kolory z arkusza w zmiennych - #90CAF9 pojawia się aż w czterech regułach (panel, dwie ramki, kolor wiersza), więc zmiana odcienia to jedna linia. Selektor * z Georgią i krojem zapasowym serif (R.4.1).

    styl.css CSS nowy plik
    :root {
        --blue: #4BA3C7;
        --light-blue: #90CAF9;
        --grey: #EFEFEF;
    }
    
    * {
        font-family: Georgia, serif;
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Baner i stopka

    Kryteria: ,

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

    R.4.6 Ustawiono rozmiar czcionki dla banera i stopki na 120%

    ok. 1 min
    Polecenie z arkusza

    Dla banera i stopki: kolor tła #4BA3C7, biały kolor czcionki, wysokość 80 px, rozmiar czcionki 120%

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

    styl.css CSS zmienione: 8 linii
    :root {
        --blue: #4BA3C7;
        --light-blue: #90CAF9;
        --grey: #EFEFEF;
    }
    
    * {
        font-family: Georgia, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--blue);
        color: white;
        height: 80px;
        font-size: 120%;
    }
    Podgląd strony po tym kroku (index.php)
  25. Krok 25: Panele lewy i prawy

    ok. 1 min
    Polecenie z arkusza

    Dla panelu lewego i prawego: kolor tła #EFEFEF wysokość 400 px, szerokość 25%

    Selektory identyfikatorów (#). Szerokość zostaje na samych panelach, tak jak ją ocenia klucz - pojemnik flex liczy procenty od <main>, czyli od całej szerokości strony.

    Typowe błędy

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

    styl.css CSS zmienione: 7 linii
    :root {
        --blue: #4BA3C7;
        --light-blue: #90CAF9;
        --grey: #EFEFEF;
    }
    
    * {
        font-family: Georgia, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--blue);
        color: white;
        height: 80px;
        font-size: 120%;
    }
    
    #prices,
    #contact {
        background-color: var(--grey);
        height: 400px;
        width: 25%;
    }
    Podgląd strony po tym kroku (index.php)
  26. Krok 26: Panel środkowy

    Kryteria: , ,

    R.4.2 Ustawiono kolor tła dla banera i stopki na #4BA3C7, panelu lewego i prawego na #EFEFEF, panelu środkowego na #90CAF9

    R.4.4 Ustawiono wysokość banera i stopki na 80px oraz trzech paneli na 400px

    R.4.5 Ustawiono szerokość panelu lewego i prawego na 25%, panelu środkowego na 50%

    ok. 1 min
    Polecenie z arkusza

    Dla panelu środkowego: kolor tła #90CAF9, wysokość 400 px, szerokość 50%

    25% + 50% + 25% = 100%. Ta reguła domyka kolory tła (R.4.2), wysokości (R.4.4) i szerokości paneli (R.4.5).

    styl.css CSS zmienione: 6 linii
    :root {
        --blue: #4BA3C7;
        --light-blue: #90CAF9;
        --grey: #EFEFEF;
    }
    
    * {
        font-family: Georgia, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--blue);
        color: white;
        height: 80px;
        font-size: 120%;
    }
    
    #prices,
    #contact {
        background-color: var(--grey);
        height: 400px;
        width: 25%;
    }
    
    #order {
        background-color: var(--light-blue);
        height: 400px;
        width: 50%;
    }
    Podgląd strony po tym kroku (index.php)
  27. Krok 27: Układ: panele w jednym rzędzie

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

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

    display: flex na <main> ustawia trzy panele w rzędzie, a baner i stopka stoją nad i pod nim same z siebie - bez float i bez clear (R.3.1).

    footer h4 { margin-top: 0; } - górny margines nagłówka stopki „wychodzi” poza stopkę (marginesy stykających się bloków się sklejają) i między panelami a stopką zostałby biały pas. W wersji z float pochłania go clear, tu trzeba go wyzerować.

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

    Typowe błędy

    Margines albo ramka dodana panelom przekracza 100% szerokości - flex ściśnie wtedy panele i żaden nie będzie miał dokładnie 25% ani 50%.

    styl.css CSS zmienione: 8 linii
    :root {
        --blue: #4BA3C7;
        --light-blue: #90CAF9;
        --grey: #EFEFEF;
    }
    
    * {
        font-family: Georgia, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--blue);
        color: white;
        height: 80px;
        font-size: 120%;
    }
    
    #prices,
    #contact {
        background-color: var(--grey);
        height: 400px;
        width: 25%;
    }
    
    #order {
        background-color: var(--light-blue);
        height: 400px;
        width: 50%;
    }
    
    main {
        display: flex;
    }
    
    footer h4 {
        margin-top: 0;
    }
    Podgląd strony po tym kroku (index.php)
  28. Krok 28: Tabela: margines lewy, szerokość, obramowanie

    ok. 1 min
    Polecenie z arkusza

    Dla selektora tabeli: margines zewnętrzny lewy 10 px, szerokość 90%, obramowanie linią ciągłą o szerokości 1 px i kolorze #90CAF9

    „Margines zewnętrzny lewy” to margin-left. Selektor znacznika table - uwaga arkusza: style tabeli, komórek i pola wyłącznie selektorami znaczników.

    styl.css CSS zmienione: 6 linii
    :root {
        --blue: #4BA3C7;
        --light-blue: #90CAF9;
        --grey: #EFEFEF;
    }
    
    * {
        font-family: Georgia, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--blue);
        color: white;
        height: 80px;
        font-size: 120%;
    }
    
    #prices,
    #contact {
        background-color: var(--grey);
        height: 400px;
        width: 25%;
    }
    
    #order {
        background-color: var(--light-blue);
        height: 400px;
        width: 50%;
    }
    
    main {
        display: flex;
    }
    
    footer h4 {
        margin-top: 0;
    }
    
    table {
        margin-left: 10px;
        width: 90%;
        border: 1px solid var(--light-blue);
    }
    Podgląd strony po tym kroku (index.php)
  29. Krok 29: Komórki tabeli

    Kryteria: ,

    R.4.7 Ustawiono styl selektora table: margines zewnętrzny lewy (np. margin-left) 10px, szerokość 90% oraz ustawiono dla selektora komórki tabeli marginesy wewnętrzne (padding) 5px

    R.4.8 Ustawiono obramowanie tabeli oraz komórki tabeli na 1px solid #90CAF9

    ok. 1 min
    Polecenie z arkusza

    Dla selektora komórek tabeli: marginesy wewnętrzne 5 px, obramowanie komórek tabeli linią ciągłą o szerokości 1 px i kolorze #90CAF9

    Komórka to td, marginesy wewnętrzne to padding. Bez border-collapse każda komórka ma osobną ramkę z odstępem od ramki tabeli - tak wygląda tabela na obrazie 2. Z poprzednią regułą: R.4.7 i R.4.8.

    Typowe błędy

    Reguła #prices td albo klasa komórki nie spełnia R.4.7 - klucz wymaga samego selektora znacznika.

    styl.css CSS zmienione: 5 linii
    :root {
        --blue: #4BA3C7;
        --light-blue: #90CAF9;
        --grey: #EFEFEF;
    }
    
    * {
        font-family: Georgia, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--blue);
        color: white;
        height: 80px;
        font-size: 120%;
    }
    
    #prices,
    #contact {
        background-color: var(--grey);
        height: 400px;
        width: 25%;
    }
    
    #order {
        background-color: var(--light-blue);
        height: 400px;
        width: 50%;
    }
    
    main {
        display: flex;
    }
    
    footer h4 {
        margin-top: 0;
    }
    
    table {
        margin-left: 10px;
        width: 90%;
        border: 1px solid var(--light-blue);
    }
    
    td {
        padding: 5px;
        border: 1px solid var(--light-blue);
    }
    Podgląd strony po tym kroku (index.php)
  30. Krok 30: Wiersz tabeli pod kursorem

    Kryteria:

    R.4.9 Po wskazaniu kursorem myszy dowolnego wiersza tabeli zmienia się kolor tekstu w tym wierszu na #90CAF9 (np. za pomocą tr:hover)

    ok. 1 min
    Polecenie z arkusza

    Po wskazaniu kursorem myszy dowolnego wiersza tabeli zostaje zmieniony kolor tekstu w tym wierszu na #90CAF9

    tr:hover - kolor czcionki dziedziczą obie komórki wiersza (R.4.9).

    styl.css CSS zmienione: 4 linie
    :root {
        --blue: #4BA3C7;
        --light-blue: #90CAF9;
        --grey: #EFEFEF;
    }
    
    * {
        font-family: Georgia, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--blue);
        color: white;
        height: 80px;
        font-size: 120%;
    }
    
    #prices,
    #contact {
        background-color: var(--grey);
        height: 400px;
        width: 25%;
    }
    
    #order {
        background-color: var(--light-blue);
        height: 400px;
        width: 50%;
    }
    
    main {
        display: flex;
    }
    
    footer h4 {
        margin-top: 0;
    }
    
    table {
        margin-left: 10px;
        width: 90%;
        border: 1px solid var(--light-blue);
    }
    
    td {
        padding: 5px;
        border: 1px solid var(--light-blue);
    }
    
    tr:hover {
        color: var(--light-blue);
    }
    Podgląd strony po tym kroku (index.php)
  31. Krok 31: Pole edycyjne: marginesy górny i dolny

    Kryteria:

    R.4.10 Ustawiono margines zewnętrzny górny i dolny (np. margin-top i margin-bottom) 15px dla selektora input

    ok. 1 min
    Polecenie z arkusza

    Dla selektora pola edycyjnego: margines zewnętrzny górny i dolny 15 px

    margin-block: 15px - jedna właściwość ustawia oba marginesy w kierunku bloku, czyli górny i dolny (w tekście poziomym), i niczego nie zmienia po bokach. Selektor znacznika input (R.4.10).

    Typowe błędy

    margin: 15px ustawia także marginesy boczne - klucz mówi o górnym i dolnym. Gdy nie masz pewności co do margin-block, wpisz margin-top i margin-bottom - to samo, tylko dłużej.

    styl.css CSS zmienione: 4 linie
    :root {
        --blue: #4BA3C7;
        --light-blue: #90CAF9;
        --grey: #EFEFEF;
    }
    
    * {
        font-family: Georgia, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--blue);
        color: white;
        height: 80px;
        font-size: 120%;
    }
    
    #prices,
    #contact {
        background-color: var(--grey);
        height: 400px;
        width: 25%;
    }
    
    #order {
        background-color: var(--light-blue);
        height: 400px;
        width: 50%;
    }
    
    main {
        display: flex;
    }
    
    footer h4 {
        margin-top: 0;
    }
    
    table {
        margin-left: 10px;
        width: 90%;
        border: 1px solid var(--light-blue);
    }
    
    td {
        padding: 5px;
        border: 1px solid var(--light-blue);
    }
    
    tr:hover {
        color: var(--light-blue);
    }
    
    input {
        margin-block: 15px;
    }
    Podgląd strony po tym kroku (index.php)
  32. Etap 4: Skrypty PHP

    Kroki 32-40 · po tym etapie masz 39 z 39 kryteriów (pkt)

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

    Kryteria: ,

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

    R.5.2 Ostatnią operacją na bazie jest zamknięcie połączenia z serwerem bazy danych

    ok. 4 min
    Polecenie z arkusza

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

    Wszystkie dane strona pobierze przed HTML-em, w jednym bloku PHP na samej górze index.php - kod rozmawiający z bazą jest w jednym miejscu, a połączenie zamyka się, zanim przeglądarka dostanie pierwszy znacznik.

    • new PDO(...) - serwer localhost, baza sklep, kodowanie utf8mb4, użytkownik root bez hasła (R.5.1). ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, zamiast zwracać false; FETCH_ASSOC - wiersze z nazwami kolumn,
    • $pdo = null - połączenie PDO zamyka się, gdy znika ostatnie odwołanie do obiektu. Stoi na końcu bloku, więc pozostanie ostatnią operacją na bazie (R.5.2),
    • try / catch (PDOException ...) - gdy baza nie działa, uczeń zobaczy zwykłe zdanie, a nie komunikat PHP ze szczegółami serwera.

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

    index.php PHP zmienione: 12 linii
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
                            <option>Zeszyt 60 kartek</option>
                            <option>Zeszyt 32 kartki</option>
                            <option>Cyrkiel</option>
                            <option>Linijka 30 cm</option>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required>
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <img src="zakupy.png" alt="hurtownia">
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
            </aside>
        </main>
        <footer>
            <h4>Witrynę wykonał: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  33. Krok 33: Skrypt 1: zapytanie 1

    Kryteria:

    R.5.3 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1

    ok. 2 min
    Polecenie z arkusza

    Skrypt 1: Wysyła do bazy danych zapytanie 1

    Zapytanie bez danych od użytkownika, więc wystarczy query. fetchAll() zwraca od razu wszystkie cztery wiersze jako tablice z kluczami nazwa i cena. W catch lista dostaje wartość pustą, żeby HTML niżej nie trafił na niezdefiniowaną zmienną (R.5.3). Na stronie jeszcze nic nie widać.

    index.php PHP zmienione: 4 linie
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - cztery pierwsze towary
        $priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $priceList = [];
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
                            <option>Zeszyt 60 kartek</option>
                            <option>Zeszyt 32 kartki</option>
                            <option>Cyrkiel</option>
                            <option>Linijka 30 cm</option>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required>
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <img src="zakupy.png" alt="hurtownia">
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
            </aside>
        </main>
        <footer>
            <h4>Witrynę wykonał: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>

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

  34. Krok 34: Skrypt 1: wiersze tabeli z nazwą i ceną

    Kryteria: ,

    R.2.6 W bloku lewym umieszczono tabelę o 2 kolumnach (w kodzie HTML lub skrypcie), zastosowano znaczniki <table>, <tr>, <td>

    R.5.4 Skrypt 1 wyświetla dane zwrócone zapytaniem w tabeli, każdy rekord w osobnym wierszu

    ok. 3 min
    Polecenie z arkusza

    Wyświetla dane pobrane zapytaniem wewnątrz komórek tabeli. W pierwszej kolumnie tabeli wypisywane są nazwy czterech towarów, w drugiej kolumnie odpowiadające im ceny (zgodnie z obrazem 2)

    • foreach z dwukropkiem i endforeach - jedna pętla na rekord, każdy rekord to osobny wiersz <tr> (R.5.4),
    • pola po nazwach kolumn ($item['nazwa'], $item['cena']), nie po numerach - zmiana kolejności pól w SELECT niczego nie psuje,
    • funkcja e() (na górze pliku) przepuszcza każdą daną z bazy przez htmlspecialchars - znak < albo & w nazwie towaru nie rozbije strony. <?= ... ?> to skrót od <?php echo ... ?>.

    Tabela ma cztery wiersze i dwie kolumny (R.2.6): Zeszyt 60 kartek 4.5, Zeszyt 32 kartki 1.2, Cyrkiel 12.4, Linijka 30 cm 7.2 - jak na obrazie 2. Po najechaniu kursorem wiersz zmienia kolor.

    index.php PHP zmienione: 11 linii
    <?php
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - cztery pierwsze towary
        $priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $priceList = [];
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
    <?php foreach ($priceList as $item): ?>
                    <tr>
                        <td><?= e($item['nazwa']) ?></td>
                        <td><?= e($item['cena']) ?></td>
                    </tr>
    <?php endforeach; ?>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
                            <option>Zeszyt 60 kartek</option>
                            <option>Zeszyt 32 kartki</option>
                            <option>Cyrkiel</option>
                            <option>Linijka 30 cm</option>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required>
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <img src="zakupy.png" alt="hurtownia">
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
            </aside>
        </main>
        <footer>
            <h4>Witrynę wykonał: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  35. Krok 35: Skrypt 2: nazwa towaru z listy

    Kryteria:

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

    ok. 4 min
    Polecenie z arkusza

    Skrypt 2: Pobiera z formularza nazwę towaru wybraną w liście rozwijalnej

    • $_POST['product'] ?? null - wybór z tablicy globalnej $_POST (R.5.5: klucz wymaga instrukcji czytającej tablicę odpowiadającą metodzie formularza, dlatego tu nie ma filter_input, które czyta dane z pominięciem tej tablicy). Operator ?? daje null, gdy formularza jeszcze nie wysłano - bez ostrzeżenia „Undefined array key”,
    • const PRODUCTS - cztery nazwy z arkusza w jednej stałej. Z niej pętla wypisuje opcje listy (wynik ten sam co wcześniej, R.2.7), a in_array(..., true) sprawdza, czy wysłana nazwa jest na tej liście (biała lista). Listę rozwijalną da się obejść, wysyłając żądanie z pominięciem formularza - sprawdzenie po stronie serwera nie,
    • selected przy opcji, która była wybrana - po wysłaniu lista pokazuje ten sam towar, jak na obrazie 2.

    Od tego kroku podgląd pokazuje stronę po wysłaniu formularza z obrazu 2 (Zeszyt 32 kartki, 2 sztuki).

    index.php PHP zmienione: 9 linii
    <?php
    const PRODUCTS = ['Zeszyt 60 kartek', 'Zeszyt 32 kartki', 'Cyrkiel', 'Linijka 30 cm'];
    
    // Skrypt 2: dane z formularza
    $product = $_POST['product'] ?? null;
    $isKnownProduct = in_array($product, PRODUCTS, true);
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - cztery pierwsze towary
        $priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $priceList = [];
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
    <?php foreach ($priceList as $item): ?>
                    <tr>
                        <td><?= e($item['nazwa']) ?></td>
                        <td><?= e($item['cena']) ?></td>
                    </tr>
    <?php endforeach; ?>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
    <?php foreach (PRODUCTS as $option): ?>
                            <option<?= $option === $product ? ' selected' : '' ?>><?= e($option) ?></option>
    <?php endforeach; ?>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required>
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <img src="zakupy.png" alt="hurtownia">
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
            </aside>
        </main>
        <footer>
            <h4>Witrynę wykonał: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  36. Krok 36: Skrypt 2: liczba sztuk z pola

    ok. 3 min
    Polecenie z arkusza

    Pobiera z formularza liczbę wpisaną w polu edycyjnym

    • filter_var(..., FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]) - wartość z $_POST zamienia się w liczbę całkowitą, ale tylko wtedy, gdy jest liczbą całkowitą od 1 wzwyż. Puste pole, „-3”, „2,5” albo „abc” dają false - nic z tego nie trafi do mnożenia,
    • pole liczbowe dostaje value ze sprawdzoną liczbą - po kliknięciu OBLICZ zostaje w nim 2, jak na obrazie 2. Wypisywana jest wartość po filter_var, czyli zawsze liczba całkowita albo nic - surowy tekst z formularza nie wraca na stronę.
    Typowe błędy

    Samo $_POST['amount'] w mnożeniu przy pustym polu kończy się w PHP 8 błędem krytycznym „Unsupported operand types: string * string” - strona urywa się w połowie.

    index.php PHP zmienione: 2 linie
    <?php
    const PRODUCTS = ['Zeszyt 60 kartek', 'Zeszyt 32 kartki', 'Cyrkiel', 'Linijka 30 cm'];
    
    // Skrypt 2: dane z formularza
    $product = $_POST['product'] ?? null;
    $isKnownProduct = in_array($product, PRODUCTS, true);
    $amount = filter_var($_POST['amount'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - cztery pierwsze towary
        $priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $priceList = [];
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
    <?php foreach ($priceList as $item): ?>
                    <tr>
                        <td><?= e($item['nazwa']) ?></td>
                        <td><?= e($item['cena']) ?></td>
                    </tr>
    <?php endforeach; ?>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
    <?php foreach (PRODUCTS as $option): ?>
                            <option<?= $option === $product ? ' selected' : '' ?>><?= e($option) ?></option>
    <?php endforeach; ?>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required value="<?= $amount === false ? '' : $amount ?>">
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <img src="zakupy.png" alt="hurtownia">
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
            </aside>
        </main>
        <footer>
            <h4>Witrynę wykonał: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  37. Krok 37: Skrypt 2: zapytanie 2 z wybraną nazwą

    Kryteria:

    R.5.6 Skrypt 2 zawiera instrukcję wysyłającą do bazy danych zapytanie 2 zmodyfikowane tak, że w sekcji warunku nazwa towaru jest zgodna z wyborem w liście rozwijanej

    ok. 3 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 2, zmodyfikowane w ten sposób, że w sekcji warunku nazwa towaru jest zgodna z wyborem w liście rozwijalnej

    Zapytanie przygotowane: w miejscu nazwy stoi parametr :name, a wartość podaje execute(["name" => $product]). Baza dostaje treść zapytania i dane osobno, więc nazwa z formularza nigdy nie stanie się częścią kodu SQL (R.5.6).

    • zapytanie wykonuje się tylko dla towaru z listy i poprawnej liczby sztuk,
    • fetchColumn() zwraca samą cenę pierwszego wiersza albo false, gdy wiersza nie ma,
    • $statement = null zwalnia zapytanie przed zamknięciem połączenia.
    index.php PHP zmienione: 10 linii
    <?php
    const PRODUCTS = ['Zeszyt 60 kartek', 'Zeszyt 32 kartki', 'Cyrkiel', 'Linijka 30 cm'];
    
    // Skrypt 2: dane z formularza
    $product = $_POST['product'] ?? null;
    $isKnownProduct = in_array($product, PRODUCTS, true);
    $amount = filter_var($_POST['amount'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - cztery pierwsze towary
        $priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();
    
        // Skrypt 2: zapytanie 2 - cena wybranego towaru
        $price = false;
        if ($isKnownProduct && $amount !== false) {
            $statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');
            $statement->execute(['name' => $product]);
            $price = $statement->fetchColumn();
            $statement = null;
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $priceList = [];
        $price = false;
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
    <?php foreach ($priceList as $item): ?>
                    <tr>
                        <td><?= e($item['nazwa']) ?></td>
                        <td><?= e($item['cena']) ?></td>
                    </tr>
    <?php endforeach; ?>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
    <?php foreach (PRODUCTS as $option): ?>
                            <option<?= $option === $product ? ' selected' : '' ?>><?= e($option) ?></option>
    <?php endforeach; ?>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required value="<?= $amount === false ? '' : $amount ?>">
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <img src="zakupy.png" alt="hurtownia">
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
            </aside>
        </main>
        <footer>
            <h4>Witrynę wykonał: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>

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

  38. Krok 38: Skrypt 2: wartość zakupów

    Kryteria:

    R.5.7 Skrypt 2 oblicza wartość zakupów przez pomnożenie ceny zwróconej zapytaniem z liczbą pobraną z pola edycyjnego

    ok. 2 min
    Polecenie z arkusza

    Oblicza wartość zakupów na podstawie ceny towaru zwróconej zapytaniem i liczby sztuk tego towaru pobranej z pola edycyjnego

    Wartość zakupów to cena razy liczba sztuk: $price * $amount (R.5.7). round(..., 2) zaokrągla do groszy - mnożenie liczb zmiennoprzecinkowych potrafi dać np. 3.5999999999999996 zamiast 3.6. Dla zeszytu za 1.2 i 2 sztuk: 2.4.

    index.php PHP zmienione: 3 linie
    <?php
    const PRODUCTS = ['Zeszyt 60 kartek', 'Zeszyt 32 kartki', 'Cyrkiel', 'Linijka 30 cm'];
    
    // Skrypt 2: dane z formularza
    $product = $_POST['product'] ?? null;
    $isKnownProduct = in_array($product, PRODUCTS, true);
    $amount = filter_var($_POST['amount'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - cztery pierwsze towary
        $priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();
    
        // Skrypt 2: zapytanie 2 - cena wybranego towaru
        $price = false;
        if ($isKnownProduct && $amount !== false) {
            $statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');
            $statement->execute(['name' => $product]);
            $price = $statement->fetchColumn();
            $statement = null;
        }
        if ($price !== false) {
            $total = round($price * $amount, 2);    // wartość zakupów
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $priceList = [];
        $price = false;
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
    <?php foreach ($priceList as $item): ?>
                    <tr>
                        <td><?= e($item['nazwa']) ?></td>
                        <td><?= e($item['cena']) ?></td>
                    </tr>
    <?php endforeach; ?>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
    <?php foreach (PRODUCTS as $option): ?>
                            <option<?= $option === $product ? ' selected' : '' ?>><?= e($option) ?></option>
    <?php endforeach; ?>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required value="<?= $amount === false ? '' : $amount ?>">
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <img src="zakupy.png" alt="hurtownia">
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
            </aside>
        </main>
        <footer>
            <h4>Witrynę wykonał: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>

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

  39. Krok 39: Skrypt 2: napis z kwotą pod formularzem

    Kryteria:

    R.5.8 Kwota wyświetlona jest pod formularzem w formacie: „wartość zakupów: <obliczona_kwota>”

    ok. 2 min
    Polecenie z arkusza

    Obliczona kwota wyświetlana jest pod formularzem w formacie: „wartość zakupów: <obliczona_kwota>”, zgodnie z obrazem 2.

    Napis z arkusza i kwota w bloku #result pod formularzem: „wartość zakupów: 2.4” (R.5.8). if ($price !== false) - przed wysłaniem formularza blok zostaje pusty. Format liczby jak na obrazie 2 (kropka dziesiętna).

    index.php PHP zmienione: 3 linie
    <?php
    const PRODUCTS = ['Zeszyt 60 kartek', 'Zeszyt 32 kartki', 'Cyrkiel', 'Linijka 30 cm'];
    
    // Skrypt 2: dane z formularza
    $product = $_POST['product'] ?? null;
    $isKnownProduct = in_array($product, PRODUCTS, true);
    $amount = filter_var($_POST['amount'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - cztery pierwsze towary
        $priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();
    
        // Skrypt 2: zapytanie 2 - cena wybranego towaru
        $price = false;
        if ($isKnownProduct && $amount !== false) {
            $statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');
            $statement->execute(['name' => $product]);
            $price = $statement->fetchColumn();
            $statement = null;
        }
        if ($price !== false) {
            $total = round($price * $amount, 2);    // wartość zakupów
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $priceList = [];
        $price = false;
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
    <?php foreach ($priceList as $item): ?>
                    <tr>
                        <td><?= e($item['nazwa']) ?></td>
                        <td><?= e($item['cena']) ?></td>
                    </tr>
    <?php endforeach; ?>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
    <?php foreach (PRODUCTS as $option): ?>
                            <option<?= $option === $product ? ' selected' : '' ?>><?= e($option) ?></option>
    <?php endforeach; ?>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required value="<?= $amount === false ? '' : $amount ?>">
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
    <?php if ($price !== false): ?>
                    wartość zakupów: <?= e($total) ?>
    <?php endif; ?>
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <img src="zakupy.png" alt="hurtownia">
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
            </aside>
        </main>
        <footer>
            <h4>Witrynę wykonał: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  40. Krok 40: Puste pole, towar spoza listy, awaria bazy: komunikaty

    ok. 3 min

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

    • zła liczba sztuk (puste pole, „-3”, „2.5”, „abc” - wysłane z pominięciem formularza, bo required i min blokują je w przeglądarce) - „Podaj liczbę sztuk większą od zera.”,
    • towar spoza listy (np. ' OR '1'='1) - zapytanie się nie wykonuje, w bloku „Wybierz towar z listy.”,
    • awaria bazy (catch) - komunikat z role="alert", a reszta strony działa dalej.

    Sprawdzone na prawdziwym PHP 8.2: cztery towary z listy (np. Cyrkiel × 3 = 37.2), cztery złe liczby, napis spoza listy i złe hasło do bazy - za każdym razem cała strona, bez ostrzeżeń PHP.

    index.php PHP zmienione: 7 linii
    <?php
    const PRODUCTS = ['Zeszyt 60 kartek', 'Zeszyt 32 kartki', 'Cyrkiel', 'Linijka 30 cm'];
    
    // Skrypt 2: dane z formularza
    $product = $_POST['product'] ?? null;
    $isKnownProduct = in_array($product, PRODUCTS, true);
    $amount = filter_var($_POST['amount'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - cztery pierwsze towary
        $priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();
    
        // Skrypt 2: zapytanie 2 - cena wybranego towaru
        $price = false;
        if ($isKnownProduct && $amount !== false) {
            $statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');
            $statement->execute(['name' => $product]);
            $price = $statement->fetchColumn();
            $statement = null;
        }
        if ($price !== false) {
            $total = round($price * $amount, 2);    // wartość zakupów
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $priceList = [];
        $price = false;
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Hurtownia szkolna</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Hurtownia z najlepszymi cenami</h1>
        </header>
        <main>
            <section id="prices">
                <h2>Nasze ceny</h2>
                <table>
    <?php foreach ($priceList as $item): ?>
                    <tr>
                        <td><?= e($item['nazwa']) ?></td>
                        <td><?= e($item['cena']) ?></td>
                    </tr>
    <?php endforeach; ?>
                </table>
            </section>
            <section id="order">
                <h2>Koszt zakupów</h2>
                <form method="post">
                    <div>
                        <label for="product">wybierz artykuł: </label>
                        <select id="product" name="product">
    <?php foreach (PRODUCTS as $option): ?>
                            <option<?= $option === $product ? ' selected' : '' ?>><?= e($option) ?></option>
    <?php endforeach; ?>
                        </select>
                    </div>
                    <div>
                        <label for="amount">liczba sztuk: </label>
                        <input type="number" id="amount" name="amount" min="1" required value="<?= $amount === false ? '' : $amount ?>">
                    </div>
                    <button type="submit">OBLICZ</button>
                </form>
                <div id="result">
    <?php if (isset($databaseError)): ?>
                    <p role="alert"><?= e($databaseError) ?></p>
    <?php elseif ($product !== null && !$isKnownProduct): ?>
                    <p role="alert">Wybierz towar z listy.</p>
    <?php elseif ($product !== null && $amount === false): ?>
                    <p role="alert">Podaj liczbę sztuk większą od zera.</p>
    <?php endif; ?>
    <?php if ($price !== false): ?>
                    wartość zakupów: <?= e($total) ?>
    <?php endif; ?>
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <img src="zakupy.png" alt="hurtownia">
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
            </aside>
        </main>
        <footer>
            <h4>Witrynę wykonał: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>

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

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

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

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

    ok. 3 min

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

    wersja podstawowawersja nowoczesna
    układfloat: left paneli i clear stopki<main> z display: flex
    formularz<br> między polamiwiersze w <div>, min, required, zapamiętany wybór
    zapytanie 1LIMIT 4ORDER BY id LIMIT 4 - kolejność ustalona
    bibliotekamysqli (tabela 1 arkusza)PDO z wyjątkami
    gdzie są zapytaniaskrypty w tabeli i pod formularzemjeden blok na górze index.php
    liczba sztuk$_POST wprost do mnożenia$_POST przez filter_var (liczba od 1)
    nazwa w zapytaniu 2wklejona z $_POSTbiała lista + parametr :name
    wypisywaniedane z bazy wproste() - htmlspecialchars
    puste pole, zła nazwabłąd krytyczny PHP / „wartość zakupów: 0”komunikaty

    Klucz wymaga tu odczytu z tablicy $_POST (R.5.5), dlatego obie wersje ją czytają. Dane logowania do bazy w prawdziwym projekcie trzyma się zwykle w osobnym pliku (np. config.php), ale arkusz każe umieścić skrypt w pliku index.php.

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie wywraca się na pustym polu i podmienionych danych. Obie biblioteki klucz przyjmuje.

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

  42. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz w folderze z numerem zdającego plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Nagraj płytę z rezultatami pracy. W folderze z numerem zdającego powinny się znajdować następujące pliki: import.jpg, index.php, kwerenda1.jpg, kwerenda2.jpg, kwerenda3.jpg, kwerenda4.jpg, kwerendy.txt, przeglądarka.txt, styl.css, zakupy.png oraz ewentualnie inne przygotowane pliki.

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

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.