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

Rozwiązanie krok po kroku: Rejestracja na forum miłośników psów (Wersja podstawowa)

Strona rejestracji na forum miłośników psów: baza psy, w której zapytania tworzą tabelę uzytkownicy (CREATE TABLE z kluczem AUTO_INCREMENT), dodają pierwszego użytkownika, a po imporcie pliku wybierają loginy i użytkowników z hasłem zaczynającym się od cyfry 4 (LIKE). Do tego zdjęcie foksteriera odbite poziomo i przeskalowane do wysokości 500 px, strona logowanie.php z formularzem zapisu (pole loginu i dwa pola hasła), listą numerowaną i odnośnikiem do regulamin.html, style w styl4.css z pseudoelementem ::before oraz skrypt PHP, który sprawdza dane z formularza, szyfruje hasło funkcją sha1 i dodaje konto do bazy.

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, a ten sam arkusz z tym samym kluczem CKE powtórzyła w czerwcu 2025 i w czerwcu 2026. Kroki idą zdanie po zdaniu za treścią arkusza - przy każdym jest polecenie z arkusza, a kod pokazuje dokładnie te linie, które je spełniają. Skrypt jest najpierw napisany najprościej, funkcjami mysqli z tabeli 1 arkusza, a na końcu sprawdzony na prawdziwym wstrzyknięciu SQL i zabezpieczony.

To jest wersja podstawowa. W zakładce „Nowoczesny kod (grid, PDO)” jest drugi sposób rozwiązania tego samego arkusza: siatka CSS z nazwanymi obszarami, etykiety <label>, PDO z zapytaniami przygotowanymi na górze pliku, sprawdzanie danych przed zapisem i przekierowanie po dodaniu konta - obie wersje są zgodne z kluczem.

