Serwis używa plików cookies do analizy ruchu i poprawy działania. Szczegóły
EE.09-04-22.01-SGStyczeń 2022 · 47 kroków · ok. 91 min · 40 kryteriów z klucza
Rozwiązanie krok po kroku: Rejestracja na forum miłośników psów (Nowoczesny kod (flex, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza psy z tabelą uzytkownicy i czterema zapytaniami, odbite i przeskalowane zdjęcie foksteriera oraz strona logowanie.php z formularzem zapisu na forum - i tak samo spełnia każde kryterium klucza. Inna jest droga:
flexbox zamiast float: trzy sekcje stoją w znaczniku <main>, który układa je w kolumny z zawijaniem - blok lewy wypełnia pierwszą kolumnę, bloki prawe drugą, a stopka nie potrzebuje clear,
zmienne CSS dla kolorów z arkusza, etykiety <label for> i wiersze formularza w blokach zamiast <br>,
PHP na PDO na górze pliku logowanie.php, przed HTML-em - w miejscu „efektu działania skryptu” zostaje jedna linijka wypisująca komunikat,
zapytanie przygotowane do INSERT, login bez spacji z brzegów i najwyżej 50 znaków, loginy porównywane bez względu na wielkość liter,
login wraca do pola po wysłaniu formularza (jak na obrazie 1), a po dodaniu konta przekierowanie - odświeżenie strony nie wyśle formularza drugi raz.
To arkusz EE.09 ze stycznia 2022 - kwalifikacji formuły 2017, którą zastąpiła INF.03 (to samo zadanie wracało w kolejnych sesjach). Baza i zdjęcie pochodzą z oryginalnego archiwum CKE pliki4.zip (załącznik na stronie arkusza), zapytania i zrzuty są policzone na MariaDB 10.4, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 (klucz każe sprawdzać w XAMPP 8.0.7 - użyte konstrukcje działają w obu wersjach).
Jak liczą się punkty. Klucz ma 40 kryteriów (R.1.1 - R.5.10), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 30 punktów. Przy każdym kroku jest napisane, które kryteria spełnia; klucz przyjmuje zarówno mysqli, jak i PDO.
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.
Folder na pulpicie nazwany numerem PESEL (w tym rozwiązaniu w jego miejscu stoi Bartosz Bryniarski), do niego archiwum pliki4.zip rozpakowane 7-Zipem z hasłem !Psy!Psy!. W środku: uzytkownicy.sql i obraz.jpg. Do tego folderu trafiają wszystkie rezultaty pracy.
Krok 2: XAMPP i pusta baza psy
ok. 2 min
Polecenie z arkusza
Uruchom usługi MySQL i Apache za pomocą XAMPP Control Panel. Za pomocą narzędzia phpMyAdmin wykonaj podane operacje na bazie danych: Utwórz bazę danych o nazwie psy
Start przy Apache i MySQL, potem http://localhost/phpmyadmin, Nowa, nazwa psy, porównywanie np. utf8mb4_polish_ci. Baza zostaje pusta: tabelę utworzy zapytanie 1, a import przyjdzie dopiero po zapytaniu 2 - plik uzytkownicy.sql zawiera same dane.
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 Utworzono 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) NULL, haslo VARCHAR(50) NULL, PRIMARY KEY(id)); oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest efekt poprawnie wykonanego zapytania
ok. 4 min
Polecenie z arkusza
Zapytanie 1: tworzące tabelę uzytkownicy. Tabela powinna zawierać klucz główny id typu całkowitego dodatniego, jawnie zapisany oraz automatycznie inkrementowany, nie może przyjmować wartości pustych, login typu napisowego maksymalnie 50-znakowego, haslo typu napisowego maksymalnie 50-znakowego
Każde wymaganie arkusza ma w zapytaniu swoje słowo: „całkowity” - INT, „dodatni” - UNSIGNED, „nie może przyjmować wartości pustych” - NOT NULL, „automatycznie inkrementowany” - AUTO_INCREMENT, „jawnie zapisany klucz główny” - osobna klauzula PRIMARY KEY (id), „napis maksymalnie 50-znakowy” - VARCHAR(50). Kolumna na 50 znaków zmieści skrót sha1, który ma 40.
Zapytanie trafia do kwerendy.txt, zrzut całego ekranu z komunikatem „MySQL zwrócił pusty wynik” - do kw1.jpg.
Typowe błędy
Zrzuty zapytań zapisuj w JPEG (kw1.jpg - kw4.jpg), a zrzut importu w PNG (egzamin.png).
kwerendy.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 Utworzono zapytanie 2 dodające do tabeli użytkownika o loginie Grzegorz z hasłem „g” (bez szyfrowania). W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO `uzytkownicy` (`login`, `haslo`) VALUES ('Grzegorz', 'g'); możliwe inne zapisy INSERT INTO oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne jest poprawnie wykonane zapytanie lub tabela z wstawionym rekordem
ok. 2 min
Polecenie z arkusza
Zapytanie 2: dodające do tabeli użytkownika o loginie Grzegorz z hasłem „g” (bez szyfrowania)
Lista kolumn bez id - numer nada AUTO_INCREMENT (Grzegorz dostaje 1), hasło g zapisane wprost. Zrzut: kw2.jpg. To samo zapytanie, z parametrami w miejscu wartości, wykona później skrypt.
kwerendy.txtTekstzmienione: 4 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
ok. 3 min
Polecenie z arkusza
Po wykonaniu zapytań 1 i 2 zaimportuj do tabeli uzytkownicy plik uzytkownicy.sql z rozpakowanego archiwum. Wykonaj zrzut ekranu po imporcie. Na zrzucie powinna być widoczna zawartość tabeli uzytkownicy. Zrzut zapisz w folderze z numerem PESEL lub w przypadku jego braku w folderze z innym numerem, którym został podpisany arkusz, w formacie PNG i nazwij egzamin. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań
Baza psy, karta Import, plik uzytkownicy.sql, Wykonaj. Plik zawiera samo INSERT jedenastu użytkowników, dlatego import działa tylko po zapytaniu 1. Po imporcie karta Przeglądaj tabeli uzytkownicy: 12 wierszy - Grzegorz z hasłem g i użytkownicy z id 2-12, których hasła są już skrótami SHA1. Zrzut całego ekranu z paskiem zadań: egzamin.png.
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: wszystkie loginy
Kryteria:
R.1.5 Utworzono zapytanie 3 wybierające jedynie pole login z tabeli uzytkownicy. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT login FROM uzytkownicy; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne jest jedynie pole login i wypisane wszystkie loginy z tabeli (widocznych jest minimum 11 rekordów)
ok. 2 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie pole login z tabeli uzytkownicy
Jedna kolumna, bez warunku - 12 loginów, od Grzegorza do Eweliny. Zrzut: kw3.jpg. Arkusz każe tym zapytaniem sprawdzać w skrypcie, czy login jest już zajęty.
kwerendy.txtTekstzmienione: 4 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 loginFROM uzytkownicy;
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: hasło zaczyna się od cyfry 4
Kryteria:
R.1.6 Utworzono 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 dwa rekordy: 9 Heniek; 10 Ola
ok. 2 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie pola id oraz login z tabeli uzytkownicy dla użytkowników, których hasło zaczyna się od cyfry 4
LIKE '4%' - pierwszy znak 4, a % zastępuje dowolny ciąg znaków po nim. Wynik: 9 Heniek i 10 Ola, zgodnie z kluczem. Zrzut: kw4.jpg. Każda klauzula w osobnej linii - przy dłuższych zapytaniach łatwiej wtedy znaleźć błąd.
kwerendy.txtTekstzmienione: 5 linii
-- 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 loginFROM uzytkownicy;-- Zapytanie 4SELECT id, loginFROM uzytkownicyWHERE 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
W GIMP-ie Obraz → Przekształć → Odbij poziomo - lewa strona zamienia się z prawą, pies patrzy teraz w prawo (obraz 1).
Podgląd strony po tym kroku
Krok 9: Grafika: wysokość 500 px z zachowaniem proporcji
Kryteria:
R.2.1 Plik obraz.jpg odbito poziomo (jak na obrazie 1 w arkuszu) oraz przeskalowano z zachowaniem proporcji tak, że jego wysokość wynosi dokładnie 500 px, odpowiadająca temu szerokość to 323 px ±5 px
ok. 2 min
Polecenie z arkusza
Obraz ma zostać przeskalowany z zachowaniem proporcji tak, aby jego wysokość wynosiła dokładnie 500 px i zapisany pod tą samą nazwą
Obraz → Skaluj obraz z zamkniętym łańcuchem proporcji, wysokość 500 px - szerokość wychodzi 414 × 500 / 640 ≈ 323 px (klucz: 323 ±5). Zapis: Plik → Nadpisz obraz.jpg.
Podgląd strony po tym kroku
Krok 10: Plik regulamin.html
ok. 1 min
Polecenie z arkusza
Składa się ze stron: logowanie.php, regulamin.html. W pliku o nazwie regulamin.html jest wpisany jedynie napis „regulamin”. Pozostałe wymagania dotyczą jedynie pliku logowanie.php.
Plik z jednym słowem regulamin - arkusz mówi „jedynie napis”, więc bez szkieletu HTML. Służy tylko jako cel odnośnika.
regulamin.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
Szkielet HTML5 w pliku logowanie.php (R.2.2). Rozszerzenie .php, bo na górze tego pliku stanie skrypt; dopóki nie ma w nim PHP, przeglądarka pokaże go jak zwykły HTML.
Tytuł strony widoczny na karcie przeglądarki: „Forum o psach”
<title>Forum o psach</title> (R.2.4). Obok znacznik viewport - telefon nie udaje wtedy szerokiego ekranu, tylko pokazuje stronę w swojej szerokości. Klucz go nie ocenia; w nowej stronie dopisuje się go z nawyku.
logowanie.phpPHPzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title></head><body></body></html>
Podgląd strony po tym kroku (logowanie.php)
Krok 14: Podpięcie arkusza stylów styl4.css
Kryteria:
R.3.5 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl4.css , formatowanie pochodzi jedynie z tego arkusza Uwaga: kryterium to nie jest spełnione również, gdy źle przypisano co najmniej jeden selektor do bloku (np. kropka zamiast #)
ok. 1 min
Polecenie z arkusza
Arkusz stylów w pliku o nazwie styl4.css prawidłowo połączony z kodem strony
<link rel="stylesheet" href="styl4.css"> - nazwa z cyfrą 4, dokładnie jak w arkuszu (R.3.5).
logowanie.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <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
Baner to <header>, stopka <footer>, a trzy bloki treści - znaczniki <section> z identyfikatorami left, right-top i right-bottom (R.2.5). Sekcje stoją we wspólnym <main>: to główna treść strony (czytnik ekranu pozwala do niej przeskoczyć), a jednocześnie pojemnik, w którym flexbox rozmieści trzy bloki. Baner i stopka zostają poza nim - zajmują całą szerokość.
Krok 17: Blok lewy: zdjęcie z tekstem alternatywnym
Kryteria:
R.2.9 W bloku lewym umieszczono obraz o nazwie obraz.jpg z tekstem alternatywnym o treści „foksterier”
ok. 1 min
Polecenie z arkusza
Zawartość bloku lewego: obraz o nazwie obraz.jpg z tekstem alternatywnym o treści „foksterier”
src i alt z arkusza (R.2.9). Zwykle przy zdjęciu dopisuje się też atrybuty width i height (przeglądarka rezerwuje wtedy miejsce, zanim pobierze plik), ale tutaj klucz przy R.3.5 wymaga, żeby całe formatowanie pochodziło z arkusza stylów - atrybuty wymiarów lepiej pominąć, a zdjęcie i tak ma już właściwe wymiary po skalowaniu.
R.3.3 Formularz wysyła dane do tej samej strony metodą post, poprawnie również bez atrybutu action
ok. 2 min
Polecenie z arkusza
Formularz wysyłający dane do tej samej strony metodą post z zawartością zgodną z Obrazem 1
<form method="post"> bez action - dane wracają do logowanie.php (R.3.3). Metoda POST przenosi hasło w treści żądania, a nie w adresie strony.
Pola formularza nie dostaną atrybutu required, choć zwykle by go miały: przeglądarka zablokowałaby wtedy wysłanie niepełnego formularza i komunikat skryptu „wypełnij wszystkie pola” nigdy by się nie pojawił - a egzaminator sprawdza go, wysyłając formularz z pustym polem (R.5.4).
Napis staje się etykietą<label for="login">, związaną z polem o id="login": kliknięcie napisu ustawia kursor w polu, a czytnik ekranu odczyta „login” przy wejściu w pole. Etykieta z polem stoją we własnym <div>, więc każde pole jest w nowej linii bez <br>.
name="login" - klucz w $_POST,
maxlength="50" - tyle znaków mieści kolumna login; dłuższego loginu nie da się wpisać,
autocomplete="username" - menedżer haseł rozpozna pole loginu.
Napis „hasło: ”, następnie pole edycyjne pozwalające na niejawne wpisanie hasła
type="password" - znaki widać jako kropki. autocomplete="new-password" mówi przeglądarce, że to zakładanie konta: zaproponuje nowe, mocne hasło, zamiast wpisywać zapamiętane.
R.2.7 W bloku prawym pierwszym umieszczono formularz z trzema polami edycyjnymi i przyciskiem
R.3.4 Przynajmniej jedno pole edycyjne do wpisywania hasła jest typu password oraz przycisk wysyła dane z formularza
ok. 1 min
Polecenie z arkusza
Przycisk o treści „Zapisz” wysyłający dane z formularza
<button type="submit">Zapisz</button> - napis jest treścią znacznika, więc przycisk może kiedyś dostać np. ikonę obok tekstu. Typ submit wysyła formularz (R.3.4); trzy pola i przycisk dają komplet elementów z obrazu 1 (R.2.7).
Krok 24: Drugi blok prawy: nagłówek „Zapraszamy wszystkich”
Kryteria:
R.2.6 W bloku banera zapisano nagłówek h1: "Forum wielbicieli psów" oraz w blokach prawych zapisano nagłówki h2: "Zapisz się" i "Zapraszamy wszystkich"
ok. 1 min
Polecenie z arkusza
Zawartość drugiego bloku prawego: Nagłówek drugiego stopnia o treści „Zapraszamy wszystkich”
Drugi <h2>; nagłówki <h1> i oba <h2> są gotowe (R.2.6).
R.3.2 Na stronie umieszczono odnośnik o treści „Przeczytaj regulamin forum”, którego kliknięcie powoduje przejście do strony regulamin.html
ok. 1 min
Polecenie z arkusza
Odnośnik do pliku regulamin.html o treści „Przeczytaj regulamin forum”
<a href="regulamin.html"> (R.3.2). Bez paragrafu <p> wokół - CSS dopisze przed każdym paragrafem „STATUS: ”, a przy odnośniku na obrazie 1 tego napisu nie ma.
R.2.10 W stopce umieszczono napis „Stronę wykonał: ”, dalej wstawiony numer PESEL zdającego lub w przypadku jego braku inny numer, którym został podpisany arkusz
ok. 1 min
Polecenie z arkusza
Zawartość stopki: napis o treści „Stronę wykonał: ”, dalej wstawiony numer PESEL zdającego lub w przypadku jego braku inny numer, którym został podpisany arkusz.
Na egzaminie po dwukropku wpisujesz swój numer PESEL; w tym rozwiązaniu stoi tam imię i nazwisko autora, Bartosz Bryniarski (R.2.10). Napis bez <p>, z tego samego powodu co odnośnik.
Kroki 28-36 · po tym etapie masz 30 z 40 kryteriów (pkt)
Krok 28: Plik styl4.css: kolory jako zmienne i krój Garamond
Kryteria:
R.4.1 Ustawiono domyślny krój czcionki dla całej strony na Garamond (dla selektora * lub body lub html lub kontenera całej strony)
ok. 2 min
Polecenie z arkusza
Cechy formatowania CSS: Są zawarte wyłącznie w zewnętrznym arkuszu stylów. Domyślne dla całej strony: krój czcionki Garamond
Na początku pliku zmienne CSS w :root: dwa kolory z arkusza dostają nazwy --brown i --tan, a reguły dalej używają var(--brown). Zmiana odcienia to wtedy jedna linia, a nie szukanie koloru w pięciu regułach.
Krój Garamond na body (R.4.1) - dziedziczą go wszystkie elementy strony poza polami formularza i przyciskami, które mają własny krój systemowy. Dlatego osobna reguła input, button { font-family: inherit; } każe im wziąć krój po rodzicu - napis „Zapisz” jest wtedy pisany Garamondem jak na obrazie 1.
Krok 29: Baner: tło, kolor tekstu, wyśrodkowanie, wysokość
Kryteria:
R.4.6 Ustawiono wyrównanie tekstu do środka dla banera
ok. 2 min
Polecenie z arkusza
Dla banera: kolor tła SaddleBrown, biały kolor czcionki, wyrównanie tekstu do środka, wysokość 50 px
Cztery właściwości z arkusza na header (R.4.6). Druga reguła dotyczy nagłówka w banerze: domyślny margines <h1> (około 21 px nad i pod) nie zmieściłby się w 50 px, więc margin: 0, a line-height: 50px - wysokość linii równa wysokości banera - stawia napis pionowo na środku paska.
Dla bloku lewego: kolor tła Tan, wysokość 500 px, szerokość 40%
Tło, wysokość i szerokość na samym bloku. Klucz z tej sesji ocenia szerokość 40% przy bloku (R.4.5 nie wspomina o kolumnach siatki, jak klucze późniejszych wydań), dlatego width stoi na #left. Procent liczy się od szerokości <main>, czyli całej strony.
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%
ok. 1 min
Polecenie z arkusza
Dla obu bloków prawych: kolor tła Tan, wysokość 250 px, szerokość 60%
Jedna reguła na dwa selektory. Wysokości (R.4.4): 250 + 250 = 500 px, tyle co blok lewy. Szerokości (R.4.5): 40% + 60% = 100% - bloki nie mogą więc mieć marginesów ani obramowań, bo przestałyby się mieścić obok siebie.
Krok 33: Układ bloków: flexbox w kolumnach z zawijaniem
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. 4 min
Polecenie z arkusza
Podział zrealizowany za pomocą znaczników sekcji tak, aby wygląd układu bloków strony po uruchomieniu w przeglądarce, był zgodny z Obrazem 1
<main> staje się pojemnikiem flex (display: flex), ale układa bloki w kolumnach, a nie w rzędzie:
flex-direction: column - kolejne sekcje stają jedna pod drugą,
height: 500px - wysokość pojemnika równa wysokości bloku lewego; kolumna mieści więc dokładnie 500 px,
flex-wrap: wrap - blok, który nie mieści się już w kolumnie, przechodzi do następnej. Blok lewy (500 px) wypełnia pierwszą kolumnę w całości, więc oba bloki prawe (2 × 250 px) trafiają do drugiej, jeden pod drugim.
Szerokości 40% i 60% stoją na blokach i liczą się od szerokości <main>, więc druga kolumna zaczyna się dokładnie tam, gdzie kończy się blok lewy. Stopka stoi pod <main> zwyczajnie, bez float i bez clear (R.3.1). Na obrazie strony widać ten sam układ co w wersji podstawowej.
Typowe błędy
Bez height na <main> kolumna rośnie bez końca i nic się nie zawija - trzy bloki staną jeden pod drugim. Wysokość pojemnika musi równać się wysokości bloku lewego.
Krok 35: Pola formularza: marginesy zewnętrzne 5 px
Kryteria:
R.4.7 Ustawiono marginesy wewnętrzne (padding) dla formularza 10px (góra i dół) i 30px (lewy i prawy) oraz marginesy zewnętrzne (margin) dla pola edycyjnego na 5px
ok. 1 min
Polecenie z arkusza
Dla zastosowanych w formularzu pól: marginesy zewnętrzne 5 px
Margines 5 px trafia do istniejącej reguły input, button - obejmuje trzy pola i przycisk (R.4.7).
R.4.9 Ustawiono dla paragrafu styl pseudoelementu before. Przed każdym paragrafem znajduje się tekst o treści "STATUS: "
ok. 1 min
Polecenie z arkusza
Dla paragrafu (akapitu): przed każdym paragrafem wstawiany jest tekst o treści „STATUS: ”
Pseudoelement ::before z content (R.4.9). Na stronie jest jeden paragraf - komunikat skryptu pod formularzem - więc „STATUS: ” pojawi się tylko przy nim.
Kroki 37-45 · po tym etapie masz 40 z 40 kryteriów (pkt)
Krok 37: Skrypt na górze pliku i połączenie z bazą psy
Kryteria:
R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy psy
ok. 4 min
Polecenie z arkusza
Efekt działania skryptu. Skrypt: Napisany w języku PHP. Skrypt łączy się z serwerem bazy danych na localhost, użytkownik root bez hasła, baza danych o nazwie psy
Skrypt stoi na samej górze logowanie.php, przed <!DOCTYPE html>: najpierw przygotowuje dane, potem HTML je tylko wypisuje. W miejscu „efektu działania skryptu” z obrazu 1 - pod formularzem - zostają trzy linie: jeśli jest komunikat, wypisz go w paragrafie <p>.
e() - krótka funkcja z htmlspecialchars: każdy tekst wypisywany na stronie zamienia <, >, & i cudzysłowy na encje, więc znaczniki wpisane w formularz nie zamienią się w kod strony,
$_SERVER['REQUEST_METHOD'] === 'POST' - skrypt pracuje tylko po wysłaniu formularza; zwykłe wejście na stronę nie łączy się z bazą,
new PDO(...) - adres serwera localhost, baza psy i kodowanie w jednym napisie, potem użytkownik root i puste hasło (R.5.1). ERRMODE_EXCEPTION - każdy błąd bazy rzuca wyjątek,
try / catch - gdy baza nie działa, uczeń dostaje zwykłe zdanie, a nie komunikat PHP z danymi serwera.
PDO zamiast funkcji mysqli z tabeli 1 - klucz przyjmuje obie biblioteki (uwaga przy R.5). Połączenie z nazwą bazy stoi w pliku strony, bo tam klucz je sprawdza, jeśli strona się nie uruchomi. Od tego kroku podgląd to wynik prawdziwego uruchomienia strony.
R.5.4 Skrypt wyświetla komunikat „wypełnij wszystkie pola”, jeżeli nie wypełniono przynajmniej jednego pola formularza
R.5.10 Przynajmniej jeden komunikat jest wyświetlany w paragrafie
ok. 4 min
Polecenie z arkusza
Skrypt obsługuje błędy danych formularza i wyświetla komunikaty w paragrafie (akapicie): „wypełnij wszystkie pola”, jeżeli nie wypełniono przynajmniej jednego pola formularza
Dane z formularza czytane przed połączeniem z bazą - pusty formularz nie potrzebuje bazy:
$_POST['login'] ?? '' - operator ?? daje pusty napis, gdy pola w ogóle nie wysłano (np. żądanie spoza formularza), zamiast ostrzeżenia PHP,
trim() na loginie - login z samych spacji jest pusty, a „Ewa ” ze spacją na końcu to „Ewa”. Haseł nie przycinasz: spacja w haśle to zwykły znak,
porównanie === '' zamiast empty() - empty('0') jest prawdziwe, więc hasło „0” uznano by za puste pole,
mb_strlen($login) > 50 - kolumna ma 50 znaków; dłuższego loginu XAMPP nie odrzuci, tylko po cichu utnie. Pole ma maxlength, ale dane da się wysłać z pominięciem formularza, więc sprawdza je też serwer,
komunikat trafia do $message i do paragrafu pod formularzem (R.5.4, R.5.10).
Pole loginu dostało value="<?= e($login) ?>" - po wysłaniu wpisany login wraca do pola i nie trzeba go wpisywać od nowa (na obrazie 1 pole loginu jest wypełnione obok komunikatu). Hasła celowo nie wracają. Podgląd: formularz wysłany bez loginu.
R.5.3 Skrypt stosuje zmodyfikowane zapytanie 2 do wysłania danych lub zapytanie 3 do sprawdzenia czy login występuje w bazie
R.5.5 Skrypt wyświetla komunikat „login występuje w bazie danych, konto nie zostało dodane”, jeżeli podany login znajduje się w bazie danych
ok. 4 min
Polecenie z arkusza
„login występuje w bazie danych, konto nie zostało dodane”, jeżeli podany login znajduje się w bazie danych, do sprawdzenia należy posłużyć się zapytaniem 3
Arkusz każe sprawdzić login zapytaniem 3, więc skrypt wysyła je bez zmian (R.5.3):
$pdo->query('SELECT login FROM uzytkownicy;') - zapytanie bez danych od użytkownika, więc wystarczy zwykłe query,
fetchAll(PDO::FETCH_COLUMN) - od razu tablica samych loginów, bez pętli while,
array_map('mb_strtolower', ...) i mb_strtolower($login) - oba napisy zamienione na małe litery: „ewa” to ten sam użytkownik co „Ewa”. Funkcja mb_ poprawnie zmniejsza też polskie litery („Łucja” → „łucja”),
in_array(..., true) - czy login jest w tablicy; true każe porównywać ściśle, bez zamiany typów.
Podgląd: dane z obrazu 1 - login Ewa i dwa różne hasła. Wynik: „STATUS: login występuje w bazie danych, konto nie zostało dodane”, a w polu loginu zostaje „Ewa” - dokładnie jak na obrazie 1 (R.5.5).
logowanie.phpPHPzmienione: 8 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$login = '';if ($_SERVER['REQUEST_METHOD'] === 'POST') {$login = trim($_POST['login'] ?? '');$password = $_POST['password'] ?? '';$password2 = $_POST['password2'] ?? '';if ($login === '' || $password === '' || $password2 === '') {$message = 'wypełnij wszystkie pola'; } elseif (mb_strlen($login) > 50) {$message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane'; } else {try {$pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);// zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter$logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);$takenLogins = array_map('mb_strtolower', $logins);if (in_array(mb_strtolower($login), $takenLogins, true)) {$message = 'login występuje w bazie danych, konto nie zostało dodane'; } } catch (PDOException $exception) {$message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę'; } }}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <main> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> <div> <labelfor="login">login: </label> <inputtype="text"id="login"name="login"maxlength="50"autocomplete="username"value="<?= e($login) ?>"> </div> <div> <labelfor="password">hasło: </label> <inputtype="password"id="password"name="password"autocomplete="new-password"> </div> <div> <labelfor="password2">powtórz hasło: </label> <inputtype="password"id="password2"name="password2"autocomplete="new-password"> </div> <buttontype="submit">Zapisz</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> </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> </main> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
Podgląd strony po tym kroku (logowanie.php)
Krok 40: Komunikat o różnych hasłach
Kryteria:
R.5.6 Skrypt wyświetla komunikat „hasła nie są takie same, konto nie zostało dodane”, jeżeli hasła wpisane do pola „hasło” oraz pola „powtórz hasło” różnią się
ok. 2 min
Polecenie z arkusza
„hasła nie są takie same, konto nie zostało dodane”, jeżeli hasła wpisane do pola „hasło” oraz pola „powtórz hasło” różnią się
elseif sprawdza hasła tylko dla wolnego loginu. !== porównuje ściśle - dwa napisy są równe tylko wtedy, gdy mają te same znaki. Podgląd: login Kasia, hasła azor i azor1 (R.5.6).
logowanie.phpPHPzmienione: 2 linie
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$login = '';if ($_SERVER['REQUEST_METHOD'] === 'POST') {$login = trim($_POST['login'] ?? '');$password = $_POST['password'] ?? '';$password2 = $_POST['password2'] ?? '';if ($login === '' || $password === '' || $password2 === '') {$message = 'wypełnij wszystkie pola'; } elseif (mb_strlen($login) > 50) {$message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane'; } else {try {$pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);// zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter$logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);$takenLogins = array_map('mb_strtolower', $logins);if (in_array(mb_strtolower($login), $takenLogins, true)) {$message = 'login występuje w bazie danych, konto nie zostało dodane'; } elseif ($password !== $password2) {$message = 'hasła nie są takie same, konto nie zostało dodane'; } } catch (PDOException $exception) {$message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę'; } }}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <main> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> <div> <labelfor="login">login: </label> <inputtype="text"id="login"name="login"maxlength="50"autocomplete="username"value="<?= e($login) ?>"> </div> <div> <labelfor="password">hasło: </label> <inputtype="password"id="password"name="password"autocomplete="new-password"> </div> <div> <labelfor="password2">powtórz hasło: </label> <inputtype="password"id="password2"name="password2"autocomplete="new-password"> </div> <buttontype="submit">Zapisz</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> </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> </main> <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 Wstawione do bazy danych pole hasło jest zaszyfrowane, a w kodzie źródłowym zastosowano funkcję sha1 do zaszyfrowania hasła
ok. 2 min
Polecenie z arkusza
W przypadku poprawnych danych skrypt szyfruje hasło algorytmem US Secure Hash Algorithm 1
sha1($password) - skrót SHA1 hasła, 40 znaków szesnastkowych; dla azor to 45a54d145a451e6624729ab8ec8051451c5fa718 (R.5.8). Klucz wymaga tej funkcji, więc zostaje. Poza egzaminem do haseł służy password_hash() - funkcja celowo wolna i z losową „solą”, której wynik (60 znaków) wymaga szerszej kolumny niż VARCHAR(50) z arkusza. Podgląd: dane poprawne - skrót policzony, nic jeszcze nie zapisano.
logowanie.phpPHPzmienione: 2 linie
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$login = '';if ($_SERVER['REQUEST_METHOD'] === 'POST') {$login = trim($_POST['login'] ?? '');$password = $_POST['password'] ?? '';$password2 = $_POST['password2'] ?? '';if ($login === '' || $password === '' || $password2 === '') {$message = 'wypełnij wszystkie pola'; } elseif (mb_strlen($login) > 50) {$message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane'; } else {try {$pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);// zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter$logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);$takenLogins = array_map('mb_strtolower', $logins);if (in_array(mb_strtolower($login), $takenLogins, true)) {$message = 'login występuje w bazie danych, konto nie zostało dodane'; } elseif ($password !== $password2) {$message = 'hasła nie są takie same, konto nie zostało dodane'; } else {$hash = sha1($password); } } catch (PDOException $exception) {$message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę'; } }}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <main> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> <div> <labelfor="login">login: </label> <inputtype="text"id="login"name="login"maxlength="50"autocomplete="username"value="<?= e($login) ?>"> </div> <div> <labelfor="password">hasło: </label> <inputtype="password"id="password"name="password"autocomplete="new-password"> </div> <div> <labelfor="password2">powtórz hasło: </label> <inputtype="password"id="password2"name="password2"autocomplete="new-password"> </div> <buttontype="submit">Zapisz</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> </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> </main> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
Podgląd strony po tym kroku (logowanie.php)
Krok 42: Zapytanie 2 jako zapytanie przygotowane
Kryteria:
R.5.7 Skrypt wstawia do bazy danych rekord z danymi pobranymi z formularza, jeżeli login nie występuje w bazie a oba hasła są takie same. Należy sprawdzić w bazie danych czy wstawiono pole login
ok. 3 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie 2 zmodyfikowane w ten sposób, że w polach login i haslo są wpisane dane pobrane z formularza (hasło powinno być zaszyfrowane)
Zapytanie 2, w którym wartości zastępują parametry:login i :haslo:
$pdo->prepare(...) - baza dostaje samą treść zapytania,
$statement->execute([...]) - osobno przychodzą dane: login z formularza i skrót hasła.
Dane nigdy nie staną się częścią kodu SQL, więc login z apostrofem (O'Neill) zapisuje się bez błędu, a wpisany w pole fragment zapytania nie doda do bazy nic poza jednym kontem z dziwnym loginem - w wersji podstawowej oba przypadki psuły zapytanie. Sprawdzenie, jak każe klucz (R.5.7): w tabeli jest nowy wiersz 13, Kasia, 45a54d145a451e6624729ab8ec8051451c5fa718.
logowanie.phpPHPzmienione: 2 linie
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$login = '';if ($_SERVER['REQUEST_METHOD'] === 'POST') {$login = trim($_POST['login'] ?? '');$password = $_POST['password'] ?? '';$password2 = $_POST['password2'] ?? '';if ($login === '' || $password === '' || $password2 === '') {$message = 'wypełnij wszystkie pola'; } elseif (mb_strlen($login) > 50) {$message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane'; } else {try {$pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);// zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter$logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);$takenLogins = array_map('mb_strtolower', $logins);if (in_array(mb_strtolower($login), $takenLogins, true)) {$message = 'login występuje w bazie danych, konto nie zostało dodane'; } elseif ($password !== $password2) {$message = 'hasła nie są takie same, konto nie zostało dodane'; } else {$hash = sha1($password);$statement = $pdo->prepare('INSERT INTO uzytkownicy (login, haslo) VALUES (:login, :haslo);');$statement->execute(['login' => $login, 'haslo' => $hash]); } } catch (PDOException $exception) {$message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę'; } }}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <main> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> <div> <labelfor="login">login: </label> <inputtype="text"id="login"name="login"maxlength="50"autocomplete="username"value="<?= e($login) ?>"> </div> <div> <labelfor="password">hasło: </label> <inputtype="password"id="password"name="password"autocomplete="new-password"> </div> <div> <labelfor="password2">powtórz hasło: </label> <inputtype="password"id="password2"name="password2"autocomplete="new-password"> </div> <buttontype="submit">Zapisz</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> </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> </main> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
R.5.9 Skrypt wyświetla komunikat „Konto zostało dodane”
ok. 1 min
Polecenie z arkusza
Wyświetla w paragrafie komunikat „Konto zostało dodane”
Komunikat ustawiany dopiero po execute - gdyby zapis się nie udał, wyjątek przeskoczy tę linię i pojawi się komunikat z catch (R.5.9).
logowanie.phpPHPzmienione: 1 linia
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$login = '';if ($_SERVER['REQUEST_METHOD'] === 'POST') {$login = trim($_POST['login'] ?? '');$password = $_POST['password'] ?? '';$password2 = $_POST['password2'] ?? '';if ($login === '' || $password === '' || $password2 === '') {$message = 'wypełnij wszystkie pola'; } elseif (mb_strlen($login) > 50) {$message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane'; } else {try {$pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);// zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter$logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);$takenLogins = array_map('mb_strtolower', $logins);if (in_array(mb_strtolower($login), $takenLogins, true)) {$message = 'login występuje w bazie danych, konto nie zostało dodane'; } elseif ($password !== $password2) {$message = 'hasła nie są takie same, konto nie zostało dodane'; } else {$hash = sha1($password);$statement = $pdo->prepare('INSERT INTO uzytkownicy (login, haslo) VALUES (:login, :haslo);');$statement->execute(['login' => $login, 'haslo' => $hash]);$message = 'Konto zostało dodane'; } } catch (PDOException $exception) {$message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę'; } }}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <main> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> <div> <labelfor="login">login: </label> <inputtype="text"id="login"name="login"maxlength="50"autocomplete="username"value="<?= e($login) ?>"> </div> <div> <labelfor="password">hasło: </label> <inputtype="password"id="password"name="password"autocomplete="new-password"> </div> <div> <labelfor="password2">powtórz hasło: </label> <inputtype="password"id="password2"name="password2"autocomplete="new-password"> </div> <buttontype="submit">Zapisz</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> </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> </main> <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 (zgodne ze składnią)
ok. 2 min
Polecenie z arkusza
Po wykonaniu operacji na bazie danych skrypt zamyka połączenie z serwerem
PDO nie ma funkcji close - połączenie zamyka się, gdy znika ostatnie odwołanie do obiektu. Dlatego $statement = null i $pdo = null.
Stoją w bloku finally: wykona się on zawsze po try, zarówno gdy wszystko się udało, jak i po wyjątku - zamknięcie jest więc ostatnią operacją na bazie w każdej gałęzi (R.5.2). Podgląd wraca do stanu z obrazu 1.
logowanie.phpPHPzmienione: 3 linie
<?phpfunctione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}$message = null;$login = '';if ($_SERVER['REQUEST_METHOD'] === 'POST') {$login = trim($_POST['login'] ?? '');$password = $_POST['password'] ?? '';$password2 = $_POST['password2'] ?? '';if ($login === '' || $password === '' || $password2 === '') {$message = 'wypełnij wszystkie pola'; } elseif (mb_strlen($login) > 50) {$message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane'; } else {try {$pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);// zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter$logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);$takenLogins = array_map('mb_strtolower', $logins);if (in_array(mb_strtolower($login), $takenLogins, true)) {$message = 'login występuje w bazie danych, konto nie zostało dodane'; } elseif ($password !== $password2) {$message = 'hasła nie są takie same, konto nie zostało dodane'; } else {$hash = sha1($password);$statement = $pdo->prepare('INSERT INTO uzytkownicy (login, haslo) VALUES (:login, :haslo);');$statement->execute(['login' => $login, 'haslo' => $hash]);$message = 'Konto zostało dodane'; } } catch (PDOException $exception) {$message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę'; } finally {$statement = null;$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie } }}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <main> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> <div> <labelfor="login">login: </label> <inputtype="text"id="login"name="login"maxlength="50"autocomplete="username"value="<?= e($login) ?>"> </div> <div> <labelfor="password">hasło: </label> <inputtype="password"id="password"name="password"autocomplete="new-password"> </div> <div> <labelfor="password2">powtórz hasło: </label> <inputtype="password"id="password2"name="password2"autocomplete="new-password"> </div> <buttontype="submit">Zapisz</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> </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> </main> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
Podgląd strony po tym kroku (logowanie.php)
Krok 45: Odświeżenie po dodaniu konta nie wysyła formularza drugi raz
ok. 4 min
Dodatek spoza arkusza - klucz go nie ocenia. Strona po wysłaniu formularza jest wynikiem żądania POST, więc odświeżenie (F5) pyta, czy wysłać dane ponownie. Wzorzec PRG (Post/Redirect/Get):
session_start() na samym początku pliku - sesja przechowa komunikat między dwoma żądaniami,
po udanym zapisie $accountAdded = true, a po bloku finally (połączenie jest już zamknięte) komunikat trafia do $_SESSION, a header('Location: logowanie.php') każe przeglądarce wczytać stronę jeszcze raz zwykłym żądaniem GET. exit jest konieczne - header tylko ustawia nagłówek i nie zatrzymuje skryptu,
nowe wejście odczytuje komunikat z sesji i od razu go usuwa (unset), więc pokaże się raz.
Przekierowanie jest tylko po dodaniu konta - przy błędach strona zostaje z komunikatem i wpisanym loginem. Sprawdzone na prawdziwym PHP: wysłanie formularza daje odpowiedź 302, potem „STATUS: Konto zostało dodane”, a kolejne odświeżenie - stronę bez komunikatu i bez drugiego konta Kasi w tabeli. Podgląd: stan po przekierowaniu.
logowanie.phpPHPzmienione: 13 linii
<?phpsession_start();functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}// komunikat zapisany przed przekierowaniem (PRG) - pokazywany raz$message = $_SESSION['message'] ?? null;unset($_SESSION['message']);$login = '';$accountAdded = false;if ($_SERVER['REQUEST_METHOD'] === 'POST') {$login = trim($_POST['login'] ?? '');$password = $_POST['password'] ?? '';$password2 = $_POST['password2'] ?? '';if ($login === '' || $password === '' || $password2 === '') {$message = 'wypełnij wszystkie pola'; } elseif (mb_strlen($login) > 50) {$message = 'login może mieć najwyżej 50 znaków, konto nie zostało dodane'; } else {try {$pdo = new PDO('mysql:host=localhost;dbname=psy;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ]);// zapytanie 3 - wszystkie loginy, porównanie bez względu na wielkość liter$logins = $pdo->query('SELECT login FROM uzytkownicy;')->fetchAll(PDO::FETCH_COLUMN);$takenLogins = array_map('mb_strtolower', $logins);if (in_array(mb_strtolower($login), $takenLogins, true)) {$message = 'login występuje w bazie danych, konto nie zostało dodane'; } elseif ($password !== $password2) {$message = 'hasła nie są takie same, konto nie zostało dodane'; } else {$hash = sha1($password);$statement = $pdo->prepare('INSERT INTO uzytkownicy (login, haslo) VALUES (:login, :haslo);');$statement->execute(['login' => $login, 'haslo' => $hash]);$accountAdded = true; } } catch (PDOException $exception) {$message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę'; } finally {$statement = null;$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie }if ($accountAdded) {$_SESSION['message'] = 'Konto zostało dodane';header('Location: logowanie.php');exit; } }}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl4.css"></head><body> <header> <h1>Forum wielbicieli psów</h1> </header> <main> <sectionid="left"> <imgsrc="obraz.jpg"alt="foksterier"> </section> <sectionid="right-top"> <h2>Zapisz się</h2> <formmethod="post"> <div> <labelfor="login">login: </label> <inputtype="text"id="login"name="login"maxlength="50"autocomplete="username"value="<?= e($login) ?>"> </div> <div> <labelfor="password">hasło: </label> <inputtype="password"id="password"name="password"autocomplete="new-password"> </div> <div> <labelfor="password2">powtórz hasło: </label> <inputtype="password"id="password2"name="password2"autocomplete="new-password"> </div> <buttontype="submit">Zapisz</button> </form><?phpif ($message !== null): ?> <p><?= e($message) ?></p><?phpendif; ?> </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> </main> <footer> Stronę wykonał: Bartosz Bryniarski </footer></body></html>
Podgląd strony po tym kroku (logowanie.php)
Etap 5: Porównanie z wersją podstawową
Krok 46 · po tym etapie masz 40 z 40 kryteriów (pkt)
Krok 46: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 3 min
Obie wersje dają komplet 40 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ
float: left trzech sekcji, clear stopki
<main> jako flexbox w kolumnach z zawijaniem
szerokości 40% i 60%
width na blokach
width na blokach (tak ocenia klucz tej sesji)
kolory
wpisane w każdej regule
zmienne --brown, --tan
formularz
napis, pole, <br>
label for, wiersze w <div>, maxlength, autocomplete
biblioteka
mysqli (tabela 1 arkusza)
PDO z wyjątkami
gdzie jest skrypt
w całości pod formularzem
na górze pliku, pod formularzem tylko wypisanie
sprawdzenie loginu
pętla while po zapytaniu 3, == rozróżnia wielkość liter
zapytanie 3, fetchAll, małe litery
dane z formularza
$_POST wprost, empty()
?? '', trim, === '', limit 50 znaków
INSERT
dane wklejone w treść zapytania
parametry :login, :haslo
zamknięcie bazy
mysqli_close na końcu
$pdo = null w finally
po wysłaniu
puste pola, F5 wysyła formularz ponownie
login wraca do pola, po dodaniu konta przekierowanie
W prawdziwym serwisie sprawdzenie loginu zrobiłaby sama baza: indeks UNIQUE na kolumnie login odrzuca drugie konto o tym samym loginie nawet wtedy, gdy dwie osoby kliknęłyby „Zapisz” w tej samej chwili. Arkusz każe jednak użyć zapytania 3 i nie przewiduje zmian w strukturze tabeli. Do haseł zamiast sha1 służy password_hash, a dane logowania do bazy trzyma się w osobnym pliku - tu klucz sprawdza je w kodzie strony.
Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie gubi loginu po błędzie, nie wywraca się na apostrofie i nie zakłada drugiego konta po odświeżeniu - a klucz przyjmuje obie.
Krok 47 · po tym etapie masz 40 z 40 kryteriów (pkt)
Krok 47: Plik przeglądarka.txt i nagranie płyty
ok. 5 min
Polecenie z arkusza
Po zakończeniu pracy utwórz w folderze z numerem PESEL lub w przypadku jego braku w folderze z innym numerem, którym został podpisany arkusz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowano poprawność działania witryny. Nagraj płytę z rezultatami pracy.
W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była strona. W folderze z numerem zdającego: egzamin.png, kw1.jpg - kw4.jpg, kwerendy.txt, logowanie.php, obraz.jpg, przeglądarka.txt, regulamin.html i styl4.css. Nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego.
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.