Przejdź do głównej treści
EE.09-01-24.06-SGCzerwiec 2024 · 47 kroków · ok. 91 min · 40 kryteriów z klucza

Rozwiązanie krok po kroku: Rejestracja na forum miłośników psów (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza psy z tabelą uzytkownicy i czterema zapytaniami, odbite i przeskalowane zdjęcie foksteriera oraz strona logowanie.php z formularzem zapisu na forum - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • siatka CSS zamiast float: trzy sekcje stoją w znaczniku <main>, a ich miejsca opisują nazwane obszary siatki (grid-template-areas) - blok lewy zajmuje dwa wiersze, bloki prawe po jednym, a stopka nie potrzebuje clear,
  • zmienne CSS dla kolorów z arkusza, etykiety <label for> i wiersze formularza w blokach zamiast <br>,
  • PHP na PDO na górze pliku logowanie.php, przed HTML-em - w miejscu „efektu działania skryptu” zostaje jedna linijka wypisująca komunikat,
  • zapytanie przygotowane do INSERT, login bez spacji z brzegów i najwyżej 50 znaków, loginy porównywane bez względu na wielkość liter,
  • login wraca do pola po wysłaniu formularza (jak na obrazie 1), a po dodaniu konta przekierowanie - odświeżenie strony nie wyśle formularza drugi raz.

To arkusz EE.09 z czerwca 2024 - kwalifikacji formuły 2017, którą zastąpiła INF.03 (zadanie pojawiło się pierwszy raz w styczniu 2022; ten sam arkusz z tym samym kluczem wrócił w czerwcu 2025 i 2026). Baza i zdjęcie pochodzą z oryginalnego archiwum CKE pliki4 (załącznik na stronie arkusza), zapytania i zrzuty są policzone na MariaDB 10.4, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 (klucz każe sprawdzać w XAMPP 8.1.2 - użyte konstrukcje działają w obu wersjach).

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

  1. Etap 1: Baza danych

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

    Krok 1: Folder zdającego i archiwum pliki4

    ok. 2 min
    Polecenie z arkusza

    Należy utworzyć folder o nazwie odpowiadającej numerowi PESEL zdającego lub w przypadku jego braku innemu numerowi, którym został podpisany arkusz. Archiwum należy rozpakować do tego folderu.

    Folder na pulpicie nazwany numerem PESEL (w tym rozwiązaniu w jego miejscu stoi Bartosz Bryniarski), do niego archiwum pliki4.7z rozpakowane 7-Zipem z hasłem !Psy!Psy!. W środku: uzytkownicy.sql i obraz.jpg. Do tego folderu trafiają wszystkie rezultaty pracy.

  2. Krok 2: XAMPP i pusta baza psy

    ok. 2 min
    Polecenie z arkusza

    Uruchom usługi MySQL i Apache za pomocą XAMPP Control Panel. Za pomocą narzędzia phpMyAdmin wykonaj podane operacje na bazie danych: Utwórz bazę danych o nazwie psy

    Start przy Apache i MySQL, potem http://localhost/phpmyadmin, Nowa, nazwa psy, porównywanie np. utf8mb4_polish_ci. Baza zostaje pusta: tabelę utworzy zapytanie 1, a import przyjdzie dopiero po zapytaniu 2 - plik uzytkownicy.sql zawiera same dane.

  3. Krok 3: Zapytanie 1: tabela uzytkownicy

    Kryteria: ,

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

    R.1.3 Wykonano zapytanie 1 tworzące tabelę uzytkownicy . Klucz główny id jest typu całkowitego dodatniego, jawnie zapisany oraz automatycznie inkrementowany. Klucz główny nie może przyjmować wartości pustych. Pola login i haslo są typu napisowego maksymalnie 50 znakowego. W pliku z kwerendami lub na zrzucie istnieje zapis np.: CREATE TABLE uzytkownicy ( id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, login VARCHAR(50), haslo VARCHAR(50), PRIMARY KEY(id)); oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest efekt poprawnie wykonanego zapytania

    ok. 4 min
    Polecenie z arkusza

    Zapytanie 1: tworzące tabelę uzytkownicy. Tabela powinna zawierać klucz główny id typu całkowitego dodatniego, jawnie zapisany oraz automatycznie inkrementowany, nie może przyjmować wartości pustych, login typu napisowego maksymalnie 50-znakowego, haslo typu napisowego maksymalnie 50-znakowego

    Każde wymaganie arkusza ma w zapytaniu swoje słowo: „całkowity” - INT, „dodatni” - UNSIGNED, „nie może przyjmować wartości pustych” - NOT NULL, „automatycznie inkrementowany” - AUTO_INCREMENT, „jawnie zapisany klucz główny” - osobna klauzula PRIMARY KEY (id), „napis maksymalnie 50-znakowy” - VARCHAR(50). Kolumna na 50 znaków zmieści skrót sha1, który ma 40.

    Zapytanie trafia do kwerendy.txt, zrzut całego ekranu z komunikatem „MySQL zwrócił pusty wynik” - do kw1.jpg.

    Typowe błędy

    Zrzuty zapytań zapisuj w JPEG (kw1.jpg - kw4.jpg), a zrzut importu w PNG (egzamin.png).

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    CREATE TABLE uzytkownicy (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT,
        login VARCHAR(50),
        haslo VARCHAR(50),
        PRIMARY KEY (id)
    );
    Podgląd strony po tym kroku
    Podgląd strony po kroku 3
  4. Krok 4: Zapytanie 2: użytkownik Grzegorz

    Kryteria:

    R.1.4 Wykonano zapytanie 2 wstawiające do tabeli użytkownika o loginie Grzegorz z hasłem „g” (bez szyfrowania). W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO `uzytkownicy` (`login`, `haslo`) VALUES ('Grzegorz', 'g'); możliwe inne zapisy INSERT INTO oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne jest poprawnie wykonane zapytanie lub tabela z wstawionym rekordem

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: dodające do tabeli użytkownika o loginie Grzegorz z hasłem „g” (bez szyfrowania)

    Lista kolumn bez id - numer nada AUTO_INCREMENT (Grzegorz dostaje 1), hasło g zapisane wprost. Zrzut: kw2.jpg. To samo zapytanie, z parametrami w miejscu wartości, wykona później skrypt.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    CREATE TABLE uzytkownicy (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT,
        login VARCHAR(50),
        haslo VARCHAR(50),
        PRIMARY KEY (id)
    );
    
    -- Zapytanie 2
    INSERT INTO uzytkownicy (login, haslo)
    VALUES ('Grzegorz', 'g');
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Import pliku uzytkownicy.sql i zrzut egzamin.png

    Kryteria:

    R.1.1 Wykonano import danych do tabeli psy.uzytkownicy , czynność udokumentowano plikiem o nazwie egzamin w formacie PNG, na którym widoczna jest zawartość tabeli uzytkownicy lub informacja o imporcie danych zakończonym sukcesem. Kryterium jest uznawane tylko wtedy, gdy przed importem utworzono tabelę uzytkownicy

    ok. 3 min
    Polecenie z arkusza

    Po wykonaniu zapytań 1 i 2 zaimportuj do tabeli uzytkownicy plik uzytkownicy.sql z rozpakowanego archiwum. Wykonaj zrzut ekranu po imporcie. Na zrzucie powinna być widoczna zawartość tabeli uzytkownicy. Zrzut zapisz w folderze z numerem PESEL lub w przypadku jego braku w folderze z innym numerem, którym został podpisany arkusz, w formacie PNG i nazwij egzamin. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań

    Baza psy, karta Import, plik uzytkownicy.sql, Wykonaj. Plik zawiera samo INSERT jedenastu użytkowników, dlatego import działa tylko po zapytaniu 1. Po imporcie karta Przeglądaj tabeli uzytkownicy: 12 wierszy - Grzegorz z hasłem g i użytkownicy z id 2-12, których hasła są już skrótami SHA1. Zrzut całego ekranu z paskiem zadań: egzamin.png.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: wszystkie loginy

    Kryteria:

    R.1.5 Wykonano zapytanie 3 wybierające jedynie pole login z tabeli uzytkownicy. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT login FROM uzytkownicy; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne jest jedynie pole login i wypisane wszystkie loginy z tabeli (widocznych jest minimum 11 rekordów)

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pole login z tabeli uzytkownicy

    Jedna kolumna, bez warunku - 12 loginów, od Grzegorza do Eweliny. Zrzut: kw3.jpg. Arkusz każe tym zapytaniem sprawdzać w skrypcie, czy login jest już zajęty.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    CREATE TABLE uzytkownicy (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT,
        login VARCHAR(50),
        haslo VARCHAR(50),
        PRIMARY KEY (id)
    );
    
    -- Zapytanie 2
    INSERT INTO uzytkownicy (login, haslo)
    VALUES ('Grzegorz', 'g');
    
    -- Zapytanie 3
    SELECT login
    FROM uzytkownicy;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: hasło zaczyna się od cyfry 4

    Kryteria:

    R.1.6 Wykonano zapytanie 4 wybierające jedynie pola id oraz login z tabeli uzytkownicy dla użytkowników, których hasło zaczyna się od cyfry 4. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id, login FROM uzytkownicy WHERE haslo LIKE "4%"; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są jedynie pola id i login dla loginów Heniek; Ola

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie pola id oraz login z tabeli uzytkownicy dla użytkowników, których hasło zaczyna się od cyfry 4

    LIKE '4%' - pierwszy znak 4, a % zastępuje dowolny ciąg znaków po nim. Wynik: 9 Heniek i 10 Ola, zgodnie z kluczem. Zrzut: kw4.jpg. Każda klauzula w osobnej linii - przy dłuższych zapytaniach łatwiej wtedy znaleźć błąd.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    CREATE TABLE uzytkownicy (
        id INT UNSIGNED NOT NULL AUTO_INCREMENT,
        login VARCHAR(50),
        haslo VARCHAR(50),
        PRIMARY KEY (id)
    );
    
    -- Zapytanie 2
    INSERT INTO uzytkownicy (login, haslo)
    VALUES ('Grzegorz', 'g');
    
    -- Zapytanie 3
    SELECT login
    FROM uzytkownicy;
    
    -- Zapytanie 4
    SELECT id, login
    FROM uzytkownicy
    WHERE haslo LIKE '4%';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

    Kroki 8-27 · po tym etapie masz 20 z 40 kryteriów (pkt)

    Krok 8: Grafika: odbicie poziome

    ok. 1 min
    Polecenie z arkusza

    Obraz z pliku obraz.jpg, wypakowany z archiwum, ma zostać odbity poziomo

    W GIMP-ie Obraz → Przekształć → Odbij poziomo - lewa strona zamienia się z prawą, pies patrzy teraz w prawo (obraz 1).

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Grafika: wysokość 500 px z zachowaniem proporcji

    Kryteria:

    R.2.1 Plik obraz.jpg odbito poziomo (jak na obrazie 1 w arkuszu) oraz przeskalowano z zachowaniem proporcji tak, że jego wysokość wynosi dokładnie 500 px, odpowiadająca temu szerokość to 323 px ±5 px

    ok. 2 min
    Polecenie z arkusza

    Obraz ma zostać przeskalowany z zachowaniem proporcji tak, aby jego wysokość wynosiła dokładnie 500 px i zapisany pod tą samą nazwą

    Obraz → Skaluj obraz z zamkniętym łańcuchem proporcji, wysokość 500 px - szerokość wychodzi 414 × 500 / 640 ≈ 323 px (klucz: 323 ±5). Zapis: Plik → Nadpisz obraz.jpg.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 9
  10. Krok 10: Plik regulamin.html

    ok. 1 min
    Polecenie z arkusza

    Składa się ze stron: logowanie.php, regulamin.html. W pliku o nazwie regulamin.html jest wpisany jedynie napis „regulamin”. Pozostałe wymagania dotyczą jedynie pliku logowanie.php.

    Plik z jednym słowem regulamin - arkusz mówi „jedynie napis”, więc bez szkieletu HTML. Służy tylko jako cel odnośnika.

    regulamin.html HTML nowy plik
    regulamin
    Podgląd strony po tym kroku (regulamin.html)
  11. Krok 11: Plik logowanie.php

    Kryteria:

    R.2.2 Witrynę internetową zapisano w pliku o nazwie logowanie.php

    ok. 2 min
    Polecenie z arkusza

    Witrynę internetową zapisano w pliku o nazwie logowanie.php

    Szkielet HTML5 w pliku logowanie.php (R.2.2). Rozszerzenie .php, bo na górze tego pliku stanie skrypt; dopóki nie ma w nim PHP, przeglądarka pokaże go jak zwykły HTML.

    logowanie.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  12. Krok 12: Kodowanie polskich znaków

    Kryteria:

    R.2.3 Zastosowano właściwy standard kodowania polskich znaków dla witryny

    ok. 1 min
    Polecenie z arkusza

    Zastosowany właściwy standard kodowania polskich znaków

    <meta charset="UTF-8"> jako pierwszy element <head>, a plik zapisany w UTF-8 (R.2.3).

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  13. Krok 13: Tytuł strony

    Kryteria:

    R.2.4 Nadano tytuł strony: "Forum o psach”

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Forum o psach”

    <title>Forum o psach</title> (R.2.4). Obok znacznik viewport - telefon nie udaje wtedy szerokiego ekranu, tylko pokazuje stronę w swojej szerokości. Klucz go nie ocenia; w nowej stronie dopisuje się go z nawyku.

    logowanie.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>Forum o psach</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  14. Krok 14: Podpięcie arkusza stylów styl4.css

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

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

    <link rel="stylesheet" href="styl4.css"> - nazwa z cyfrą 4, dokładnie jak w arkuszu (R.3.5).

    logowanie.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>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  15. Krok 15: Bloki: baner, lewy, dwa prawe i stopka

    Kryteria:

    R.2.5 Wygląd strony zdefiniowano dzieląc ją na bloki: baner, blok lewy oraz dwa bloki prawe, stopkę. Zastosowano znaczniki sekcji

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: baner, blok lewy oraz dwa bloki prawe jeden pod drugim, poniżej stopka. Podział zrealizowany za pomocą znaczników sekcji

    Baner to <header>, stopka <footer>, a trzy bloki treści - znaczniki <section> z identyfikatorami left, right-top i right-bottom (R.2.5). Sekcje stoją we wspólnym <main>: to główna treść strony (czytnik ekranu pozwala do niej przeskoczyć), a jednocześnie pojemnik, w którym siatka CSS rozmieści trzy bloki. Baner i stopka zostają poza nim - zajmują całą szerokość.

    logowanie.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>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
        </header>
        <main>
            <section id="left">
            </section>
            <section id="right-top">
            </section>
            <section id="right-bottom">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  16. Krok 16: Baner: nagłówek pierwszego stopnia

    ok. 1 min
    Polecenie z arkusza

    Zawartość banera: nagłówek pierwszego stopnia o treści „Forum wielbicieli psów”

    <h1> w banerze - jedyny nagłówek pierwszego stopnia na stronie.

    logowanie.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>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
            </section>
            <section id="right-top">
            </section>
            <section id="right-bottom">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  17. Krok 17: Blok lewy: zdjęcie z tekstem alternatywnym

    Kryteria:

    R.2.9 W bloku lewym umieszczono obraz z psem z tekstem alternatywnym o treści „foksterier”

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku lewego: obraz o nazwie obraz.jpg z tekstem alternatywnym o treści „foksterier”

    src i alt z arkusza (R.2.9). Zwykle przy zdjęciu dopisuje się też atrybuty width i height (przeglądarka rezerwuje wtedy miejsce, zanim pobierze plik), ale tutaj klucz przy R.3.5 wymaga, żeby całe formatowanie pochodziło z arkusza stylów - atrybuty wymiarów lepiej pominąć, a zdjęcie i tak ma już właściwe wymiary po skalowaniu.

    logowanie.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>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
            </section>
            <section id="right-bottom">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  18. Krok 18: Pierwszy blok prawy: nagłówek „Zapisz się”

    ok. 1 min
    Polecenie z arkusza

    Zawartość pierwszego bloku prawego: Nagłówek drugiego stopnia o treści „Zapisz się”

    <h2> - nagłówek drugiego stopnia.

    logowanie.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>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
            </section>
            <section id="right-bottom">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  19. Krok 19: Formularz wysyłający dane metodą POST

    Kryteria:

    R.3.3 Formularz wysyła dane do tej samej strony metodą post, poprawnie również bez atrybutu action

    ok. 2 min
    Polecenie z arkusza

    Formularz wysyłający dane do tej samej strony metodą post z zawartością zgodną z Obrazem 1

    <form method="post"> bez action - dane wracają do logowanie.php (R.3.3). Metoda POST przenosi hasło w treści żądania, a nie w adresie strony.

    Pola formularza nie dostaną atrybutu required, choć zwykle by go miały: przeglądarka zablokowałaby wtedy wysłanie niepełnego formularza i komunikat skryptu „wypełnij wszystkie pola” nigdy by się nie pojawił - a egzaminator sprawdza go, wysyłając formularz z pustym polem (R.5.4).

    logowanie.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>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                </form>
            </section>
            <section id="right-bottom">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  20. Krok 20: Napis „login: ” i pole edycyjne

    ok. 2 min
    Polecenie z arkusza

    Napis „login: ”, następnie pole edycyjne

    Napis staje się etykietą <label for="login">, związaną z polem o id="login": kliknięcie napisu ustawia kursor w polu, a czytnik ekranu odczyta „login” przy wejściu w pole. Etykieta z polem stoją we własnym <div>, więc każde pole jest w nowej linii bez <br>.

    • name="login" - klucz w $_POST,
    • maxlength="50" - tyle znaków mieści kolumna login; dłuższego loginu nie da się wpisać,
    • autocomplete="username" - menedżer haseł rozpozna pole loginu.
    logowanie.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                </form>
            </section>
            <section id="right-bottom">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  21. Krok 21: Napis „hasło: ” i pole na hasło

    ok. 1 min
    Polecenie z arkusza

    Napis „hasło: ”, następnie pole edycyjne pozwalające na niejawne wpisanie hasła

    type="password" - znaki widać jako kropki. autocomplete="new-password" mówi przeglądarce, że to zakładanie konta: zaproponuje nowe, mocne hasło, zamiast wpisywać zapamiętane.

    logowanie.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                </form>
            </section>
            <section id="right-bottom">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  22. Krok 22: Napis „powtórz hasło: ” i drugie pole na hasło

    ok. 1 min
    Polecenie z arkusza

    Napis „powtórz hasło: ”, następnie pole edycyjne pozwalające na niejawne wpisanie hasła

    Drugie pole hasła z własnym id i name - password2. Skrypt porówna $_POST['password'] z $_POST['password2'].

    logowanie.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                </form>
            </section>
            <section id="right-bottom">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  23. Krok 23: Przycisk „Zapisz”

    Kryteria: ,

    R.2.7 W bloku prawym pierwszym umieszczono formularz z trzema polami edycyjnymi i przyciskiem

    R.3.4 Przynajmniej jedno pole edycyjne do wpisywania hasła jest typu password oraz przycisk wysyła dane z formularza

    ok. 1 min
    Polecenie z arkusza

    Przycisk o treści „Zapisz” wysyłający dane z formularza

    <button type="submit">Zapisz</button> - napis jest treścią znacznika, więc przycisk może kiedyś dostać np. ikonę obok tekstu. Typ submit wysyła formularz (R.3.4); trzy pola i przycisk dają komplet elementów z obrazu 1 (R.2.7).

    logowanie.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>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
            </section>
            <section id="right-bottom">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  24. Krok 24: Drugi blok prawy: nagłówek „Zapraszamy wszystkich”

    Kryteria:

    R.2.6 W bloku banera zapisano nagłówek h1: "Forum wielbicieli psów" oraz w blokach prawych zapisano nagłówki h2: "Zapisz się" i "Zapraszamy wszystkich"

    ok. 1 min
    Polecenie z arkusza

    Zawartość drugiego bloku prawego: Nagłówek drugiego stopnia o treści „Zapraszamy wszystkich”

    Drugi <h2>; nagłówki <h1> i oba <h2> są gotowe (R.2.6).

    logowanie.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>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  25. Krok 25: Lista numerowana

    Kryteria:

    R.2.8 W bloku prawym drugim umieszczono listę numerowaną z czterema elementami, zastosowano znaczniki <ol>, <li>

    ok. 1 min
    Polecenie z arkusza

    Lista numerowana (uporządkowana) z elementami: „właścicieli psów”, „weterynarzy”, „tych, co chcą kupić psa”, „tych, co lubią psy”

    <ol> z czterema <li> - numery dopisze przeglądarka (R.2.8).

    logowanie.php PHP zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  26. Krok 26: Odnośnik do regulaminu

    Kryteria:

    R.3.2 Na stronie umieszczono odnośnik o treści „Przeczytaj regulamin forum”, którego kliknięcie powoduje przejście do strony regulamin.html

    ok. 1 min
    Polecenie z arkusza

    Odnośnik do pliku regulamin.html o treści „Przeczytaj regulamin forum”

    <a href="regulamin.html"> (R.3.2). Bez paragrafu <p> wokół - CSS dopisze przed każdym paragrafem „STATUS: ”, a przy odnośniku na obrazie 1 tego napisu nie ma.

    logowanie.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>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
                <a href="regulamin.html">Przeczytaj regulamin forum</a>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  27. Krok 27: Stopka z autorem strony

    Kryteria:

    R.2.10 W stopce umieszczono napis „Stronę wykonał: ”, dalej wstawiony numer zdającego

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: napis o treści „Stronę wykonał: ”, dalej wstawiony numer PESEL zdającego lub w przypadku jego braku inny numer, którym został podpisany arkusz.

    Na egzaminie po dwukropku wpisujesz swój numer PESEL; w tym rozwiązaniu stoi tam imię i nazwisko autora, Bartosz Bryniarski (R.2.10). Napis bez <p>, z tego samego powodu co odnośnik.

    logowanie.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>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
                <a href="regulamin.html">Przeczytaj regulamin forum</a>
            </section>
        </main>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  28. Etap 3: Styl CSS

    Kroki 28-36 · po tym etapie masz 30 z 40 kryteriów (pkt)

    Krok 28: Plik styl4.css: kolory jako zmienne i krój Garamond

    Kryteria:

    R.4.1 Ustawiono domyślny krój czcionki dla całej strony na Garamond (dla selektora * lub body lub html lub kontenera całej strony)

    ok. 2 min
    Polecenie z arkusza

    Cechy formatowania CSS: Są zawarte wyłącznie w zewnętrznym arkuszu stylów. Domyślne dla całej strony: krój czcionki Garamond

    Na początku pliku zmienne CSS w :root: dwa kolory z arkusza dostają nazwy --brown i --tan, a reguły dalej używają var(--brown). Zmiana odcienia to wtedy jedna linia, a nie szukanie koloru w pięciu regułach.

    Krój Garamond na body (R.4.1) - dziedziczą go wszystkie elementy strony poza polami formularza i przyciskami, które mają własny krój systemowy. Dlatego osobna reguła input, button { font-family: inherit; } każe im wziąć krój po rodzicu - napis „Zapisz” jest wtedy pisany Garamondem jak na obrazie 1.

    styl4.css CSS nowy plik
    :root {
        --brown: SaddleBrown;
        --tan: Tan;
    }
    
    body {
        font-family: Garamond;
    }
    
    input,
    button {
        font-family: inherit;
    }
    Podgląd strony po tym kroku (logowanie.php)
  29. Krok 29: Baner: tło, kolor tekstu, wyśrodkowanie, wysokość

    Kryteria:

    R.4.6 Ustawiono wyrównanie tekstu do środka dla banera

    ok. 2 min
    Polecenie z arkusza

    Dla banera: kolor tła SaddleBrown, biały kolor czcionki, wyrównanie tekstu do środka, wysokość 50 px

    Cztery właściwości z arkusza na header (R.4.6). Druga reguła dotyczy nagłówka w banerze: domyślny margines <h1> (około 21 px nad i pod) nie zmieściłby się w 50 px, więc margin: 0, a line-height: 50px - wysokość linii równa wysokości banera - stawia napis pionowo na środku paska.

    styl4.css CSS zmienione: 12 linii
    :root {
        --brown: SaddleBrown;
        --tan: Tan;
    }
    
    body {
        font-family: Garamond;
    }
    
    input,
    button {
        font-family: inherit;
    }
    
    header {
        background-color: var(--brown);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    header h1 {
        margin: 0;
        line-height: 50px;
    }
    Podgląd strony po tym kroku (logowanie.php)
  30. Krok 30: Blok lewy: tło Tan i wysokość

    ok. 1 min
    Polecenie z arkusza

    Dla bloku lewego: kolor tła Tan, wysokość 500 px, szerokość 40%

    Tło i wysokość na samym bloku. Szerokość 40% ustawi siatka - to szerokość kolumny, w której blok stoi (krok „Układ bloków”).

    styl4.css CSS zmienione: 5 linii
    :root {
        --brown: SaddleBrown;
        --tan: Tan;
    }
    
    body {
        font-family: Garamond;
    }
    
    input,
    button {
        font-family: inherit;
    }
    
    header {
        background-color: var(--brown);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    header h1 {
        margin: 0;
        line-height: 50px;
    }
    
    #left {
        background-color: var(--tan);
        height: 500px;
    }
    Podgląd strony po tym kroku (logowanie.php)
  31. Krok 31: Oba bloki prawe: tło Tan i wysokość

    Kryteria:

    R.4.4 Ustawiono wysokość bloków: 50px dla banera, 500px dla bloku lewego, 250px dla obu bloków prawych

    ok. 1 min
    Polecenie z arkusza

    Dla obu bloków prawych: kolor tła Tan, wysokość 250 px, szerokość 60%

    Jedna reguła na dwa selektory. Wysokości bloków z arkusza są ustawione (R.4.4): 250 + 250 = 500 px, tyle co blok lewy. Szerokość 60% - w siatce.

    styl4.css CSS zmienione: 6 linii
    :root {
        --brown: SaddleBrown;
        --tan: Tan;
    }
    
    body {
        font-family: Garamond;
    }
    
    input,
    button {
        font-family: inherit;
    }
    
    header {
        background-color: var(--brown);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    header h1 {
        margin: 0;
        line-height: 50px;
    }
    
    #left {
        background-color: var(--tan);
        height: 500px;
    }
    
    #right-top,
    #right-bottom {
        background-color: var(--tan);
        height: 250px;
    }
    Podgląd strony po tym kroku (logowanie.php)
  32. Krok 32: Stopka: tło i kolor tekstu

    Kryteria: ,

    R.4.2 Ustawiono kolor tła SaddleBrown dla banera i stopki, Tan dla bloku lewego i obu prawych

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

    ok. 1 min
    Polecenie z arkusza

    Dla bloku stopki: kolor tła SaddleBrown, biały kolor czcionki

    Ten sam kolor co baner - stąd zmienna. Kolory tła wszystkich bloków (R.4.2) i biały tekst banera i stopki (R.4.3) są ustawione.

    styl4.css CSS zmienione: 5 linii
    :root {
        --brown: SaddleBrown;
        --tan: Tan;
    }
    
    body {
        font-family: Garamond;
    }
    
    input,
    button {
        font-family: inherit;
    }
    
    header {
        background-color: var(--brown);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    header h1 {
        margin: 0;
        line-height: 50px;
    }
    
    #left {
        background-color: var(--tan);
        height: 500px;
    }
    
    #right-top,
    #right-bottom {
        background-color: var(--tan);
        height: 250px;
    }
    
    footer {
        background-color: var(--brown);
        color: white;
    }
    Podgląd strony po tym kroku (logowanie.php)
  33. Krok 33: Układ bloków: siatka z nazwanymi obszarami

    Kryteria: ,

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

    R.4.5 Ustawiono szerokość bloku lewego 40% oraz obu prawych 60% (zastosowano np.: width lub grid-template-columns lub flex)

    ok. 4 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 1

    <main> staje się siatką (display: grid):

    • grid-template-columns: 40% 60% - dwie kolumny o szerokościach z arkusza. Klucz przyjmuje tę właściwość wprost jako ustawienie szerokości bloków (R.4.5),
    • grid-template-areas rysuje układ słowami: każdy napis w cudzysłowie to wiersz siatki, każde słowo - komórka. left stoi w obu wierszach, więc blok lewy zajmuje dwa wiersze, a right-top i right-bottom po jednym,
    • grid-area przypisuje każdą sekcję do jej obszaru.

    Wysokości wierszy wynikają z bloków (po 250 px), a stopka stoi pod <main> zwyczajnie, bez float i bez clear (R.3.1). Na obrazie strony widać ten sam układ co w wersji podstawowej: 40% na zdjęcie, 60% na dwa bloki jeden pod drugim.

    Typowe błędy

    Gdy nazwy w grid-template-areas nie tworzą prostokątów (np. left w dwóch niesąsiednich komórkach), przeglądarka odrzuca całą właściwość i bloki układają się jeden pod drugim.

    styl4.css CSS zmienione: 20 linii
    :root {
        --brown: SaddleBrown;
        --tan: Tan;
    }
    
    body {
        font-family: Garamond;
    }
    
    input,
    button {
        font-family: inherit;
    }
    
    header {
        background-color: var(--brown);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    header h1 {
        margin: 0;
        line-height: 50px;
    }
    
    #left {
        background-color: var(--tan);
        height: 500px;
    }
    
    #right-top,
    #right-bottom {
        background-color: var(--tan);
        height: 250px;
    }
    
    footer {
        background-color: var(--brown);
        color: white;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 60%;
        grid-template-areas:
            "left right-top"
            "left right-bottom";
    }
    
    #left {
        grid-area: left;
    }
    
    #right-top {
        grid-area: right-top;
    }
    
    #right-bottom {
        grid-area: right-bottom;
    }
    Podgląd strony po tym kroku (logowanie.php)
  34. Krok 34: Formularz: tło, kolor tekstu, marginesy wewnętrzne, szerokość

    Kryteria:

    R.4.8 Ustawiono pozostałe cechy formularza: kolor tła SaddleBrown, biały kolor czcionki, szerokość 300px

    ok. 2 min
    Polecenie z arkusza

    Dla formularza: kolor tła SaddleBrown, biały kolor czcionki, marginesy wewnętrzne 10 px (góra i dół) i 30 px (lewy i prawy), szerokość 300 px

    padding: 10px 30px - pion, potem poziom. Tło, biały tekst i szerokość dają R.4.8.

    styl4.css CSS zmienione: 7 linii
    :root {
        --brown: SaddleBrown;
        --tan: Tan;
    }
    
    body {
        font-family: Garamond;
    }
    
    input,
    button {
        font-family: inherit;
    }
    
    header {
        background-color: var(--brown);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    header h1 {
        margin: 0;
        line-height: 50px;
    }
    
    #left {
        background-color: var(--tan);
        height: 500px;
    }
    
    #right-top,
    #right-bottom {
        background-color: var(--tan);
        height: 250px;
    }
    
    footer {
        background-color: var(--brown);
        color: white;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 60%;
        grid-template-areas:
            "left right-top"
            "left right-bottom";
    }
    
    #left {
        grid-area: left;
    }
    
    #right-top {
        grid-area: right-top;
    }
    
    #right-bottom {
        grid-area: right-bottom;
    }
    
    form {
        background-color: var(--brown);
        color: white;
        padding: 10px 30px;
        width: 300px;
    }
    Podgląd strony po tym kroku (logowanie.php)
  35. Krok 35: Pola formularza: marginesy zewnętrzne 5 px

    Kryteria:

    R.4.7 Ustawiono marginesy wewnętrzne (padding) dla formularza 10px (góra i dół) i 30px (lewy i prawy) oraz marginesy zewnętrzne (margin) dla pola edycyjnego na 5px

    ok. 1 min
    Polecenie z arkusza

    Dla zastosowanych w formularzu pól: marginesy zewnętrzne 5 px

    Margines 5 px trafia do istniejącej reguły input, button - obejmuje trzy pola i przycisk (R.4.7).

    styl4.css CSS zmienione: 1 linia
    :root {
        --brown: SaddleBrown;
        --tan: Tan;
    }
    
    body {
        font-family: Garamond;
    }
    
    input,
    button {
        font-family: inherit;
        margin: 5px;
    }
    
    header {
        background-color: var(--brown);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    header h1 {
        margin: 0;
        line-height: 50px;
    }
    
    #left {
        background-color: var(--tan);
        height: 500px;
    }
    
    #right-top,
    #right-bottom {
        background-color: var(--tan);
        height: 250px;
    }
    
    footer {
        background-color: var(--brown);
        color: white;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 60%;
        grid-template-areas:
            "left right-top"
            "left right-bottom";
    }
    
    #left {
        grid-area: left;
    }
    
    #right-top {
        grid-area: right-top;
    }
    
    #right-bottom {
        grid-area: right-bottom;
    }
    
    form {
        background-color: var(--brown);
        color: white;
        padding: 10px 30px;
        width: 300px;
    }
    Podgląd strony po tym kroku (logowanie.php)
  36. Krok 36: Paragraf: tekst „STATUS: ” przed treścią

    Kryteria:

    R.4.9 Ustawiono dla paragrafu styl pseudoelementu before. Przed każdym paragrafem znajduje się tekst o treści "STATUS: " (content: "STATUS: ")

    ok. 1 min
    Polecenie z arkusza

    Dla paragrafu (akapitu): przed każdym paragrafem wstawiany jest tekst o treści „STATUS: ”

    Pseudoelement ::before z content (R.4.9). Na stronie jest jeden paragraf - komunikat skryptu pod formularzem - więc „STATUS: ” pojawi się tylko przy nim.

    styl4.css CSS zmienione: 4 linie
    :root {
        --brown: SaddleBrown;
        --tan: Tan;
    }
    
    body {
        font-family: Garamond;
    }
    
    input,
    button {
        font-family: inherit;
        margin: 5px;
    }
    
    header {
        background-color: var(--brown);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    header h1 {
        margin: 0;
        line-height: 50px;
    }
    
    #left {
        background-color: var(--tan);
        height: 500px;
    }
    
    #right-top,
    #right-bottom {
        background-color: var(--tan);
        height: 250px;
    }
    
    footer {
        background-color: var(--brown);
        color: white;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 60%;
        grid-template-areas:
            "left right-top"
            "left right-bottom";
    }
    
    #left {
        grid-area: left;
    }
    
    #right-top {
        grid-area: right-top;
    }
    
    #right-bottom {
        grid-area: right-bottom;
    }
    
    form {
        background-color: var(--brown);
        color: white;
        padding: 10px 30px;
        width: 300px;
    }
    
    p::before {
        content: "STATUS: ";
    }
    Podgląd strony po tym kroku (logowanie.php)
  37. Etap 4: Skrypt PHP

    Kroki 37-45 · po tym etapie masz 40 z 40 kryteriów (pkt)

    Krok 37: Skrypt na górze pliku i połączenie z bazą psy

    Kryteria:

    R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy psy

    ok. 4 min
    Polecenie z arkusza

    Efekt działania skryptu. Skrypt: Napisany w języku PHP. Skrypt łączy się z serwerem bazy danych na localhost, użytkownik root bez hasła, baza danych o nazwie psy

    Skrypt stoi na samej górze logowanie.php, przed <!DOCTYPE html>: najpierw przygotowuje dane, potem HTML je tylko wypisuje. W miejscu „efektu działania skryptu” z obrazu 1 - pod formularzem - zostają trzy linie: jeśli jest komunikat, wypisz go w paragrafie <p>.

    • e() - krótka funkcja z htmlspecialchars: każdy tekst wypisywany na stronie zamienia <, >, & i cudzysłowy na encje, więc znaczniki wpisane w formularz nie zamienią się w kod strony,
    • $_SERVER['REQUEST_METHOD'] === 'POST' - skrypt pracuje tylko po wysłaniu formularza; zwykłe wejście na stronę nie łączy się z bazą,
    • new PDO(...) - adres serwera localhost, baza psy i kodowanie w jednym napisie, potem użytkownik root i puste hasło (R.5.1). ERRMODE_EXCEPTION - każdy błąd bazy rzuca wyjątek,
    • try / catch - gdy baza nie działa, uczeń dostaje zwykłe zdanie, a nie komunikat PHP z danymi serwera.

    PDO zamiast funkcji mysqli z tabeli 1 - klucz przyjmuje obie biblioteki (uwaga przy R.5). Połączenie z nazwą bazy stoi w pliku strony, bo tam klucz je sprawdza, jeśli strona się nie uruchomi. Od tego kroku podgląd to wynik prawdziwego uruchomienia strony.

    logowanie.php PHP zmienione: 21 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = null;
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        try {
            $pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            ]);
        } catch (PDOException $exception) {
            $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
    <?php if ($message !== null): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
                <a href="regulamin.html">Przeczytaj regulamin forum</a>
            </section>
        </main>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  38. Krok 38: Komunikat „wypełnij wszystkie pola”

    Kryteria: ,

    R.5.4 Wyświetlono komunikat „wypełnij wszystkie pola”, jeżeli nie wypełniono przynajmniej jednego pola formularza

    R.5.10 Przynajmniej jeden komunikat jest zapisany w paragrafie

    ok. 4 min
    Polecenie z arkusza

    Skrypt obsługuje błędy danych formularza i wyświetla komunikaty w paragrafie (akapicie): „wypełnij wszystkie pola”, jeżeli nie wypełniono przynajmniej jednego pola formularza

    Dane z formularza czytane przed połączeniem z bazą - pusty formularz nie potrzebuje bazy:

    • $_POST['login'] ?? '' - operator ?? daje pusty napis, gdy pola w ogóle nie wysłano (np. żądanie spoza formularza), zamiast ostrzeżenia PHP,
    • trim() na loginie - login z samych spacji jest pusty, a „Ewa ” ze spacją na końcu to „Ewa”. Haseł nie przycinasz: spacja w haśle to zwykły znak,
    • porównanie === '' zamiast empty() - empty('0') jest prawdziwe, więc hasło „0” uznano by za puste pole,
    • mb_strlen($login) > 50 - kolumna ma 50 znaków; dłuższego loginu XAMPP nie odrzuci, tylko po cichu utnie. Pole ma maxlength, ale dane da się wysłać z pominięciem formularza, więc sprawdza je też serwer,
    • komunikat trafia do $message i do paragrafu pod formularzem (R.5.4, R.5.10).

    Pole loginu dostało value="<?= e($login) ?>" - po wysłaniu wpisany login wraca do pola i nie trzeba go wpisywać od nowa (na obrazie 1 pole loginu jest wypełnione obok komunikatu). Hasła celowo nie wracają. Podgląd: formularz wysłany bez loginu.

    logowanie.php PHP zmienione: 18 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = null;
    $login = '';
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $login = trim($_POST['login'] ?? '');
        $password = $_POST['password'] ?? '';
        $password2 = $_POST['password2'] ?? '';
    
        if ($login === '' || $password === '' || $password2 === '') {
            $message = 'wypełnij wszystkie pola';
        } elseif (mb_strlen($login) > 50) {
            $message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
            } catch (PDOException $exception) {
                $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
            }
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
    <?php if ($message !== null): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
                <a href="regulamin.html">Przeczytaj regulamin forum</a>
            </section>
        </main>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  39. Krok 39: Zapytanie 3: czy login jest już w bazie

    Kryteria: ,

    R.5.3 Skrypt zawiera instrukcję wysyłającą zapytanie 3 lub zmodyfikowane zapytanie 2 (pola login i hasło są pobrane z formularza)

    R.5.5 Wyświetlono komunikat „login występuje w bazie danych, konto nie zostało dodane”, jeżeli podany login znajduje się w bazie danych

    ok. 4 min
    Polecenie z arkusza

    „login występuje w bazie danych, konto nie zostało dodane”, jeżeli podany login znajduje się w bazie danych, do sprawdzenia należy posłużyć się zapytaniem 3

    Arkusz każe sprawdzić login zapytaniem 3, więc skrypt wysyła je bez zmian (R.5.3):

    • $pdo->query('SELECT login FROM uzytkownicy;') - zapytanie bez danych od użytkownika, więc wystarczy zwykłe query,
    • fetchAll(PDO::FETCH_COLUMN) - od razu tablica samych loginów, bez pętli while,
    • array_map('mb_strtolower', ...) i mb_strtolower($login) - oba napisy zamienione na małe litery: „ewa” to ten sam użytkownik co „Ewa”. Funkcja mb_ poprawnie zmniejsza też polskie litery („Łucja” → „łucja”),
    • in_array(..., true) - czy login jest w tablicy; true każe porównywać ściśle, bez zamiany typów.

    Podgląd: dane z obrazu 1 - login Ewa i dwa różne hasła. Wynik: „STATUS: login występuje w bazie danych, konto nie zostało dodane”, a w polu loginu zostaje „Ewa” - dokładnie jak na obrazie 1 (R.5.5).

    logowanie.php PHP zmienione: 8 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = null;
    $login = '';
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $login = trim($_POST['login'] ?? '');
        $password = $_POST['password'] ?? '';
        $password2 = $_POST['password2'] ?? '';
    
        if ($login === '' || $password === '' || $password2 === '') {
            $message = 'wypełnij wszystkie pola';
        } elseif (mb_strlen($login) > 50) {
            $message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
    
                // zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter
                $logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);
                $takenLogins = array_map('mb_strtolower', $logins);
    
                if (in_array(mb_strtolower($login), $takenLogins, true)) {
                    $message = 'login występuje w bazie danych, konto nie zostało dodane';
                }
            } catch (PDOException $exception) {
                $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
            }
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
    <?php if ($message !== null): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
                <a href="regulamin.html">Przeczytaj regulamin forum</a>
            </section>
        </main>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  40. Krok 40: Komunikat o różnych hasłach

    Kryteria:

    R.5.6 Wyświetlono komunikat „hasła nie są takie same, konto nie zostało dodane”, jeżeli hasła wpisane do pola „hasło” oraz pola „powtórz hasło” różnią się

    ok. 2 min
    Polecenie z arkusza

    „hasła nie są takie same, konto nie zostało dodane”, jeżeli hasła wpisane do pola „hasło” oraz pola „powtórz hasło” różnią się

    elseif sprawdza hasła tylko dla wolnego loginu. !== porównuje ściśle - dwa napisy są równe tylko wtedy, gdy mają te same znaki. Podgląd: login Kasia, hasła azor i azor1 (R.5.6).

    logowanie.php PHP zmienione: 2 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = null;
    $login = '';
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $login = trim($_POST['login'] ?? '');
        $password = $_POST['password'] ?? '';
        $password2 = $_POST['password2'] ?? '';
    
        if ($login === '' || $password === '' || $password2 === '') {
            $message = 'wypełnij wszystkie pola';
        } elseif (mb_strlen($login) > 50) {
            $message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
    
                // zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter
                $logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);
                $takenLogins = array_map('mb_strtolower', $logins);
    
                if (in_array(mb_strtolower($login), $takenLogins, true)) {
                    $message = 'login występuje w bazie danych, konto nie zostało dodane';
                } elseif ($password !== $password2) {
                    $message = 'hasła nie są takie same, konto nie zostało dodane';
                }
            } catch (PDOException $exception) {
                $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
            }
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
    <?php if ($message !== null): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
                <a href="regulamin.html">Przeczytaj regulamin forum</a>
            </section>
        </main>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  41. Krok 41: Szyfrowanie hasła funkcją sha1

    Kryteria:

    R.5.8 Zastosowano funkcję sha1 do zaszyfrowania hasła

    ok. 2 min
    Polecenie z arkusza

    W przypadku poprawnych danych skrypt szyfruje hasło algorytmem US Secure Hash Algorithm 1

    sha1($password) - skrót SHA1 hasła, 40 znaków szesnastkowych; dla azor to 45a54d145a451e6624729ab8ec8051451c5fa718 (R.5.8). Klucz wymaga tej funkcji, więc zostaje. Poza egzaminem do haseł służy password_hash() - funkcja celowo wolna i z losową „solą”, której wynik (60 znaków) wymaga szerszej kolumny niż VARCHAR(50) z arkusza. Podgląd: dane poprawne - skrót policzony, nic jeszcze nie zapisano.

    logowanie.php PHP zmienione: 2 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = null;
    $login = '';
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $login = trim($_POST['login'] ?? '');
        $password = $_POST['password'] ?? '';
        $password2 = $_POST['password2'] ?? '';
    
        if ($login === '' || $password === '' || $password2 === '') {
            $message = 'wypełnij wszystkie pola';
        } elseif (mb_strlen($login) > 50) {
            $message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
    
                // zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter
                $logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);
                $takenLogins = array_map('mb_strtolower', $logins);
    
                if (in_array(mb_strtolower($login), $takenLogins, true)) {
                    $message = 'login występuje w bazie danych, konto nie zostało dodane';
                } elseif ($password !== $password2) {
                    $message = 'hasła nie są takie same, konto nie zostało dodane';
                } else {
                    $hash = sha1($password);
                }
            } catch (PDOException $exception) {
                $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
            }
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
    <?php if ($message !== null): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
                <a href="regulamin.html">Przeczytaj regulamin forum</a>
            </section>
        </main>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  42. Krok 42: Zapytanie 2 jako zapytanie przygotowane

    Kryteria:

    R.5.7 Wstawiono do bazy danych rekord z danymi pobranymi z formularza. Należy sprawdzić w bazie danych czy rekord został wstawiony

    ok. 3 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 2 zmodyfikowane w ten sposób, że w polach login i haslo są wpisane dane pobrane z formularza (hasło powinno być zaszyfrowane)

    Zapytanie 2, w którym wartości zastępują parametry :login i :haslo:

    • $pdo->prepare(...) - baza dostaje samą treść zapytania,
    • $statement->execute([...]) - osobno przychodzą dane: login z formularza i skrót hasła.

    Dane nigdy nie staną się częścią kodu SQL, więc login z apostrofem (O'Neill) zapisuje się bez błędu, a wpisany w pole fragment zapytania nie doda do bazy nic poza jednym kontem z dziwnym loginem - w wersji podstawowej oba przypadki psuły zapytanie. Sprawdzenie, jak każe klucz (R.5.7): w tabeli jest nowy wiersz 13, Kasia, 45a54d145a451e6624729ab8ec8051451c5fa718.

    logowanie.php PHP zmienione: 2 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = null;
    $login = '';
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $login = trim($_POST['login'] ?? '');
        $password = $_POST['password'] ?? '';
        $password2 = $_POST['password2'] ?? '';
    
        if ($login === '' || $password === '' || $password2 === '') {
            $message = 'wypełnij wszystkie pola';
        } elseif (mb_strlen($login) > 50) {
            $message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
    
                // zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter
                $logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);
                $takenLogins = array_map('mb_strtolower', $logins);
    
                if (in_array(mb_strtolower($login), $takenLogins, true)) {
                    $message = 'login występuje w bazie danych, konto nie zostało dodane';
                } elseif ($password !== $password2) {
                    $message = 'hasła nie są takie same, konto nie zostało dodane';
                } else {
                    $hash = sha1($password);
                    $statement = $pdo->prepare('INSERT INTO uzytkownicy (login, haslo) VALUES (:login, :haslo);');
                    $statement->execute(['login' => $login, 'haslo' => $hash]);
                }
            } catch (PDOException $exception) {
                $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
            }
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
    <?php if ($message !== null): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
                <a href="regulamin.html">Przeczytaj regulamin forum</a>
            </section>
        </main>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>

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

  43. Krok 43: Komunikat „Konto zostało dodane”

    Kryteria:

    R.5.9 Po dodaniu użytkownika wyświetlono komunikat „Konto zostało dodane”

    ok. 1 min
    Polecenie z arkusza

    Wyświetla w paragrafie komunikat „Konto zostało dodane”

    Komunikat ustawiany dopiero po execute - gdyby zapis się nie udał, wyjątek przeskoczy tę linię i pojawi się komunikat z catch (R.5.9).

    logowanie.php PHP zmienione: 1 linia
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = null;
    $login = '';
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $login = trim($_POST['login'] ?? '');
        $password = $_POST['password'] ?? '';
        $password2 = $_POST['password2'] ?? '';
    
        if ($login === '' || $password === '' || $password2 === '') {
            $message = 'wypełnij wszystkie pola';
        } elseif (mb_strlen($login) > 50) {
            $message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
    
                // zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter
                $logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);
                $takenLogins = array_map('mb_strtolower', $logins);
    
                if (in_array(mb_strtolower($login), $takenLogins, true)) {
                    $message = 'login występuje w bazie danych, konto nie zostało dodane';
                } elseif ($password !== $password2) {
                    $message = 'hasła nie są takie same, konto nie zostało dodane';
                } else {
                    $hash = sha1($password);
                    $statement = $pdo->prepare('INSERT INTO uzytkownicy (login, haslo) VALUES (:login, :haslo);');
                    $statement->execute(['login' => $login, 'haslo' => $hash]);
                    $message = 'Konto zostało dodane';
                }
            } catch (PDOException $exception) {
                $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
            }
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
    <?php if ($message !== null): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
                <a href="regulamin.html">Przeczytaj regulamin forum</a>
            </section>
        </main>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  44. Krok 44: Zamknięcie połączenia z bazą

    Kryteria:

    R.5.2 Ostatnią operacją na bazie jest jej zamknięcie

    ok. 2 min
    Polecenie z arkusza

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

    PDO nie ma funkcji close - połączenie zamyka się, gdy znika ostatnie odwołanie do obiektu. Dlatego $statement = null i $pdo = null.

    Stoją w bloku finally: wykona się on zawsze po try, zarówno gdy wszystko się udało, jak i po wyjątku - zamknięcie jest więc ostatnią operacją na bazie w każdej gałęzi (R.5.2). Podgląd wraca do stanu z obrazu 1.

    logowanie.php PHP zmienione: 3 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $message = null;
    $login = '';
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $login = trim($_POST['login'] ?? '');
        $password = $_POST['password'] ?? '';
        $password2 = $_POST['password2'] ?? '';
    
        if ($login === '' || $password === '' || $password2 === '') {
            $message = 'wypełnij wszystkie pola';
        } elseif (mb_strlen($login) > 50) {
            $message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
    
                // zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter
                $logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);
                $takenLogins = array_map('mb_strtolower', $logins);
    
                if (in_array(mb_strtolower($login), $takenLogins, true)) {
                    $message = 'login występuje w bazie danych, konto nie zostało dodane';
                } elseif ($password !== $password2) {
                    $message = 'hasła nie są takie same, konto nie zostało dodane';
                } else {
                    $hash = sha1($password);
                    $statement = $pdo->prepare('INSERT INTO uzytkownicy (login, haslo) VALUES (:login, :haslo);');
                    $statement->execute(['login' => $login, 'haslo' => $hash]);
                    $message = 'Konto zostało dodane';
                }
            } catch (PDOException $exception) {
                $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
            } finally {
                $statement = null;
                $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
            }
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
    <?php if ($message !== null): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
                <a href="regulamin.html">Przeczytaj regulamin forum</a>
            </section>
        </main>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  45. Krok 45: Odświeżenie po dodaniu konta nie wysyła formularza drugi raz

    ok. 4 min

    Dodatek spoza arkusza - klucz go nie ocenia. Strona po wysłaniu formularza jest wynikiem żądania POST, więc odświeżenie (F5) pyta, czy wysłać dane ponownie. Wzorzec PRG (Post/Redirect/Get):

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

    Przekierowanie jest tylko po dodaniu konta - przy błędach strona zostaje z komunikatem i wpisanym loginem. Sprawdzone na prawdziwym PHP: wysłanie formularza daje odpowiedź 302, potem „STATUS: Konto zostało dodane”, a kolejne odświeżenie - stronę bez komunikatu i bez drugiego konta Kasi w tabeli. Podgląd: stan po przekierowaniu.

    logowanie.php PHP zmienione: 13 linii
    <?php
    session_start();
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    // komunikat zapisany przed przekierowaniem (PRG) - pokazywany raz
    $message = $_SESSION['message'] ?? null;
    unset($_SESSION['message']);
    $login = '';
    $accountAdded = false;
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $login = trim($_POST['login'] ?? '');
        $password = $_POST['password'] ?? '';
        $password2 = $_POST['password2'] ?? '';
    
        if ($login === '' || $password === '' || $password2 === '') {
            $message = 'wypełnij wszystkie pola';
        } elseif (mb_strlen($login) > 50) {
            $message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane';
        } else {
            try {
                $pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                ]);
    
                // zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter
                $logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);
                $takenLogins = array_map('mb_strtolower', $logins);
    
                if (in_array(mb_strtolower($login), $takenLogins, true)) {
                    $message = 'login występuje w bazie danych, konto nie zostało dodane';
                } elseif ($password !== $password2) {
                    $message = 'hasła nie są takie same, konto nie zostało dodane';
                } else {
                    $hash = sha1($password);
                    $statement = $pdo->prepare('INSERT INTO uzytkownicy (login, haslo) VALUES (:login, :haslo);');
                    $statement->execute(['login' => $login, 'haslo' => $hash]);
                    $accountAdded = true;
                }
            } catch (PDOException $exception) {
                $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
            } finally {
                $statement = null;
                $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
            }
    
            if ($accountAdded) {
                $_SESSION['message'] = 'Konto zostało dodane';
                header('Location: logowanie.php');
                exit;
            }
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <main>
            <section id="left">
                <img src="obraz.jpg" alt="foksterier">
            </section>
            <section id="right-top">
                <h2>Zapisz się</h2>
                <form method="post">
                    <div>
                        <label for="login">login: </label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div>
                        <label for="password">hasło: </label>
                        <input type="password" id="password" name="password" autocomplete="new-password">
                    </div>
                    <div>
                        <label for="password2">powtórz hasło: </label>
                        <input type="password" id="password2" name="password2" autocomplete="new-password">
                    </div>
                    <button type="submit">Zapisz</button>
                </form>
    <?php if ($message !== null): ?>
                <p><?= e($message) ?></p>
    <?php endif; ?>
            </section>
            <section id="right-bottom">
                <h2>Zapraszamy wszystkich</h2>
                <ol>
                    <li>właścicieli psów</li>
                    <li>weterynarzy</li>
                    <li>tych, co chcą kupić psa</li>
                    <li>tych, co lubią psy</li>
                </ol>
                <a href="regulamin.html">Przeczytaj regulamin forum</a>
            </section>
        </main>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  46. Etap 5: Porównanie z wersją podstawową

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

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

    ok. 3 min

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

    wersja podstawowawersja nowoczesna
    układfloat: left trzech sekcji, clear stopki<main> z siatką i nazwanymi obszarami
    szerokości 40% i 60%width na blokachgrid-template-columns
    kolorywpisane w każdej regulezmienne --brown, --tan
    formularznapis, pole, <br>label for, wiersze w <div>, maxlength, autocomplete
    bibliotekamysqli (tabela 1 arkusza)PDO z wyjątkami
    gdzie jest skryptw całości pod formularzemna górze pliku, pod formularzem tylko wypisanie
    sprawdzenie loginupętla while po zapytaniu 3, == rozróżnia wielkość literzapytanie 3, fetchAll, małe litery
    dane z formularza$_POST wprost, empty()?? '', trim, === '', limit 50 znaków
    INSERTdane wklejone w treść zapytaniaparametry :login, :haslo
    zamknięcie bazymysqli_close na końcu$pdo = null w finally
    po wysłaniupuste pola, F5 wysyła formularz ponownielogin wraca do pola, po dodaniu konta przekierowanie

    W prawdziwym serwisie sprawdzenie loginu zrobiłaby sama baza: indeks UNIQUE na kolumnie login odrzuca drugie konto o tym samym loginie nawet wtedy, gdy dwie osoby kliknęłyby „Zapisz” w tej samej chwili. Arkusz każe jednak użyć zapytania 3 i nie przewiduje zmian w strukturze tabeli. Do haseł zamiast sha1 służy password_hash, a dane logowania do bazy trzyma się w osobnym pliku - tu klucz sprawdza je w kodzie strony.

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie gubi loginu po błędzie, nie wywraca się na apostrofie i nie zakłada drugiego konta po odświeżeniu - a klucz przyjmuje obie.

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

  47. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz w folderze z numerem PESEL lub w przypadku jego braku w folderze z innym numerem, którym został podpisany arkusz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowano poprawność działania witryny. Nagraj płytę z rezultatami pracy.

    W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była strona. W folderze z numerem zdającego: egzamin.png, kw1.jpg - kw4.jpg, kwerendy.txt, logowanie.php, obraz.jpg, przeglądarka.txt, regulamin.html i styl4.css. 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 45.

Podgląd

To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.

Autor rozwiązania: . Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.

Potwierdzenie

Informacja