Przejdź do głównej treści
E.14-04-20.06-SGCzerwiec 2020 · 29 kroków · ok. 82 min · 39 kryteriów z klucza

Rozwiązanie krok po kroku: Sklep z ozdobami choinkowymi (Nowoczesny kod (flex, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza sklep z tabelą zamowienia, użytkownikiem klient i zamówieniem Jana Kowalskiego, ozdoby 300 × 300 px, animacja zmieniająca obraz co 2 sekundy oraz strony galeria.html i zamowienie.php - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • flex zamiast float: trzy panele stoją we wspólnym znaczniku <main>, stopka nie potrzebuje clear. Szerokości 25%, 50% i 25% zostają na panelach, bo tam sprawdza je klucz z 2020 roku,
  • zmienne CSS dla kolorów arkusza, odnośniki w <nav>, formularz z etykietami <label for>,
  • kolumna id autonumerowana już w kwerendzie 2 - formularz nie ma pola na numer, więc numer nadaje baza,
  • skrypt JavaScript w osobnym pliku galeria.js, z addEventListener i textContent zamiast atrybutów onmouseover i innerHTML,
  • PHP na PDO na górze zamowienie.php: sprawdzenie danych na serwerze, zapytanie przygotowane, komunikat dla klienta i przekierowanie po zapisie - odświeżenie strony nie złoży zamówienia drugi raz.

To arkusz E.14 z czerwca 2020 - kwalifikacji formuły 2012, którą zastąpiła INF.03. Zdjęcia pochodzą z oryginalnego archiwum CKE egzamin4.zip (załącznik na stronie arkusza), zapytania są wykonane na MariaDB 10.4, skrypt PHP uruchomiony w PHP 8.2, a JavaScript sprawdzony w Chrome.

Jak liczą się punkty. Klucz ma 39 kryteriów (R.1.1 - R.5.7), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 30 punktów. Przy każdym kroku jest napisane, które kryteria spełnia; klucz przyjmuje zarówno mysqli, jak i PDO (uwaga przy R.5).

  1. Etap 1: Baza danych

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

    Krok 1: Folder zdającego, archiwum i baza sklep

    ok. 3 min
    Polecenie z arkusza

    Na pulpicie utwórz folder i nazwij go Twoim numerem PESEL. Wszystkie pliki utworzone w czasie egzaminu umieść w tym folderze. Za pomocą narzędzia phpMyAdmin, utwórz bazę sklep. Stosując kwerendy SQL wykonaj operacje na bazie danych. Kwerendy zapisz w pliku kwerendy.txt.

    Folder na pulpicie nazwany numerem PESEL (tu: Bartosz Bryniarski), do niego zdjęcia z archiwum egzamin4.zip rozpakowanego z hasłem 6hM9N#k@. W phpMyAdmin nowa baza sklep z porównywaniem utf8mb4_polish_ci, a każde z pięciu zapytań wykonane na karcie SQL trafia do pliku kwerendy.txt.

  2. Krok 2: Kwerenda 1: tabela zamowienia

    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 tworzące tabelę zamowienia zawierającą pola: id, imie, nazwisko, adres_email, liczba_choinek, liczba_mikolajow, liczba_reniferow, info. W pliku z kwerendami istnieje zapis (możliwe alternatywne typy), np.: CREATE TABLE `zamowienia` ( `id` int NOT NULL, `imie` text, `nazwisko` text, `adres_email` text, `liczba_choinek` int, `liczba_mikolajow` int, `liczba_reniferow` int, `info` tinyint(1) );

    ok. 4 min
    Polecenie z arkusza

    Kwerenda 1: tworząca tabelę zamowienia wraz kolumnami: id typ całkowity bez znaku, nie może przyjmować wartości NULL; imie, nazwisko, adres_email typu tekstowego; liczba_choinek, liczba_mikolajow, liczba_reniferow typu całkowitego; info typu logicznego (decyzja klienta, czy chce otrzymywać informacje handlowe). Wszystkie pola oprócz id domyślnie przyjmują wartość NULL

    CREATE TABLE zamowienia (
        id INT UNSIGNED NOT NULL,
        imie VARCHAR(50) DEFAULT NULL,
        nazwisko VARCHAR(50) DEFAULT NULL,
        adres_email VARCHAR(100) DEFAULT NULL,
        liczba_choinek INT UNSIGNED DEFAULT NULL,
        liczba_mikolajow INT UNSIGNED DEFAULT NULL,
        liczba_reniferow INT UNSIGNED DEFAULT NULL,
        info BOOLEAN DEFAULT NULL
    );
    
    • id INT UNSIGNED NOT NULL - całkowity bez znaku, bez NULL,
    • imię i nazwisko jako VARCHAR(50), adres jako VARCHAR(100) - typ tekstowy z limitem długości. TEXT (jak w przykładzie z klucza) też jest tekstowy, ale na krótkie pola z formularza wystarczy VARCHAR, który baza może indeksować w całości,
    • liczby sztuk INT UNSIGNED - nie da się zamówić minus trzech choinek,
    • info BOOLEAN - w MariaDB skrót od TINYINT(1),
    • DEFAULT NULL przy wszystkich polach oprócz id.

    Plik z zapytaniem daje R.1.2, samo zapytanie R.1.3. Podgląd: karta Struktura po kwerendach 1 i 2.

    kwerendy.txt Tekst nowy plik
    -- Kwerenda 1
    CREATE TABLE zamowienia (
        id INT UNSIGNED NOT NULL,
        imie VARCHAR(50) DEFAULT NULL,
        nazwisko VARCHAR(50) DEFAULT NULL,
        adres_email VARCHAR(100) DEFAULT NULL,
        liczba_choinek INT UNSIGNED DEFAULT NULL,
        liczba_mikolajow INT UNSIGNED DEFAULT NULL,
        liczba_reniferow INT UNSIGNED DEFAULT NULL,
        info BOOLEAN DEFAULT NULL
    );
    Podgląd strony po tym kroku
    Podgląd strony po kroku 2
  3. Krok 3: Kwerenda 2: klucz główny i autonumerowanie

    Kryteria:

    R.1.4 Zapisano zapytanie 2 ustawiająca pole id jako klucz główny. W pliku z kwerendami istnieje zapis, np.: ALTER TABLE `zamowienia` ADD PRIMARY KEY(`id`); lub ustawiono pole id jako klucz podstawowy w zapytaniu z R.1.3

    ok. 3 min
    Polecenie z arkusza

    Kwerenda 2: ustawiająca pole id jako klucz główny

    ALTER TABLE zamowienia
        ADD PRIMARY KEY (id),
        MODIFY id INT UNSIGNED NOT NULL AUTO_INCREMENT;
    

    ADD PRIMARY KEY (id) to polecenie z arkusza (R.1.4). Druga część tego samego ALTER TABLE, MODIFY id ... AUTO_INCREMENT, wykracza poza arkusz: formularz na stronie zamowienie.php nie ma pola id, więc numer zamówienia musi nadać baza. AUTO_INCREMENT wymaga klucza na kolumnie - dlatego oba zmiany są w jednym zapytaniu, a MariaDB wykonuje je razem.

    W wersji podstawowej, wiernej literze arkusza, numer wylicza skrypt PHP przez MAX(id) + 1 - działa, ale dwa zamówienia wysłane w tej samej chwili mogłyby dostać ten sam numer.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Kwerenda 1
    CREATE TABLE zamowienia (
        id INT UNSIGNED NOT NULL,
        imie VARCHAR(50) DEFAULT NULL,
        nazwisko VARCHAR(50) DEFAULT NULL,
        adres_email VARCHAR(100) DEFAULT NULL,
        liczba_choinek INT UNSIGNED DEFAULT NULL,
        liczba_mikolajow INT UNSIGNED DEFAULT NULL,
        liczba_reniferow INT UNSIGNED DEFAULT NULL,
        info BOOLEAN DEFAULT NULL
    );
    
    -- Kwerenda 2
    ALTER TABLE zamowienia
        ADD PRIMARY KEY (id),
        MODIFY id INT UNSIGNED NOT NULL AUTO_INCREMENT;
  4. Krok 4: Kwerendy 3 i 4: użytkownik klient tylko do dopisywania

    Kryteria: ,

    R.1.5 Zapisano zapytanie 3 tworzące użytkownika klient z widocznym hasłem klient123 W pliku z kwerendami istnieje zapis np.: CREATE USER 'klient' IDENTIFIED BY 'klient123';

    R.1.6 Zapisano zapytanie 4 nadające użytkownikowi uprawnienia jedynie do wpisywania rekordów do tabel bazy sklep . W pliku z kwerendami istnieje zapis np.: GRANT INSERT ON sklep.* TO klient;

    ok. 3 min
    Polecenie z arkusza

    Kwerenda 3: tworząca użytkownika klient z hasłem klient123. Kwerenda 4: nadająca użytkownikowi uprawnienia jedynie do wpisywania rekordów do tabel bazy sklep

    • CREATE USER 'klient'@'localhost' IDENTIFIED BY 'klient123' - konto, które łączy się tylko z tego samego komputera (R.1.5),
    • GRANT INSERT ON sklep.* TO 'klient'@'localhost' - jedyne uprawnienie: dopisywanie wierszy we wszystkich tabelach bazy sklep (R.1.6).

    To zasada najmniejszych uprawnień: formularz zamówień potrzebuje tylko INSERT, więc konto nie odczyta ani nie usunie danych klientów, nawet gdy ktoś przejmie hasło.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Kwerenda 1
    CREATE TABLE zamowienia (
        id INT UNSIGNED NOT NULL,
        imie VARCHAR(50) DEFAULT NULL,
        nazwisko VARCHAR(50) DEFAULT NULL,
        adres_email VARCHAR(100) DEFAULT NULL,
        liczba_choinek INT UNSIGNED DEFAULT NULL,
        liczba_mikolajow INT UNSIGNED DEFAULT NULL,
        liczba_reniferow INT UNSIGNED DEFAULT NULL,
        info BOOLEAN DEFAULT NULL
    );
    
    -- Kwerenda 2
    ALTER TABLE zamowienia
        ADD PRIMARY KEY (id),
        MODIFY id INT UNSIGNED NOT NULL AUTO_INCREMENT;
    
    -- Kwerenda 3
    CREATE USER 'klient'@'localhost' IDENTIFIED BY 'klient123';
    
    -- Kwerenda 4
    GRANT INSERT ON sklep.* TO 'klient'@'localhost';
  5. Krok 5: Kwerenda 5: zamówienie Jana Kowalskiego

    Kryteria:

    R.1.7 Zapisano zapytanie 5 dodające rekord z danymi: Jan Kowalski, [email protected]. W pliku z kwerendami istnieje zapis np.: INSERT INTO `zamowienia` VALUES ('1', 'Jan', 'Kowalski', '[email protected]', NULL, NULL, NULL, NULL); możliwe również z nazwami pól lub przez SET oraz na zrzucie widoczny jest komunikat o poprawnie wykonanej kwerendzie lub wstawiony rekord

    ok. 2 min
    Polecenie z arkusza

    Kwerenda 5: dodająca rekord z danymi: Jan Kowalski, [email protected] Wykonanie kwerendy udokumentuj zrzutem o nazwie kw5.jpg, zrzut powinien zawierać cały obszar ekranu, z widocznym paskiem zadań

    INSERT INTO zamowienia (imie, nazwisko, adres_email)
    VALUES ('Jan', 'Kowalski', '[email protected]');
    

    Bez kolumny id - numer 1 nadało autonumerowanie, co phpMyAdmin pokazuje w komunikacie. Zrzut całego ekranu z paskiem zadań: kw5.jpg (R.1.7).

    kwerendy.txt Tekst zmienione: 4 linie
    -- Kwerenda 1
    CREATE TABLE zamowienia (
        id INT UNSIGNED NOT NULL,
        imie VARCHAR(50) DEFAULT NULL,
        nazwisko VARCHAR(50) DEFAULT NULL,
        adres_email VARCHAR(100) DEFAULT NULL,
        liczba_choinek INT UNSIGNED DEFAULT NULL,
        liczba_mikolajow INT UNSIGNED DEFAULT NULL,
        liczba_reniferow INT UNSIGNED DEFAULT NULL,
        info BOOLEAN DEFAULT NULL
    );
    
    -- Kwerenda 2
    ALTER TABLE zamowienia
        ADD PRIMARY KEY (id),
        MODIFY id INT UNSIGNED NOT NULL AUTO_INCREMENT;
    
    -- Kwerenda 3
    CREATE USER 'klient'@'localhost' IDENTIFIED BY 'klient123';
    
    -- Kwerenda 4
    GRANT INSERT ON sklep.* TO 'klient'@'localhost';
    
    -- Kwerenda 5
    INSERT INTO zamowienia (imie, nazwisko, adres_email)
    VALUES ('Jan', 'Kowalski', '[email protected]');
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Eksport tabeli do zamowienia.sql

    Kryteria:

    R.1.1 Wykonano eksport tabeli zamowienia lub bazy sklep do pliku zamowienia.sql. Czas generowania wpisany w nagłówku pliku jest zgodny z datą i godziną egzaminu

    ok. 2 min
    Polecenie z arkusza

    Tabelę zamowienia wyeksportuj do pliku zamowienia.sql

    Tabela zamowienia, karta Eksport, format SQL. Czas generowania w nagłówku pliku musi się zgadzać z datą i godziną egzaminu (R.1.1). Plik w archiwum rozwiązania wyeksportowano z tej samej tabeli narzędziem mysqldump.

    zamowienia.sql SQL nowy plik
    -- MariaDB dump 10.19  Distrib 10.4.27-MariaDB, for Win64 (AMD64)
    --
    -- Host: localhost    Database: sklep
    -- ------------------------------------------------------
    -- Server version	10.4.27-MariaDB
    
    /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
    /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
    /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
    /*!40101 SET NAMES utf8mb4 */;
    /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
    /*!40103 SET TIME_ZONE='+00:00' */;
    /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
    /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
    /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
    /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
    
    --
    -- Table structure for table `zamowienia`
    --
    
    DROP TABLE IF EXISTS `zamowienia`;
    /*!40101 SET @saved_cs_client     = @@character_set_client */;
    /*!40101 SET character_set_client = utf8 */;
    CREATE TABLE `zamowienia` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `imie` varchar(50) DEFAULT NULL,
      `nazwisko` varchar(50) DEFAULT NULL,
      `adres_email` varchar(100) DEFAULT NULL,
      `liczba_choinek` int(10) unsigned DEFAULT NULL,
      `liczba_mikolajow` int(10) unsigned DEFAULT NULL,
      `liczba_reniferow` int(10) unsigned DEFAULT NULL,
      `info` tinyint(1) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_polish_ci;
    /*!40101 SET character_set_client = @saved_cs_client */;
    
    --
    -- Dumping data for table `zamowienia`
    --
    
    LOCK TABLES `zamowienia` WRITE;
    /*!40000 ALTER TABLE `zamowienia` DISABLE KEYS */;
    INSERT INTO `zamowienia` VALUES (1,'Jan','Kowalski','[email protected]',NULL,NULL,NULL,NULL);
    /*!40000 ALTER TABLE `zamowienia` ENABLE KEYS */;
    UNLOCK TABLES;
    /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
    
    /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
    /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
    /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
    /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
    /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
    /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
    /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
    
    -- Dump completed
  7. Etap 2: Grafika i animacja

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

    Krok 7: Ozdoby 1:1, 300 × 300 px

    Kryteria:

    R.2.1 Zapisano przynajmniej jeden obraz z choinka, mikolaj, renifer o rozmiarze 300 px na 300 px (± 2 px, z zachowaniem proporcji 1:1)

    ok. 5 min
    Polecenie z arkusza

    Przygotowanie grafiki: obrazy choinka, mikolaj, renifer wykadrowane do proporcji 1:1, rozmiar grafiki 300 px na 300 px

    Grafika jest taka sama jak w wersji podstawowej - wymagania nie zostawiają wyboru. W GIMP-ie Kadrowanie z proporcjami 1:1 wyśrodkowane na ozdobie, Skaluj obraz do 300 × 300 px, eksport do choinka.jpg, mikolaj.jpg, renifer.jpg (R.2.1).

    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Krok 8: Animacja co 2 sekundy i zrzut interwal.jpg

    Kryteria: ,

    R.2.2 Utworzono animację animacja.gif składającą się z trzech obrazów

    R.2.3 Obrazy w animacji zmieniają się co 2 sekundy. Ustawiony czas interwału udokumentowano zrzutem interwal.jpg , który zawiera cały obszar ekranu

    ok. 5 min
    Polecenie z arkusza

    Przygotowanie animacji o nazwie animacja.gif: wykonana z wcześniej przygotowanych grafik choinka, mikolaj, renifer; obrazy zmieniane co 2 sekundy. Należy wykonać zrzut ekranu dokumentujący ustawienie czasu i zapisać jako plik interwal.jpg. Zrzut powinien zawierać cały obszar ekranu, z widocznym paskiem zadań

    Plik → Otwórz jako warstwy - trzy ozdoby w jednym obrazie, od dołu: choinka, mikołaj, renifer (najniższa warstwa to pierwsza klatka). Eksportuj jako animacja.gif, w oknie opcji Jako animację, Pętla i opóźnienie 2000 ms z zaznaczonym „Użyj podanego opóźnienia dla wszystkich klatek” (R.2.2). Zrzut całego ekranu z tym oknem: interwal.jpg (R.2.3). Podgląd: odtworzenie okna opcji; gotowy plik ma trzy klatki po 2000 ms.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Etap 3: Strona galeria.html

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

    Krok 9: Szkielet: kodowanie, tytuł i arkusz stylów

    Kryteria: ,

    R.2.5 Przynajmniej jednej stronie nadano tytuł: "Sklep”

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

    ok. 2 min
    Polecenie z arkusza

    Wspólne wymagania dla wszystkich podstron: Kodowanie polskich znaków. Strona połączona z zewnętrznym arkuszem stylów CSS o nazwie styl.css. Tytuł strony: Sklep

    <meta charset="UTF-8">, viewport dla telefonów, <title>Sklep</title> (R.2.5) i <link rel="stylesheet" href="styl.css"> (R.3.6). Całe formatowanie trafi do pliku stylów.

    galeria.html HTML nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.html)
  10. Krok 10: Baner, trzy panele w main i stopka

    Kryteria:

    R.2.6 Układ przynajmniej jednej strony zdefiniowano dzieląc ją na baner, panel lewy, środkowy i prawy oraz stopka za pomocą znaczników sekcji

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: baner, panel lewy, środkowy i prawy oraz stopka za pomocą znaczników sekcji. Zawartość banera: nagłówek pierwszego stopnia o treści: „Ozdoby – sklep”. Zawartość stopki: nagłówek trzeciego stopnia o treści: „Autor strony:”, dalej wstawiony numer PESEL zdającego

    <header> z <h1>, trzy znaczniki <section> w pojemniku <main> i <footer> z <h3> (R.2.6). <main> oznacza główną treść strony, a w CSS ułoży panele obok siebie. W stopce numer PESEL - tu Bartosz Bryniarski.

    ⚠️ Rozbieżność treść-ilustracja: obraz 1 pokazuje w stopce „Stronę opracował:”, a treść arkusza i klucz - „Autor strony:”. Obowiązuje treść i klucz.

    galeria.html HTML zmienione: 14 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
            </section>
            <section id="middle">
            </section>
            <section id="right">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.html)
  11. Krok 11: Panel lewy: nagłówek i nawigacja

    Kryteria: ,

    R.3.4 Na przynajmniej jednej stronie istnieje odnośnik „Galeria”, który prowadzi do pliku galeria.html

    R.3.5 Na przynajmniej jednej stronie istnieje odnośnik „Zamówienie”, który prowadzi do pliku zamowienie.php

    ok. 2 min
    Polecenie z arkusza

    Zawartość panelu lewego: nagłówek drugiego stopnia o treści: „OZDOBY”; odnośnik „Galeria” prowadzący do pliku galeria.html; poniżej odnośnik „Zamówienie” prowadzący do pliku zamowienie.php

    <h2> i dwa odnośniki ze ścieżkami względnymi (R.3.4, R.3.5) w znaczniku <nav> - to menu strony. Bez <br>: jeden pod drugim ustawi je CSS.

    galeria.html HTML zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
            </section>
            <section id="right">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.html)
  12. Krok 12: Panel prawy: animacja

    Kryteria:

    R.2.9 W bloku prawym umieszczono plik animacja.gif

    ok. 1 min
    Polecenie z arkusza

    Zawartość panelu prawego: animacja.gif

    <img> z animacją i tekstem alternatywnym, który mówi, co się zmienia na obrazie (R.2.9).

    galeria.html HTML zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
            </section>
            <section id="right">
                <img src="animacja.gif" alt="Animacja: choinka, mikołaj i renifer">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.html)
  13. Krok 13: Panel środkowy: trzy ozdoby

    Kryteria:

    R.2.8 W bloku środkowym strony galeria.html umieszczono obrazy choinka, mikolaj, renifer z tekstem alternatywnym „ozdoby choinkowe”

    ok. 2 min
    Polecenie z arkusza

    Zawartość panelu środkowego dla strony galeria.html: Obrazy choinka, mikolaj, renifer z tekstem alternatywnym „ozdoby choinkowe”, umieszczone obok siebie

    Trzy obrazy z tekstem alternatywnym z arkusza (R.2.8). Atrybut data-ornament to własna etykieta obrazu (atrybuty data- są do tego przeznaczone) - skrypt odczyta z niej, którą ozdobę wskazano, bez osobnej funkcji dla każdego obrazu.

    galeria.html HTML zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
                <img src="choinka.jpg" alt="ozdoby choinkowe" data-ornament="choinka">
                <img src="mikolaj.jpg" alt="ozdoby choinkowe" data-ornament="mikolaj">
                <img src="renifer.jpg" alt="ozdoby choinkowe" data-ornament="renifer">
            </section>
            <section id="right">
                <img src="animacja.gif" alt="Animacja: choinka, mikołaj i renifer">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.html)
  14. Krok 14: Paragraf z poleceniem i paragraf na komunikat

    Kryteria:

    R.2.7 Na przynajmniej jednej stronie zapisano nagłówki: w banerze h1: "Ozdoby - sklep”, w bloku lewym h2 "OZDOBY", w stopce h3: "Autor strony" i dalej wstawiony numer PESEL zdającego oraz zapisano w bloku środkowym paragraf (znacznik <p>)

    ok. 1 min
    Polecenie z arkusza

    Paragraf (akapit) o treści: „Wybierz ozdobę w kółku”. Efekt działania skryptu 1 umieszczony w kolejnym paragrafie

    Dwa paragrafy (z nagłówkami h1, h2, h3 - R.2.7). Drugi jest pusty, a aria-live="polite" każe czytnikowi ekranu odczytać jego treść, gdy skrypt ją zmieni - bez tego niewidomy użytkownik nie dowie się o cenie.

    galeria.html HTML zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
                <img src="choinka.jpg" alt="ozdoby choinkowe" data-ornament="choinka">
                <img src="mikolaj.jpg" alt="ozdoby choinkowe" data-ornament="mikolaj">
                <img src="renifer.jpg" alt="ozdoby choinkowe" data-ornament="renifer">
                <p>Wybierz ozdobę w kółku</p>
                <p id="info" aria-live="polite"></p>
            </section>
            <section id="right">
                <img src="animacja.gif" alt="Animacja: choinka, mikołaj i renifer">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.html)
  15. Etap 4: Strona zamowienie.php

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

    Krok 15: Plik zamowienie.php: paragraf „Dodaj użytkownika”

    Kryteria:

    R.2.4 Witrynę internetową zapisano w plikach galeria.html, zamowienie.php oraz z astosowano właściwy standard kodowania polskich znaków na przynajmniej jednej stronie

    ok. 2 min
    Polecenie z arkusza

    Zawartość panelu środkowego dla strony zamowienie.php: Paragraf o treści: „Dodaj użytkownika”

    Kopia galeria.html, w której panel środkowy ma tylko paragraf „Dodaj użytkownika”. Obie strony mają nazwy z arkusza i UTF-8 (R.2.4).

    zamowienie.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
                <p>Dodaj użytkownika</p>
            </section>
            <section id="right">
                <img src="animacja.gif" alt="Animacja: choinka, mikołaj i renifer">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zamowienie.php)
  16. Krok 16: Formularz z etykietami

    Kryteria: , ,

    R.2.10 W bloku środkowym strony zamowienie.php umieszczono formularz z trzema polami edycyjnymi i przyciskiem WYŚLIJ

    R.3.2 Dane z formularza przesyłane są za pomocą metody POST, zastosowano przycisk do wysyłania danych z formularza

    R.3.3 Pole edycyjne do wpisania adresu email jest typu email

    ok. 3 min
    Polecenie z arkusza

    Formularz: Dane wysyłane metodą post do tego samego pliku. Pola edycyjne i podpisy zgodne z Obrazem 2. Przycisk WYŚLIJ wysyła dane z formularza. Pole edycyjne do wprowadzenia adresu email jest odpowiedniego typu tak, aby poprawność adresu była sprawdzana automatycznie

    • method="post" do tego samego pliku i przycisk type="submit" (R.3.2),
    • <label for="…"> - podpis powiązany z polem przez id: kliknięcie w napis „Imię:” ustawia kursor w polu, a czytnik ekranu czyta podpis razem z polem,
    • każdy wiersz w <div> - wiersze stoją jeden pod drugim bez <br>,
    • type="email" - przeglądarka sprawdza adres przed wysłaniem (R.3.3),
    • required i maxlength - puste pola i za długie wpisy przeglądarka zatrzyma od razu; maxlength odpowiada długościom kolumn w tabeli,
    • autocomplete - przeglądarka podpowie imię, nazwisko i adres z zapisanych danych.

    Trzy pola i przycisk WYŚLIJ (R.2.10).

    zamowienie.php PHP zmienione: 15 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
                <p>Dodaj użytkownika</p>
                <form method="post" action="zamowienie.php">
                    <div>
                        <label for="name">Imię:</label>
                        <input type="text" id="name" name="name" maxlength="50" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">Nazwisko:</label>
                        <input type="text" id="surname" name="surname" maxlength="50" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="email">e-mail:</label>
                        <input type="email" id="email" name="email" maxlength="100" required autocomplete="email">
                    </div>
                    <button type="submit">WYŚLIJ</button>
                </form>
            </section>
            <section id="right">
                <img src="animacja.gif" alt="Animacja: choinka, mikołaj i renifer">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zamowienie.php)
  17. Etap 5: Styl CSS

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

    Krok 17: Zmienne kolorów i formatowanie strony

    Kryteria:

    R.4.1 Ustawiono formatowanie dla * lub html, lub body, lub kontenera całej strony: czcionka Garamond białego koloru, wyśrodkowana

    ok. 2 min
    Polecenie z arkusza

    Styl CSS zawiera formatowanie: domyślnie: czcionka Garamond koloru białego, tekst wyśrodkowany

    Kolory arkusza w zmiennych CSS w :root - ForestGreen występuje trzy razy, a zmienna pozwala zmienić go jednym wpisem. Reguła body: Garamond z krojami zapasowymi, biały tekst, wyśrodkowanie (R.4.1).

    styl.css CSS nowy plik
    :root {
        --green: ForestGreen;
        --brown: Chocolate;
    }
    
    body {
        font-family: Garamond, "Times New Roman", serif;
        color: white;
        text-align: center;
    }
    Podgląd strony po tym kroku (galeria.html)
  18. Krok 18: Baner, stopka i flex w main

    Kryteria:

    R.4.6 Ustawiono wszystkie marginesy wewnętrzne (padding) dla banera i stopki na 10px

    ok. 2 min
    Polecenie z arkusza

    dla banera i stopki: kolor tła ForestGreen, margines wewnętrzny 10 px

    padding: 10px z czterech stron (R.4.6). display: flex na <main> ustawia trzy panele w jednym wierszu - bez float, więc stopka sama staje pod nimi. Siatka (grid-template-columns: 25% 50% 25%) byłaby równie dobra, ale klucz z 2020 roku ocenia szerokości na panelach (R.4.4), więc pojemnik tylko układa, a szerokości zostają w regułach paneli.

    styl.css CSS zmienione: 10 linii
    :root {
        --green: ForestGreen;
        --brown: Chocolate;
    }
    
    body {
        font-family: Garamond, "Times New Roman", serif;
        color: white;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--green);
        padding: 10px;
    }
    
    main {
        display: flex;
    }
    Podgląd strony po tym kroku (galeria.html)
  19. Krok 19: Trzy panele

    Kryteria: , , , ,

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest w pełni zgodny z obrazem 1 w arkuszu

    R.4.2 Ustawiono kolor tła dla panelu środkowego na Chocolate, dla pozostałych: ForestGreen

    R.4.3 Ustawiono wysokość trzech paneli na 490px

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

    R.4.5 Ustawiono rozmiar czcionki dla panelu lewego i prawego na 30px, środkowego na 25px

    ok. 2 min
    Polecenie z arkusza

    dla panelu lewego i prawego: kolor tła ForestGreen, wysokość 490 px, szerokość: 25%, rozmiar czcionki 30 px; dla panelu środkowego: kolor tła Chocolate, wysokość 490 px, szerokość: 50%, rozmiar czcionki 25 px

    Kolory tła (R.4.2), wysokości 490 px (R.4.3), szerokości 25/50/25% (R.4.4), czcionki 30/25/30 px (R.4.5). Panele obok siebie, jak na obrazie 1 (R.3.1).

    styl.css CSS zmienione: 15 linii
    :root {
        --green: ForestGreen;
        --brown: Chocolate;
    }
    
    body {
        font-family: Garamond, "Times New Roman", serif;
        color: white;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--green);
        padding: 10px;
    }
    
    main {
        display: flex;
    }
    
    #left,
    #right {
        background-color: var(--green);
        height: 490px;
        width: 25%;
        font-size: 30px;
    }
    
    #middle {
        background-color: var(--brown);
        height: 490px;
        width: 50%;
        font-size: 25px;
    }
    Podgląd strony po tym kroku (galeria.html)
  20. Krok 20: Ozdoby w kółkach, animacja w granicach panelu

    Kryteria: ,

    R.4.7 Ustawiono styl wyłącznie dla obrazów z panelu środkowego: wysokość i szerokość 150px

    R.4.8 Ustawiono zaokrąglenie rogów obrazu (border-radius) 100% (lub wartości >= 50%)

    ok. 2 min
    Polecenie z arkusza

    wyłącznie dla obrazów z panelu środkowego: rozmiar 150 px na 150 px, zaokrąglenie rogów obrazu 100%

    • #middle img - tylko obrazy panelu środkowego, 150 × 150 px (R.4.7),
    • border-radius: 50% - promień zaokrąglenia równy połowie boku zamienia kwadrat w koło. Arkusz podaje 100%, ale większa wartość nic nie zmienia na ekranie, a klucz przyjmuje każdą od 50% (R.4.8),
    • #right img z max-width: 100% - animacja 300 px nie wyjdzie poza panel, gdy okno jest wąskie (25% z 1000 px to 250 px).
    styl.css CSS zmienione: 11 linii
    :root {
        --green: ForestGreen;
        --brown: Chocolate;
    }
    
    body {
        font-family: Garamond, "Times New Roman", serif;
        color: white;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--green);
        padding: 10px;
    }
    
    main {
        display: flex;
    }
    
    #left,
    #right {
        background-color: var(--green);
        height: 490px;
        width: 25%;
        font-size: 30px;
    }
    
    #middle {
        background-color: var(--brown);
        height: 490px;
        width: 50%;
        font-size: 25px;
    }
    
    #middle img {
        width: 150px;
        height: 150px;
        border-radius: 50%;
    }
    
    #right img {
        max-width: 100%;
        height: auto;
    }
    Podgląd strony po tym kroku (galeria.html)
  21. Krok 21: Odnośniki i zielony tekst formularza

    Kryteria:

    R.4.9 Ustawiono zielony kolor czcionki dla wszystkich elementów formularza

    ok. 2 min
    Polecenie z arkusza

    dla selektora pola edycyjnego i przycisku: zielony kolor czcionki

    • input, button { color: green; } - pola i przycisk (w wersji nowoczesnej to znacznik <button>, więc potrzebny jest w selektorze) (R.4.9),
    • nav a - odnośnik w osobnej linii (display: block) i w kolorze tekstu panelu (inherit, tu biały, jak na obrazie 1); domyślny niebieski odnośnik na zielonym tle jest słabo czytelny.
    styl.css CSS zmienione: 10 linii
    :root {
        --green: ForestGreen;
        --brown: Chocolate;
    }
    
    body {
        font-family: Garamond, "Times New Roman", serif;
        color: white;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--green);
        padding: 10px;
    }
    
    main {
        display: flex;
    }
    
    #left,
    #right {
        background-color: var(--green);
        height: 490px;
        width: 25%;
        font-size: 30px;
    }
    
    #middle {
        background-color: var(--brown);
        height: 490px;
        width: 50%;
        font-size: 25px;
    }
    
    #middle img {
        width: 150px;
        height: 150px;
        border-radius: 50%;
    }
    
    #right img {
        max-width: 100%;
        height: auto;
    }
    
    nav a {
        display: block;
        color: inherit;
    }
    
    input,
    button {
        color: green;
    }
    Podgląd strony po tym kroku (zamowienie.php)
  22. Etap 6: Skrypt JavaScript

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

    Krok 22: Plik galeria.js: komunikaty po wskazaniu ozdoby

    Kryteria: , ,

    R.5.1 Napisano skrypt dla strony galeria.html w języku JavaScript, który zawiera przynajmniej jedną instrukcję zgodną ze składnią języka

    R.5.3 W skrypcie 1 istnieje funkcja (funkcje) wywoływana po wskazaniu kursorem każdego obrazu z panelu środkowego

    R.5.4 Skrypt 1 wyświetla pod obrazami w paragrafie tekst dla choinki: "Wybrałeś choinkę. Cena 10 zł"; dla Mikołaja: "Wybrałeś Mikołaja. Cena 12 zł"; dla renifera: „Wybrałeś renifera. Cena 8 zł”

    ok. 5 min
    Polecenie z arkusza

    Skrypt 1 ze strony galeria.html: Skrypt jest uruchamiany po stronie przeglądarki. Po wskazaniu kursorem obrazu w panelu środkowym jest wyświetlany komunikat w paragrafie przeznaczonym dla tej akcji (obraz 1): wybór choinki: „Wybrałeś choinkę. Cena 10 zł”; wybór Mikołaja: „Wybrałeś mikołaja. Cena 12 zł”; wybór renifera: „Wybrałeś renifera. Cena 8 zł”

    Skrypt w osobnym pliku galeria.js, dołączony w <head> z atrybutem defer - przeglądarka pobiera go od razu, a wykonuje dopiero po wczytaniu całej strony, gdy obrazy już istnieją (R.5.1):

    • messages - obiekt: etykieta ozdoby z data-ornament → komunikat. Ceny są w jednym miejscu, a nowa ozdoba to jedna linia, nie nowa gałąź if,
    • showMessage(event) - event.target to obraz, nad którym jest kursor, a dataset.ornament odczytuje jego atrybut data-ornament,
    • textContent zamiast innerHTML - wstawia zwykły tekst; przeglądarka nie potraktuje go jak kodu HTML,
    • pętla for … of podpina funkcję do zdarzenia mouseover każdego obrazu przez addEventListener - HTML zostaje bez atrybutów onmouseover (R.5.3).

    Sprawdzone w Chrome dla każdej ozdoby: trzy komunikaty z klucza (R.5.4). Arkusz pisze „mikołaja”, klucz „Mikołaja” - klucz dopuszcza różnicę wielkości liter. Podgląd: kursor na mikołaju, jak na obrazie 1.

    galeria.html HTML zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep</title>
        <link rel="stylesheet" href="styl.css">
        <script src="galeria.js" defer></script>
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
                <img src="choinka.jpg" alt="ozdoby choinkowe" data-ornament="choinka">
                <img src="mikolaj.jpg" alt="ozdoby choinkowe" data-ornament="mikolaj">
                <img src="renifer.jpg" alt="ozdoby choinkowe" data-ornament="renifer">
                <p>Wybierz ozdobę w kółku</p>
                <p id="info" aria-live="polite"></p>
            </section>
            <section id="right">
                <img src="animacja.gif" alt="Animacja: choinka, mikołaj i renifer">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    galeria.js JavaScript nowy plik
    const messages = {
        choinka: 'Wybrałeś choinkę. Cena 10 zł',
        mikolaj: 'Wybrałeś Mikołaja. Cena 12 zł',
        renifer: 'Wybrałeś renifera. Cena 8 zł',
    };
    
    const info = document.getElementById('info');
    
    function showMessage(event) {
        info.textContent = messages[event.target.dataset.ornament];
    }
    
    for (const image of document.querySelectorAll('#middle img')) {
        image.addEventListener('mouseover', showMessage);
    }
    Podgląd strony po tym kroku (galeria.html)
  23. Etap 7: Skrypt PHP

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

    Krok 23: Skrypt na górze pliku i połączenie PDO z bazą sklep

    Kryteria: ,

    R.5.2 Napisano skrypt dla formularza w języku PHP, który zawiera przynajmniej jedną instrukcję zgodną ze składnią języka

    R.5.5 W skrypcie 2 zastosowano funkcję realizującą połączenie z serwerem bazy danych na localhost, użytkownik root, bez hasła, baza danych sklep

    ok. 4 min
    Polecenie z arkusza

    Skrypt 2 uruchamiany po wysłaniu danych z formularza na stronie zamowienie.php: Napisany w języku PHP. Skrypt łączy się z serwerem bazy danych na localhost, użytkownik root bez hasła, baza danych o nazwie sklep

    Skrypt stoi na samej górze zamowienie.php, przed <!DOCTYPE html>, a pod formularzem zostają trzy linie wypisujące komunikat w paragrafie (R.5.2):

    • e() - funkcja z htmlspecialchars do wszystkiego, co wypisuje strona,
    • $_SERVER['REQUEST_METHOD'] === 'POST' - skrypt pracuje tylko po wysłaniu formularza (arkusz: „uruchamiany po wysłaniu danych”),
    • new PDO(...) - serwer localhost, baza sklep, kodowanie utf8mb4, użytkownik root bez hasła (R.5.5), ERRMODE_EXCEPTION - błędy bazy jako wyjątki,
    • try / catch - gdy baza nie działa, klient zobaczy zwykłe zdanie zamiast komunikatu PHP z danymi serwera.

    Klucz przyjmuje PDO tak samo jak mysqli (uwaga przy R.5). Podgląd: zwykłe wejście na stronę.

    zamowienie.php PHP zmienione: 20 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            ]);
        } catch (PDOException $exception) {
            $message = 'Nie udało się zapisać zamówienia, 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>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
                <p>Dodaj użytkownika</p>
                <form method="post" action="zamowienie.php">
                    <div>
                        <label for="name">Imię:</label>
                        <input type="text" id="name" name="name" maxlength="50" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">Nazwisko:</label>
                        <input type="text" id="surname" name="surname" maxlength="50" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="email">e-mail:</label>
                        <input type="email" id="email" name="email" maxlength="100" required autocomplete="email">
                    </div>
                    <button type="submit">WYŚLIJ</button>
                </form>
                <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
                <?php endif; ?>
            </section>
            <section id="right">
                <img src="animacja.gif" alt="Animacja: choinka, mikołaj i renifer">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zamowienie.php)
  24. Krok 24: Sprawdzenie danych na serwerze

    ok. 4 min
    Polecenie z arkusza

    Tworzy w tabeli zamowienia bazy danych nowy rekord z danymi przesłanymi formularzem (zmodyfikowane zapytanie 5)

    required i type="email" sprawdzają dane w przeglądarce, ale żądanie POST można wysłać z pominięciem formularza - serwer sprawdza je jeszcze raz:

    • filter_input(INPUT_POST, 'name') odczytuje pole albo zwraca null, gdy go nie wysłano; (string) i trim() dają napis bez spacji z brzegów,
    • filter_input(..., FILTER_VALIDATE_EMAIL) zwraca adres albo false, gdy adres jest niepoprawny,
    • mb_strlen pilnuje długości kolumn - za długiego imienia baza w trybie ścisłym nie zapisze.

    Połączenie z bazą powstaje dopiero dla poprawnych danych. Podgląd: imię z samych spacji i adres nie-email wysłane z pominięciem przeglądarki.

    zamowienie.php PHP zmienione: 13 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $name = trim((string) filter_input(INPUT_POST, 'name'));
        $surname = trim((string) filter_input(INPUT_POST, 'surname'));
        $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
        if ($name === '' || $surname === '' || !$email || mb_strlen($name) > 50 || mb_strlen($surname) > 50 || mb_strlen($email) > 100) {
            $message = 'Wpisz imię, nazwisko i poprawny adres e-mail.';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
            } catch (PDOException $exception) {
                $message = 'Nie udało się zapisać zamówienia, 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>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
                <p>Dodaj użytkownika</p>
                <form method="post" action="zamowienie.php">
                    <div>
                        <label for="name">Imię:</label>
                        <input type="text" id="name" name="name" maxlength="50" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">Nazwisko:</label>
                        <input type="text" id="surname" name="surname" maxlength="50" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="email">e-mail:</label>
                        <input type="email" id="email" name="email" maxlength="100" required autocomplete="email">
                    </div>
                    <button type="submit">WYŚLIJ</button>
                </form>
                <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
                <?php endif; ?>
            </section>
            <section id="right">
                <img src="animacja.gif" alt="Animacja: choinka, mikołaj i renifer">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zamowienie.php)
  25. Krok 25: Zapytanie 5 jako zapytanie przygotowane

    Kryteria:

    R.5.7 Skrypt 2 wysyła do bazy danych zapytanie 5 zmodyfikowane w ten sposób, że odpowiednimi wartościami pól są dane pobrane z formularza

    ok. 3 min
    Polecenie z arkusza

    Tworzy w tabeli zamowienia bazy danych nowy rekord z danymi przesłanymi formularzem (zmodyfikowane zapytanie 5)

    Kwerenda 5, w której wartości zastępują parametry :name, :surname, :email: prepare wysyła do bazy treść zapytania, a execute - osobno dane z formularza (R.5.7). Dane nigdy nie staną się kodem SQL, więc nazwisko O'Neill zapisuje się bez błędu, a wpis z fragmentem zapytania (w wersji podstawowej dopisywał drugi wiersz) zapisuje się jako zwykłe, dziwne imię. Numer zamówienia nadaje autonumerowanie z kwerendy 2.

    Sprawdzone: po wysłaniu danych Anny Nowak tabela ma wiersze 1 (Jan Kowalski) i 2 (Anna Nowak), a pod formularzem jest „Zamówienie zostało zapisane.”.

    zamowienie.php PHP zmienione: 3 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $name = trim((string) filter_input(INPUT_POST, 'name'));
        $surname = trim((string) filter_input(INPUT_POST, 'surname'));
        $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
        if ($name === '' || $surname === '' || !$email || mb_strlen($name) > 50 || mb_strlen($surname) > 50 || mb_strlen($email) > 100) {
            $message = 'Wpisz imię, nazwisko i poprawny adres e-mail.';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
                $statement = $pdo->prepare('INSERT INTO zamowienia (imie, nazwisko, adres_email) VALUES (:name, :surname, :email);');
                $statement->execute(['name' => $name, 'surname' => $surname, 'email' => $email]);
                $message = 'Zamówienie zostało zapisane.';
            } catch (PDOException $exception) {
                $message = 'Nie udało się zapisać zamówienia, 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>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
                <p>Dodaj użytkownika</p>
                <form method="post" action="zamowienie.php">
                    <div>
                        <label for="name">Imię:</label>
                        <input type="text" id="name" name="name" maxlength="50" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">Nazwisko:</label>
                        <input type="text" id="surname" name="surname" maxlength="50" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="email">e-mail:</label>
                        <input type="email" id="email" name="email" maxlength="100" required autocomplete="email">
                    </div>
                    <button type="submit">WYŚLIJ</button>
                </form>
                <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
                <?php endif; ?>
            </section>
            <section id="right">
                <img src="animacja.gif" alt="Animacja: choinka, mikołaj i renifer">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zamowienie.php)
  26. Krok 26: Zamknięcie połączenia w finally

    Kryteria:

    R.5.6 Po wykonaniu wszystkich operacji na bazie zastosowano funkcję zamknięcia połączenia z serwerem bazy danych

    ok. 2 min
    Polecenie z arkusza

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

    PDO zamyka połączenie, gdy znika ostatnie odwołanie do obiektu: $statement = null i $pdo = null. Stoją w bloku finally, który wykona się po try zawsze - po udanym zapisie i po wyjątku (R.5.6).

    zamowienie.php PHP zmienione: 3 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = '';
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $name = trim((string) filter_input(INPUT_POST, 'name'));
        $surname = trim((string) filter_input(INPUT_POST, 'surname'));
        $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
        if ($name === '' || $surname === '' || !$email || mb_strlen($name) > 50 || mb_strlen($surname) > 50 || mb_strlen($email) > 100) {
            $message = 'Wpisz imię, nazwisko i poprawny adres e-mail.';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
                $statement = $pdo->prepare('INSERT INTO zamowienia (imie, nazwisko, adres_email) VALUES (:name, :surname, :email);');
                $statement->execute(['name' => $name, 'surname' => $surname, 'email' => $email]);
                $message = 'Zamówienie zostało zapisane.';
            } catch (PDOException $exception) {
                $message = 'Nie udało się zapisać zamówienia, 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>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
                <p>Dodaj użytkownika</p>
                <form method="post" action="zamowienie.php">
                    <div>
                        <label for="name">Imię:</label>
                        <input type="text" id="name" name="name" maxlength="50" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">Nazwisko:</label>
                        <input type="text" id="surname" name="surname" maxlength="50" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="email">e-mail:</label>
                        <input type="email" id="email" name="email" maxlength="100" required autocomplete="email">
                    </div>
                    <button type="submit">WYŚLIJ</button>
                </form>
                <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
                <?php endif; ?>
            </section>
            <section id="right">
                <img src="animacja.gif" alt="Animacja: choinka, mikołaj i renifer">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>

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

  27. Krok 27: Odświeżenie po zapisie nie składa zamówienia drugi raz

    ok. 4 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 tabeli ląduje drugie, identyczne zamówienie. Wzorzec PRG (Post/Redirect/Get):

    • session_start() na początku pliku - sesja przechowa komunikat między dwoma żądaniami,
    • po udanym zapisie $saved = true, a po bloku finally (połączenie jest już zamknięte) komunikat trafia do $_SESSION, a header('Location: zamowienie.php') każe przeglądarce wczytać stronę zwykłym żądaniem GET; exit zatrzymuje skrypt, bo header tylko ustawia nagłówek,
    • kolejne wejście odczytuje komunikat z sesji i od razu go usuwa (unset), więc pokaże się raz.

    Sprawdzone na prawdziwym PHP: wysłanie formularza daje odpowiedź 302, potem „Zamówienie zostało zapisane.”, a kolejne odświeżenie - stronę bez komunikatu i bez drugiego zamówienia w tabeli. Przy błędnych danych przekierowania nie ma - komunikat zostaje na stronie. Podgląd: stan po przekierowaniu.

    zamowienie.php PHP zmienione: 11 linii
    <?php
    session_start();
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = $_SESSION['message'] ?? '';
    unset($_SESSION['message']);
    $saved = false;
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $name = trim((string) filter_input(INPUT_POST, 'name'));
        $surname = trim((string) filter_input(INPUT_POST, 'surname'));
        $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
        if ($name === '' || $surname === '' || !$email || mb_strlen($name) > 50 || mb_strlen($surname) > 50 || mb_strlen($email) > 100) {
            $message = 'Wpisz imię, nazwisko i poprawny adres e-mail.';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
                $statement = $pdo->prepare('INSERT INTO zamowienia (imie, nazwisko, adres_email) VALUES (:name, :surname, :email);');
                $statement->execute(['name' => $name, 'surname' => $surname, 'email' => $email]);
                $saved = true;
            } catch (PDOException $exception) {
                $message = 'Nie udało się zapisać zamówienia, spróbuj ponownie za chwilę.';
            } finally {
                $statement = null;
                $pdo = null;
            }
        }
    }
    if ($saved) {
        $_SESSION['message'] = 'Zamówienie zostało zapisane.';
        header('Location: zamowienie.php');
        exit;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ozdoby - sklep</h1>
        </header>
        <main>
            <section id="left">
                <h2>OZDOBY</h2>
                <nav>
                    <a href="galeria.html">Galeria</a>
                    <a href="zamowienie.php">Zamówienie</a>
                </nav>
            </section>
            <section id="middle">
                <p>Dodaj użytkownika</p>
                <form method="post" action="zamowienie.php">
                    <div>
                        <label for="name">Imię:</label>
                        <input type="text" id="name" name="name" maxlength="50" required autocomplete="given-name">
                    </div>
                    <div>
                        <label for="surname">Nazwisko:</label>
                        <input type="text" id="surname" name="surname" maxlength="50" required autocomplete="family-name">
                    </div>
                    <div>
                        <label for="email">e-mail:</label>
                        <input type="email" id="email" name="email" maxlength="100" required autocomplete="email">
                    </div>
                    <button type="submit">WYŚLIJ</button>
                </form>
                <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
                <?php endif; ?>
            </section>
            <section id="right">
                <img src="animacja.gif" alt="Animacja: choinka, mikołaj i renifer">
            </section>
        </main>
        <footer>
            <h3>Autor strony: Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>

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

  28. Etap 8: Porównanie z wersją podstawową

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

    Krok 28: 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 sekcji, clear stopki<main> z display: flex
    szerokości 25/50/25%width na panelachwidth na panelach (tak ocenia klucz)
    kolorywpisane w każdej regulezmienne --green, --brown
    numer zamówieniaMAX(id) + 1 w skrypcieAUTO_INCREMENT z kwerendy 2
    formularznapis, pole, <br>label for, required, maxlength, autocomplete
    skrypt JSw stronie, onmouseover, if, innerHTMLgaleria.js z defer, addEventListener, obiekt z komunikatami, textContent
    biblioteka bazymysqli (tabela 1 arkusza)PDO z wyjątkami
    dane w INSERTwklejone w treść zapytaniaparametry :name, :surname, :email
    sprawdzenie danychtylko w przeglądarcetakże na serwerze (filter_input, długości)
    po wysłaniubrak komunikatu, F5 dubluje zamówieniekomunikat, przekierowanie PRG
    zamknięcie bazymysqli_close na końcu$pdo = null w finally

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie daje się oszukać spreparowanym formularzem, nie wywraca się na apostrofie, informuje klienta o wyniku i nie dubluje zamówień - a klucz przyjmuje obie.

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

  29. Etap 9: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

    UWAGA: Po zakończeniu pracy utwórz w folderze z numerem PESEL 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 folderze z numerem zdającego: animacja.gif, choinka.jpg, galeria.html, galeria.js, kw5.jpg, kwerendy.txt, interwal.jpg, mikolaj.jpg, przeglądarka.txt, renifer.jpg, styl.css, zamowienia.sql, zamowienie.php. Plik galeria.js mieści się w „ewentualnie inne przygotowane pliki” z arkusza - bez niego galeria nie wyświetli cen. 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 25.

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