Przejdź do głównej treści
E.14-01-21.06-SGCzerwiec 2021 · 24 kroki · ok. 62 min · 37 kryteriów z klucza

Rozwiązanie krok po kroku: Sklep papierniczy z ceną promocyjną (Nowoczesny kod (flex, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza sklep zaimportowana z pliku sklep.sql, cztery zapytania, grafika promocja2.png o szerokości 150 px z przezroczystością i strona index.php z listą towarów w promocji i kalkulatorem ceny po rabacie - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • flex zamiast float: trzy panele stoją we wspólnym znaczniku <main>, który układa je obok siebie, a stopka nie potrzebuje clear. Szerokości 25%, 50% i 25% zostają na panelach, bo tam sprawdza je klucz,
  • zmienne CSS dla dwóch kolorów z arkusza,
  • PHP na PDO na górze pliku, przed HTML-em - w treści strony zostają tylko pętle wypisujące wyniki,
  • jedno zapytanie zasila obie listy: towary w promocji tworzą listę punktowaną i opcje listy rozwijanej, więc nowy towar w promocji pojawi się w obu miejscach,
  • wybór z formularza sprawdzony na białej liście, cena pobierana zapytaniem przygotowanym, dane przez htmlspecialchars, wybrany towar zostaje zaznaczony, a cena ma zawsze dwa miejsca po przecinku.

To arkusz E.14 z czerwca 2021 - kwalifikacji formuły 2012, którą zastąpiła INF.03. Materiały pochodzą z oryginalnego archiwum CKE do pokrewnego zadania E.14-01-19.06 (szczegóły we wstępie wersji podstawowej, archiwum do pobrania na stronie arkusza). Zapytania są wykonane na MariaDB 10.4, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2.

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

  1. Etap 1: Baza danych

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

    Krok 1: Folder zdającego, archiwum i import bazy sklep

    Kryteria:

    R.1.1 Wykonano import tabel z pliku sklep.sql do bazy danych o nazwie sklep, czynność udokumentowano plikiem o nazwie baza.jpg

    ok. 4 min
    Polecenie z arkusza

    Do bazy zaimportuj tabele z pliku sklep.sql, z wcześniej rozpakowanego archiwum. Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w folderze z Twoim numerem PESEL, w formacie JPEG i nazwij baza. 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

    Folder na pulpicie nazwany numerem PESEL (w tym rozwiązaniu w jego miejscu stoi Bartosz Bryniarski), do niego archiwum pliki1.zip rozpakowane z hasłem Z@daniE_14. W phpMyAdmin nowa baza sklep, karta Import, plik sklep.sql - powstają tabele towary (10 wierszy) i dostawcy (3 wiersze). Zrzut całego ekranu z komunikatem o udanym imporcie zapisz jako baza.jpg (R.1.1).

    Tabele z pliku mają kodowanie latin1 - polskich liter w nazwach towarów nie ma, więc nie trzeba go zmieniać. Strona i tak połączy się z bazą w utf8mb4, a serwer sam przeliczy znaki.

    sklep.sql SQL nowy plik
    -- phpMyAdmin SQL Dump
    -- version 4.5.1
    -- http://www.phpmyadmin.net
    --
    -- Host: 127.0.0.1
    -- Czas generowania: 14 Lis 2017, 11:08
    -- Wersja serwera: 10.1.10-MariaDB
    -- Wersja PHP: 5.6.15
    
    SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
    SET time_zone = "+00:00";
    
    
    /*!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 */;
    
    --
    -- Baza danych: `sklep2`
    --
    
    -- --------------------------------------------------------
    
    --
    -- Struktura tabeli dla tabeli `dostawcy`
    --
    
    CREATE TABLE `dostawcy` (
      `id` int(11) NOT NULL,
      `nazwa` varchar(50) NOT NULL,
      `adres` varchar(50) NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
    
    --
    -- Zrzut danych tabeli `dostawcy`
    --
    
    INSERT INTO `dostawcy` (`id`, `nazwa`, `adres`) VALUES
    (1, 'Papiernictwo', 'ul. Boczna 12, 23-355 Bukowina'),
    (2, 'Art. szkolne', 'ul. Dworcowa 20, 34-565 Warszawa'),
    (3, 'Wszystko dla ucznia', 'ul. Towarowa 35, 88-123 Zakopane');
    
    -- --------------------------------------------------------
    
    --
    -- Struktura tabeli dla tabeli `towary`
    --
    
    CREATE TABLE `towary` (
      `id` int(11) NOT NULL,
      `nazwa` varchar(20) NOT NULL,
      `cena` float NOT NULL,
      `promocja` tinyint(1) NOT NULL,
      `idDostawcy` int(11) NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
    
    --
    -- Zrzut danych tabeli `towary`
    --
    
    INSERT INTO `towary` (`id`, `nazwa`, `cena`, `promocja`, `idDostawcy`) VALUES
    (1, 'Zeszyt 60 kartek', 4.5, 0, 1),
    (2, 'Zeszyt 32 kartki', 1.2, 0, 2),
    (3, 'Cyrkiel', 12.4, 0, 1),
    (4, 'Linijka 30 cm', 7.2, 0, 3),
    (5, 'Ekierka', 5.5, 0, 3),
    (6, 'Linijka 50 cm', 8.2, 0, 2),
    (7, 'Gumka do mazania', 3.2, 1, 1),
    (8, 'Cienkopis', 2.5, 1, 2),
    (9, 'Pisaki 60 szt.', 55, 1, 2),
    (10, 'Markery 4 szt.', 22.4, 1, 1);
    
    --
    -- Indeksy dla zrzutów tabel
    --
    
    --
    -- Indexes for table `dostawcy`
    --
    ALTER TABLE `dostawcy`
      ADD PRIMARY KEY (`id`);
    
    --
    -- Indexes for table `towary`
    --
    ALTER TABLE `towary`
      ADD PRIMARY KEY (`id`);
    
    --
    -- AUTO_INCREMENT for dumped tables
    --
    
    --
    -- AUTO_INCREMENT dla tabeli `dostawcy`
    --
    ALTER TABLE `dostawcy`
      MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
    --
    -- AUTO_INCREMENT dla tabeli `towary`
    --
    ALTER TABLE `towary`
      MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=11;
    /*!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 */;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 1
  2. Krok 2: Zapytanie 1: towary w promocji

    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 pole nazwa dla produktów objętych promocją z tabeli towary. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa FROM towary WHERE promocja=1; oraz na zrzucie widoczne są jedynie nazwy towarów: Gumka do mazania, Cienkopis, Pisaki 60 szt., Markery 4 szt.

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pole nazwa dla produktów objętych promocją z tabeli towary

    SELECT nazwa FROM towary WHERE promocja = 1; - jedno pole, tylko wiersze z promocją. Wynik: Gumka do mazania, Cienkopis, Pisaki 60 szt., Markery 4 szt. (R.1.3). Zapytanie trafia do nowego pliku kwerendy.txt (R.1.2), a zrzut do kw1.jpg. W tej wersji strony ten jeden wynik zasili dwie listy.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT nazwa FROM towary WHERE promocja = 1;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 2
  3. Krok 3: Zapytanie 2: cena cienkopisu

    Kryteria:

    R.1.4 Zapisano zapytanie 2 wybierające jedynie pole cena dla produktu cienkopis z tabeli towary. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT cena FROM towary WHERE nazwa='cienkopis' (alternatywnie id) oraz na zrzucie widoczna jest jedynie cena 2.5

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pole cena dla produktu cienkopis z tabeli towary

    Wynik: 2.5 (R.1.4), zrzut kw2.jpg. W skrypcie w miejscu 'Cienkopis' stanie parametr :name zapytania przygotowanego.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT nazwa FROM towary WHERE promocja = 1;
    
    -- Zapytanie 2
    SELECT cena FROM towary WHERE nazwa = 'Cienkopis';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 3
  4. Krok 4: Zapytanie 3: liczba towarów każdego dostawcy

    Kryteria:

    R.1.5 Zapisano zapytanie 3 wybierające jedynie pole nazwa z tabeli dostawcy oraz zliczające liczbę towarów dostarczanych przez każdego z dostawców. Pole zliczające nazwano ilosc. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT dostawcy.nazwa, COUNT(towary.nazwa) FROM dostawcy INNER JOIN towary ON dostawcy.id=towary.idDostawcy GROUP BY dostawcy.nazwa; oraz na zrzucie widoczne jest jedynie pole nazwa z tabeli dostawcy oraz odpowiadająca każdemu dostawcy liczba: Art. szkolne - 4, Papiernictwo - 4, Wszystko dla ucznia - 2 (widoczne są dokładnie trzy rekordy)

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pole nazwa dla wszystkich rekordów z tabeli dostawcy oraz zliczające ilość towarów dostarczanych przez każdego z dostawców. Pole zliczające należy nazwać ilosc

    • INNER JOIN towary ON towary.idDostawcy = dostawcy.id - każdy towar dołączony do swojego dostawcy,
    • GROUP BY dostawcy.id, dostawcy.nazwa - grupowanie po kluczu głównym, a nie po samej nazwie: dwaj dostawcy o tej samej nazwie zostaliby inaczej policzeni razem,
    • COUNT(towary.id) AS ilosc - kolumna o nazwie z arkusza.

    Wynik: Art. szkolne 4, Papiernictwo 4, Wszystko dla ucznia 2 (R.1.5), zrzut kw3.jpg.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT nazwa FROM towary WHERE promocja = 1;
    
    -- Zapytanie 2
    SELECT cena FROM towary WHERE nazwa = 'Cienkopis';
    
    -- Zapytanie 3
    SELECT dostawcy.nazwa, COUNT(towary.id) AS ilosc
    FROM dostawcy
    INNER JOIN towary ON towary.idDostawcy = dostawcy.id
    GROUP BY dostawcy.id, dostawcy.nazwa;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 4: kolumna e_mail

    Kryteria:

    R.1.6 Zapisano zapytanie 4 modyfikujące strukturę tabeli dostawcy poprzez dodanie pola o nazwie e_mail i typie TEXT. W pliku z kwerendami lub na zrzucie istnieje zapis np.: ALTER TABLE dostawcy ADD e_mail TEXT; (możliwa jest również postać ADD COLUMN) oraz na zrzucie ekranu widoczny jest komunikat o poprawnie wykonanej kwerendzie lub zmieniona struktura tabeli

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: modyfikujące strukturę tabeli dostawcy poprzez dodanie pola o nazwie e_mail i typie TEXT

    ALTER TABLE dostawcy ADD COLUMN e_mail TEXT; - słowo COLUMN jest opcjonalne, ale czytelniej mówi, co dodajesz. Na zrzucie kw4.jpg komunikat o wykonaniu albo struktura tabeli z nową kolumną (R.1.6).

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT nazwa FROM towary WHERE promocja = 1;
    
    -- Zapytanie 2
    SELECT cena FROM towary WHERE nazwa = 'Cienkopis';
    
    -- Zapytanie 3
    SELECT dostawcy.nazwa, COUNT(towary.id) AS ilosc
    FROM dostawcy
    INNER JOIN towary ON towary.idDostawcy = dostawcy.id
    GROUP BY dostawcy.id, dostawcy.nazwa;
    
    -- Zapytanie 4
    ALTER TABLE dostawcy ADD COLUMN e_mail TEXT;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Etap 2: Grafika

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

    Krok 6: promocja2.png: 150 px szerokości, przezroczyste tło

    Kryteria: ,

    R.2.1 Obraz przeskalowano z zachowaniem proporcji do szerokości 150 px, odpowiadająca temu wysokość wynosi 135 px (± 5 px)

    R.2.2 Zachowano przezroczystość tła w obrazie zapisanym w pliku promocja2.png

    ok. 3 min
    Polecenie z arkusza

    Plik promocja.png, wypakowany z archiwum, należy przeskalować z zachowaniem proporcji tak, aby jego szerokość wynosiła dokładnie 150 px. Plik po przeskalowaniu należy zapisać jako promocja2.png, z zachowaniem pozostałych atrybutów, w tym przezroczystości

    W GIMP-ie Obraz → Skaluj obraz z zamkniętym łańcuchem: szerokość 150 px, wysokość przeliczy się sama na 135 px (378 × 340 → 150 × 135) (R.2.1). Plik → Eksportuj jako promocja2.png z kanałem alfa - tło zostaje przezroczyste (R.2.2). Podgląd: grafika na tle panelu.

    Proporcje można policzyć i w CSS (width: 150px; height: auto), ale arkusz wymaga pliku o tym rozmiarze - przeskalowany plik jest też lżejszy.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Etap 3: Strona index.php

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

    Krok 7: Szkielet index.php: kodowanie, tytuł i arkusz stylów

    Kryteria: , ,

    R.2.3 Witrynę internetową zapisano w pliku index.php oraz zastosowano właściwy standard kodowania polskich znaków

    R.2.4 Nadano tytuł stronie "Artykuły papiernicze”, jest on widoczny na karcie przeglądarki

    R.3.5 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 źle przypisano co najmniej jeden selektor do bloku (np. kropka zamiast #)

    ok. 2 min
    Polecenie z arkusza

    Cechy witryny: Składa się ze strony o nazwie index.php. Zastosowany właściwy standard kodowania polskich znaków. Tytuł strony widoczny na karcie przeglądarki „Artykuły papiernicze”. Arkusz stylów w pliku o nazwie styl.css prawidłowo połączony z kodem strony

    • <meta charset="UTF-8"> - kodowanie polskich znaków (R.2.3),
    • <meta name="viewport"> - na telefonie strona nie udaje szerokiego ekranu,
    • <title> - napis na karcie (R.2.4),
    • <link rel="stylesheet" href="styl.css"> - całe formatowanie z pliku CSS (R.3.5).
    index.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>Artykuły papiernicze</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  8. Krok 8: Baner, trzy panele w znaczniku main i stopka

    Kryteria:

    R.2.5 Wygląd strony zdefiniowano dzieląc ją na 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 po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z obrazem 2

    <header> z nagłówkiem <h1>, trzy <section> z identyfikatorami left, middle, right i <footer> z nagłówkiem <h3> (R.2.5). Panele są objęte znacznikiem <main> - to główna treść strony i zarazem pojemnik, który w CSS ułoży je w rzędzie. Na egzaminie w stopce po napisie „Autor strony” stoi numer PESEL, tu imię i nazwisko autora - Bartosz Bryniarski.

    index.php PHP zmienione: 14 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Artykuły papiernicze</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>W naszym sklepie internetowym kupisz najtaniej</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 (index.php)
  9. Krok 9: Panel lewy: kontakt i grafika

    Kryteria: ,

    R.2.7 W bloku lewym umieszczono akapit lub 2 akapity z tekstem „telefon: 444333222 e-mail: [email protected]” oraz tekst jest podzielony na dwa wiersze (zgodnie z obrazem 2 w arkuszu) oraz poniżej umieszczono obraz z tekstem alternatywnym "promocja"

    R.3.4 Napis [email protected] jest odsyłaczem pocztowym mającym postać np.: <a href= "mailto:[email protected]" >…

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku lewego: Nagłówek drugiego stopnia o treści: „Kontakt”. Umieszczony w akapicie (paragrafie) tekst „telefon: 444333222 e-mail: [email protected]” podzielony na dwa wiersze (zgodnie z obrazem 2). Napis [email protected] jest odsyłaczem pocztowym łączącym z tym samym adresem. Obraz promocja2.png z tekstem alternatywnym „promocja”

    • akapit z <br> - dwa wiersze bez odstępu, jak na obrazie 2 (R.2.7),
    • href="mailto:[email protected]" - odsyłacz pocztowy (R.3.4),
    • width i height przy <img> - przeglądarka zna wymiary obrazu, zanim go pobierze, i rezerwuje na niego miejsce, więc treść pod nim nie podskakuje przy ładowaniu.
    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>Artykuły papiernicze</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>W naszym sklepie internetowym kupisz najtaniej</h1>
        </header>
        <main>
            <section id="left">
                <h2>Kontakt</h2>
                <p>telefon: 444333222<br>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja2.png" alt="promocja" width="150" height="135">
            </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 (index.php)
  10. Krok 10: Panel środkowy i prawy: lista i formularz

    Kryteria: , , ,

    R.2.6 Zapisano wszystkie nagłówki: h1 "W naszym sklepie internetowym kupisz najtaniej” - w banerze, h2 "Kontakt" - w bloku lewym, h2 "Promocja 10% obejmuje artykuły:” - w bloku środkowym, h2 "Cena wybranego artykułu w promocji” - w bloku prawym, h3 "Autor strony" i dalej wstawiony numer PESEL zdającego - w stopce

    R.2.10 Na stronie umieszczono przycisk WYBIERZ

    R.3.2 Dane z formularza przesyłane są za pomocą metody POST

    R.3.3 Przycisk wysyła dane z formularza (input lub button z typem submit lub button bez typu)

    ok. 3 min
    Polecenie z arkusza

    Zawartość bloku środkowego: Nagłówek drugiego stopnia o treści „Promocja 10% obejmuje artykuły:”. Lista punktowana (nieuporządkowana) zgodna z obrazem 3, elementy listy generowane są skryptem 1. Zawartość bloku prawego: Nagłówek drugiego stopnia o treści „Cena wybranego artykułu w promocji”. Formularz wysyła dane do serwera za pomocą przycisku WYBIERZ, metodą post

    • w panelu środkowym <h2> i pusta lista <ul> - elementy wstawi skrypt,
    • w prawym <h2> i formularz z method="post" (R.3.2); lista rozwijana <select name="product"> też jest na razie pusta - opcje wygeneruje ten sam skrypt co listę punktowaną,
    • aria-label - lista nie ma widocznej etykiety, więc czytnik ekranu dostaje jej opis z atrybutu,
    • <button type="submit">WYBIERZ</button> - przycisk wysyłający formularz (R.2.10, R.3.3); w <button> może stać dowolna treść, a nie tylko napis z atrybutu.

    Komplet nagłówków h1, trzy h2 i h3 jest na stronie (R.2.6).

    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>Artykuły papiernicze</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>W naszym sklepie internetowym kupisz najtaniej</h1>
        </header>
        <main>
            <section id="left">
                <h2>Kontakt</h2>
                <p>telefon: 444333222<br>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja2.png" alt="promocja" width="150" height="135">
            </section>
            <section id="middle">
                <h2>Promocja 10% obejmuje artykuły:</h2>
                <ul>
                </ul>
            </section>
            <section id="right">
                <h2>Cena wybranego artykułu w promocji</h2>
                <form method="post">
                    <select name="product" aria-label="Artykuł w promocji">
                    </select>
                    <button type="submit">WYBIERZ</button>
                </form>
            </section>
        </main>
        <footer>
            <h3>Autor strony Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  11. Etap 4: Styl CSS

    Kroki 11-16 · po tym etapie masz 27 z 37 kryteriów (pkt)

    Krok 11: Zmienne z kolorami arkusza

    ok. 2 min
    Polecenie z arkusza

    Format banera: kolor tła #B284BE, wysokość 60 px, wielkość czcionki 120%, wyrównanie tekstu do środka

    Arkusz używa dwóch kolorów: fioletu (baner, stopka, elementy listy) i zieleni (trzy panele). Zmienne CSS w selektorze :root nazywają je raz, a reguły odczytują je funkcją var(--violet) - zmiana odcienia to jeden wpis zamiast pięciu.

    styl.css CSS nowy plik
    :root {
        --violet: #B284BE;
        --green: #BCE27F;
    }
    Podgląd strony po tym kroku (index.php)
  12. Krok 12: Baner

    ok. 1 min
    Polecenie z arkusza

    Format banera: kolor tła #B284BE, wysokość 60 px, wielkość czcionki 120%, wyrównanie tekstu do środka

    Tło ze zmiennej, wysokość, czcionka i wyśrodkowanie - wartości z arkusza.

    styl.css CSS zmienione: 7 linii
    :root {
        --violet: #B284BE;
        --green: #BCE27F;
    }
    
    header {
        background-color: var(--violet);
        height: 60px;
        font-size: 120%;
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  13. Krok 13: Panele obok siebie: flex w main

    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 po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z obrazem 2

    display: flex na <main> ustawia trzy sekcje w jednym wierszu. Nie ma float, więc stopka sama staje pod <main>, bez clear: both.

    Siatka (grid-template-columns: 25% 50% 25%) byłaby równie dobra, ale klucz ocenia szerokość na panelach (R.4.4) - dlatego pojemnik flex tylko układa, a szerokości zostają w regułach paneli.

    styl.css CSS zmienione: 4 linie
    :root {
        --violet: #B284BE;
        --green: #BCE27F;
    }
    
    header {
        background-color: var(--violet);
        height: 60px;
        font-size: 120%;
        text-align: center;
    }
    
    main {
        display: flex;
    }
    Podgląd strony po tym kroku (index.php)
  14. Krok 14: Trzy panele

    Kryteria: , , ,

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

    R.4.3 Ustawiono wyrównanie tekstu do środka dla banera, panelu środkowego i prawego

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

    R.4.5 Ustawiono wielkość czcionki dla banera, paneli: lewego i prawego na 120%

    ok. 3 min
    Polecenie z arkusza

    Format panelu lewego: kolor tła #BCE27F, wysokość 600 px, szerokość 25%, wielkość czcionki 120%. Format panelu środkowego: kolor tła #BCE27F, wysokość 600 px, szerokość 50%, wyrównanie tekstu do środka. Format panelu prawego: kolor tła #BCE27F, wysokość 600 px, szerokość 25%, wielkość czcionki 120%, wyrównanie tekstu do środka

    To, co wspólne dla trzech paneli (tło i wysokość 600 px), stoi w jednej regule z trzema selektorami; każdy panel ma potem tylko swoje różnice. Selektory z krzyżykiem, bo panele mają id - kropka zamiast krzyżyka unieważnia R.3.5.

    Szerokości 25% + 50% + 25% (R.4.4), wyśrodkowany baner, panel środkowy i prawy (R.4.3), czcionka 120% w banerze i panelach bocznych (R.4.5). Panele w rzędzie flex - układ jak na obrazie 2 (R.3.1).

    styl.css CSS zmienione: 23 linie
    :root {
        --violet: #B284BE;
        --green: #BCE27F;
    }
    
    header {
        background-color: var(--violet);
        height: 60px;
        font-size: 120%;
        text-align: center;
    }
    
    main {
        display: flex;
    }
    
    #left,
    #middle,
    #right {
        background-color: var(--green);
        height: 600px;
    }
    
    #left {
        width: 25%;
        font-size: 120%;
    }
    
    #middle {
        width: 50%;
        text-align: center;
    }
    
    #right {
        width: 25%;
        font-size: 120%;
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Stopka

    Kryteria: ,

    R.4.1 Ustawiono kolor tła dla banera i stopki na #B284BE oraz panelu lewego, środkowego i prawego na #BCE27F

    R.4.2 Ustawiono wysokość banera i stopki na 60px oraz paneli: lewego, prawego i środkowego na 600px

    ok. 1 min
    Polecenie z arkusza

    Format stopki: kolor tła #B284BE, wysokość 60 px, wyrównanie tekstu do lewej strony, marginesy wewnętrzne 5 px

    padding: 5px - margines wewnętrzny z czterech stron. Kolory tła wszystkich bloków (R.4.1) i wysokości 60/600/60 px (R.4.2) są ustawione. Wewnętrzny margines stopki ma też drugą zaletę: margines nagłówka <h3> nie „wypływa” przez krawędź stopki, więc między panelami a stopką nie powstaje biały pasek.

    styl.css CSS zmienione: 7 linii
    :root {
        --violet: #B284BE;
        --green: #BCE27F;
    }
    
    header {
        background-color: var(--violet);
        height: 60px;
        font-size: 120%;
        text-align: center;
    }
    
    main {
        display: flex;
    }
    
    #left,
    #middle,
    #right {
        background-color: var(--green);
        height: 600px;
    }
    
    #left {
        width: 25%;
        font-size: 120%;
    }
    
    #middle {
        width: 50%;
        text-align: center;
    }
    
    #right {
        width: 25%;
        font-size: 120%;
        text-align: center;
    }
    
    footer {
        background-color: var(--violet);
        height: 60px;
        text-align: left;
        padding: 5px;
    }
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Elementy listy, marginesy obrazu, nagłówków i formularza

    Kryteria: , ,

    R.4.6 Ustawiono wszystkie marginesy wewnętrzne (padding) dla stopki na 5px oraz margines wewnętrzny górny (padding-top) dla elementów listy na 20px

    R.4.7 Ustawiono margines zewnętrzny górny (margin-top) dla obrazu i nagłówka h2 na 50px oraz margines zewnętrzny dolny (margin-bottom) dla formularza na 100px

    R.4.8 Ustawiono kolor tła elementów listy na #B284BE

    ok. 2 min
    Polecenie z arkusza

    Format elementów listy punktowanej: margines wewnętrzny górny 20 px, kolor tła #B284BE. Formatowanie obrazu i nagłówka drugiego stopnia: margines zewnętrzny górny 50 px. Formatowanie formularza: margines zewnętrzny dolny 100 px

    li - padding-top: 20px i fioletowe tło (R.4.6 razem z wewnętrznym marginesem stopki, R.4.8). img, h2 - margin-top: 50px, form - margin-bottom: 100px (R.4.7). Elementów listy jeszcze nie ma - wypisze je skrypt.

    styl.css CSS zmienione: 14 linii
    :root {
        --violet: #B284BE;
        --green: #BCE27F;
    }
    
    header {
        background-color: var(--violet);
        height: 60px;
        font-size: 120%;
        text-align: center;
    }
    
    main {
        display: flex;
    }
    
    #left,
    #middle,
    #right {
        background-color: var(--green);
        height: 600px;
    }
    
    #left {
        width: 25%;
        font-size: 120%;
    }
    
    #middle {
        width: 50%;
        text-align: center;
    }
    
    #right {
        width: 25%;
        font-size: 120%;
        text-align: center;
    }
    
    footer {
        background-color: var(--violet);
        height: 60px;
        text-align: left;
        padding: 5px;
    }
    
    li {
        padding-top: 20px;
        background-color: var(--violet);
    }
    
    img,
    h2 {
        margin-top: 50px;
    }
    
    form {
        margin-bottom: 100px;
    }
    Podgląd strony po tym kroku (index.php)
  17. Etap 5: Skrypty PHP

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

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

    Kryteria: ,

    R.5.1 Skrypt napisano w języku PHP oraz skrypt umieszczono w pliku index.php

    R.5.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 połączenia z bazą. 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

    Cały kod PHP stoi na samej górze index.php, przed <!DOCTYPE html>: najpierw przygotowuje dane, a HTML tylko je wypisuje (R.5.1).

    • e() - krótka funkcja z htmlspecialchars, przez którą przejdzie każdy tekst z bazy wypisany na stronie,
    • new PDO(...) - serwer localhost, baza sklep, kodowanie utf8mb4, użytkownik root i puste hasło (R.5.2),
    • ERRMODE_EXCEPTION - każdy błąd bazy rzuca wyjątek; try / catch zamienia go na zwykłe zdanie zamiast komunikatu PHP z danymi serwera.

    Klucz uznaje zarówno MySQLi, jak i PDO. Od tego kroku podgląd to wynik prawdziwego uruchomienia w PHP 8.2.

    index.php PHP zmienione: 17 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $message = '';
    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,
        ]);
    } catch (PDOException $exception) {
        $message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Artykuły papiernicze</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>W naszym sklepie internetowym kupisz najtaniej</h1>
        </header>
        <main>
            <section id="left">
                <h2>Kontakt</h2>
                <p>telefon: 444333222<br>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja2.png" alt="promocja" width="150" height="135">
            </section>
            <section id="middle">
                <h2>Promocja 10% obejmuje artykuły:</h2>
                <ul>
                </ul>
            </section>
            <section id="right">
                <h2>Cena wybranego artykułu w promocji</h2>
                <form method="post">
                    <select name="product" aria-label="Artykuł w promocji">
                    </select>
                    <button type="submit">WYBIERZ</button>
                </form>
            </section>
        </main>
        <footer>
            <h3>Autor strony Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Zapytanie 1 i lista punktowana

    Kryteria: , , ,

    R.2.8 Na stronie umieszczono listę punktowaną (nieuporządkowaną), zawierającą nazwy artykułów z bazy danych. W kodzie HTML lub PHP użyto znaczników <ul>, <li> oraz ich zamknięć

    R.5.4 Skrypt 1 wysyła do bazy danych zapytanie 1

    R.5.5 Skrypt 1 wyświetla 4 nazwy produktów zwrócone zapytaniem (Gumka do mazania, Cienkopis, Pisaki 60 szt., Markery 4 szt.)

    R.5.6 Wyświetlane przez skrypt 1 nazwy towarów są umieszczone w elementach listy (zgodnie z obrazem 3 w arkuszu)

    ok. 3 min
    Polecenie z arkusza

    Skrypt 1: Wysyła do bazy danych zapytanie 1 (z pliku kwerendy.txt). Nazwy produktów pobrane zapytaniem wypisywane są jako elementy listy punktowanej (nieuporządkowanej) zgodnie z obrazem 3

    • $pdo->query(...) - zapytanie 1 bez żadnych danych z zewnątrz, więc nie musi być przygotowane (R.5.4),
    • fetchAll(PDO::FETCH_COLUMN) - od razu tablica samych nazw: ['Gumka do mazania', 'Cienkopis', ...].

    W liście <ul> pętla foreach wypisuje każdą nazwę w <li> przez e(). Zapis <?= … ?> to skrót od <?php echo … ?>, a składnia foreach (...): / endforeach; czyta się w HTML-u łatwiej niż nawiasy klamrowe.

    Podgląd: cztery towary w fioletowych elementach listy (R.2.8, R.5.5, R.5.6).

    index.php PHP zmienione: 4 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $message = '';
    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,
        ]);
        $products = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1;')->fetchAll(PDO::FETCH_COLUMN);
    } catch (PDOException $exception) {
        $message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Artykuły papiernicze</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>W naszym sklepie internetowym kupisz najtaniej</h1>
        </header>
        <main>
            <section id="left">
                <h2>Kontakt</h2>
                <p>telefon: 444333222<br>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja2.png" alt="promocja" width="150" height="135">
            </section>
            <section id="middle">
                <h2>Promocja 10% obejmuje artykuły:</h2>
                <ul>
                    <?php foreach ($products as $name): ?>
                    <li><?= e($name) ?></li>
                    <?php endforeach; ?>
                </ul>
            </section>
            <section id="right">
                <h2>Cena wybranego artykułu w promocji</h2>
                <form method="post">
                    <select name="product" aria-label="Artykuł w promocji">
                    </select>
                    <button type="submit">WYBIERZ</button>
                </form>
            </section>
        </main>
        <footer>
            <h3>Autor strony Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  19. Krok 19: Lista rozwijana z tego samego wyniku

    Kryteria:

    R.2.9 Na stronie umieszczono listę rozwijalną, zawierającą nazwy artykułów objętych promocją pochodzących z bazy danych (Gumka do mazania, Cienkopis, Pisaki 60 szt. Markery 4 szt.). W kodzie HTML użyto znaczników <select>, <option> oraz ich zamknięć

    ok. 3 min
    Polecenie z arkusza

    Formularz składający się z: Listy rozwijalnej zawierającej nazwy wszystkich towarów w promocji, zgodnej z obrazem 4

    Opcje listy rozwijanej to te same nazwy co w liście punktowanej - ta sama tablica $products wypisana drugi raz, bez drugiego zapytania (R.2.9). Gdy towar wejdzie do promocji w bazie, pojawi się w obu listach naraz.

    • filter_input(INPUT_POST, 'product') - towar z wysłanego formularza albo null przy pierwszym wejściu, bez ostrzeżenia; (string) zamienia null na pusty napis,
    • $name === $product ? ' selected' : '' - po wysłaniu zaznaczona zostaje wybrana pozycja, a nie pierwsza z listy.

    Podgląd: wysłany formularz z cienkopisem - cienkopis jest zaznaczony.

    index.php PHP zmienione: 5 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $product = (string) filter_input(INPUT_POST, 'product');
    $price = null;
    $message = '';
    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,
        ]);
        $products = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1;')->fetchAll(PDO::FETCH_COLUMN);
    } catch (PDOException $exception) {
        $message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Artykuły papiernicze</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>W naszym sklepie internetowym kupisz najtaniej</h1>
        </header>
        <main>
            <section id="left">
                <h2>Kontakt</h2>
                <p>telefon: 444333222<br>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja2.png" alt="promocja" width="150" height="135">
            </section>
            <section id="middle">
                <h2>Promocja 10% obejmuje artykuły:</h2>
                <ul>
                    <?php foreach ($products as $name): ?>
                    <li><?= e($name) ?></li>
                    <?php endforeach; ?>
                </ul>
            </section>
            <section id="right">
                <h2>Cena wybranego artykułu w promocji</h2>
                <form method="post">
                    <select name="product" aria-label="Artykuł w promocji">
                        <?php foreach ($products as $name): ?>
                        <option<?= $name === $product ? ' selected' : '' ?>><?= e($name) ?></option>
                        <?php endforeach; ?>
                    </select>
                    <button type="submit">WYBIERZ</button>
                </form>
            </section>
        </main>
        <footer>
            <h3>Autor strony Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  20. Krok 20: Zapytanie 2 dla towaru z białej listy

    Kryteria:

    R.5.7 Skrypt 2 wysyła do bazy danych zapytanie 2 zmodyfikowane tak, że w sekcji warunku sprawdzana nazwa produktu jest zgodna z wyborem w formularzu

    ok. 3 min
    Polecenie z arkusza

    Skrypt 2: Pobiera z formularza nazwę produktu wybranego w liście rozwijalnej. Wysyła do bazy danych zapytanie 2 (z pliku kwerendy.txt), zmodyfikowane w ten sposób, że w sekcji warunku nazwa produktu jest zgodna z wyborem w liście rozwijalnej

    Lista rozwijana podsuwa cztery nazwy, ale serwer może dostać dowolny tekst. Dlatego:

    • in_array($product, $products, true) - zapytanie idzie do bazy tylko wtedy, gdy wybrany towar jest na liście towarów w promocji. To biała lista: nazwa spoza promocji (np. zeszyt podany ręcznie w narzędziach przeglądarki) nie dostanie ceny z rabatem,
    • prepare z parametrem :name i execute(['name' => $product]) - zapytanie 2, w którym nazwę z formularza baza dostaje osobno od treści zapytania (R.5.7),
    • fetchColumn() - pierwsza kolumna jedynego wiersza, czyli cena.
    index.php PHP zmienione: 5 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $product = (string) filter_input(INPUT_POST, 'product');
    $price = null;
    $message = '';
    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,
        ]);
        $products = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1;')->fetchAll(PDO::FETCH_COLUMN);
        if (in_array($product, $products, true)) {
            $statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name;');
            $statement->execute(['name' => $product]);
            $price = round($statement->fetchColumn() * 0.9, 2);
        }
    } catch (PDOException $exception) {
        $message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Artykuły papiernicze</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>W naszym sklepie internetowym kupisz najtaniej</h1>
        </header>
        <main>
            <section id="left">
                <h2>Kontakt</h2>
                <p>telefon: 444333222<br>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja2.png" alt="promocja" width="150" height="135">
            </section>
            <section id="middle">
                <h2>Promocja 10% obejmuje artykuły:</h2>
                <ul>
                    <?php foreach ($products as $name): ?>
                    <li><?= e($name) ?></li>
                    <?php endforeach; ?>
                </ul>
            </section>
            <section id="right">
                <h2>Cena wybranego artykułu w promocji</h2>
                <form method="post">
                    <select name="product" aria-label="Artykuł w promocji">
                        <?php foreach ($products as $name): ?>
                        <option<?= $name === $product ? ' selected' : '' ?>><?= e($name) ?></option>
                        <?php endforeach; ?>
                    </select>
                    <button type="submit">WYBIERZ</button>
                </form>
            </section>
        </main>
        <footer>
            <h3>Autor strony Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>

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

  21. Krok 21: Cena po rabacie pod formularzem

    Kryteria:

    R.5.8 Skrypt 2 oblicza i wyświetla zaokrągloną do dwóch miejsc po przecinku cenę w promocji (jako 90% ceny pobranej zapytaniem)

    ok. 3 min
    Polecenie z arkusza

    Oblicza cenę w promocji jako 90% ceny pobranej zapytaniem. Zaokrągla obliczoną cenę do dwóch miejsc po przecinku. Obliczona cena w promocji wyświetlana jest poniżej formularza

    • w skrypcie: round(… * 0.9, 2) - 90% ceny zaokrąglone do dwóch miejsc (R.5.8),
    • pod formularzem: akapit tylko wtedy, gdy cena jest policzona, a w nim number_format($price, 2, ',', ' ') - zawsze dwie cyfry po przecinku i przecinek dziesiętny, jak w polskich cenach: pisaki kosztują 49,50 zł, a nie 49.5.

    Podgląd: dla cienkopisu (2,50 zł) Cena w promocji: 2,25 zł.

    index.php PHP zmienione: 3 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $product = (string) filter_input(INPUT_POST, 'product');
    $price = null;
    $message = '';
    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,
        ]);
        $products = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1;')->fetchAll(PDO::FETCH_COLUMN);
        if (in_array($product, $products, true)) {
            $statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name;');
            $statement->execute(['name' => $product]);
            $price = round($statement->fetchColumn() * 0.9, 2);
        }
    } catch (PDOException $exception) {
        $message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Artykuły papiernicze</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>W naszym sklepie internetowym kupisz najtaniej</h1>
        </header>
        <main>
            <section id="left">
                <h2>Kontakt</h2>
                <p>telefon: 444333222<br>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja2.png" alt="promocja" width="150" height="135">
            </section>
            <section id="middle">
                <h2>Promocja 10% obejmuje artykuły:</h2>
                <ul>
                    <?php foreach ($products as $name): ?>
                    <li><?= e($name) ?></li>
                    <?php endforeach; ?>
                </ul>
            </section>
            <section id="right">
                <h2>Cena wybranego artykułu w promocji</h2>
                <form method="post">
                    <select name="product" aria-label="Artykuł w promocji">
                        <?php foreach ($products as $name): ?>
                        <option<?= $name === $product ? ' selected' : '' ?>><?= e($name) ?></option>
                        <?php endforeach; ?>
                    </select>
                    <button type="submit">WYBIERZ</button>
                </form>
                <?php if ($price !== null): ?>
                <p>Cena w promocji: <?= number_format($price, 2, ',', ' ') ?> zł</p>
                <?php endif; ?>
            </section>
        </main>
        <footer>
            <h3>Autor strony Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Zamknięcie połączenia

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Na końcu działania skrypt zamyka połączenie z serwerem

    PDO nie ma funkcji close - połączenie zamyka się, gdy znika ostatnie odwołanie do obiektu, czyli po $statement = null i $pdo = null. Stoją w bloku finally, który wykona się zawsze po try - po udanych zapytaniach i po wyjątku (R.5.3). To ostatnia operacja na bazie: HTML pod spodem korzysta już tylko z tablicy $products i liczby $price.

    Pod formularzem jest też miejsce na komunikat, gdy serwer bazy nie działa - zamiast pustej strony z błędem PHP.

    index.php PHP zmienione: 6 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $products = [];
    $product = (string) filter_input(INPUT_POST, 'product');
    $price = null;
    $message = '';
    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,
        ]);
        $products = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1;')->fetchAll(PDO::FETCH_COLUMN);
        if (in_array($product, $products, true)) {
            $statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name;');
            $statement->execute(['name' => $product]);
            $price = round($statement->fetchColumn() * 0.9, 2);
        }
    } catch (PDOException $exception) {
        $message = 'Nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę.';
    } finally {
        $statement = null;
        $pdo = null;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Artykuły papiernicze</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>W naszym sklepie internetowym kupisz najtaniej</h1>
        </header>
        <main>
            <section id="left">
                <h2>Kontakt</h2>
                <p>telefon: 444333222<br>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja2.png" alt="promocja" width="150" height="135">
            </section>
            <section id="middle">
                <h2>Promocja 10% obejmuje artykuły:</h2>
                <ul>
                    <?php foreach ($products as $name): ?>
                    <li><?= e($name) ?></li>
                    <?php endforeach; ?>
                </ul>
            </section>
            <section id="right">
                <h2>Cena wybranego artykułu w promocji</h2>
                <form method="post">
                    <select name="product" aria-label="Artykuł w promocji">
                        <?php foreach ($products as $name): ?>
                        <option<?= $name === $product ? ' selected' : '' ?>><?= e($name) ?></option>
                        <?php endforeach; ?>
                    </select>
                    <button type="submit">WYBIERZ</button>
                </form>
                <?php if ($price !== null): ?>
                <p>Cena w promocji: <?= number_format($price, 2, ',', ' ') ?> zł</p>
                <?php endif; ?>
                <?php if ($message !== ''): ?>
                <p><?= e($message) ?></p>
                <?php endif; ?>
            </section>
        </main>
        <footer>
            <h3>Autor strony Bartosz Bryniarski</h3>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  23. Etap 6: Porównanie z wersją podstawową

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

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

    ok. 3 min

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

    wersja podstawowawersja nowoczesna
    układfloat: left 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 --violet, --green
    lista rozwijanacztery opcje wpisane w HTMLopcje z tego samego zapytania co lista punktowana
    bibliotekamysqli (tabela 1 arkusza)PDO z wyjątkami
    gdzie jest kod PHPw dwóch miejscach stronyna górze pliku, w HTML-u tylko pętle
    towar w zapytaniuwklejony w treść zapytaniabiała lista i parametr :name
    wypisywanieecho wprost z bazye() z htmlspecialchars
    zamknięcie bazymysqli_close na końcu$pdo = null w finally
    cena2.25, 49.52,25 zł, 49,50 zł
    po wysłaniulista wraca do pierwszej pozycjiwybrany towar zostaje zaznaczony

    Sprawdzone na tej wersji: wartość x' OR '1'='1, nazwa z apostrofem, nazwa towaru spoza promocji i znacznik HTML wysłane w polu product nie dają ceny ani błędu.

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie daje się oszukać spreparowanym formularzem i nie wymaga przepisywania nazw towarów do HTML-u - a klucz przyjmuje obie.

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

  24. Etap 7: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz plik tekstowy. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowałeś poprawność działania witryny. Nazwij plik przeglądarka.txt i zapisz go w folderze z numerem PESEL. Nagraj płytę z rezultatami pracy.

    W przeglądarka.txt nazwa przeglądarki. W folderze z numerem zdającego: baza.jpg, kw1.jpg - kw4.jpg, kwerendy.txt, index.php, styl.css, promocja.png, promocja2.png, przeglądarka.txt (i rozpakowany sklep.sql). 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 22.

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