Przejdź do głównej treści
INF.03-03-23.01-SGStyczeń 2023 · 36 kroków · ok. 75 min · 35 kryteriów z klucza

Rozwiązanie krok po kroku: Witryna kwiaciarni z bazą danych (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza kwiaciarnia z czterema zapytaniami, trzy zdjęcia kwiatów 250 × 250 px i strony index.html i znajdz.php z arkuszem stylów styl3.css - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • siatka CSS zamiast float: cały układ opisuje jedna reguła dla <body> - kolumny 25% i 75% oraz nazwane obszary (grid-template-areas), bez clear przy stopce,
  • znaczniki opisujące treść: menu w <nav>, blok prawy w <main>, a kolory z arkusza w zmiennych CSS,
  • zapytania pisane czytelnie: klauzule w osobnych liniach, aliasy tabel, pole rabat dobrane do zakresu wartości,
  • PHP na PDO: wyszukiwanie wykonuje się na górze pliku znajdz.php, przed HTML-em, a miasto z formularza trafia do bazy zapytaniem przygotowanym - wpis x' OR '1'='1 nie zmieni zapytania,
  • komunikaty dla miasta bez kwiaciarni, pustego pola i awarii bazy, a wpisane miasto zostaje w polu po wysłaniu.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i zdjęcia pochodzą z prawdziwego archiwum zad3, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 po wysłaniu formularza z miastem Warszawa - jak na obrazie 3.

Jak liczą się punkty. Klucz ma 35 kryteriów (R.1.1 - R.5.5), każde za 1 punkt. Próg zdania to 75%, czyli 27 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 35 kryteriów (pkt)

    Krok 1: Baza danych kwiaciarnia

    ok. 2 min
    Polecenie z arkusza

    Utwórz bazę danych o nazwie kwiaciarnia

    Rozpakuj zad3.zip z pulpitu (hasło 5T2k@W$2n - wielkość liter ma znaczenie) do folderu z numerem zdającego: kwiaciarnia.sql i trzy zdjęcia PNG. W phpMyAdmin Nowa → kwiaciarnia albo zapytaniem:

    CREATE DATABASE kwiaciarnia CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;
    

    Arkusz nie podaje porównywania. Tabele z kwiaciarnia.sql mają zestaw utf8mb4 zapisany we własnej definicji, więc ustawienie bazy dziedziczy dopiero tabela klienci z zapytania 4.

    Typowe błędy

    Nazwa bazy musi brzmieć dokładnie kwiaciarnia - skrypt łączy się z bazą po nazwie (R.5.1).

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

    ok. 2 min
    Polecenie z arkusza

    Do bazy kwiaciarnia zaimportuj tabele z pliku kwiaciarnia.sql z rozpakowanego archiwum

    Zaznaczona baza kwiaciarnia, Import → kwiaciarnia.sql → Wykonaj. Dwie tabele z obrazu 1: kwiaciarnie (Astra w Warszawie i Melisa w Malborku) i zamowienia (6 zamówień z datą, kwiatami i ceną), połączone polem id_kwiaciarni.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonano import tabel do bazy danych kwiaciarnia czynność udokumentowano plikiem o nazwie import w formacie PNG

    ok. 2 min
    Polecenie z arkusza

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

    Cały ekran z paskiem zadań, w PNG, pod nazwą import. Zrzuty zapytań kw1 - kw4 będą już w JPEG.

    Typowe błędy

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

  4. Krok 4: Zapytanie 1: kwiaciarnie w Malborku

    Kryteria: ,

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

    R.1.3 Utworzono zapytanie 1 wybierające jedynie pola nazwa i ulica dla kwiaciarni z miasta Malbork. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, ulica FROM kwiaciarnie WHERE miasto = "Malbork"; oraz wynik działania udokumentowano zrzutem, na którym są widoczne jedynie dane: Melisa, Konopnickiej

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pola nazwa i ulica dla kwiaciarni z miasta Malbork

    Każda klauzula w osobnej linii, tekst w apostrofach. Wynik: Melisa, Konopnickiej - jak w kluczu. To zapytanie wróci w skrypcie - z parametrem w miejscu nazwy miasta. Zapytanie do kwerendy.txt, zrzut kw1.jpg.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT nazwa, ulica
    FROM kwiaciarnie
    WHERE miasto = 'Malbork';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: liczba zamówień każdej kwiaciarni

    Kryteria:

    R.1.4 Utworzono zapytanie 2 wybierające jedynie pole id_kwiaciarni oraz liczące liczbę zamówień dla poszczególnych kwiaciarni. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id_kwiaciarni, COUNT(*) FROM zamowienia GROUP BY id_kwiaciarni; (zamiast * możliwe dowolne pole) oraz wynik działania udokumentowano zrzutem, na którym widoczne są jedynie dane: 1, 4; 2, 2

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pole id_kwiaciarni oraz liczące liczbę zamówień dla poszczególnych kwiaciarni

    GROUP BY id_kwiaciarni zlewa zamówienia jednej kwiaciarni w jeden wiersz, a COUNT(id_zam) liczy w grupie wiersze z wypełnionym numerem zamówienia (klucz dopuszcza dowolne pole zamiast *). Alias liczba_zamowien opisuje kolumnę wyniku. Wynik: 1, 4 oraz 2, 2. Zrzut: kw2.jpg.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT nazwa, ulica
    FROM kwiaciarnie
    WHERE miasto = 'Malbork';
    
    -- Zapytanie 2
    SELECT id_kwiaciarni, COUNT(id_zam) AS liczba_zamowien
    FROM zamowienia
    GROUP BY id_kwiaciarni;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: zamówienia z 7 stycznia 2017

    Kryteria:

    R.1.5 Utworzono zapytanie 3 wybierające jedynie pola nazwa i miasto z tabeli kwiaciarnie i odpowiadające im pole kwiaty z tabeli zamowienia dla zamówień z dnia 2017.01.07. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, miasto, kwiaty FROM zamowienia JOIN kwiaciarnie ON zamowienia.id_kwiaciarni = kwiaciarnie.id_kwiaciarni WHERE data="2017.01.07"; (możliwe też INNER JOIN lub porównanie kluczy po WHERE) oraz wynik działania udokumentowano zrzutem, na którym są widoczne jedynie dane: Astra, Warszawa, róża

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pola nazwa i miasto z tabeli kwiaciarnie i odpowiadające im pole kwiaty z tabeli zamowienia dla zamówień z dnia 7 stycznia 2017. Należy posłużyć się relacją

    • aliasy tabel z i k - pole id_kwiaciarni jest w obu tabelach, a przedrostek mówi, którą z nich ma na myśli zapytanie,
    • JOIN ... ON k.id_kwiaciarni = z.id_kwiaciarni - relacja z obrazu 1 przy złączeniu, nie w WHERE,
    • WHERE z.data = '2017-01-07' - data w postaci rok-miesiąc-dzień, w apostrofach.

    Wynik: Astra, Warszawa, róża - jak w kluczu. Zrzut: kw3.jpg.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT nazwa, ulica
    FROM kwiaciarnie
    WHERE miasto = 'Malbork';
    
    -- Zapytanie 2
    SELECT id_kwiaciarni, COUNT(id_zam) AS liczba_zamowien
    FROM zamowienia
    GROUP BY id_kwiaciarni;
    
    -- Zapytanie 3
    SELECT k.nazwa, k.miasto, z.kwiaty
    FROM zamowienia AS z
    JOIN kwiaciarnie AS k ON k.id_kwiaciarni = z.id_kwiaciarni
    WHERE z.data = '2017-01-07';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: tabela klienci

    Kryteria:

    R.1.6 Utworzono zapytanie 4 tworzące nową tabelę klienci o polach: id, klucz główny, NOT NULL AUTO_INCREMENT; imie i nazwisko, typu tekstowego o długości 15 znaków każde; rabat, przyjmujące jedynie liczby naturalne. W pliku z kwerendami lub na zrzucie istnieje zapis np.: CREATE TABLE klienci ( id int NOT NULL AUTO_INCREMENT, imie varchar(15), nazwisko varchar(15), rabat int UNSIGNED, PRIMARY KEY (id) ); (lub id int NOT NULL AUTO_INCREMENT PRIMARY KEY, dodatkowo id i rabat- dowolny typ całkowity; rabat - wymagane UNSIGNED) oraz wynik działania udokumentowano zrzutem na którym widnieje komunikat o poprawnym utworzeniu tabeli lub poprawna struktura tabeli

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 4: tworzące nową tabelę klienci o polach: id, klucz główny, automatycznie inkrementowany, nie może przyjmować wartości NULL; imie, typu tekstowego o długości 15 znaków; nazwisko, typu tekstowego o długości 15 znaków; rabat, przyjmujący jedynie liczby naturalne

    • id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY - numer klienta nadawany automatycznie; UNSIGNED, bo numer nigdy nie jest ujemny,
    • imie i nazwisko - VARCHAR(15), tekst do 15 znaków,
    • rabat TINYINT UNSIGNED - UNSIGNED (bez znaku) dopuszcza tylko liczby nieujemne, a TINYINT to najmniejszy typ całkowity: 0 - 255, więcej niż trzeba na rabat w procentach. Klucz przyjmuje dowolny typ całkowity, byle z UNSIGNED.

    Sprawdzone na MariaDB: w trybie ścisłym (STRICT_ALL_TABLES) rabat −5 jest odrzucony błędem „Out of range”, a 15 zapisuje się normalnie; XAMPP domyślnie nie ma trybu ścisłego i zamiast błędu zapisze 0 z ostrzeżeniem. Zrzut kw4.jpg: komunikat i struktura tabeli.

    Typowe błędy

    Bez UNSIGNED przy polu rabat klucz nie uzna zapytania - samo INT albo TINYINT przyjmuje liczby ujemne.

    kwerendy.txt Tekst zmienione: 8 linii
    -- Zapytanie 1
    SELECT nazwa, ulica
    FROM kwiaciarnie
    WHERE miasto = 'Malbork';
    
    -- Zapytanie 2
    SELECT id_kwiaciarni, COUNT(id_zam) AS liczba_zamowien
    FROM zamowienia
    GROUP BY id_kwiaciarni;
    
    -- Zapytanie 3
    SELECT k.nazwa, k.miasto, z.kwiaty
    FROM zamowienia AS z
    JOIN kwiaciarnie AS k ON k.id_kwiaciarni = z.id_kwiaciarni
    WHERE z.data = '2017-01-07';
    
    -- Zapytanie 4
    CREATE TABLE klienci (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        imie VARCHAR(15),
        nazwisko VARCHAR(15),
        rabat TINYINT UNSIGNED
    );
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strony HTML

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

    Krok 8: Zdjęcia kwiatów: kwadrat 250 × 250, JPEG

    Kryteria:

    R.2.1 Przynajmniej jeden z obrazów z roza , gerbera , gozdzik zachowuje proporcje 1:1, rozmiar 250 px i nie jest rozciągnięty w pionie lub poziomie oraz jest formatu JPEG

    ok. 5 min
    Polecenie z arkusza

    Obrazy roza.png, gerbera.png, gozdzik.png należy przyciąć (wykadrować) do proporcji 1:1 do rozmiaru 250 px na 250 px i zapisać w formacie JPEG

    Grafika jest taka sama jak w wersji podstawowej. W GIMP-ie: Kadrowanie ze stałymi proporcjami 1:1, kwadrat na środku kwiatu, potem Obraz → Skaluj obraz do 250 × 250 i Wyeksportuj jako gerbera.jpg (gozdzik.jpg, roza.jpg).

    Typowe błędy

    Samo skalowanie 640 × 426 do 250 × 250 bez kadrowania ściska kwiat - klucz wymaga obrazu nierozciągniętego.

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

    ok. 2 min
    Polecenie z arkusza

    Cechy wspólne obu stron: Zapisane w języku HTML 5. Jawnie zastosowany właściwy standard kodowania polskich znaków. Zadeklarowany język witryny: polski.

    Najpierw cała strona index.html; znajdz.php będzie jej kopią z innym blokiem prawym. <!DOCTYPE html>, <html lang="pl"> i <meta charset="UTF-8"> jako pierwszy element <head>.

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

    Kryteria:

    R.2.3 Nadano tytuł stronie: "Kwiaty"

    ok. 1 min
    Polecenie z arkusza

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

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

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

    Kryteria:

    R.3.6 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl3.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 styl3.css prawidłowo połączony z kodem strony

    Nazwa z cyfrą 3: styl3.css. Całe formatowanie pochodzi z tego pliku (R.3.6).

    Typowe błędy

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

    index.html HTML zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  12. Krok 12: Bloki: baner, menu, treść, stopka

    Kryteria:

    R.2.4 Układ strony zdefiniowano dzieląc ją na bloki: baner, lewy, prawy i stopka. Zastosowano znaczniki sekcji

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: na górze blok banera, poniżej obok siebie bloki lewy i prawy, 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

    Cztery znaczniki sekcji HTML5, każdy mówi, co zawiera: <header> - baner, <nav> - blok lewy z menu, <main> - blok prawy, główna treść (tylko on różni się między stronami), <footer> - stopka (R.2.4). Każdy występuje na stronie raz, więc w CSS wystarczy nazwa znacznika - bez identyfikatorów.

    index.html HTML zmienione: 8 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
        </header>
        <nav>
        </nav>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  13. Krok 13: Baner: nagłówek pierwszego stopnia

    ok. 1 min
    Polecenie z arkusza

    Zawartość banera: nagłówek pierwszego stopnia o treści „Grupa Polskich Kwiaciarni”

    <h1> w <header>.

    index.html HTML zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
        </nav>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  14. Krok 14: Blok lewy: nagłówek i lista numerowana

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku lewego: Nagłówek drugiego stopnia o treści: „Menu”. Lista numerowana (uporządkowana) z trzema elementami oraz z zagnieżdżoną listą punktowaną (nieuporządkowaną) z trzema elementami. Zawartość list jest zgodna z obrazem 2.

    <h2> i lista numerowana <ol> z trzema elementami. Odnośniki i lista zagnieżdżona dojdą w kolejnych krokach.

    index.html HTML zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li>Strona główna</li>
                <li>Rozpoznaj kwiaty</li>
                <li>Znajdź kwiaciarnię</li>
            </ol>
        </nav>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  15. Krok 15: Odnośnik „Strona główna”

    ok. 1 min
    Polecenie z arkusza

    Elementy listy numerowanej są odnośnikami: „Strona główna” prowadzi do strony index.html

    Odnośnik stoi wewnątrz <li>. aria-current="page" oznacza odnośnik do strony, która jest właśnie otwarta - czytnik ekranu odczyta „bieżąca strona”; na znajdz.php atrybut przejdzie na trzeci odnośnik.

    index.html HTML zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html" aria-current="page">Strona główna</a></li>
                <li>Rozpoznaj kwiaty</li>
                <li>Znajdź kwiaciarnię</li>
            </ol>
        </nav>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  16. Krok 16: Odnośnik „Rozpoznaj kwiaty” w nowej karcie

    Kryteria:

    R.3.3 Wybranie odnośnika "Rozpoznaj kwiaty" inicjuje otwarcie strony „https://www.kwiaty.pl/” w nowym oknie / karcie przeglądarki

    ok. 2 min
    Polecenie z arkusza

    „Rozpoznaj kwiaty” prowadzi do strony „https://www.kwiaty.pl/”, odnośnik otwiera się w osobnej karcie przeglądarki

    Adres zewnętrzny w całości, z https://. target="_blank" otwiera nową kartę (R.3.3), a rel="noopener" odcina otwartej stronie dostęp do karty, z której przyszła (obiekt window.opener) - obca strona nie podmieni wtedy adresu karty z kwiaciarnią. Nowe przeglądarki robią to same, ale atrybut zapisuje intencję wprost.

    index.html HTML zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html" aria-current="page">Strona główna</a></li>
                <li><a href="https://www.kwiaty.pl/" target="_blank" rel="noopener">Rozpoznaj kwiaty</a></li>
                <li>Znajdź kwiaciarnię</li>
            </ol>
        </nav>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  17. Krok 17: Odnośnik „Znajdź kwiaciarnię” z listą zagnieżdżoną

    Kryteria: ,

    R.2.6 W bloku lewym umieszczono listę numerowaną z trzema elementami, zastosowano znaczniki <ol>, <li>, trzeci element <li> zawiera listę punktowaną ze znacznikami <ul>, <li>

    R.3.2 Po wybraniu odnośnika "Strona główna" otwiera się strona główna oraz po wybraniu odnośnika "Znajdź kwiaciarnię" otwiera się podstrona

    ok. 2 min
    Polecenie z arkusza

    „Znajdź kwiaciarnię” prowadzi do strony znajdz.php i zagnieżdża listę punktowaną z elementami: w Warszawie, w Malborku, w Poznaniu

    Lista punktowana <ul> stoi wewnątrz trzeciego <li>, za jego odnośnikiem - tylko wtedy jest zagnieżdżona (R.2.6). Oba odnośniki wewnętrzne już działają (R.3.2).

    Typowe błędy

    <ul> postawione między dwoma <li> (poza elementem) to nie jest lista zagnieżdżona - klucz wymaga, żeby trzeci <li> zawierał listę.

    index.html HTML zmienione: 8 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html" aria-current="page">Strona główna</a></li>
                <li><a href="https://www.kwiaty.pl/" target="_blank" rel="noopener">Rozpoznaj kwiaty</a></li>
                <li>
                    <a href="znajdz.php">Znajdź kwiaciarnię</a>
                    <ul>
                        <li>w Warszawie</li>
                        <li>w Malborku</li>
                        <li>w Poznaniu</li>
                    </ul>
                </li>
            </ol>
        </nav>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  18. Krok 18: Stopka

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: paragraf (akapit) o treści: „Stronę opracował:”, dalej wstawiony numer zdającego

    Akapit w <footer>. Na egzaminie wpisujesz swój numer zdającego (PESEL), tu stoi autor rozwiązania.

    index.html HTML zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html" aria-current="page">Strona główna</a></li>
                <li><a href="https://www.kwiaty.pl/" target="_blank" rel="noopener">Rozpoznaj kwiaty</a></li>
                <li>
                    <a href="znajdz.php">Znajdź kwiaciarnię</a>
                    <ul>
                        <li>w Warszawie</li>
                        <li>w Malborku</li>
                        <li>w Poznaniu</li>
                    </ul>
                </li>
            </ol>
        </nav>
        <main>
        </main>
        <footer>
            <p>Stronę opracował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  19. Krok 19: index.html, blok prawy: nagłówek i trzy zdjęcia

    Kryteria: ,

    R.2.5 W banerze zapisano nagłówek h1: "Grupa Polskich Kwiaciarni", w bloku lewym h2: "Menu", w bloku prawym h2: "Strona dla miłośników kwiatów" lub "Znajdź kwiaciarnię", paragraf w stopce

    R.2.7 W bloku prawym strony głównej umieszczono trzy obrazy: gerbera, gozdzik, roza z tekstem alternatywnym - nazwa kwiatu

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku prawego dla strony index.html: Nagłówek drugiego stopnia o treści: „Strona dla miłośników kwiatów”. Obok siebie trzy obrazy z gerberą, goździkiem i różą, z tekstem alternatywnym, który jest nazwą danego kwiatu

    Trzy <img> jeden za drugim - obrazy są elementami w linii, więc stają obok siebie. Tekst alternatywny z polskimi literami („goździk”, „róża”), choć nazwy plików ich nie mają (R.2.7). Strona ma już wszystkie nagłówki i akapit w stopce (R.2.5).

    index.html HTML zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html" aria-current="page">Strona główna</a></li>
                <li><a href="https://www.kwiaty.pl/" target="_blank" rel="noopener">Rozpoznaj kwiaty</a></li>
                <li>
                    <a href="znajdz.php">Znajdź kwiaciarnię</a>
                    <ul>
                        <li>w Warszawie</li>
                        <li>w Malborku</li>
                        <li>w Poznaniu</li>
                    </ul>
                </li>
            </ol>
        </nav>
        <main>
            <h2>Strona dla miłośników kwiatów</h2>
            <img src="gerbera.jpg" alt="gerbera">
            <img src="gozdzik.jpg" alt="goździk">
            <img src="roza.jpg" alt="róża">
        </main>
        <footer>
            <p>Stronę opracował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  20. Krok 20: znajdz.php: kopia strony z nowym nagłówkiem

    Kryteria:

    R.2.2 Witrynę internetową zapisano w plikach index.html i znajdz.php oraz zapisano jawnie standard kodowania polskich znaków i deklarację HTML5 <!DOCTYPE HTML> i zadeklarowano język witryny: polski np. <html lang="pl">

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku prawego dla strony znajdz.php: Nagłówek drugiego stopnia o treści: „Znajdź kwiaciarnię”

    Kopia index.html zapisana jako znajdz.php: w <main> zostaje nagłówek „Znajdź kwiaciarnię”, a aria-current przechodzi na trzeci odnośnik. Rozszerzenie .php - strona działa tylko przez http://localhost/.... Witryna jest w obu plikach, z HTML5, kodowaniem i językiem (R.2.2).

    znajdz.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>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html">Strona główna</a></li>
                <li><a href="https://www.kwiaty.pl/" target="_blank" rel="noopener">Rozpoznaj kwiaty</a></li>
                <li>
                    <a href="znajdz.php" aria-current="page">Znajdź kwiaciarnię</a>
                    <ul>
                        <li>w Warszawie</li>
                        <li>w Malborku</li>
                        <li>w Poznaniu</li>
                    </ul>
                </li>
            </ol>
        </nav>
        <main>
            <h2>Znajdź kwiaciarnię</h2>
        </main>
        <footer>
            <p>Stronę opracował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (znajdz.php)
  21. Krok 21: Formularz: pole miasta i przycisk SPRAWDŹ

    Kryteria: , ,

    R.2.8 W bloku prawym podstrony umieszczono formularz z polem edycyjnym i przyciskiem, zastosowano znacznik <form>

    R.3.4 Przycisk ma funkcję wysyłania danych (input lub button z typem submit lub button bez typu)

    R.3.5 Formularz wysyła dane metodą POST do tego samego pliku (poprawne też bez atrybutu action)

    ok. 3 min
    Polecenie z arkusza

    Formularz wysyłający dane metodą bezpieczną do tego samego pliku i zawierający: pole edycyjne poprzedzone napisem „Podaj nazwę miasta:”, przycisk „SPRAWDŹ” wysyłający dane

    • method="post" - „metodę bezpieczną” arkusz rozumie jako POST: dane idą w treści żądania, a nie w adresie (w specyfikacji HTTP słowo „bezpieczna” znaczy co innego, ale klucz R.3.5 wymaga POST). Bez atrybutu action formularz wysyła dane do tego samego pliku - klucz to przyjmuje,
    • <label for="city"> - napis połączony z polem: kliknięcie napisu ustawia w nim kursor,
    • name="city" - pod tą nazwą miasto trafi do PHP; maxlength="20" - tyle znaków mieści kolumna miasto w bazie; required - przeglądarka nie wyśle pustego pola,
    • <button type="submit"> wysyła formularz (R.2.8, R.3.4).
    Typowe błędy

    method="get" też wysyła dane, ale klucz R.3.5 wymaga POST, a skrypt czytający $_POST nic by nie dostał.

    znajdz.php PHP zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html">Strona główna</a></li>
                <li><a href="https://www.kwiaty.pl/" target="_blank" rel="noopener">Rozpoznaj kwiaty</a></li>
                <li>
                    <a href="znajdz.php" aria-current="page">Znajdź kwiaciarnię</a>
                    <ul>
                        <li>w Warszawie</li>
                        <li>w Malborku</li>
                        <li>w Poznaniu</li>
                    </ul>
                </li>
            </ol>
        </nav>
        <main>
            <h2>Znajdź kwiaciarnię</h2>
            <form method="post">
                <label for="city">Podaj nazwę miasta:</label>
                <input type="text" id="city" name="city" maxlength="20" required>
                <button type="submit">SPRAWDŹ</button>
            </form>
        </main>
        <footer>
            <p>Stronę opracował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (znajdz.php)
  22. Etap 3: Styl CSS

    Kroki 22-29 · po tym etapie masz 30 z 35 kryteriów (pkt)

    Krok 22: Plik styl3.css: zmienne i krój czcionki

    Kryteria:

    R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (*): krój czcionki Georgia

    ok. 2 min
    Polecenie z arkusza

    Ustawione domyślne wartości dla wszystkich selektorów stylu CSS: krój czcionki Georgia

    Kolory banera i menu w zmiennych (--brand, --menu), wywoływane przez var(...). Selektor * z Georgią i krojem zapasowym serif (R.4.1).

    styl3.css CSS nowy plik
    :root {
        --brand: #C75B39;
        --menu: #FF8A65;
    }
    
    * {
        font-family: Georgia, serif;
    }
    Podgląd strony po tym kroku (index.html)
  23. Krok 23: Układ: siatka z nazwanymi obszarami

    Kryteria:

    R.3.1 Układ bloków po uruchomieniu 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 flex albo grid)

    ok. 3 min
    Polecenie z arkusza

    Podział strony na bloki: na górze blok banera, poniżej obok siebie bloki lewy i prawy, 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: grid na <body>: grid-template-columns: 25% 75% to szerokości bloków z arkusza (klucz przy R.4.4 przyjmuje je zapisane w ten sposób), a grid-template-areas rysuje mapę strony - baner i stopka przez obie kolumny, w środku menu i treść. grid-area przypisuje każdy znacznik do obszaru. Nie ma float ani clear (R.3.1).

    Typowe błędy

    width: 25% dopisane jeszcze na <nav> liczy się w siatce od kolumny - menu miałoby 25% z 25% strony.

    styl3.css CSS zmienione: 25 linii
    :root {
        --brand: #C75B39;
        --menu: #FF8A65;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 75%;
        grid-template-areas:
            "header header"
            "nav    main"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    nav {
        grid-area: nav;
    }
    
    main {
        grid-area: main;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (index.html)
  24. Krok 24: Baner i stopka

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Wspólne dla banera i stopki: kolor tła #C75B39, biały kolor czcionki, marginesy wewnętrzne 10 px, wyrównanie tekstu do środka

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

    styl3.css CSS zmienione: 8 linii
    :root {
        --brand: #C75B39;
        --menu: #FF8A65;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 75%;
        grid-template-areas:
            "header header"
            "nav    main"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    nav {
        grid-area: nav;
    }
    
    main {
        grid-area: main;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--brand);
        color: white;
        padding: 10px;
        text-align: center;
    }
    Podgląd strony po tym kroku (index.html)
  25. Krok 25: Blok lewy

    ok. 1 min
    Polecenie z arkusza

    Dla bloku lewego: kolor tła #FF8A65, szerokość 25%, wysokość 500 px, margines wewnętrzny górny 50 px

    Szerokość daje pierwsza kolumna siatki. Tu kolor, wysokość i padding-top - blok ma na stronie 500 px treści i 50 px marginesu wewnętrznego.

    styl3.css CSS zmienione: 6 linii
    :root {
        --brand: #C75B39;
        --menu: #FF8A65;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 75%;
        grid-template-areas:
            "header header"
            "nav    main"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    nav {
        grid-area: nav;
    }
    
    main {
        grid-area: main;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--brand);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    nav {
        background-color: var(--menu);
        height: 500px;
        padding-top: 50px;
    }
    Podgląd strony po tym kroku (index.html)
  26. Krok 26: Blok prawy

    Kryteria: , , ,

    R.4.2 Ustawiono kolor tła #C75B39 dla banera i stopki, #FF8A65 dla bloku lewego i Snow dla prawego

    R.4.5 Ustawiono wysokość 500px dla bloków lewego i prawego

    R.4.6 Ustawiono marginesy wewnętrzne (padding) 10px dla banera i stopki, padding-top: 50px dla bloków lewego i prawego

    R.4.8 Ustawiono wyrównanie tekstu do środka dla banera, stopki i bloku prawego

    ok. 1 min
    Polecenie z arkusza

    Dla bloku prawego: kolor tła Snow, szerokość 75%, wysokość 500 px, wyrównanie tekstu do środka, margines wewnętrzny górny 50 px

    Szerokość daje druga kolumna. Ta reguła domyka kolory tła (R.4.2), wysokości (R.4.5), marginesy wewnętrzne (R.4.6) i wyśrodkowania (R.4.8).

    styl3.css CSS zmienione: 7 linii
    :root {
        --brand: #C75B39;
        --menu: #FF8A65;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 75%;
        grid-template-areas:
            "header header"
            "nav    main"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    nav {
        grid-area: nav;
    }
    
    main {
        grid-area: main;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--brand);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    nav {
        background-color: var(--menu);
        height: 500px;
        padding-top: 50px;
    }
    
    main {
        background-color: Snow;
        height: 500px;
        text-align: center;
        padding-top: 50px;
    }
    Podgląd strony po tym kroku (index.html)
  27. Krok 27: Obraz: szerokość, marginesy i cień

    Kryteria: ,

    R.4.4 Ustawiono szerokość 25% dla bloku lewego i 75% dla prawego (width lub grid-template- columns lub flex) oraz 25% dla selektora img

    R.4.9 Ustawiono dla obrazu cień box-shadow: 15px 15px 10px DimGray (poprawnie również 15px 15px 10px 0 DimGray)

    ok. 1 min
    Polecenie z arkusza

    Dla selektora obrazu: szerokość 25%, marginesy zewnętrzne 10 px, cień o przesunięciu 15 px w obu osiach, rozmyciu 10 px i kolorze DimGray

    Selektor znacznika img. Szerokość 25% liczy się od <main>, w którym stoją zdjęcia - trzy obok siebie zajmują trzy czwarte bloku. Szerokości z arkusza są komplet (R.4.4); box-shadow według składni z arkusza (R.4.9).

    styl3.css CSS zmienione: 6 linii
    :root {
        --brand: #C75B39;
        --menu: #FF8A65;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 75%;
        grid-template-areas:
            "header header"
            "nav    main"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    nav {
        grid-area: nav;
    }
    
    main {
        grid-area: main;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--brand);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    nav {
        background-color: var(--menu);
        height: 500px;
        padding-top: 50px;
    }
    
    main {
        background-color: Snow;
        height: 500px;
        text-align: center;
        padding-top: 50px;
    }
    
    img {
        width: 25%;
        margin: 10px;
        box-shadow: 15px 15px 10px DimGray;
    }
    Podgląd strony po tym kroku (index.html)
  28. Krok 28: Elementy listy i ich markery

    Kryteria:

    R.4.10 Ustawiono jedynie dla elementu listy styl pseudoelementu ::marker - kolor czcionki Snow

    ok. 1 min
    Polecenie z arkusza

    Dla selektora elementu listy: marginesy zewnętrzne 5 px. Dla znaku markera (punktora / cyfry) w elemencie listy: kolor czcionki Snow

    li - elementy obu list. Pseudoelement ::marker to sam numer albo punktor: zmienia się tylko jego kolor, tekst zostaje czarny (R.4.10).

    Typowe błędy

    li { color: Snow; } zabarwia cały tekst elementu, nie tylko marker.

    styl3.css CSS zmienione: 8 linii
    :root {
        --brand: #C75B39;
        --menu: #FF8A65;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 75%;
        grid-template-areas:
            "header header"
            "nav    main"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    nav {
        grid-area: nav;
    }
    
    main {
        grid-area: main;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--brand);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    nav {
        background-color: var(--menu);
        height: 500px;
        padding-top: 50px;
    }
    
    main {
        background-color: Snow;
        height: 500px;
        text-align: center;
        padding-top: 50px;
    }
    
    img {
        width: 25%;
        margin: 10px;
        box-shadow: 15px 15px 10px DimGray;
    }
    
    li {
        margin: 5px;
    }
    
    li::marker {
        color: Snow;
    }
    Podgląd strony po tym kroku (index.html)
  29. Krok 29: Nagłówek trzeciego stopnia: marginesy

    Kryteria:

    R.4.7 Ustawiono marginesy zewnętrzne (margin) 10px dla obrazu, 5px dla selektora li, dla selektora h3 górny i dolny 50px oraz lewy i prawy 0

    ok. 1 min
    Polecenie z arkusza

    Dla selektora nagłówka trzeciego stopnia: marginesy zewnętrzne górny i dolny 50 px, pozostałe 0

    <h3> wypisze skrypt z wynikiem wyszukiwania. margin: 50px 0 - górny i dolny, potem lewy i prawy (R.4.7).

    styl3.css CSS zmienione: 4 linie
    :root {
        --brand: #C75B39;
        --menu: #FF8A65;
    }
    
    * {
        font-family: Georgia, serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 25% 75%;
        grid-template-areas:
            "header header"
            "nav    main"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    nav {
        grid-area: nav;
    }
    
    main {
        grid-area: main;
    }
    
    footer {
        grid-area: footer;
    }
    
    header,
    footer {
        background-color: var(--brand);
        color: white;
        padding: 10px;
        text-align: center;
    }
    
    nav {
        background-color: var(--menu);
        height: 500px;
        padding-top: 50px;
    }
    
    main {
        background-color: Snow;
        height: 500px;
        text-align: center;
        padding-top: 50px;
    }
    
    img {
        width: 25%;
        margin: 10px;
        box-shadow: 15px 15px 10px DimGray;
    }
    
    li {
        margin: 5px;
    }
    
    li::marker {
        color: Snow;
    }
    
    h3 {
        margin: 50px 0;
    }
    Podgląd strony po tym kroku (index.html)
  30. Etap 4: Skrypt PHP

    Kroki 30-34 · po tym etapie masz 35 z 35 kryteriów (pkt)

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

    Kryteria: ,

    R.5.1 Skrypt zawiera działające połączenie z serwerem bazy danych i wybór bazy kwiaciarnia

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

    ok. 4 min
    Polecenie z arkusza

    Skrypt łączy się z serwerem bazy danych na localhost, użytkownik root bez hasła, baza danych o nazwie kwiaciarnia. Po wykonaniu operacji na bazie danych skrypt zamyka połączenie z serwerem.

    Kod rozmawiający z bazą stoi na górze znajdz.php, przed HTML-em - strona najpierw wyszukuje, potem wypisuje wynik, a połączenie jest zamknięte, zanim przeglądarka dostanie pierwszy znacznik.

    • new PDO(...) - serwer localhost, baza kwiaciarnia, kodowanie utf8mb4, użytkownik root bez hasła (R.5.1); ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze z nazwami kolumn,
    • $pdo = null na końcu bloku - PDO zamyka połączenie, gdy znika odwołanie do obiektu; zostanie ostatnią operacją na bazie (R.5.2),
    • try / catch (PDOException ...) - przy awarii bazy uczeń zobaczy zwykłe zdanie, a nie szczegóły serwera.

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

    znajdz.php PHP zmienione: 12 linii
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kwiaciarnia;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 = 'Wyszukiwarka jest chwilowo niedostępna. 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>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html">Strona główna</a></li>
                <li><a href="https://www.kwiaty.pl/" target="_blank" rel="noopener">Rozpoznaj kwiaty</a></li>
                <li>
                    <a href="znajdz.php" aria-current="page">Znajdź kwiaciarnię</a>
                    <ul>
                        <li>w Warszawie</li>
                        <li>w Malborku</li>
                        <li>w Poznaniu</li>
                    </ul>
                </li>
            </ol>
        </nav>
        <main>
            <h2>Znajdź kwiaciarnię</h2>
            <form method="post">
                <label for="city">Podaj nazwę miasta:</label>
                <input type="text" id="city" name="city" maxlength="20" required>
                <button type="submit">SPRAWDŹ</button>
            </form>
        </main>
        <footer>
            <p>Stronę opracował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (znajdz.php)
  31. Krok 31: Miasto z formularza

    Kryteria:

    R.5.4 Skrypt pobiera dane z tablicy $_POST

    ok. 3 min
    Polecenie z arkusza

    Skrypt pobiera dane z pola edycyjnego

    Miasto jest w tablicy $_POST pod nazwą z atrybutu name pola (R.5.4). Przy pierwszym wejściu na stronę formularz nie był jeszcze wysłany i klucza city nie ma: operator ?? podstawia wtedy pusty tekst - bez ostrzeżenia „Undefined array key”. trim obcina spacje z początku i końca, więc „ Warszawa ” też znajdzie kwiaciarnię.

    Odczyt stoi przed blokiem try, bo nie dotyczy bazy - zmienna $city będzie potrzebna także wtedy, gdy baza nie odpowie. Od tego kroku podgląd pokazuje stronę po wysłaniu formularza z miastem Warszawa.

    Typowe błędy

    $city = $_POST["city"]; bez ?? przy pierwszym otwarciu strony daje ostrzeżenie „Undefined array key” nad całą stroną.

    znajdz.php PHP zmienione: 3 linie
    <?php
    // miasto z formularza (przy pierwszym wejściu na stronę - pusty tekst)
    $city = trim($_POST['city'] ?? '');
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kwiaciarnia;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 = 'Wyszukiwarka jest chwilowo niedostępna. 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>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html">Strona główna</a></li>
                <li><a href="https://www.kwiaty.pl/" target="_blank" rel="noopener">Rozpoznaj kwiaty</a></li>
                <li>
                    <a href="znajdz.php" aria-current="page">Znajdź kwiaciarnię</a>
                    <ul>
                        <li>w Warszawie</li>
                        <li>w Malborku</li>
                        <li>w Poznaniu</li>
                    </ul>
                </li>
            </ol>
        </nav>
        <main>
            <h2>Znajdź kwiaciarnię</h2>
            <form method="post">
                <label for="city">Podaj nazwę miasta:</label>
                <input type="text" id="city" name="city" maxlength="20" required>
                <button type="submit">SPRAWDŹ</button>
            </form>
        </main>
        <footer>
            <p>Stronę opracował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  32. Krok 32: Zapytanie 1 z miastem z formularza

    Kryteria:

    R.5.3 Skrypt wysyła do bazy danych zmodyfikowane zapytanie 1 w ten sposób, że w miejscu miasta jest wpisana wartość z pola edycyjnego

    ok. 3 min
    Polecenie z arkusza

    Wysyła do bazy danych zmodyfikowane zapytanie 1 w ten sposób, że w miejscu miasta jest wpisana wartość z pola edycyjnego

    Zapytanie przygotowane: w miejscu „Malbork” stoi parametr :city, a wartość podaje execute(['city' => $city]) (R.5.3). Baza dostaje treść zapytania i miasto osobno, więc tekst z formularza nigdy nie stanie się kodem SQL - nawet z apostrofem w środku.

    • zapytanie wykonuje się tylko dla niepustego miasta (if ($city !== '')),
    • fetchAll() zwraca listę kwiaciarni - w mieście może być ich kilka albo żadna,
    • $statement = null zwalnia zapytanie przed zamknięciem połączenia.
    znajdz.php PHP zmienione: 10 linii
    <?php
    // miasto z formularza (przy pierwszym wejściu na stronę - pusty tekst)
    $city = trim($_POST['city'] ?? '');
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kwiaciarnia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // zapytanie 1 z miastem z formularza w miejscu „Malbork”
        $shops = [];
        if ($city !== '') {
            $statement = $pdo->prepare('SELECT nazwa, ulica FROM kwiaciarnie WHERE miasto = :city');
            $statement->execute(['city' => $city]);
            $shops = $statement->fetchAll();
            $statement = null;
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $shops = [];
        $databaseError = 'Wyszukiwarka jest chwilowo niedostępna. 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>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html">Strona główna</a></li>
                <li><a href="https://www.kwiaty.pl/" target="_blank" rel="noopener">Rozpoznaj kwiaty</a></li>
                <li>
                    <a href="znajdz.php" aria-current="page">Znajdź kwiaciarnię</a>
                    <ul>
                        <li>w Warszawie</li>
                        <li>w Malborku</li>
                        <li>w Poznaniu</li>
                    </ul>
                </li>
            </ol>
        </nav>
        <main>
            <h2>Znajdź kwiaciarnię</h2>
            <form method="post">
                <label for="city">Podaj nazwę miasta:</label>
                <input type="text" id="city" name="city" maxlength="20" required>
                <button type="submit">SPRAWDŹ</button>
            </form>
        </main>
        <footer>
            <p>Stronę opracował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  33. Krok 33: Nazwa i ulica w nagłówku trzeciego stopnia

    Kryteria:

    R.5.5 Skrypt wyświetla pod przyciskiem zwrócone zapytaniem dane w nagłówku h3: nazwę i ulicę

    ok. 2 min
    Polecenie z arkusza

    Wyświetla pod przyciskiem zwrócone zapytaniem dane w nagłówku trzeciego stopnia: nazwę i ulicę oddzielone przecinkiem

    Pętla foreach pod formularzem: każda znaleziona kwiaciarnia w osobnym <h3>, nazwa i ulica rozdzielone przecinkiem (R.5.5). Pola po nazwach ($shop['ulica']), a funkcja e() przepuszcza tekst z bazy przez htmlspecialchars. Podgląd: „Astra, Sobieskiego” pod przyciskiem, jak na obrazie 3; odstęp nad i pod napisem to marginesy h3 z arkusza stylów.

    znajdz.php PHP zmienione: 8 linii
    <?php
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    // miasto z formularza (przy pierwszym wejściu na stronę - pusty tekst)
    $city = trim($_POST['city'] ?? '');
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kwiaciarnia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // zapytanie 1 z miastem z formularza w miejscu „Malbork”
        $shops = [];
        if ($city !== '') {
            $statement = $pdo->prepare('SELECT nazwa, ulica FROM kwiaciarnie WHERE miasto = :city');
            $statement->execute(['city' => $city]);
            $shops = $statement->fetchAll();
            $statement = null;
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $shops = [];
        $databaseError = 'Wyszukiwarka jest chwilowo niedostępna. 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>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html">Strona główna</a></li>
                <li><a href="https://www.kwiaty.pl/" target="_blank" rel="noopener">Rozpoznaj kwiaty</a></li>
                <li>
                    <a href="znajdz.php" aria-current="page">Znajdź kwiaciarnię</a>
                    <ul>
                        <li>w Warszawie</li>
                        <li>w Malborku</li>
                        <li>w Poznaniu</li>
                    </ul>
                </li>
            </ol>
        </nav>
        <main>
            <h2>Znajdź kwiaciarnię</h2>
            <form method="post">
                <label for="city">Podaj nazwę miasta:</label>
                <input type="text" id="city" name="city" maxlength="20" required>
                <button type="submit">SPRAWDŹ</button>
            </form>
    <?php foreach ($shops as $shop): ?>
            <h3><?= e($shop['nazwa']) ?>, <?= e($shop['ulica']) ?></h3>
    <?php endforeach; ?>
        </main>
        <footer>
            <p>Stronę opracował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (znajdz.php)
  34. Krok 34: Miasto bez kwiaciarni, puste pole i awaria bazy

    ok. 3 min

    Dodatek spoza arkusza - klucz go nie ocenia. W menu jest „w Poznaniu”, a w bazie nie ma kwiaciarni z Poznania - bez komunikatu pod przyciskiem nie byłoby nic i nie wiadomo, czy wyszukiwanie zadziałało. Teraz:

    • brak wyników - „Brak kwiaciarni w mieście Poznań.”,
    • puste pole albo same spacje (np. po obejściu required) - prośba o wpisanie miasta; $submitted odróżnia wysłany formularz od pierwszego wejścia na stronę,
    • awaria bazy - komunikat z role="alert", a formularz i menu działają dalej,
    • wpisane miasto wraca do pola (value="<?= e($city) ?>") - po wyszukiwaniu widać, czego szukano.

    Sprawdzone na prawdziwym PHP: wpis x' OR '1'='1 nie znajduje żadnej kwiaciarni (zapytanie przygotowane - porównanie z tekstem, nie fragment SQL), „ warszawa ” małymi literami ze spacjami znajduje Astrę, a złe hasło do bazy daje komunikat bez ostrzeżeń PHP. Podgląd: wyszukiwanie dla Poznania.

    znajdz.php PHP zmienione: 9 linii
    <?php
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    // miasto z formularza (przy pierwszym wejściu na stronę - pusty tekst)
    $city = trim($_POST['city'] ?? '');
    $submitted = $_SERVER['REQUEST_METHOD'] === 'POST';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kwiaciarnia;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // zapytanie 1 z miastem z formularza w miejscu „Malbork”
        $shops = [];
        if ($city !== '') {
            $statement = $pdo->prepare('SELECT nazwa, ulica FROM kwiaciarnie WHERE miasto = :city');
            $statement->execute(['city' => $city]);
            $shops = $statement->fetchAll();
            $statement = null;
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $shops = [];
        $databaseError = 'Wyszukiwarka jest chwilowo niedostępna. 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>Kwiaty</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <h1>Grupa Polskich Kwiaciarni</h1>
        </header>
        <nav>
            <h2>Menu</h2>
            <ol>
                <li><a href="index.html">Strona główna</a></li>
                <li><a href="https://www.kwiaty.pl/" target="_blank" rel="noopener">Rozpoznaj kwiaty</a></li>
                <li>
                    <a href="znajdz.php" aria-current="page">Znajdź kwiaciarnię</a>
                    <ul>
                        <li>w Warszawie</li>
                        <li>w Malborku</li>
                        <li>w Poznaniu</li>
                    </ul>
                </li>
            </ol>
        </nav>
        <main>
            <h2>Znajdź kwiaciarnię</h2>
            <form method="post">
                <label for="city">Podaj nazwę miasta:</label>
                <input type="text" id="city" name="city" maxlength="20" required value="<?= e($city) ?>">
                <button type="submit">SPRAWDŹ</button>
            </form>
    <?php foreach ($shops as $shop): ?>
            <h3><?= e($shop['nazwa']) ?>, <?= e($shop['ulica']) ?></h3>
    <?php endforeach; ?>
    <?php if (isset($databaseError)): ?>
            <p role="alert"><?= e($databaseError) ?></p>
    <?php elseif ($submitted && $city === ''): ?>
            <p role="alert">Wpisz nazwę miasta.</p>
    <?php elseif ($city !== '' && $shops === []): ?>
            <p>Brak kwiaciarni w mieście <?= e($city) ?>.</p>
    <?php endif; ?>
        </main>
        <footer>
            <p>Stronę opracował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (znajdz.php)
  35. Etap 5: Porównanie z wersją podstawową

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

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

    ok. 3 min

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

    wersja podstawowawersja nowoczesna
    układfloat: left i clear stopkisiatka na <body>, grid-template-areas
    bloki<section> z id<nav>, <main>
    kolorywpisane w regułyzmienne CSS
    zapytaniabez aliasów, INT UNSIGNEDaliasy tabel, TINYINT UNSIGNED
    bibliotekamysqli (tabela 1 arkusza)PDO z wyjątkami
    gdzie jest skryptpod formularzemna górze znajdz.php, pod formularzem tylko pętla
    miasto w zapytaniuwklejone do napisu SQLparametr :city
    wpis x' OR '1'='1pokazuje obie kwiaciarnienie znajduje nic
    puste pole, brak wynikówpusto pod przyciskiemkomunikaty

    Dane logowania do bazy w prawdziwym projekcie trzyma się zwykle w osobnym pliku (np. config.php), ale klucz sprawdza skrypt w kodzie strony - tu cały kod PHP zostaje w znajdz.php. W prawdziwej wyszukiwarce pasowałaby metoda GET (wynik da się zapisać w zakładce), ale klucz wymaga POST.

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i łatwiej ją napisać bez dokumentacji. Nowoczesna jest odporna na wstrzyknięcie SQL. Obie biblioteki klucz przyjmuje.

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

  36. Etap 6: Oddanie pracy

    Krok 36 · po tym etapie masz 35 z 35 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 powinny się znajdować pliki: gerbera.jpg, gozdzik.jpg, import.png, index.html, kw1.jpg, kw2.jpg, kw3.jpg, kw4.jpg, kwerendy.txt, przeglądarka.txt, roza.jpg, styl3.css, znajdz.php, ewentualnie inne przygotowane pliki.

    W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była witryna przez http://localhost/.... 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 34.

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.