Przejdź do głównej treści
INF.03-01-23.06-SGCzerwiec 2023 · 36 kroków · ok. 72 min · 38 kryteriów z klucza

Rozwiązanie krok po kroku: Portal sklepu dla uczniów (Nowoczesny kod (flex, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza sklep z czterema zapytaniami, przeskalowana grafika z przezroczystym tłem i strona index.php z banerem, trzema panelami, formularzem sprawdzania ceny i stopką - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • flexbox zamiast float: trzy panele stoją w znaczniku <main> ustawionym w rząd, bez clear przy stopce. Szerokości 35%, 30% i 35% zostają na samych panelach - tak ocenia je klucz,
  • zmienne CSS dla kolorów z arkusza i panele nazwane po treści (#offers, #price-check, #contact),
  • PHP na PDO: wszystkie dane strona pobiera na górze pliku index.php, przed HTML-em, w bloku try, a w HTML-u zostają tylko pętle i wypisywanie,
  • jedna lista towarów w stałej PRODUCTS: z niej powstają opcje listy rozwijalnej i z nią porównywany jest wybór z formularza (biała lista), a do bazy nazwa trafia zapytaniem przygotowanym,
  • bezpieczne wypisywanie (htmlspecialchars w funkcji e()) i komunikaty dla towaru spoza listy i dla awarii bazy.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafika pochodzą z prawdziwego archiwum materialy1, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 na bazie z arkusza - od kroku z zapytaniem 2 po wysłaniu formularza z wybraną gumką do mazania, jak na obrazie 2.

Jak liczą się punkty. Klucz ma 38 kryteriów (R.1.1 - R.5.8), każde za 1 punkt. Próg zdania to 75%, czyli 29 punktów. Przy każdym kroku jest napisane, które kryteria spełnia.

  1. Etap 1: Baza danych

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

    Krok 1: Baza danych sklep

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Utwórz nową bazę danych o nazwie sklep.

    Rozpakuj materialy1.7z z pulpitu (hasło Z@dani3x - małpa zamiast „a”, cyfra 3 zamiast „e”) do folderu z numerem zdającego: sklep.sql i promocja.png. W phpMyAdmin Nowa → sklep albo zapytaniem:

    CREATE DATABASE sklep CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;
    

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

    Typowe błędy

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

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

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

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

    Zaznaczona baza sklep, Import → sklep.sql → Wykonaj. Dwie tabele z obrazu 1: dostawcy (3 rekordy) i towary (10 rekordów). promocja ma wartość 0 albo 1, a idDostawcy wskazuje id dostawcy - na tej parze pól opiera się relacja.

  3. Krok 3: Zrzut ekranu baza.jpg

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Wykonaj zrzut ekranu po imporcie. Zrzut zapisz 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.

    Cały ekran z paskiem zadań, w JPEG, pod nazwą baza - w tym arkuszu zrzut importu nie nazywa się import. Zrzuty zapytań to kwerenda1 - kwerenda4, też w JPEG, i muszą pokazywać także treść zapytania (phpMyAdmin wyświetla ją nad wynikiem).

    Typowe błędy

    Wykadrowany zrzut, zrzut bez paska zadań albo nazwa import.jpg zamiast baza.jpg - R.1.1 przepada.

  4. Krok 4: Zapytanie 1: towary objęte promocją

    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 z tabeli towary dla towarów objętych promocją. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa FROM towary WHERE promocja = 1; oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym 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 z tabeli towary dla towarów objętych promocją.

    „Objęte promocją” to w bazie promocja = 1. Każda klauzula w osobnej linii - łatwiej przeczytać i poprawić. Wynik: 4 wiersze - Gumka do mazania, Cienkopis, Pisaki 60 szt., Markery 4 szt. Zapytanie do kwerendy.txt, zrzut kwerenda1.jpg.

    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 4
  5. Krok 5: Zapytanie 2: cena markerów

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pole cena z tabeli towary dla towaru Markery 4 szt.

    Warunek porównuje cały napis, razem ze spacjami i kropką. Wynik: 22.4. Zrzut: kwerenda2.jpg. To samo zapytanie wyśle Skrypt 2 - z parametrem w miejscu nazwy.

    Typowe błędy

    nazwa = 'Markery 4 szt' bez kropki na końcu nie znajdzie towaru - wynik jest pusty.

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

    Kryteria:

    R.1.5 Zapisano zapytanie 3 wybierające jedynie pola id i nazwa z tabeli dostawcy oraz zliczające liczbę towarów dostarczanych przez każdego z dostawców. Nazwa kolumny (alias) zliczającej liczba_towarow. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT dostawcy.id, dostawcy.nazwa, COUNT(towary.id) AS liczba_towarow FROM dostawcy JOIN towary ON idDostawcy = dostawcy.id GROUP BY dostawcy.nazwa; (lub INNER JOIN, lub wersja bez JOIN z porównaniem kluczy po WHERE, w funkcji COUNT możliwa również *) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są dokładnie 3 rekordy, jedynie pola id, nazwa z tabeli dostawcy oraz odpowiadająca im liczba_towarow: Art. szkolne - 4, Papiernictwo - 4, Wszystko dla ucznia - 2

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pola id oraz nazwa dla wszystkich rekordów z tabeli dostawcy oraz zliczające liczbę towarów dostarczanych przez każdego z dostawców. Nazwa kolumny (alias) dla zliczenia towarów: liczba_towarow. W zapytaniu 3 należy posłużyć się relacją.

    • aliasy tabel d i t - obie tabele mają kolumny id i nazwa, a krótki przedrostek mówi, z której tabeli pochodzi każde pole,
    • LEFT JOIN ... ON t.idDostawcy = d.id - relacja z obrazu 1. Arkusz chce „wszystkich rekordów z tabeli dostawcy”: złączenie lewostronne zostawia także dostawcę bez towarów, a COUNT(t.id) policzy mu wtedy 0 (COUNT(*) policzyłby 1 - pusty wiersz złączenia),
    • GROUP BY d.id, d.nazwa - grupa na dostawcę, po obu wybranych polach.

    Wynik: 3 wiersze - Papiernictwo 4, Art. szkolne 4, Wszystko dla ucznia 2. Każdy dostawca ma tu towary, więc zwykły JOIN z przykładu w kluczu daje te same wiersze. Zrzut: kwerenda3.jpg.

    Typowe błędy

    SELECT id, nazwa ... bez przedrostków tabel kończy się błędem „column is ambiguous”.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT nazwa
    FROM towary
    WHERE promocja = 1;
    
    -- Zapytanie 2
    SELECT cena
    FROM towary
    WHERE nazwa = 'Markery 4 szt.';
    
    -- Zapytanie 3
    SELECT d.id, d.nazwa, COUNT(t.id) AS liczba_towarow
    FROM dostawcy AS d
    LEFT JOIN towary AS t ON t.idDostawcy = d.id
    GROUP BY d.id, d.nazwa;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: nowe pole informacje

    Kryteria:

    R.1.6 Zapisano zapytanie 4 modyfikujące strukturę tabeli dostawcy poprzez dodanie pola typu napisowego o nazwie informacje. W pliku z kwerendami lub na zrzucie istnieje zapis np.: ALTER TABLE dostawcy ADD COLUMN informacje TEXT; (możliwe również bez słowa COLUMN, dowolny typ tekstowy) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest komunikat o poprawnie wykonanym zapytaniu lub zmieniona struktura tabeli

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: modyfikujące strukturę tabeli dostawcy, poprzez dodanie pola typu tekstowego o nazwie informacje.

    ALTER TABLE zmienia strukturę tabeli, ADD COLUMN informacje TEXT dodaje pole tekstowe (sprawdzone na bazie z archiwum: tabela ma potem cztery kolumny). Zapytanie wykonaj raz - drugie wykonanie zgłosi, że kolumna już istnieje. Zrzut: kwerenda4.jpg z komunikatem albo strukturą tabeli.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT nazwa
    FROM towary
    WHERE promocja = 1;
    
    -- Zapytanie 2
    SELECT cena
    FROM towary
    WHERE nazwa = 'Markery 4 szt.';
    
    -- Zapytanie 3
    SELECT d.id, d.nazwa, COUNT(t.id) AS liczba_towarow
    FROM dostawcy AS d
    LEFT JOIN towary AS t ON t.idDostawcy = d.id
    GROUP BY d.id, d.nazwa;
    
    -- Zapytanie 4
    ALTER TABLE dostawcy ADD COLUMN informacje TEXT;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

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

    Krok 8: promocja.png: 180 px szerokości z przezroczystością

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Przygotowanie grafiki: Plik promocja.png, wypakowany z archiwum, należy przeskalować z zachowaniem proporcji tak, aby jego szerokość wynosiła dokładnie 180 px. Plik powinien być zapisany z zachowaniem przezroczystości pod tą samą nazwą.

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

    Typowe błędy

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

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

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

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

    <!DOCTYPE html>, <html lang="pl"> i <meta charset="UTF-8"> jako pierwszy element <head> - razem R.2.2. Plik ma rozszerzenie .php, bo za chwilę dojdzie do niego kod PHP; na egzaminie leży w htdocs XAMPP-a i otwierasz go przez http://localhost/....

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

    Kryteria:

    R.2.3 Nadano stronie tytuł "Sklep dla uczniów”

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki „Sklep dla uczniów”.

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

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

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

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

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

    Typowe błędy

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

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  12. Krok 12: Bloki: baner, trzy panele w main, stopka

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

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

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

    • lewy <section id="offers"> - towary w promocji,
    • środkowy <section id="price-check"> - sprawdzanie ceny,
    • prawy <aside id="contact"> - kontakt, czyli treść dodatkowa obok głównej.

    <section> i <aside> to znaczniki sekcji HTML5 (R.2.4). Identyfikatory mówią, co jest w panelu, a nie gdzie stoi - po zmianie układu nazwa #left przestałaby pasować.

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

    Kryteria:

    R.2.5 Zapisano w banerze nagłówek h1 "Dzisiejsze promocje naszego sklepu”; w panelach przynajmniej jeden z nagłówków h2: "Taniej o 30%”, "Sprawdź cenę”, "Kontakt"; w stopce nagłówek h4 z numerem zdającego oraz w panelu prawym zapisano paragraf z adresem e-mail

    ok. 1 min
    Polecenie z arkusza

    Zawartość banera: nagłówek pierwszego stopnia o treści „Dzisiejsze promocje naszego sklepu”.

    <h1> w <header>.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
            </section>
            <section id="price-check">
            </section>
            <aside id="contact">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  14. Krok 14: Panel lewy: nagłówek i lista numerowana

    Kryteria:

    R.2.6 W bloku lewym umieszczono jedną listę numerowaną (w kodzie HTML lub skrypcie), użyto znaczników <ol>, <li>

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku lewego: Nagłówek drugiego stopnia o treści „Taniej o 30%”. Lista numerowana (uporządkowana) zgodna z obrazem 2, elementy listy generowane są skryptem 1.

    <h2> i pusta <ol> - elementy <li> wypisze Skrypt 1. Numerację literami (a, b, c) da CSS.

    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>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
                </ol>
            </section>
            <section id="price-check">
            </section>
            <aside id="contact">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Panel środkowy: formularz z listą rozwijalną

    Kryteria: , , , ,

    R.2.5 Zapisano w banerze nagłówek h1 "Dzisiejsze promocje naszego sklepu”; w panelach przynajmniej jeden z nagłówków h2: "Taniej o 30%”, "Sprawdź cenę”, "Kontakt"; w stopce nagłówek h4 z numerem zdającego oraz w panelu prawym zapisano paragraf z adresem e-mail

    R.2.7 W bloku środkowym umieszczono listę rozwijaną, zawierającą nazwy: Gumka do mazania, Cienkopis, Pisaki 60 szt., Markery 4 szt. użyto znaczników <select>, <option>

    R.2.8 W bloku środkowym umieszczono przycisk SPRAWDŹ

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

    R.3.3 Przycisk w formularzu realizuje wysyłanie danych do skryptu (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 „Sprawdź cenę”. Formularz wysyłający dane do tego samego pliku metodą bezpieczną oraz składający się z: Listy rozwijalnej zawierającej nazwy towarów: „Gumka do mazania”, „Cienkopis”, „Pisaki 60 szt.”, „Markery 4 szt.”. Przycisku SPRAWDŹ, którego wybranie powoduje wysłanie danych do skryptu 2.

    • method="post" - „metodę bezpieczną” arkusz rozumie jako POST: dane idą w treści żądania, a nie w adresie strony (w specyfikacji HTTP słowo „bezpieczna” znaczy co innego, ale klucz R.3.2 wymaga POST). Bez atrybutu action formularz wysyła dane do tego samego pliku,
    • <select name="product"> z czterema <option> (R.2.7). Opcja bez value wysyła swój tekst, czyli nazwę towaru - tej Skrypt 2 potrzebuje. aria-label="Towar" podaje czytnikowi ekranu nazwę listy, której na obrazie 2 nie ma żadnej widocznej etykiety,
    • <button type="submit"> z napisem SPRAWDŹ wysyła formularz (R.2.8, R.3.3).
    Typowe błędy

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

    index.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
                        <option>Gumka do mazania</option>
                        <option>Cienkopis</option>
                        <option>Pisaki 60 szt.</option>
                        <option>Markery 4 szt.</option>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
            </section>
            <aside id="contact">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Blok na wynik skryptu 2

    ok. 1 min
    Polecenie z arkusza

    Blok zawierający efekt działania skryptu 2.

    Pusty <div id="result"> pod formularzem - wypełni go Skrypt 2. To zwykły blok na wynik, a nie sekcja z własnym nagłówkiem, więc <div> jest tu właściwym znacznikiem.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
                        <option>Gumka do mazania</option>
                        <option>Cienkopis</option>
                        <option>Pisaki 60 szt.</option>
                        <option>Markery 4 szt.</option>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  17. Krok 17: Panel prawy: kontakt z odsyłaczem pocztowym i obraz

    Kryteria: , ,

    R.2.5 Zapisano w banerze nagłówek h1 "Dzisiejsze promocje naszego sklepu”; w panelach przynajmniej jeden z nagłówków h2: "Taniej o 30%”, "Sprawdź cenę”, "Kontakt"; w stopce nagłówek h4 z numerem zdającego oraz w panelu prawym zapisano paragraf z adresem e-mail

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

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

    ok. 2 min
    Polecenie z arkusza

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

    Odnośnik z adresem zaczynającym się od mailto: otwiera program pocztowy z wpisanym odbiorcą (R.3.4); odnośnikiem jest sam adres. Pod spodem obraz z tekstem alternatywnym z arkusza (R.2.9).

    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>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
                        <option>Gumka do mazania</option>
                        <option>Cienkopis</option>
                        <option>Pisaki 60 szt.</option>
                        <option>Markery 4 szt.</option>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja.png" alt="promocja">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Stopka: nagłówek czwartego stopnia

    Kryteria:

    R.2.5 Zapisano w banerze nagłówek h1 "Dzisiejsze promocje naszego sklepu”; w panelach przynajmniej jeden z nagłówków h2: "Taniej o 30%”, "Sprawdź cenę”, "Kontakt"; w stopce nagłówek h4 z numerem zdającego oraz w panelu prawym zapisano paragraf z adresem e-mail

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: nagłówek czwartego stopnia o treści „Autor strony: ”, dalej wstawiony numer zdającego.

    <h4> w <footer> - w tym arkuszu stopka ma nagłówek, nie akapit. Na egzaminie po dwukropku wpisujesz swój numer zdającego (PESEL), tu stoi autor rozwiązania.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
                        <option>Gumka do mazania</option>
                        <option>Cienkopis</option>
                        <option>Pisaki 60 szt.</option>
                        <option>Markery 4 szt.</option>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja.png" alt="promocja">
            </aside>
        </main>
        <footer>
            <h4>Autor strony: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  19. Etap 3: Styl CSS

    Kroki 19-26 · po tym etapie masz 30 z 38 kryteriów (pkt)

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

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Domyślne formatowanie wszystkich selektorów: krój czcionki Garamond, wyrównanie tekstu do środka.

    Trzy kolory z arkusza w zmiennych - #8C7B75 pojawia się w czterech regułach (baner, stopka, dwie ramki), więc zmiana odcienia to jedna linia. Selektor * z Garamondem i krojem zapasowym serif: gdy Garamonda nie ma w systemie, przeglądarka weźmie dowolny krój szeryfowy zamiast domyślnego (R.4.1).

    styl.css CSS nowy plik
    :root {
        --dark: #8C7B75;
        --side: #BCAAA4;
        --middle: #EFDCD5;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  20. Krok 20: Baner i stopka

    Kryteria: ,

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

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

    ok. 1 min
    Polecenie z arkusza

    Dla banera i stopki: kolor tła #8C7B75, biały kolor czcionki, wysokość 70 px, rozmiar czcionki 150%.

    Jedna reguła dla dwóch selektorów, kolor ze zmiennej var(--dark).

    styl.css CSS zmienione: 8 linii
    :root {
        --dark: #8C7B75;
        --side: #BCAAA4;
        --middle: #EFDCD5;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        height: 70px;
        font-size: 150%;
    }
    Podgląd strony po tym kroku (index.php)
  21. Krok 21: Panele lewy i prawy

    ok. 1 min
    Polecenie z arkusza

    Dla panelu lewego i prawego: kolor tła #BCAAA4, wysokość 400 px, szerokość 35%.

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

    Typowe błędy

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

    styl.css CSS zmienione: 7 linii
    :root {
        --dark: #8C7B75;
        --side: #BCAAA4;
        --middle: #EFDCD5;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        height: 70px;
        font-size: 150%;
    }
    
    #offers,
    #contact {
        background-color: var(--side);
        height: 400px;
        width: 35%;
    }
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Panel środkowy

    Kryteria: , ,

    R.4.2 Ustawiono kolor tła dla banera i stopki na #8C7B75, panelu lewego i prawego na #BCAAA4, panelu środkowego na #EFDCD5

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

    R.4.5 Ustawiono szerokość panelu lewego i prawego na 35%, panelu środkowego na 30%

    ok. 1 min
    Polecenie z arkusza

    Dla panelu środkowego: kolor tła #EFDCD5, wysokość 400 px, szerokość 30%.

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

    styl.css CSS zmienione: 6 linii
    :root {
        --dark: #8C7B75;
        --side: #BCAAA4;
        --middle: #EFDCD5;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        height: 70px;
        font-size: 150%;
    }
    
    #offers,
    #contact {
        background-color: var(--side);
        height: 400px;
        width: 35%;
    }
    
    #price-check {
        background-color: var(--middle);
        height: 400px;
        width: 30%;
    }
    Podgląd strony po tym kroku (index.php)
  23. Krok 23: Układ: panele w jednym rzędzie

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

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

    display: flex na <main> ustawia trzy panele w rzędzie. Baner i stopka są poza tym pojemnikiem, więc stoją nad i pod nim same z siebie - nie ma float, więc nie ma też clear (R.3.1).

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

    Dlaczego nie siatka? W siatce (grid) procent szerokości elementu liczy się od jego kolumny: panel z width: 35% w kolumnie szerokiej na 35% strony zająłby 12% strony. Szerokości trzeba by przenieść do grid-template-columns, a klucz z tego arkusza ocenia je na panelach. Flex liczy procenty od pojemnika, więc wymiary z arkusza działają wprost.

    Typowe błędy

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

    styl.css CSS zmienione: 8 linii
    :root {
        --dark: #8C7B75;
        --side: #BCAAA4;
        --middle: #EFDCD5;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        height: 70px;
        font-size: 150%;
    }
    
    #offers,
    #contact {
        background-color: var(--side);
        height: 400px;
        width: 35%;
    }
    
    #price-check {
        background-color: var(--middle);
        height: 400px;
        width: 30%;
    }
    
    main {
        display: flex;
    }
    
    footer h4 {
        margin-top: 0;
    }
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Lista: numeracja literami, elementy do lewej

    Kryteria:

    R.4.7 Ustawiono styl selektora ol: małe litery (np. list-style-type: lower-alpha;) oraz styl selektora li: wyrównanie tekstu do lewej strony

    ok. 1 min
    Polecenie z arkusza

    Dla selektora listy numerowanej: numeracja małymi literami. Dla selektora elementu listy: wyrównanie tekstu do lewej strony.

    lower-alpha numeruje literami a, b, c. li { text-align: left; } nadpisuje wyśrodkowanie z * tylko dla elementów listy. Uwaga arkusza: wyłącznie selektory znaczników ol i li - nie #offers ol ani klasa (R.4.7).

    Typowe błędy

    Reguła #offers ol albo klasa listy nie spełnia R.4.7 - klucz wymaga samych selektorów ol i li.

    styl.css CSS zmienione: 8 linii
    :root {
        --dark: #8C7B75;
        --side: #BCAAA4;
        --middle: #EFDCD5;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        height: 70px;
        font-size: 150%;
    }
    
    #offers,
    #contact {
        background-color: var(--side);
        height: 400px;
        width: 35%;
    }
    
    #price-check {
        background-color: var(--middle);
        height: 400px;
        width: 30%;
    }
    
    main {
        display: flex;
    }
    
    footer h4 {
        margin-top: 0;
    }
    
    ol {
        list-style-type: lower-alpha;
    }
    
    li {
        text-align: left;
    }
    Podgląd strony po tym kroku (index.php)
  25. Krok 25: Obraz: ramka kreskowa i zaokrąglenie

    Kryteria:

    R.4.10 Ustawiono dla obrazu zaokrąglenie obramowania 10% (np. border-radius: 10%;)

    ok. 1 min
    Polecenie z arkusza

    Dla selektora obrazu: obramowanie o szerokości 1 px linią kreskową koloru #8C7B75, marginesy wewnętrzne 5 px, zaokrąglenie obramowania 10%.

    dashed to linia kreskowa, padding odsuwa ramkę od obrazu, border-radius: 10% zaokrągla rogi ramki. Selektor znacznika img, jak każe uwaga arkusza.

    styl.css CSS zmienione: 6 linii
    :root {
        --dark: #8C7B75;
        --side: #BCAAA4;
        --middle: #EFDCD5;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        height: 70px;
        font-size: 150%;
    }
    
    #offers,
    #contact {
        background-color: var(--side);
        height: 400px;
        width: 35%;
    }
    
    #price-check {
        background-color: var(--middle);
        height: 400px;
        width: 30%;
    }
    
    main {
        display: flex;
    }
    
    footer h4 {
        margin-top: 0;
    }
    
    ol {
        list-style-type: lower-alpha;
    }
    
    li {
        text-align: left;
    }
    
    img {
        border: 1px dashed var(--dark);
        padding: 5px;
        border-radius: 10%;
    }
    Podgląd strony po tym kroku (index.php)
  26. Krok 26: Blok wyniku skryptu 2

    Kryteria: ,

    R.4.8 Ustawiono obramowanie dla selektora img na 1px dashed #8C7B75 oraz obramowanie dla bloku z wynikiem działania skryptu 2: 1px solid #8C7B75

    R.4.9 Ustawiono dla obrazu marginesy wewnętrzne (padding) na 5px oraz dla bloku z wynikami skryptu 2 jedynie margines zewnętrzny górny (np. margin-top) na 100px

    ok. 1 min
    Polecenie z arkusza

    Dla bloku z wynikiem działania skryptu 2: margines zewnętrzny górny 100 px, obramowanie o szerokości 1 px linią ciągłą koloru #8C7B75.

    margin-top: 100px - jedynie górny margines, jak chce klucz; linia ciągła to solid. Razem z regułą obrazu: obie ramki (R.4.8) i odstępy (R.4.9). Pusty blok to na razie pozioma kreska.

    styl.css CSS zmienione: 5 linii
    :root {
        --dark: #8C7B75;
        --side: #BCAAA4;
        --middle: #EFDCD5;
    }
    
    * {
        font-family: Garamond, serif;
        text-align: center;
    }
    
    header,
    footer {
        background-color: var(--dark);
        color: white;
        height: 70px;
        font-size: 150%;
    }
    
    #offers,
    #contact {
        background-color: var(--side);
        height: 400px;
        width: 35%;
    }
    
    #price-check {
        background-color: var(--middle);
        height: 400px;
        width: 30%;
    }
    
    main {
        display: flex;
    }
    
    footer h4 {
        margin-top: 0;
    }
    
    ol {
        list-style-type: lower-alpha;
    }
    
    li {
        text-align: left;
    }
    
    img {
        border: 1px dashed var(--dark);
        padding: 5px;
        border-radius: 10%;
    }
    
    #result {
        margin-top: 100px;
        border: 1px solid var(--dark);
    }
    Podgląd strony po tym kroku (index.php)
  27. Etap 4: Skrypty PHP

    Kroki 27-34 · po tym etapie masz 38 z 38 kryteriów (pkt)

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

    Kryteria: ,

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

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

    ok. 4 min
    Polecenie z arkusza

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

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

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

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

    index.php PHP zmienione: 12 linii
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
                        <option>Gumka do mazania</option>
                        <option>Cienkopis</option>
                        <option>Pisaki 60 szt.</option>
                        <option>Markery 4 szt.</option>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja.png" alt="promocja">
            </aside>
        </main>
        <footer>
            <h4>Autor strony: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  28. Krok 28: Skrypt 1: zapytanie 1

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Skrypt 1: Wysyła do bazy danych zapytanie 1.

    Zapytanie bez danych od użytkownika, więc wystarczy query. fetchAll(PDO::FETCH_COLUMN) zwraca od razu prostą listę nazw: ["Gumka do mazania", "Cienkopis", ...]. W catch lista dostaje wartość pustą, żeby HTML niżej nie trafił na niezdefiniowaną zmienną (R.5.3). Na stronie jeszcze nic nie widać.

    index.php PHP zmienione: 4 linie
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - towary w promocji
        $promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $promoNames = [];
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
                        <option>Gumka do mazania</option>
                        <option>Cienkopis</option>
                        <option>Pisaki 60 szt.</option>
                        <option>Markery 4 szt.</option>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja.png" alt="promocja">
            </aside>
        </main>
        <footer>
            <h4>Autor strony: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>

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

  29. Krok 29: Skrypt 1: nazwy jako elementy listy

    Kryteria: ,

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

    R.5.5 Wyświetlane przez skrypt 1 nazwy towarów są elementami listy numerowanej

    ok. 3 min
    Polecenie z arkusza

    Nazwy towarów zwrócone zapytaniem wypisywane są jako elementy listy numerowanej.

    • foreach z dwukropkiem i endforeach - w HTML-u czytelniejsze niż klamry rozrzucone między znacznikami; jedna pętla na każdą nazwę,
    • <?= ... ?> to skrót od <?php echo ... ?>,
    • funkcja e() (na górze pliku) przepuszcza każdy tekst z bazy przez htmlspecialchars - znak < albo cudzysłów w nazwie towaru nie rozbije strony ani nie wstrzyknie do niej kodu.

    W panelu lewym: a. Gumka do mazania, b. Cienkopis, c. Pisaki 60 szt., d. Markery 4 szt. - jak na obrazie 2 (R.5.4, R.5.5).

    index.php PHP zmienione: 8 linii
    <?php
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - towary w promocji
        $promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $promoNames = [];
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
    <?php foreach ($promoNames as $name): ?>
                    <li><?= e($name) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
                        <option>Gumka do mazania</option>
                        <option>Cienkopis</option>
                        <option>Pisaki 60 szt.</option>
                        <option>Markery 4 szt.</option>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja.png" alt="promocja">
            </aside>
        </main>
        <footer>
            <h4>Autor strony: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  30. Krok 30: Skrypt 2: wybór z formularza i lista towarów w jednym miejscu

    ok. 4 min
    Polecenie z arkusza

    Skrypt 2: Pobiera z formularza nazwę towaru wybranego w liście rozwijalnej.

    • const PRODUCTS - cztery nazwy z arkusza w jednej stałej na górze pliku. Z niej pętla wypisuje teraz opcje listy rozwijalnej (wynik jest ten sam co wcześniej, R.2.7), a za chwilę - z nią porównany zostanie wybór,
    • filter_input(INPUT_POST, 'product') czyta pole formularza; przy pierwszym wejściu na stronę formularza jeszcze nie wysłano i funkcja zwraca null - bez ostrzeżenia „Undefined array key”, które dałoby $_POST['product'],
    • in_array(..., true) - biała lista: dalej przejdzie tylko nazwa, która jest na liście. Listę rozwijalną da się obejść (wysłane dane można podmienić w narzędziach przeglądarki), a sprawdzenie po stronie serwera - nie. true to porównanie ścisłe, bez zamiany typów,
    • selected przy opcji, która była wybrana - po wysłaniu lista pokazuje dalej ten sam towar, a nie wraca do pierwszego.
    Typowe błędy

    Bez sprawdzenia, czy formularz wysłano, pierwsze otwarcie strony z $_POST['product'] pokazuje ostrzeżenie „Undefined array key”.

    index.php PHP zmienione: 10 linii
    <?php
    const PRODUCTS = ['Gumka do mazania', 'Cienkopis', 'Pisaki 60 szt.', 'Markery 4 szt.'];
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - towary w promocji
        $promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);
    
        // Skrypt 2: nazwa towaru z formularza - tylko z listy PRODUCTS
        $product = filter_input(INPUT_POST, 'product');
        $isKnownProduct = in_array($product, PRODUCTS, true);
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $promoNames = [];
        $product = null;
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
    <?php foreach ($promoNames as $name): ?>
                    <li><?= e($name) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
    <?php foreach (PRODUCTS as $option): ?>
                        <option<?= $option === $product ? ' selected' : '' ?>><?= e($option) ?></option>
    <?php endforeach; ?>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja.png" alt="promocja">
            </aside>
        </main>
        <footer>
            <h4>Autor strony: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>

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

  31. Krok 31: Skrypt 2: zapytanie 2 z wybraną nazwą

    Kryteria:

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

    ok. 3 min
    Polecenie z arkusza

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

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

    • zapytanie wykonuje się tylko dla towaru z listy (if ($isKnownProduct)),
    • fetchColumn() zwraca od razu pierwszą kolumnę pierwszego wiersza - samą cenę - albo false, gdy wiersza nie ma,
    • $statement = null zwalnia zapytanie przed zamknięciem połączenia.

    Od tego kroku podgląd pokazuje stronę po wysłaniu formularza z wybraną gumką do mazania. Cena jest już pobrana, ale jeszcze niewypisana.

    index.php PHP zmienione: 8 linii
    <?php
    const PRODUCTS = ['Gumka do mazania', 'Cienkopis', 'Pisaki 60 szt.', 'Markery 4 szt.'];
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - towary w promocji
        $promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);
    
        // Skrypt 2: nazwa towaru z formularza - tylko z listy PRODUCTS
        $product = filter_input(INPUT_POST, 'product');
        $isKnownProduct = in_array($product, PRODUCTS, true);
        $price = false;
        if ($isKnownProduct) {
            $statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');
            $statement->execute(['name' => $product]);
            $price = $statement->fetchColumn();
            $statement = null;
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $promoNames = [];
        $product = null;
        $price = false;
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
    <?php foreach ($promoNames as $name): ?>
                    <li><?= e($name) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
    <?php foreach (PRODUCTS as $option): ?>
                        <option<?= $option === $product ? ' selected' : '' ?>><?= e($option) ?></option>
    <?php endforeach; ?>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja.png" alt="promocja">
            </aside>
        </main>
        <footer>
            <h4>Autor strony: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  32. Krok 32: Skrypt 2: cena niższa o 30%

    Kryteria:

    R.5.7 Skrypt 2 oblicza na podstawie zwróconej zapytaniem wartości cenę promocyjną obniżoną o 30% (np. *0.7). Zaokrąglenie nie jest błędem.

    ok. 2 min
    Polecenie z arkusza

    Na podstawie ceny zwróconej zapytaniem oblicza cenę promocyjną jako wartość niższą o 30%.

    Cena niższa o 30% to 70% ceny: $price * 0.7 (R.5.7). round(..., 2) zaokrągla wynik do groszy - mnożenie liczb zmiennoprzecinkowych potrafi dać np. 15.679999999999 zamiast 15.68 (klucz: zaokrąglenie nie jest błędem). Dla gumki: 3.2 × 0,7 = 2.24.

    index.php PHP zmienione: 3 linie
    <?php
    const PRODUCTS = ['Gumka do mazania', 'Cienkopis', 'Pisaki 60 szt.', 'Markery 4 szt.'];
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - towary w promocji
        $promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);
    
        // Skrypt 2: nazwa towaru z formularza - tylko z listy PRODUCTS
        $product = filter_input(INPUT_POST, 'product');
        $isKnownProduct = in_array($product, PRODUCTS, true);
        $price = false;
        if ($isKnownProduct) {
            $statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');
            $statement->execute(['name' => $product]);
            $price = $statement->fetchColumn();
            $statement = null;
        }
        if ($price !== false) {
            $promoPrice = round($price * 0.7, 2);    // cena niższa o 30%
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $promoNames = [];
        $product = null;
        $price = false;
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
    <?php foreach ($promoNames as $name): ?>
                    <li><?= e($name) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
    <?php foreach (PRODUCTS as $option): ?>
                        <option<?= $option === $product ? ' selected' : '' ?>><?= e($option) ?></option>
    <?php endforeach; ?>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
                <div id="result">
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja.png" alt="promocja">
            </aside>
        </main>
        <footer>
            <h4>Autor strony: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>

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

  33. Krok 33: Skrypt 2: dwa wiersze z cenami

    Kryteria:

    R.5.8 Skrypt 2 wyświetla dane w bloku pod formularzem, w dwóch wierszach, zgodnie z formatem cena regularna: <cena_pobrana_z_bazy> cena w promocji 30%: <obliczona_cena>

    ok. 2 min
    Polecenie z arkusza

    Pod formularzem wypisuje dane w bloku, zgodnie z obrazem 2, w formacie: cena regularna: <cena_pobrana_z_bazy> cena w promocji 30%: <obliczona_cena>.

    Dwa wiersze w bloku #result, rozdzielone <br> - łamanie linii w jednym bloku tekstu, bez odstępów akapitów: „cena regularna: 3.2” i „cena w promocji 30%: 2.24” - jak na obrazie 2 (R.5.8). if ($price !== false) - przed wysłaniem formularza blok zostaje pusty. Format liczb jest taki jak w bazie i na obrazie 2 (kropka dziesiętna), bo arkusz podaje go wprost.

    index.php PHP zmienione: 4 linie
    <?php
    const PRODUCTS = ['Gumka do mazania', 'Cienkopis', 'Pisaki 60 szt.', 'Markery 4 szt.'];
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - towary w promocji
        $promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);
    
        // Skrypt 2: nazwa towaru z formularza - tylko z listy PRODUCTS
        $product = filter_input(INPUT_POST, 'product');
        $isKnownProduct = in_array($product, PRODUCTS, true);
        $price = false;
        if ($isKnownProduct) {
            $statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');
            $statement->execute(['name' => $product]);
            $price = $statement->fetchColumn();
            $statement = null;
        }
        if ($price !== false) {
            $promoPrice = round($price * 0.7, 2);    // cena niższa o 30%
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $promoNames = [];
        $product = null;
        $price = false;
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
    <?php foreach ($promoNames as $name): ?>
                    <li><?= e($name) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
    <?php foreach (PRODUCTS as $option): ?>
                        <option<?= $option === $product ? ' selected' : '' ?>><?= e($option) ?></option>
    <?php endforeach; ?>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
                <div id="result">
    <?php if ($price !== false): ?>
                    cena regularna: <?= e($price) ?><br>
                    cena w promocji 30%: <?= e($promoPrice) ?>
    <?php endif; ?>
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja.png" alt="promocja">
            </aside>
        </main>
        <footer>
            <h4>Autor strony: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  34. Krok 34: Towar spoza listy i awaria bazy: komunikaty

    ok. 3 min

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

    • towar spoza listy (np. podmieniony w narzędziach przeglądarki na ' OR 1=1 --) - zapytanie się nie wykonuje, a w bloku pojawia się „Wybierz towar z listy.”,
    • awaria bazy (catch) - komunikat z role="alert" (czytnik ekranu przeczyta go od razu), a reszta strony - formularz, kontakt, stopka - działa dalej, bo zmienne mają w catch wartości puste.

    Sprawdzone na prawdziwym PHP: wszystkie cztery towary z listy (3.2 / 2.24, 2.5 / 1.75, 55 / 38.5, 22.4 / 15.68), napis spoza listy i złe hasło do bazy - za każdym razem bez ostrzeżeń PHP.

    index.php PHP zmienione: 6 linii
    <?php
    const PRODUCTS = ['Gumka do mazania', 'Cienkopis', 'Pisaki 60 szt.', 'Markery 4 szt.'];
    
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 1 - towary w promocji
        $promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);
    
        // Skrypt 2: nazwa towaru z formularza - tylko z listy PRODUCTS
        $product = filter_input(INPUT_POST, 'product');
        $isKnownProduct = in_array($product, PRODUCTS, true);
        $price = false;
        if ($isKnownProduct) {
            $statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');
            $statement->execute(['name' => $product]);
            $price = $statement->fetchColumn();
            $statement = null;
        }
        if ($price !== false) {
            $promoPrice = round($price * 0.7, 2);    // cena niższa o 30%
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $promoNames = [];
        $product = null;
        $isKnownProduct = false;
        $price = false;
        $databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sklep dla uczniów</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Dzisiejsze promocje naszego sklepu</h1>
        </header>
        <main>
            <section id="offers">
                <h2>Taniej o 30%</h2>
                <ol>
    <?php foreach ($promoNames as $name): ?>
                    <li><?= e($name) ?></li>
    <?php endforeach; ?>
                </ol>
            </section>
            <section id="price-check">
                <h2>Sprawdź cenę</h2>
                <form method="post">
                    <select name="product" aria-label="Towar">
    <?php foreach (PRODUCTS as $option): ?>
                        <option<?= $option === $product ? ' selected' : '' ?>><?= e($option) ?></option>
    <?php endforeach; ?>
                    </select>
                    <button type="submit">SPRAWDŹ</button>
                </form>
                <div id="result">
    <?php if (isset($databaseError)): ?>
                    <p role="alert"><?= e($databaseError) ?></p>
    <?php elseif ($product !== null && !$isKnownProduct): ?>
                    <p role="alert">Wybierz towar z listy.</p>
    <?php endif; ?>
    <?php if ($price !== false): ?>
                    cena regularna: <?= e($price) ?><br>
                    cena w promocji 30%: <?= e($promoPrice) ?>
    <?php endif; ?>
                </div>
            </section>
            <aside id="contact">
                <h2>Kontakt</h2>
                <p>e-mail: <a href="mailto:[email protected]">[email protected]</a></p>
                <img src="promocja.png" alt="promocja">
            </aside>
        </main>
        <footer>
            <h4>Autor strony: Bartosz Bryniarski</h4>
        </footer>
    </body>
    </html>

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

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

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

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

    ok. 3 min

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

    wersja podstawowawersja nowoczesna
    układfloat: left paneli i clear stopki<main> z display: flex
    panele#left, #middle, #right#offers, #price-check, #contact (<aside>)
    kolorywpisane w każdą regułęzmienne CSS
    zapytanie 3JOIN, GROUP BY dostawcy.nazwaaliasy, LEFT JOIN, GROUP BY d.id, d.nazwa
    bibliotekamysqli (tabela 1 arkusza)PDO z wyjątkami
    gdzie są zapytaniaskrypty rozrzucone po HTML-ujeden blok na górze index.php
    lista towarówwpisana w HTMLstała PRODUCTS - opcje i biała lista
    nazwa w zapytaniu 2wklejona z $_POSTparametr :name
    wypisywaniedane z bazy wproste() - htmlspecialchars
    zła nazwa, awaria bazyostrzeżenia PHPkomunikaty

    Dane logowania do bazy w prawdziwym projekcie trzyma się zwykle w osobnym pliku (np. config.php), ale arkusz każe umieścić skrypt w pliku index.php - tu cały kod PHP zostaje w nim.

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i łatwiej ją napisać bez dokumentacji pod ręką. Nowoczesna jest odporna na podmienione dane z formularza. Obie biblioteki klucz przyjmuje.

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

  36. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

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

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

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.