Baza i zdjęcie pochodzą z oryginalnego archiwum CKE pliki4 (załącznik do pobrania na stronie arkusza, hasło w treści zadania; arkusz nazywa je pliki4.7z, zawartość jest ta sama). Zapytania i zrzuty są policzone na MariaDB 10.4 - tej samej, która jest w XAMPP. 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 funkcje działają w obu wersjach tak samo).

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 - można stracić 10 kryteriów i nadal zdać. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.

  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.

    Na pulpicie tworzysz folder nazwany numerem PESEL - w tym rozwiązaniu w jego miejscu stoi imię i nazwisko autora, Bartosz Bryniarski. Archiwum pliki4.7z otwierasz programem 7-Zip i rozpakowujesz do tego folderu, podając hasło !Psy!Psy! (wielkość liter i wykrzykniki mają znaczenie). W środku są dwa pliki: uzytkownicy.sql (dane do importu) i obraz.jpg (zdjęcie psa).

    Do tego folderu trafia wszystko, co powstanie: zrzuty, kwerendy.txt, pliki strony i stylów. Na stronie arkusza w serwisie archiwum jest w wersji pliki4.zip z pierwszego wydania zadania - zawartość i hasło są te same.

    Typowe błędy

    Pliki zapisane poza folderem zdającego (np. w htdocs bez kopii w folderze) nie trafią na płytę - a ocenia się tylko to, co jest na płycie.

  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

    W XAMPP Control Panel klikasz Start przy Apache i MySQL (nazwy modułów robią się zielone), potem w przeglądarce otwierasz http://localhost/phpmyadmin. Nowa w lewym panelu, nazwa psy, porównywanie np. utf8mb4_polish_ci (arkusz go nie narzuca; tabela utworzona za chwilę dziedziczy je po bazie), Utwórz.

    Baza jest pusta - tabelę utworzy zapytanie 1. Uwaga na kolejność w tym arkuszu: najpierw zapytania 1 i 2, dopiero potem import, bo plik uzytkownicy.sql zawiera same dane, bez tabeli.

    Typowe błędy

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

  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

    Zaznacz bazę psy w lewym panelu i otwórz kartę SQL. Zapytanie linia po linii:

    • CREATE TABLE uzytkownicy ( - nowa tabela; nazwa bez polskiej litery, tak jak w arkuszu,
    • id INT UNSIGNED NOT NULL AUTO_INCREMENT, - INT to typ całkowity, UNSIGNED - bez znaku, czyli tylko liczby nieujemne („dodatni”), NOT NULL - pole nie może być puste, AUTO_INCREMENT - baza sama nada kolejny numer,
    • login VARCHAR(50), i haslo VARCHAR(50), - napisy o zmiennej długości, najwyżej 50 znaków,
    • PRIMARY KEY (id) - klucz główny zapisany jawnie, osobną klauzulą.

    phpMyAdmin odpowiada „MySQL zwrócił pusty wynik” - CREATE TABLE niczego nie wybiera, więc to znak sukcesu. Zapytanie kopiujesz do pliku kwerendy.txt, a cały ekran zapisujesz jako kw1.jpg (Print Screen, wklejenie do edytora grafiki, eksport do JPEG).

    Długość 50 znaków ma znaczenie dla skryptu: funkcja sha1 zwraca 40 znaków, więc zaszyfrowane hasło zmieści się w kolumnie haslo.

    Typowe błędy

    AUTO_INCREMENT działa tylko na kolumnie, która jest kluczem (bez PRIMARY KEY baza zgłosi błąd #1075). Zrzuty kwerend mają być w JPEG (kw1.jpg), a zrzut importu w PNG - zły format to strata punktu.

    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)

    • INSERT INTO uzytkownicy (login, haslo) - do której tabeli i do których kolumn; id pomijasz, bo nada go AUTO_INCREMENT,
    • VALUES ('Grzegorz', 'g') - wartości w tej samej kolejności co kolumny, napisy w apostrofach.

    „Bez szyfrowania” znaczy, że w kolumnie haslo stoi dosłownie litera g. Komunikat „1 wiersz wstawiony”, identyfikator nowego wiersza 1 - zrzut kw2.jpg (klucz przyjmuje też zrzut tabeli z Grzegorzem). To zapytanie jest wzorem dla skryptu: w miejsce „Grzegorz” i „g” trafią login i zaszyfrowane hasło z formularza.

    kwerendy.txt Tekst zmienione: 3 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ń

    Plik uzytkownicy.sql zawiera tylko polecenie INSERT INTO uzytkownicy (login, haslo) VALUES ... z jedenastoma użytkownikami - bez tworzenia tabeli. Dlatego import udaje się wyłącznie wtedy, gdy tabela już istnieje (klucz R.1.1 mówi wprost: kryterium jest uznawane tylko, gdy przed importem utworzono tabelę).

    Zaznacz bazę psy, karta Import, Wybierz plik → uzytkownicy.sql, Wykonaj. Potem kliknij tabelę uzytkownicy (karta Przeglądaj) i zrób zrzut całego ekranu: egzamin.png. Tabela ma 12 wierszy: Grzegorz z id 1 i hasłem g, a pod nim importowani użytkownicy z id 2-12 - ich hasła to już skróty SHA1, po 40 znaków szesnastkowych.

    Typowe błędy

    Ten zrzut ma nazwę egzamin i format PNG (nie import, nie JPEG). Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany (uwaga w kluczu przy R.1).

    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

    SELECT login FROM uzytkownicy - jedna kolumna, bez warunku. „Jedynie” wyklucza SELECT *. Wynik: 12 loginów, od Grzegorza do Eweliny (klucz wymaga co najmniej 11 widocznych rekordów - to jedenaście z importu). Zrzut: kw3.jpg.

    Z tego zapytania skorzysta skrypt: arkusz każe nim sprawdzić, czy login z formularza jest już w bazie.

    kwerendy.txt Tekst zmienione: 3 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

    • SELECT id, login - dwie kolumny z arkusza,
    • FROM uzytkownicy,
    • WHERE haslo LIKE '4%' - operator LIKE porównuje z wzorcem: 4 na początku, a % oznacza dowolny ciąg znaków (także pusty) po niej.

    Wynik: 9 Heniek i 10 Ola - dokładnie te loginy wymienia klucz. Ich hasła (skróty SHA1) zaczynają się od 425f… i 4ae9…. Zrzut: kw4.jpg. Plik kwerendy.txt ma komplet czterech zapytań.

    Typowe błędy

    LIKE '%4%' znajdzie hasła z czwórką w dowolnym miejscu (prawie wszystkie), a haslo = '4' - tylko hasło złożone z samej cyfry 4. Wzorzec „zaczyna się od” to '4%'.

    kwerendy.txt Tekst zmienione: 3 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;
    
    -- 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

    Otwórz obraz.jpg w edytorze grafiki rastrowej (np. GIMP). Odbicie poziome to lustrzane odwrócenie lewej i prawej strony: w GIMP-ie Obraz → Przekształć → Odbij poziomo. Pies na oryginale patrzy w lewo, po odbiciu - w prawo, jak na obrazie 1 w arkuszu.

    Typowe błędy

    Odbij pionowo postawi psa do góry nogami - to inna operacja. Klucz sprawdza odbicie porównaniem z obrazem 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ą

    Oryginał ma 414 × 640 px. Obraz → Skaluj obraz, łańcuch między szerokością a wysokością zamknięty (to jest „zachowanie proporcji”), wysokość 500 px - szerokość przelicza się sama: 414 × 500 / 640 = 323,4, czyli 323 px (klucz: 323 px ±5). Zapis pod tą samą nazwą: Plik → Nadpisz obraz.jpg (zwykłe Zapisz w GIMP-ie zapisuje format .xcf).

    Typowe błędy

    Wpisanie samej wysokości przy rozpiętym łańcuchu daje obraz 414 × 500 - spłaszczony pies i brak punktu.

    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.

    W edytorze tworzysz plik regulamin.html z jednym słowem: regulamin. Arkusz mówi „jedynie napis”, więc bez szkieletu HTML - przeglądarka i tak wyświetli tekst. Plik jest tylko celem odnośnika z logowanie.php (R.3.2).

    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

    Plik logowanie.php w folderze zdającego, obok obraz.jpg. Rozszerzenie .php, bo dojdzie skrypt - na razie to czysty HTML. <!DOCTYPE html> oznacza HTML5, lang="pl" mówi przeglądarce i czytnikom ekranu, że treść jest po polsku. Nazwę pliku sprawdza klucz (R.2.2) - logowanie.html albo index.php to strata punktu.

    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 pierwsza rzecz w <head>. Bez tej linii „psów”, „się” i „hasło” mogą wyświetlić się jako krzaki. Plik trzeba też zapisać w UTF-8 (w Notepad++ menu Kodowanie).

    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> w <head> - napis na karcie przeglądarki. Uwaga na dwa podobne teksty: tytuł to „Forum o psach”, a nagłówek banera - „Forum wielbicieli psów”.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <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"> w <head>. Nazwa ma w sobie cyfrę: styl4.css, nie styl.css. Plik stylów powstanie po HTML-u.

    Typowe błędy

    Zła nazwa w href to brak stylów - a klucz uznaje R.3.5 tylko przy działającym połączeniu, w którym wszystkie selektory trafiają w bloki (kropka zamiast # też odbiera punkt).

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <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

    Pięć bloków w kolejności, w jakiej ułoży je CSS: baner <header>, blok lewy i dwa prawe jako <section>, stopka <footer>. Bloki różnią się wymiarami, więc sekcje dostają identyfikatory: left, right-top i right-bottom (nazwy po angielsku - arkusz ich nie narzuca). Na razie bloki są puste i stoją jeden pod drugim; obok siebie ustawi je CSS.

    logowanie.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
        </header>
        <section id="left">
        </section>
        <section id="right-top">
        </section>
        <section id="right-bottom">
        </section>
        <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 <header> - jedyny nagłówek pierwszego stopnia na stronie.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
        </section>
        <section id="right-top">
        </section>
        <section id="right-bottom">
        </section>
        <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”

    <img> z atrybutami src (nazwa pliku z folderu zdającego) i alt - tekst, który czytnik ekranu przeczyta zamiast zdjęcia i który pokaże się, gdy plik się nie wczyta. Znacznik <img> w HTML5 nie ma znacznika zamykającego ani ukośnika na końcu.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
        </section>
        <section id="right-bottom">
        </section>
        <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ę”

    Drugiego stopnia to <h2>. Klucz R.2.6 sprawdza go razem z <h1> banera i drugim <h2> - punkt przyjdzie po kroku z „Zapraszamy wszystkich”.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
        </section>
        <section id="right-bottom">
        </section>
        <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. 1 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 atrybutu action formularz wysyła dane do tego samego adresu, z którego został wczytany, czyli do logowanie.php (klucz R.3.3 przyjmuje to wprost). Metoda POST przesyła dane w treści żądania, a nie w adresie strony - hasło nie pojawi się w pasku adresu ani w historii przeglądarki. W PHP odczytasz je z tablicy $_POST.

    Typowe błędy

    method="get" dopisze hasło do adresu (logowanie.php?login=Ewa&password=...) - i skrypt czytający $_POST niczego nie dostanie.

    logowanie.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <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>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  20. Krok 20: Napis „login: ” i pole edycyjne

    ok. 1 min
    Polecenie z arkusza

    Napis „login: ”, następnie pole edycyjne

    Napis wprost w formularzu, a za nim <input type="text">. Atrybut name="login" to klucz, pod którym wartość trafi do PHP - $_POST['login']. <br> przenosi kolejne pole do nowej linii, jak na obrazie 1.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
            </form>
        </section>
        <section id="right-bottom">
        </section>
        <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

    „Niejawne wpisanie” to type="password" - przeglądarka pokazuje kropki zamiast znaków. Nazwa pola: password.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
            </form>
        </section>
        <section id="right-bottom">
        </section>
        <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 type="password", ale z inną nazwą - password2. Skrypt porówna obie wartości, więc każda musi przyjść pod własnym kluczem.

    Typowe błędy

    Dwa pola z tym samym name="password" wyślą do PHP tylko jedną wartość (ostatnią) - skrypt nie miałby czego porównać.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
            </form>
        </section>
        <section id="right-bottom">
        </section>
        <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

    <input type="submit" value="Zapisz"> - przycisk, który wysyła formularz; napis na nim to wartość value. Formularz ma komplet: trzy pola edycyjne i przycisk (R.2.7), z czego dwa pola są typu password, a przycisk wysyła dane (R.3.4). Przycisk jako <input> przyda się też w CSS: jedna reguła dla input obejmie wtedy wszystkie elementy formularza.

    Typowe błędy

    <input type="button"> wygląda tak samo, ale niczego nie wysyła - brak punktu R.3.4 i skrypt nigdy się nie uruchomi.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
        </section>
        <section id="right-bottom">
        </section>
        <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>. Strona ma już nagłówek <h1> w banerze i oba nagłówki <h2> w blokach prawych (R.2.6).

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
        </section>
        <section id="right-bottom">
            <h2>Zapraszamy wszystkich</h2>
        </section>
        <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”

    Lista uporządkowana to <ol> (ordered list), każdy element w <li>. Numery 1-4 dopisuje przeglądarka - w tekście ich nie wpisujesz.

    Typowe błędy

    <ul> to lista wypunktowana (kropki zamiast numerów) - klucz wymaga znaczników <ol> i <li>.

    logowanie.php PHP zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </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>
        <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"> - kliknięcie otwiera plik regulaminu z tego samego folderu (R.3.2). Odnośnik stoi bez paragrafu <p>: arkusz stylów dopisze przed każdym paragrafem słowo „STATUS: ”, a na obrazie 1 przed odnośnikiem go nie ma.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </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>
        <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 w jego miejscu stoi imię i nazwisko autora: Bartosz Bryniarski. Napis stoi w stopce bez paragrafu - z tego samego powodu co odnośnik: w <p> dostałby przedrostek „STATUS: ”, którego na obrazie 1 w stopce nie ma.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </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>
        <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: krój Garamond dla całej strony

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

    Nowy plik styl4.css w folderze zdającego - od teraz każda reguła trafia tylko tutaj, bez atrybutów style w HTML-u. Pierwsza reguła: selektor uniwersalny * z font-family: Garamond.

    Dlaczego *, a nie body: pola formularza i przyciski nie dziedziczą kroju po body, a na obrazie 1 napis „Zapisz” na przycisku jest pisany Garamondem. Klucz R.4.1 przyjmuje oba selektory.

    styl4.css CSS nowy plik
    * {
        font-family: Garamond;
    }
    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

    Selektor znacznika header - na stronie jest jeden baner. SaddleBrown i white to nazwy kolorów CSS (wielkość liter bez znaczenia), text-align: center wyśrodkowuje nagłówek (R.4.6). Nazwy kolorów z arkusza wpisuj dosłownie - przeglądarka zna je bez zamiany na kod szesnastkowy.

    styl4.css CSS zmienione: 6 linii
    * {
        font-family: Garamond;
    }
    header {
        background-color: SaddleBrown;
        color: white;
        text-align: center;
        height: 50px;
    }
    Podgląd strony po tym kroku (logowanie.php)
  30. Krok 30: Blok lewy: tło Tan i wymiary

    ok. 1 min
    Polecenie z arkusza

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

    Selektor identyfikatora #left - z krzyżykiem, bo w HTML-u jest id="left". Wysokość 500 px to dokładnie wysokość zdjęcia po skalowaniu.

    styl4.css CSS zmienione: 5 linii
    * {
        font-family: Garamond;
    }
    header {
        background-color: SaddleBrown;
        color: white;
        text-align: center;
        height: 50px;
    }
    #left {
        background-color: Tan;
        height: 500px;
        width: 40%;
    }
    Podgląd strony po tym kroku (logowanie.php)
  31. Krok 31: Oba bloki prawe: tło Tan i wymiary

    Kryteria: ,

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

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

    ok. 1 min
    Polecenie z arkusza

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

    Dwa selektory rozdzielone przecinkiem dostają jedną wspólną regułę. Szerokości 40% + 60% = 100%, a wysokości 250 + 250 = 500 px - dwa bloki prawe razem są tak wysokie jak lewy, więc obok niego zmieszczą się jeden pod drugim. Wszystkie wysokości (R.4.4) i szerokości (R.4.5) bloków są ustawione.

    Typowe błędy

    Bloki o sumie szerokości 100% nie mogą mieć marginesów, padding ani obramowań - każdy piksel więcej zepchnie blok prawy pod lewy.

    styl4.css CSS zmienione: 5 linii
    * {
        font-family: Garamond;
    }
    header {
        background-color: SaddleBrown;
        color: white;
        text-align: center;
        height: 50px;
    }
    #left {
        background-color: Tan;
        height: 500px;
        width: 40%;
    }
    #right-top, #right-bottom {
        background-color: Tan;
        height: 250px;
        width: 60%;
    }
    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 brąz i biały tekst co w banerze. 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: 4 linie
    * {
        font-family: Garamond;
    }
    header {
        background-color: SaddleBrown;
        color: white;
        text-align: center;
        height: 50px;
    }
    #left {
        background-color: Tan;
        height: 500px;
        width: 40%;
    }
    #right-top, #right-bottom {
        background-color: Tan;
        height: 250px;
        width: 60%;
    }
    footer {
        background-color: SaddleBrown;
        color: white;
    }
    Podgląd strony po tym kroku (logowanie.php)
  33. Krok 33: Układ bloków zgodny z obrazem 1

    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)

    ok. 3 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

    Wszystkie trzy sekcje dostają float: left. Blok lewy (40%) staje przy lewej krawędzi, pierwszy prawy (60%) obok niego. Drugi prawy nie mieści się już w tym rzędzie, więc przeglądarka opuszcza go niżej - do pierwszego miejsca z 60% wolnej szerokości, czyli tuż pod pierwszy prawy (blok lewy ma 500 px, pierwszy prawy tylko 250 px). Stopka z clear: both zaczyna się dopiero pod wszystkimi pływającymi blokami.

    Klucz R.3.1 przyjmuje float z clear, flex albo grid. Tutaj float wystarcza bez żadnego dodatkowego pojemnika - wersja nowoczesna pokazuje ten sam układ na siatce.

    Typowe błędy

    Bez clear: both stopka wsunie się pod pływające bloki i jej brązowy pasek schowa się za zdjęciem.

    styl4.css CSS zmienione: 6 linii
    * {
        font-family: Garamond;
    }
    header {
        background-color: SaddleBrown;
        color: white;
        text-align: center;
        height: 50px;
    }
    #left {
        background-color: Tan;
        height: 500px;
        width: 40%;
    }
    #right-top, #right-bottom {
        background-color: Tan;
        height: 250px;
        width: 60%;
    }
    footer {
        background-color: SaddleBrown;
        color: white;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    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

    Selektor znacznika form. Skrót padding: 10px 30px - pierwsza wartość dotyczy góry i dołu, druga lewej i prawej strony. Margines wewnętrzny dolicza się do szerokości: formularz zajmuje 30 + 300 + 30 = 360 px. Tło, biały tekst i szerokość dają R.4.8.

    Typowe błędy

    padding: 30px 10px odwraca kierunki (30 px u góry i na dole). Kolejność w skrócie z dwiema wartościami: najpierw pion, potem poziom.

    styl4.css CSS zmienione: 6 linii
    * {
        font-family: Garamond;
    }
    header {
        background-color: SaddleBrown;
        color: white;
        text-align: center;
        height: 50px;
    }
    #left {
        background-color: Tan;
        height: 500px;
        width: 40%;
    }
    #right-top, #right-bottom {
        background-color: Tan;
        height: 250px;
        width: 60%;
    }
    footer {
        background-color: SaddleBrown;
        color: white;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    form {
        background-color: SaddleBrown;
        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

    Selektor input obejmuje trzy pola edycyjne i przycisk (też jest <input>). margin to margines zewnętrzny - odsuwa pola od napisów i od siebie. Razem z padding formularza daje R.4.7.

    styl4.css CSS zmienione: 3 linie
    * {
        font-family: Garamond;
    }
    header {
        background-color: SaddleBrown;
        color: white;
        text-align: center;
        height: 50px;
    }
    #left {
        background-color: Tan;
        height: 500px;
        width: 40%;
    }
    #right-top, #right-bottom {
        background-color: Tan;
        height: 250px;
        width: 60%;
    }
    footer {
        background-color: SaddleBrown;
        color: white;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    form {
        background-color: SaddleBrown;
        color: white;
        padding: 10px 30px;
        width: 300px;
    }
    input {
        margin: 5px;
    }
    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. 2 min
    Polecenie z arkusza

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

    Pseudoelement ::before wstawia treść z właściwości content na początku każdego <p> - tekstu nie ma w HTML-u, dokłada go CSS. Na razie na stronie nie ma żadnego paragrafu, więc nic się nie zmienia; paragraf z komunikatem wypisze skrypt, np. „STATUS: login występuje w bazie danych, konto nie zostało dodane” z obrazu 1. Dlatego odnośnik i stopka stoją bez <p>. Arkusz stylów jest kompletny (R.4.9).

    Typowe błędy

    Bez content pseudoelement się nie pojawia. Spację po dwukropku wpisz wewnątrz cudzysłowu - inaczej „STATUS:” sklei się z komunikatem.

    styl4.css CSS zmienione: 3 linie
    * {
        font-family: Garamond;
    }
    header {
        background-color: SaddleBrown;
        color: white;
        text-align: center;
        height: 50px;
    }
    #left {
        background-color: Tan;
        height: 500px;
        width: 40%;
    }
    #right-top, #right-bottom {
        background-color: Tan;
        height: 250px;
        width: 60%;
    }
    footer {
        background-color: SaddleBrown;
        color: white;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    form {
        background-color: SaddleBrown;
        color: white;
        padding: 10px 30px;
        width: 300px;
    }
    input {
        margin: 5px;
    }
    p::before {
        content: "STATUS: ";
    }
    Podgląd strony po tym kroku (logowanie.php)
  37. Etap 4: Skrypt PHP

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

    Krok 37: Miejsce skryptu i połączenie z bazą psy

    Kryteria:

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

    ok. 3 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

    „Efekt działania skryptu” ma być w pierwszym bloku prawym, pod formularzem (obraz 1) - tam więc piszesz blok PHP, między <?php a ?>. Serwer wykona go, zanim strona trafi do przeglądarki.

    Pierwsza instrukcja: mysqli_connect z tabeli 1 arkusza - kolejno serwer localhost, użytkownik root, puste hasło '' i nazwa bazy psy. Połączenie trafia do zmiennej $connection, z której skorzystają zapytania (R.5.1).

    Od tego kroku podgląd to wynik działania strony po stronie serwera (prawdziwe uruchomienie w PHP 8.2) - na razie wygląda jak wcześniej. Stronę sprawdzasz przez http://localhost/…, a nie otwierając plik z dysku: przeglądarka sama nie wykona PHP.

    logowanie.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'psy');
    ?>
        </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>
        <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. 3 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

    • isset($_POST['login']) - prawdziwe dopiero po kliknięciu „Zapisz”. Przy pierwszym wejściu na stronę formularz nie był wysłany i skrypt nic nie wypisuje,
    • trzy wartości z $_POST do zmiennych o czytelnych nazwach - klucze to atrybuty name pól,
    • empty() jest prawdziwe dla pustego napisu; trzy warunki łączy || („lub”) - wystarczy jedno puste pole,
    • komunikat w paragrafie <p> (R.5.10), więc CSS dopisze przed nim „STATUS: ”.

    Podgląd: formularz wysłany bez loginu - „STATUS: wypełnij wszystkie pola” (R.5.4).

    Typowe błędy

    && zamiast || wyświetli komunikat dopiero wtedy, gdy puste są wszystkie trzy pola. Samo isset bez empty nie wystarczy: po kliknięciu przycisku wszystkie trzy klucze są w $_POST, także puste.

    logowanie.php PHP zmienione: 8 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'psy');
    if (isset($_POST['login'])) {
        $login = $_POST['login'];
        $password = $_POST['password'];
        $password2 = $_POST['password2'];
        if (empty($login) || empty($password) || empty($password2)) {
            echo '<p>wypełnij wszystkie pola</p>';
        }
    }
    ?>
        </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>
        <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

    Gałąź else - wszystkie pola są wypełnione. Arkusz każe sprawdzić login zapytaniem 3, więc skrypt pobiera wszystkie loginy i przegląda je po kolei:

    • mysqli_query($connection, 'SELECT login FROM uzytkownicy;') - zapytanie 3 z kwerendy.txt (R.5.3), wynik w $result,
    • $loginExists = false; - na początku zakładasz, że loginu nie ma,
    • while ($row = mysqli_fetch_row($result)) - kolejne wiersze jako tablice; $row[0] to jedyna kolumna, login. Gdy wierszy zabraknie, funkcja zwraca false i pętla się kończy,
    • gdy któryś login jest równy wpisanemu, zmienna dostaje true, a po pętli skrypt wypisuje komunikat (R.5.5).

    Podgląd: dane z obrazu 1 - login Ewa (jest w bazie z importu) i dwa różne hasła. Wynik: „STATUS: login występuje w bazie danych, konto nie zostało dodane”, jak na obrazie 1. Login jest sprawdzany przed hasłami, dlatego pojawia się ten komunikat, a nie ten o różnych hasłach. Pola po wysłaniu są puste - arkusz nie wymaga ich ponownego wypełnienia (obraz 1 pokazuje stronę w trakcie wpisywania kolejnych danych).

    Typowe błędy

    Porównanie == w PHP rozróżnia wielkość liter: „ewa” przejdzie sprawdzenie i do bazy trafi drugi użytkownik o prawie tym samym loginie. Arkusz tego nie wymaga; wersja nowoczesna porównuje loginy po zamianie na małe litery (mb_strtolower).

    logowanie.php PHP zmienione: 11 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'psy');
    if (isset($_POST['login'])) {
        $login = $_POST['login'];
        $password = $_POST['password'];
        $password2 = $_POST['password2'];
        if (empty($login) || empty($password) || empty($password2)) {
            echo '<p>wypełnij wszystkie pola</p>';
        } else {
            $result = mysqli_query($connection, 'SELECT login FROM uzytkownicy;');
            $loginExists = false;
            while ($row = mysqli_fetch_row($result)) {
                if ($row[0] == $login) {
                    $loginExists = true;
                }
            }
            if ($loginExists) {
                echo '<p>login występuje w bazie danych, konto nie zostało dodane</p>';
            }
        }
    }
    ?>
        </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>
        <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ę

    Gałąź elseif sprawdza hasła dopiero wtedy, gdy loginu nie ma w bazie. Operator != - „różne od”. Podgląd: nowy login Kasia, hasła azor i azor1 - „STATUS: hasła nie są takie same, konto nie zostało dodane” (R.5.6).

    logowanie.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'psy');
    if (isset($_POST['login'])) {
        $login = $_POST['login'];
        $password = $_POST['password'];
        $password2 = $_POST['password2'];
        if (empty($login) || empty($password) || empty($password2)) {
            echo '<p>wypełnij wszystkie pola</p>';
        } else {
            $result = mysqli_query($connection, 'SELECT login FROM uzytkownicy;');
            $loginExists = false;
            while ($row = mysqli_fetch_row($result)) {
                if ($row[0] == $login) {
                    $loginExists = true;
                }
            }
            if ($loginExists) {
                echo '<p>login występuje w bazie danych, konto nie zostało dodane</p>';
            } elseif ($password != $password2) {
                echo '<p>hasła nie są takie same, konto nie zostało dodane</p>';
            }
        }
    }
    ?>
        </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>
        <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

    Ostatnia gałąź else - login wolny, hasła zgodne. US Secure Hash Algorithm 1 to SHA1, a w PHP funkcja sha1() - jej opis jest na ostatniej stronie arkusza. Zwraca 40 znaków szesnastkowych: sha1('azor') daje 45a54d145a451e6624729ab8ec8051451c5fa718. Ten sam napis daje zawsze ten sam skrót, ale ze skrótu nie da się odczytać hasła - tak samo zapisane są hasła z importu (R.5.8).

    Podgląd: login Kasia, oba hasła azor - skrót jest policzony, ale strona jeszcze nic nie mówi i nic nie zapisuje.

    logowanie.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'psy');
    if (isset($_POST['login'])) {
        $login = $_POST['login'];
        $password = $_POST['password'];
        $password2 = $_POST['password2'];
        if (empty($login) || empty($password) || empty($password2)) {
            echo '<p>wypełnij wszystkie pola</p>';
        } else {
            $result = mysqli_query($connection, 'SELECT login FROM uzytkownicy;');
            $loginExists = false;
            while ($row = mysqli_fetch_row($result)) {
                if ($row[0] == $login) {
                    $loginExists = true;
                }
            }
            if ($loginExists) {
                echo '<p>login występuje w bazie danych, konto nie zostało dodane</p>';
            } elseif ($password != $password2) {
                echo '<p>hasła nie są takie same, konto nie zostało dodane</p>';
            } else {
                $hash = sha1($password);
            }
        }
    }
    ?>
        </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>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  42. Krok 42: Zapytanie 2 z loginem i zaszyfrowanym hasłem

    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 z kwerendy.txt z dwiema zmianami: w miejsce 'Grzegorz' stoi '$login', a w miejsce 'g' - '$hash', czyli skrót, nie hasło z formularza. Całe zapytanie jest w cudzysłowie "…", więc PHP podstawia wartości zmiennych; apostrofy wewnątrz są ogranicznikami napisów w SQL.

    Sprawdzenie, jak każe klucz (R.5.7): po wysłaniu formularza w tabeli uzytkownicy jest nowy wiersz - 13, Kasia, 45a54d145a451e6624729ab8ec8051451c5fa718.

    Typowe błędy

    Zapytanie w apostrofach 'INSERT ... VALUES ('$login', ...)' - PHP nie podstawi zmiennych, a apostrofy w środku zakończą napis i powstanie błąd składni. Wstawienie $password zamiast $hash zapisze hasło jawnym tekstem.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'psy');
    if (isset($_POST['login'])) {
        $login = $_POST['login'];
        $password = $_POST['password'];
        $password2 = $_POST['password2'];
        if (empty($login) || empty($password) || empty($password2)) {
            echo '<p>wypełnij wszystkie pola</p>';
        } else {
            $result = mysqli_query($connection, 'SELECT login FROM uzytkownicy;');
            $loginExists = false;
            while ($row = mysqli_fetch_row($result)) {
                if ($row[0] == $login) {
                    $loginExists = true;
                }
            }
            if ($loginExists) {
                echo '<p>login występuje w bazie danych, konto nie zostało dodane</p>';
            } elseif ($password != $password2) {
                echo '<p>hasła nie są takie same, konto nie zostało dodane</p>';
            } else {
                $hash = sha1($password);
                mysqli_query($connection, "INSERT INTO uzytkownicy (login, haslo) VALUES ('$login', '$hash');");
            }
        }
    }
    ?>
        </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>
        <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”

    Po zapytaniu echo z paragrafem. Podgląd: konto Kasi jest w bazie, a strona mówi „STATUS: Konto zostało dodane” (R.5.9). Drugie wysłanie tych samych danych da już komunikat o zajętym loginie.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'psy');
    if (isset($_POST['login'])) {
        $login = $_POST['login'];
        $password = $_POST['password'];
        $password2 = $_POST['password2'];
        if (empty($login) || empty($password) || empty($password2)) {
            echo '<p>wypełnij wszystkie pola</p>';
        } else {
            $result = mysqli_query($connection, 'SELECT login FROM uzytkownicy;');
            $loginExists = false;
            while ($row = mysqli_fetch_row($result)) {
                if ($row[0] == $login) {
                    $loginExists = true;
                }
            }
            if ($loginExists) {
                echo '<p>login występuje w bazie danych, konto nie zostało dodane</p>';
            } elseif ($password != $password2) {
                echo '<p>hasła nie są takie same, konto nie zostało dodane</p>';
            } else {
                $hash = sha1($password);
                mysqli_query($connection, "INSERT INTO uzytkownicy (login, haslo) VALUES ('$login', '$hash');");
                echo '<p>Konto zostało dodane</p>';
            }
        }
    }
    ?>
        </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>
        <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. 1 min
    Polecenie z arkusza

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

    mysqli_close($connection) na końcu bloku PHP, poza warunkami - wykona się zawsze, także przy pierwszym wejściu na stronę i przy każdym komunikacie. To ostatnia operacja na bazie (R.5.2). Strona spełnia wszystkie kryteria arkusza; podgląd wraca do stanu z obrazu 1. Dalej trzy kroki o zabezpieczeniach - klucz ich nie wymaga.

    Typowe błędy

    mysqli_close przed zapytaniami (np. zaraz po połączeniu) sprawia, że mysqli_query dostaje zamknięte połączenie i PHP kończy się błędem.

    logowanie.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'psy');
    if (isset($_POST['login'])) {
        $login = $_POST['login'];
        $password = $_POST['password'];
        $password2 = $_POST['password2'];
        if (empty($login) || empty($password) || empty($password2)) {
            echo '<p>wypełnij wszystkie pola</p>';
        } else {
            $result = mysqli_query($connection, 'SELECT login FROM uzytkownicy;');
            $loginExists = false;
            while ($row = mysqli_fetch_row($result)) {
                if ($row[0] == $login) {
                    $loginExists = true;
                }
            }
            if ($loginExists) {
                echo '<p>login występuje w bazie danych, konto nie zostało dodane</p>';
            } elseif ($password != $password2) {
                echo '<p>hasła nie są takie same, konto nie zostało dodane</p>';
            } else {
                $hash = sha1($password);
                mysqli_query($connection, "INSERT INTO uzytkownicy (login, haslo) VALUES ('$login', '$hash');");
                echo '<p>Konto zostało dodane</p>';
            }
        }
    }
    mysqli_close($connection);
    ?>
        </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>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  45. Etap 5: Dobre praktyki (poza kluczem)

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

    Krok 45: Zabezpieczenie: zapytanie przygotowane dla INSERT

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

    Login z formularza trafia do zapytania wprost - to SQL injection. Sprawdzone na wersji z poprzednich kroków:

    • login Zosia', 'x'), ('Ewa (hasła azor) przechodzi sprawdzenie, bo takiego loginu w bazie nie ma, a zapytanie zamienia się w VALUES ('Zosia', 'x'), ('Ewa', '…') - do bazy trafiają dwa konta: Zosia z dowolnie wybranym „skrótem” x i druga Ewa, mimo że skrypt miał zajętych loginów nie dopuszczać,
    • login z apostrofem, np. O'Neill, rozbija składnię zapytania - PHP 8.1 i nowsze zgłaszają wtedy wyjątek mysqli_sql_exception i zamiast strony uczeń widzi „Fatal error”.

    Lekarstwo to zapytanie przygotowane: mysqli_prepare z dwoma znakami ? w miejscu danych, mysqli_stmt_bind_param z typami 'ss' (dwa napisy) i mysqli_stmt_execute. Baza dostaje treść zapytania i dane osobno, więc dane nigdy nie staną się kodem SQL. Podgląd: ten sam „login-atak” zapisuje się jako jedno konto o dziwnym loginie, a O'Neill zakłada konto bez błędu. Stara linia zostaje w komentarzu.

    Typowe błędy

    Liczba liter w 'ss' musi równać się liczbie znaków ? i liczbie zmiennych - inaczej mysqli_stmt_bind_param zgłosi błąd i konto nie powstanie.

    logowanie.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'psy');
    if (isset($_POST['login'])) {
        $login = $_POST['login'];
        $password = $_POST['password'];
        $password2 = $_POST['password2'];
        if (empty($login) || empty($password) || empty($password2)) {
            echo '<p>wypełnij wszystkie pola</p>';
        } else {
            $result = mysqli_query($connection, 'SELECT login FROM uzytkownicy;');
            $loginExists = false;
            while ($row = mysqli_fetch_row($result)) {
                if ($row[0] == $login) {
                    $loginExists = true;
                }
            }
            if ($loginExists) {
                echo '<p>login występuje w bazie danych, konto nie zostało dodane</p>';
            } elseif ($password != $password2) {
                echo '<p>hasła nie są takie same, konto nie zostało dodane</p>';
            } else {
                $hash = sha1($password);
                // mysqli_query($connection, "INSERT INTO uzytkownicy (login, haslo) VALUES ('$login', '$hash');");
                $statement = mysqli_prepare($connection, 'INSERT INTO uzytkownicy (login, haslo) VALUES (?, ?)');
                mysqli_stmt_bind_param($statement, 'ss', $login, $hash);
                mysqli_stmt_execute($statement);
                echo '<p>Konto zostało dodane</p>';
            }
        }
    }
    mysqli_close($connection);
    ?>
        </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>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  46. Krok 46: Zabezpieczenie: sprawdzenie danych z formularza

    ok. 4 min
    Polecenie z arkusza

    Skrypt obsługuje błędy danych formularza i wyświetla komunikaty w paragrafie (akapicie)

    Trzy poprawki tego, co przychodzi z formularza:

    • trim() zdejmuje spacje z brzegów loginu. Bez tego login z samych spacji przechodzi empty i zakłada konto bez nazwy, a „Ewa ” ze spacją na końcu omija sprawdzenie zajętego loginu,
    • porównanie === '' zamiast empty(): empty('0') jest prawdziwe, więc hasło „0” było odrzucane jako puste pole. Haseł nie przycinasz - spacja w haśle jest pełnoprawnym znakiem,
    • mb_strlen($login) > 50 - kolumna login ma 50 znaków, a XAMPP pracuje w trybie, który za długi tekst po cichu ucina. Sprawdzone: login z 60 znaków przeszedł sprawdzenie (60-znakowego w bazie nie było) i zapisał się obcięty do 50 - mógł więc powtórzyć czyjś istniejący login. Teraz dostaje własny komunikat.

    htmlspecialchars nie jest tu potrzebny: strona nie wypisuje niczego, co wpisał użytkownik - wszystkie komunikaty są stałe. Gdyby komunikat zawierał login (np. „Konto Kasia zostało dodane”), login trzeba przepuścić przez htmlspecialchars przed echo. Podgląd: login z samych spacji - „STATUS: wypełnij wszystkie pola”.

    logowanie.php PHP zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'psy');
    if (isset($_POST['login'])) {
        // $login = $_POST['login'];
        $login = trim($_POST['login']);
        $password = $_POST['password'];
        $password2 = $_POST['password2'];
        // if (empty($login) || empty($password) || empty($password2)) {
        if ($login === '' || $password === '' || $password2 === '') {
            echo '<p>wypełnij wszystkie pola</p>';
        } elseif (mb_strlen($login) > 50) {
            echo '<p>login może mieć najwyżej 50 znaków, konto nie zostało dodane</p>';
        } else {
            $result = mysqli_query($connection, 'SELECT login FROM uzytkownicy;');
            $loginExists = false;
            while ($row = mysqli_fetch_row($result)) {
                if ($row[0] == $login) {
                    $loginExists = true;
                }
            }
            if ($loginExists) {
                echo '<p>login występuje w bazie danych, konto nie zostało dodane</p>';
            } elseif ($password != $password2) {
                echo '<p>hasła nie są takie same, konto nie zostało dodane</p>';
            } else {
                $hash = sha1($password);
                // mysqli_query($connection, "INSERT INTO uzytkownicy (login, haslo) VALUES ('$login', '$hash');");
                $statement = mysqli_prepare($connection, 'INSERT INTO uzytkownicy (login, haslo) VALUES (?, ?)');
                mysqli_stmt_bind_param($statement, 'ss', $login, $hash);
                mysqli_stmt_execute($statement);
                echo '<p>Konto zostało dodane</p>';
            }
        }
    }
    mysqli_close($connection);
    ?>
        </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>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  47. Krok 47: Hasła: dlaczego sha1 zostaje tylko na egzaminie

    ok. 2 min
    Polecenie z arkusza

    Szyfruje hasło algorytmem US Secure Hash Algorithm 1

    Klucz R.5.8 wymaga funkcji sha1, więc w pliku egzaminacyjnym zostaje. W prawdziwym serwisie SHA1 do haseł się nie nadaje: liczy się w ułamku mikrosekundy, więc skróty popularnych haseł da się odgadnąć zgadywaniem na zwykłej karcie graficznej, a te same hasła mają te same skróty (widać to od razu po tabeli). Do haseł służą funkcje celowo wolne i z losową „solą”:

    $hash = password_hash($password, PASSWORD_DEFAULT);   // zapis konta
    if (password_verify($password, $hashZBazy)) { … }     // logowanie
    

    password_hash zwraca 60 znaków (algorytm bcrypt), a docelowo nawet więcej - nie zmieści się w kolumnie haslo VARCHAR(50) z arkusza (tryb XAMPP po cichu by go uciął i konto nie dałoby się zalogować). Dlatego zmiana wymaga też tabeli: ALTER TABLE uzytkownicy MODIFY haslo VARCHAR(255);. Na egzaminie tego nie rób - arkusz określa strukturę tabeli (R.1.3) i funkcję szyfrującą (R.5.8). Plik strony bez zmian.

    logowanie.php PHP zmienione: 0 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl4.css">
    </head>
    <body>
        <header>
            <h1>Forum wielbicieli psów</h1>
        </header>
        <section id="left">
            <img src="obraz.jpg" alt="foksterier">
        </section>
        <section id="right-top">
            <h2>Zapisz się</h2>
            <form method="post">
                login: <input type="text" name="login"><br>
                hasło: <input type="password" name="password"><br>
                powtórz hasło: <input type="password" name="password2"><br>
                <input type="submit" value="Zapisz">
            </form>
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'psy');
    if (isset($_POST['login'])) {
        // $login = $_POST['login'];
        $login = trim($_POST['login']);
        $password = $_POST['password'];
        $password2 = $_POST['password2'];
        // if (empty($login) || empty($password) || empty($password2)) {
        if ($login === '' || $password === '' || $password2 === '') {
            echo '<p>wypełnij wszystkie pola</p>';
        } elseif (mb_strlen($login) > 50) {
            echo '<p>login może mieć najwyżej 50 znaków, konto nie zostało dodane</p>';
        } else {
            $result = mysqli_query($connection, 'SELECT login FROM uzytkownicy;');
            $loginExists = false;
            while ($row = mysqli_fetch_row($result)) {
                if ($row[0] == $login) {
                    $loginExists = true;
                }
            }
            if ($loginExists) {
                echo '<p>login występuje w bazie danych, konto nie zostało dodane</p>';
            } elseif ($password != $password2) {
                echo '<p>hasła nie są takie same, konto nie zostało dodane</p>';
            } else {
                $hash = sha1($password);
                // mysqli_query($connection, "INSERT INTO uzytkownicy (login, haslo) VALUES ('$login', '$hash');");
                $statement = mysqli_prepare($connection, 'INSERT INTO uzytkownicy (login, haslo) VALUES (?, ?)');
                mysqli_stmt_bind_param($statement, 'ss', $login, $hash);
                mysqli_stmt_execute($statement);
                echo '<p>Konto zostało dodane</p>';
            }
        }
    }
    mysqli_close($connection);
    ?>
        </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>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (logowanie.php)
  48. Etap 6: Oddanie pracy

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

    Krok 48: 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 wpisujesz nazwę przeglądarki, w której sprawdzasz stronę przez http://localhost/….

    W folderze z numerem zdającego muszą być: egzamin.png, kw1.jpg, kw2.jpg, kw3.jpg, kw4.jpg, kwerendy.txt, logowanie.php, obraz.jpg, przeglądarka.txt, regulamin.html i styl4.css (plik uzytkownicy.sql z archiwum może zostać). Nagraj płytę, sprawdź jej odczyt w obecności przewodniczącego zespołu nadzorującego i opisz ją numerem zdającego. Pusta lub nieczytelna płyta to zero punktów za wszystkie pięć rezultatów.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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