Serwis używa plików cookies do analizy ruchu i poprawy działania. Szczegóły
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.
Suwak i strzałki obok niego - albo ←→ na klawiaturze - przechodzą krok po kroku; wyjaśnienie, pliki i podgląd zmieniają się razem z krokiem.
Lista „Etap” przenosi od razu do wybranego etapu.
„Rozwiązuję samodzielnie” chowa rozwiązanie kroku - najpierw własna próba, potem odsłonięcie.
„Tryb prezentacji” pokazuje same kroki, bez menu strony - na rzutnik.
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.
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).
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.txtTekstnowy plik
-- Zapytanie 1CREATE TABLE uzytkownicy ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, login VARCHAR(50), haslo VARCHAR(50), PRIMARY KEY (id));
Podgląd strony po tym kroku
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.txtTekstzmienione: 3 linie
-- Zapytanie 1CREATE TABLE uzytkownicy ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, login VARCHAR(50), haslo VARCHAR(50), PRIMARY KEY (id));-- Zapytanie 2INSERT INTO uzytkownicy (login, haslo) VALUES ('Grzegorz', 'g');
Podgląd strony po tym kroku
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
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.txtTekstzmienione: 3 linie
-- Zapytanie 1CREATE TABLE uzytkownicy ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, login VARCHAR(50), haslo VARCHAR(50), PRIMARY KEY (id));-- Zapytanie 2INSERT INTO uzytkownicy (login, haslo) VALUES ('Grzegorz', 'g');-- Zapytanie 3SELECT login FROM uzytkownicy;
Podgląd strony po tym kroku
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.txtTekstzmienione: 3 linie
-- Zapytanie 1CREATE TABLE uzytkownicy ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, login VARCHAR(50), haslo VARCHAR(50), PRIMARY KEY (id));-- Zapytanie 2INSERT INTO uzytkownicy (login, haslo) VALUES ('Grzegorz', 'g');-- Zapytanie 3SELECT login FROM uzytkownicy;-- Zapytanie 4SELECT id, login FROM uzytkownicy WHERE haslo LIKE '4%';
Podgląd strony po tym kroku
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
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
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.htmlHTMLnowy plik
regulamin
Podgląd strony po tym kroku (regulamin.html)
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.
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).
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title></head><body></body></html>
Podgląd strony po tym kroku (logowanie.php)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body></body></html>
Podgląd strony po tym kroku (logowanie.php)
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.
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.
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.
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.
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.
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).
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.
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.
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.cssCSSnowy plik
* {font-family: Garamond;}
Podgląd strony po tym kroku (logowanie.php)
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.phpPHPzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> login: <inputtype="text"name="login"><br> hasło: <inputtype="password"name="password"><br> powtórz hasło: <inputtype="password"name="password2"><br> <inputtype="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> <sectionid="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> <ahref="regulamin.html">Przeczytaj regulamin forum</a> </section> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
Podgląd strony po tym kroku (logowanie.php)
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.phpPHPzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> login: <inputtype="text"name="login"><br> hasło: <inputtype="password"name="password"><br> powtórz hasło: <inputtype="password"name="password2"><br> <inputtype="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> <sectionid="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> <ahref="regulamin.html">Przeczytaj regulamin forum</a> </section> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
Podgląd strony po tym kroku (logowanie.php)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> login: <inputtype="text"name="login"><br> hasło: <inputtype="password"name="password"><br> powtórz hasło: <inputtype="password"name="password2"><br> <inputtype="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> <sectionid="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> <ahref="regulamin.html">Przeczytaj regulamin forum</a> </section> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> login: <inputtype="text"name="login"><br> hasło: <inputtype="password"name="password"><br> powtórz hasło: <inputtype="password"name="password2"><br> <inputtype="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> <sectionid="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> <ahref="regulamin.html">Przeczytaj regulamin forum</a> </section> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
Podgląd strony po tym kroku (logowanie.php)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> login: <inputtype="text"name="login"><br> hasło: <inputtype="password"name="password"><br> powtórz hasło: <inputtype="password"name="password2"><br> <inputtype="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> <sectionid="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> <ahref="regulamin.html">Przeczytaj regulamin forum</a> </section> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
Podgląd strony po tym kroku (logowanie.php)
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.phpPHPzmienione: 4 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> login: <inputtype="text"name="login"><br> hasło: <inputtype="password"name="password"><br> powtórz hasło: <inputtype="password"name="password2"><br> <inputtype="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> <sectionid="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> <ahref="regulamin.html">Przeczytaj regulamin forum</a> </section> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
Podgląd strony po tym kroku (logowanie.php)
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.phpPHPzmienione: 6 linii
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> login: <inputtype="text"name="login"><br> hasło: <inputtype="password"name="password"><br> powtórz hasło: <inputtype="password"name="password2"><br> <inputtype="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> <sectionid="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> <ahref="regulamin.html">Przeczytaj regulamin forum</a> </section> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
Podgląd strony po tym kroku (logowanie.php)
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.phpPHPzmienione: 0 linii
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> login: <inputtype="text"name="login"><br> hasło: <inputtype="password"name="password"><br> powtórz hasło: <inputtype="password"name="password2"><br> <inputtype="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> <sectionid="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> <ahref="regulamin.html">Przeczytaj regulamin forum</a> </section> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
Podgląd strony po tym kroku (logowanie.php)
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.
Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.
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: Bartosz Bryniarski. 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.