INF.03-10-26.06-SGCzerwiec 2026 · 40 kroków · ok. 92 min · 41 kryteriów z klucza
Rozwiązanie krok po kroku: Portal wyników laboratorium (Nowoczesny kod (PDO, bezpieczeństwo))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza laboratorium z czterema zapytaniami, strona logowania, strona wyników dostępna tylko po zalogowaniu i plik wylogowania - i tak samo spełnia każde kryterium klucza. Inna jest droga:
PHP na PDO z trybem wyjątków: dane pobierane na górze pliku, a tabela wypisywana pętlą foreach w szablonie HTML,
bezpieczeństwo od pierwszej linii: exit po każdym przekierowaniu (inaczej strona wyników wysyła dane pacjentów także niezalogowanym), htmlspecialchars na wszystkim, co trafia z bazy i sesji na stronę, filter_input przy odczycie formularza, porównanie hasła hash_equals, nowy identyfikator sesji po zalogowaniu, try/catch zamiast białej strony przy awarii bazy,
formularz na siatce CSS zamiast znaczników <br>, tabela z <thead> i <tbody>, zmienne CSS z kolorami.
Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza pochodzi z prawdziwego archiwum pliki20, zapytania zostały wykonane na tej bazie w MariaDB z XAMPP-a, a podgląd każdej strony PHP to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2: z zalogowaniem, złym hasłem, wejściem bez sesji i wylogowaniem.
Jak liczą się punkty. Klucz ma 41 kryteriów (R.1.1 - R.5.10), każde za 1 punkt. Próg zdania to 75%, czyli 31 punktów. Przy każdym kroku jest napisane, które kryteria spełnia.
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 41 kryteriów (pkt)
Krok 1: Baza danych laboratorium
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie laboratorium, z zestawem polskich znaków (np. utf8_polish_ci).
Archiwum pliki20.7z rozpakuj hasłem LaBoRaToR2um (w środku cyfra 2) do folderu z numerem zdającego: laboratorium.sql i trzy pliki PDF z wynikami. W phpMyAdmin Nowa → laboratorium, porównywanie utf8_polish_ci, albo:
CREATE DATABASE laboratorium CHARACTER SET utf8 COLLATE utf8_polish_ci;
Krok 2: Import tabel z pliku laboratorium.sql
ok. 2 min
Polecenie z arkusza
Do utworzonej bazy zaimportuj tabele z pliku laboratorium.sql.
Import → laboratorium.sql. Cztery tabele: Diagnosta, Pacjent, Zlecenie (data i godzina, pacjent) i Wynik (plik PDF, data wykonania, diagnosta, zlecenie). Wynik mają tylko trzy z dwunastu zleceń - to zdecyduje o rodzaju złączenia w zapytaniu 4.
Krok 3: Zrzut ekranu import.png
Kryteria:
R.1.1 Wykonany import tabel do bazy danych laboratorium - plik import.png
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie PNG pod nazwą import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań. Na zrzucie powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel.
Cały ekran z paskiem zadań, zapis jako import.png.
Typowe błędy
Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany.
Krok 4: Zapytanie 1: diagności malejąco po nazwisku
Kryteria: ,
R.1.2 W pliku kwerendy.txt zapisane co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Wykonane zapytanie 1 wybierające jedynie imię i nazwisko diagnosty laboratoryjnego z tabeli Diagnosta posortowane malejąco według nazwiska. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT imie,nazwisko FROM Diagnosta ORDER BY nazwisko DESC; oraz na zrzucie widocznych jest dokładnie 10 rekordów
ok. 1 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie imię i nazwisko diagnosty laboratoryjnego z tabeli Diagnosta posortowane malejąco według nazwiska.
Klauzula w linii: co, skąd, w jakiej kolejności (DESC - malejąco, od Z do A). Wynik: 10 wierszy, od Anny Zalewskiej. Zapytania zapisuj w kwerendy.txt, zrzut jako kw1.png.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT imie, nazwiskoFROM DiagnostaORDER BY nazwisko DESC;
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: liczba zleceń w kolejnych dniach
Kryteria:
R.1.4 Wykonane zapytanie 2 zliczające liczbę zleceń pogrupowanych po datach (bez uwzględnienia godziny). W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT DATE(data),COUNT(*)FROM Zlecenie GROUP BY DATE(data); oraz na zrzucie widocznych jest dokładnie 6 rekordów
ok. 2 min
Polecenie z arkusza
Zapytanie 2: zliczające liczbę zleceń pogrupowanych po datach (bez uwzględnienia godziny). Wybierane są jedynie daty i liczby zleceń.
DATE(data) obcina godzinę, a GROUP BY DATE(data) łączy w jedną grupę wszystkie zlecenia z tego samego dnia - grupować trzeba po tym samym wyrażeniu, które się wybiera,
COUNT(*) liczy zlecenia w grupie,
aliasy dzien i liczba_zlecen dają czytelne nagłówki kolumn, a ORDER BY dzien - dni po kolei.
Wynik: 6 wierszy - po jednym na dzień. Zrzut: kw2.png.
Typowe błędy
GROUP BY data bez DATE() tworzy osobną grupę dla każdej godziny - każde zlecenie ma inną, więc wychodzi 12 grup po 1.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT imie, nazwiskoFROM DiagnostaORDER BY nazwisko DESC;-- Zapytanie 2SELECT DATE(data) AS dzien, COUNT(*) AS liczba_zlecenFROM ZlecenieGROUP BY DATE(data)ORDER BY dzien;
R.1.5 Wykonane zapytanie 3 wybierające tylko daty zleceń dla pacjenta o nazwisku Nowicki. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT data FROM Zlecenie JOIN Pacjent ON Pacjent.id = idPacjenta WHERE nazwisko = "Nowicki"; (możliwe również porównanie kluczy po WHERE) oraz na zrzucie widoczne są dokładnie 2 rekordy
ok. 2 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie daty zleceń dla pacjenta o nazwisku Nowicki. Należy posłużyć się relacją.
Aliasy z (zlecenie) i p (pacjent), warunek złączenia przy JOIN ... ON, warunek z zadania w WHERE. z.data, bo kolumnę data ma też tabela Wynik - prefiks od razu mówi, o którą chodzi. Wynik: 2 wiersze (8 czerwca 2024, 10:00 i 11:00). Zrzut: kw3.png.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT imie, nazwiskoFROM DiagnostaORDER BY nazwisko DESC;-- Zapytanie 2SELECT DATE(data) AS dzien, COUNT(*) AS liczba_zlecenFROM ZlecenieGROUP BY DATE(data)ORDER BY dzien;-- Zapytanie 3SELECT z.dataFROM Zlecenie AS zJOIN Pacjent AS p ON p.id = z.idPacjentaWHERE p.nazwisko = 'Nowicki';
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: wszystkie zlecenia z wynikiem albo bez
Kryteria:
R.1.6 Wykonane zapytanie 4 wybierające jedynie imię, nazwisko oraz pesel pacjenta z tabeli Pacjent, datę z tabeli Zlecenie, datę i plik z tabeli Wynik. W wyniku wyświetlane są wszystkie zlecenia (także te, które nie mają jeszcze wyniku) . W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT imie, nazwisko, PESEL, Zlecenie.data, Wynik.data, plik FROM Pacjent JOIN Zlecenie ON idPacjenta = Pacjent.id LEFT JOIN Wynik ON Zlecenie.id = idZlecenia; (możliwe użycie dwa razy LEFT JOIN) oraz na zrzucie widocznych jest dokładnie 12 rekordów
ok. 3 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie imię, nazwisko oraz pesel pacjenta z tabeli Pacjent, datę z tabeli Zlecenie, datę i plik z tabeli Wynik. Należy posłużyć się relacją i odpowiednim rodzajem złączenia. Wybierane są wszystkie zlecenia niezależenie od tego czy jest im przypisany wynik.
Zapytanie zaczyna się od tabeli, której wszystkie wiersze mają się pojawić - FROM Zlecenie:
JOIN Pacjent AS p - każde zlecenie ma pacjenta, wystarczy złączenie wewnętrzne,
LEFT JOIN Wynik AS w - złączenie zewnętrzne lewe: zlecenie bez wyniku zostaje, a kolumny z Wynik mają wtedy NULL,
dwie kolumny o nazwie data dostają aliasy data_zlecenia i data_wyniku. To przyda się w PHP: w tablicy asocjacyjnej dwa klucze data nadpisałyby się nawzajem.
Wynik: 12 wierszy, z czego 9 bez wyniku. Sprawdzone: ze zwykłym JOIN zostają 3 wiersze. Zrzut: kw4.png.
Typowe błędy
JOIN Wynik (wewnętrzne) gubi zlecenia bez wyniku - 3 wiersze zamiast 12.
kwerendy.txtTekstzmienione: 8 linii
-- Zapytanie 1SELECT imie, nazwiskoFROM DiagnostaORDER BY nazwisko DESC;-- Zapytanie 2SELECT DATE(data) AS dzien, COUNT(*) AS liczba_zlecenFROM ZlecenieGROUP BY DATE(data)ORDER BY dzien;-- Zapytanie 3SELECT z.dataFROM Zlecenie AS zJOIN Pacjent AS p ON p.id = z.idPacjentaWHERE p.nazwisko = 'Nowicki';-- Zapytanie 4SELECT p.imie, p.nazwisko, p.pesel, z.data AS data_zlecenia, w.data AS data_wyniku, w.plikFROM Zlecenie AS zJOIN Pacjent AS p ON p.id = z.idPacjentaLEFT JOIN Wynik AS w ON w.idZlecenia = z.id;
Podgląd strony po tym kroku
Etap 2: Strony HTML
Kroki 8-18 · po tym etapie masz 17 z 41 kryteriów (pkt)
Krok 8: Plik login.php w HTML5
ok. 2 min
Polecenie z arkusza
Witryna internetowa składa się ze stron login.php, wyniki.php oraz pliku wyloguj.php. Cechy wspólne dla stron login.php i wyniki.php: Zapisana w języku HTML5.
Najpierw login.php; wyniki.php powstanie z jego kopii (nagłówek i stopka są wspólne). Pliki leżą w htdocs XAMPP-a i otwierasz je przez http://localhost/... - otwarte z dysku nie wykonają PHP.
Jawnie zastosowany właściwy standard kodowania polskich znaków.
<meta charset="UTF-8"> jako pierwszy element <head>. Druga linia, viewport, to standard każdego współczesnego szablonu: telefon nie udaje szerokiego ekranu i nie pomniejsza strony.
R.2.2 W sekcji head znajduje się jawnie podany standard kodowania polskich znaków i stronie login.php nadano tytuł "Zaloguj się" lub stronie wyniki.php tytuł "Przeglądaj wyniki"
R.3.2 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.css formatowanie CSS pochodzi jedynie z tego arkusza
ok. 1 min
Polecenie z arkusza
Cechy strony login.php: Tytuł strony widoczny na karcie przeglądarki: „Zaloguj się”. Arkusz stylów w pliku o nazwie styl.css prawidłowo połączony z kodem strony.
<title> z treścią z arkusza (z kodowaniem to R.2.2) i <link> do styl.css - całe formatowanie z tego pliku, bez atrybutów style (R.3.2).
Krok 12: Bloki: nagłówek z h1, blok główny, stopka
Kryteria:
R.2.3 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML 5: header, main, footer (dopuszcza się stosowanie bloków pomocniczych)
ok. 2 min
Polecenie z arkusza
Podział strony na bloki: nagłówkowy, główny oraz stopka. Podział zrealizowany za pomocą semantycznych znaczników języka HTML5, tak aby po uruchomieniu w przeglądarce układ bloków na stronie był zgody z ilustracją 4. Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści „Portal laboratorium”. Zwartość bloku stopki: Paragraf o treści „Wykonał:” dalej wstawiony numer zdającego.
<header>, <main>, <footer> (R.2.3) - bloki stoją jeden pod drugim w zwykłym przepływie dokumentu, jak na ilustracji 4. W nagłówku <h1>, w stopce akapit: na egzaminie po „Wykonał:” wpisujesz swój numer zdającego (PESEL), tu stoi imię i nazwisko autora rozwiązania.
Blok główny logowania wygląda inaczej niż blok główny strony wyników, więc dostaje identyfikator login-box.
Krok 13: Nagłówek i formularz wysyłany metodą POST do siebie
Kryteria:
R.3.3 Formularz wysyła dane metodą POST do tego samego pliku (poprawne też bez atrybutu action)
ok. 2 min
Polecenie z arkusza
Zawartość bloku głównego: Nagłówek drugiego stopnia o treści „Zaloguj się do systemu”. Formularz wysyłający dane metodą bezpieczną do pliku login.php.
method="post" - login i hasło idą w treści żądania, nie w adresie (przy get hasło zostałoby w historii przeglądarki i w logach serwera). action="login.php" - dane wracają do tego samego pliku, gdzie sprawdzi je skrypt 1 (R.3.3).
Typowe błędy
method="get" dopisuje hasło do adresu strony - arkusz pod „metodą bezpieczną” ma na myśli POST i klucz uznaje tylko taki zapis.
R.2.5 Strona z logowaniem zawiera formularz składający się z dwóch pól edycyjnych i przycisku. Co najmniej jedno pole ma powiązaną etykietę (label z for i id lub zagnieżdżenie kontrolki w label)
ok. 2 min
Polecenie z arkusza
Etykieta o treści „Login:”. Pole edycyjne. Etykiety są powiązane z odpowiednimi polami edycyjnymi.
<label for="login"> i id="login" - powiązanie etykiety z polem (R.2.5): klik w napis ustawia kursor w polu, czytnik ekranu czyta nazwę pola. name="login" to klucz w $_POST.
Dwa atrybuty spoza arkusza: required - przeglądarka nie wyśle pustego formularza (wygoda, nie zabezpieczenie: serwer i tak sprawdza dane sam), autocomplete="username" - menedżer haseł wie, co tu wpisać.
Etykieta o treści „Hasło:”. Pole edycyjne służące, do wpisywania hasła, ukrywające wprowadzone znaki.
type="password" zastępuje znaki kropkami (R.3.5) - tylko na ekranie. Przez sieć hasło idzie zwykłym tekstem, dlatego POST, a na prawdziwym serwerze także HTTPS.
R.3.4 Przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu)
ok. 1 min
Polecenie z arkusza
Przycisk o treści „Zaloguj” wysyłający dane z formularza do skryptu 1. Układ pól zgodny z ilustracją 2.
<button type="submit"> wysyła formularz pod adres z action (R.3.4). Etykiety, pola i przycisk stoją na razie w jednym rzędzie - zamiast łamać linie znacznikami <br>, układ z ilustracji 2 ustawi siatka CSS w etapie stylów.
Krok 17: Strona wyniki.php: tytuł, nagłówek i tabela
Kryteria: ,
R.2.4 W bloku nagłówkowym znajduje się <h1>: "Portal laboratorium", w stopce <p> z numerem zdającego, w bloku głównym strony logowania <h2>: "Zaloguj się do systemu", w bloku głównym strony z wynikami <h2>: "Wyniki pacjentów"
R.2.7 Pierwszy wiersz tabeli zawiera komórki nagłówkowe o treści: "Imię pacjenta", "Nazwisko pacjenta", "PESEL", "Data zlecenia", "Data wykonania", "Wynik", znaczniki <tr>, <th>
ok. 3 min
Polecenie z arkusza
Cechy strony wyniki.php: Tytuł strony widoczny na karcie przeglądarki: „Przeglądaj wyniki”. Zawartość bloku głównego: Nagłówek drugiego stopnia o treści „Wyniki pacjentów”. Blok zawierający tabelę z 6 kolumnami. Pierwszy wiersz zawiera komórki nagłówkowe kolejno: „Imię pacjenta”, „Nazwisko pacjenta”, „PESEL”, „Data zlecenia”, „Data wykonania”, „Wynik”. Zawartość tabeli generowana jest skryptem 3.
Kopia login.php jako wyniki.php: nowy tytuł, w bloku głównym <h2> i blok <div id="results"> z tabelą. Tabela ma dwie części:
<thead> - wiersz komórek nagłówkowych <th> (R.2.7),
<tbody> - na razie pusty; wiersze z danymi wypisze skrypt 3.
Podział mówi przeglądarce i czytnikowi ekranu, który wiersz opisuje kolumny; przy wydruku długiej tabeli <thead> powtarza się na każdej stronie, a w stylach łatwo wskazać tylko wiersze z danymi. Komplet nagłówków z R.2.4: <h1>, oba <h2> i <p> w stopce.
Typowe błędy
Na ilustracji 3 ostatnia kolumna ma napis „Wyniki”, a w treści arkusza i w kluczu „Wynik” - wpisz jak w treści.
Kroki 19-25 · po tym etapie masz 28 z 41 kryteriów (pkt)
Krok 19: Zmienne z kolorami i ciało strony
Kryteria:
R.4.1 selektor body: krój czcionki Verdana - jako pierwszy, sans-serif - jako ostatni, kolor tła #D4F1F4, kolor czcionki #05445E
ok. 2 min
Polecenie z arkusza
Dla selektora ciała strony: krój czcionki Verdana, w przypadku jego braku sans-serif, kolor tła #D4F1F4, kolor czcionki #05445E.
Plik styl.css zaczyna się od zmiennych w :root: cztery kolory z arkusza, każdy wpisany raz. Turkus #75E6DA ma trzy bloki - w regule stoi var(--box), więc zmiana odcienia to jedna linia. Klucz każe sprawdzać kryteria R.4 w kodzie CSS: wartości są w kodzie, tylko w jednym miejscu.
Reguła body (selektor znacznika, jak wymaga uwaga): Verdana, a gdy jej brak - sans-serif, tło i kolor tekstu (R.4.1).
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 4 w arkuszu egzaminacyjnym (żaden blok nie zachodzi na inne bloki - sprawdzić w inspektorze)
ok. 3 min
Polecenie z arkusza
Dla bloku głównego na stronie login.php: szerokość 50 em, marginesy zewnętrzne automatycznie wyliczane przez przeglądarkę, marginesy wewnętrzne 20 px. Dla bloku stopki: wyrównanie tekstu do prawej strony. Dla bloku nagłówkowego: marginesy zewnętrzne 40 px. Dla bloku z tabelą: marginesy zewnętrzne 40 px, marginesy wewnętrzne 20 px.
Reguły w kolejności, w jakiej bloki stoją na stronie: nagłówek, okno logowania, blok z tabelą, stopka.
margin: auto przy ustalonej szerokości (50em - względem rozmiaru czcionki, więc okno rośnie razem z powiększeniem tekstu) dzieli wolne miejsce po równo na lewy i prawy margines - blok staje na środku,
bloki stoją jeden pod drugim bez float, flex czy siatki - to zwykły przepływ dokumentu i dokładnie układ z ilustracji 4 (R.3.1).
Dla bloku głównego na stronie login.php, dla bloku nagłówkowego oraz dla bloku z tabelą: kolor tła #75E6DA, zaokrąglenie obramowania 5 px, cień elementu o przesunięciu poziomym i pionowym 10 px, rozmyciu 5 px i kolorze LightBlue.
Jedna reguła, trzy selektory - każdy w osobnej linii, łatwo dopisać czwarty. box-shadow: 10px 10px 5px LightBlue: przesunięcie w poziomie, w pionie, rozmycie, kolor; dodatnie przesunięcia kładą cień w prawo i w dół (R.4.2, R.4.7, R.4.8).
Krok 22: Nagłówki i formularz na środku, pola na siatce
Kryteria:
R.4.4 wyrównanie tekstu: blok stopki - do prawej strony, selektory h1, h2, form - do środka
ok. 3 min
Polecenie z arkusza
Dla selektora nagłówka pierwszego stopnia, selektora nagłówka drugiego stopnia oraz selektora formularza: wyrównanie tekstu do środka. Dla selektora formularza: marginesy zewnętrzne 10 px.
text-align: center dla h1, h2 i form (selektory znaczników, jak wymaga uwaga - R.4.4). Druga reguła dla formularza - margines 10 px z arkusza i siatka zamiast znaczników <br>:
display: grid + grid-template-columns: max-content max-content - dwie kolumny, każda szeroka na swoją najszerszą zawartość: w lewej etykiety, w prawej pola. Elementy formularza wpadają do komórek po kolei, więc pary etykieta - pole same ustawiają się w wierszach,
justify-content: center - siatka węższa niż blok staje na środku,
gap: 4px - odstęp między komórkami zamiast marginesów pól.
Przycisk zajmie cały wiersz pod polami w kolejnym kroku.
Krok 23: Przycisk „Zaloguj”: szerokość i własny wiersz
Kryteria:
R.4.3 szerokość bloku głównego na stronie logowania - 50em, dla przycisku "Zaloguj" - 14em
ok. 2 min
Polecenie z arkusza
Dla przycisku o treści “Zaloguj”: szerokość 14 em.
width: 14em z arkusza (z oknem 50 em to R.4.3). grid-column: 1 / -1 rozciąga obszar przycisku od pierwszej do ostatniej linii siatki (-1 to zawsze ostatnia), czyli na oba kolumny, a justify-self: center stawia przycisk na środku tego obszaru - jak na ilustracji 2.
Krok 24: Tabela: wyśrodkowanie, połączone linie, komórki
Kryteria: ,
R.4.5 marginesy wewnętrzne (padding) : blok główny na stronie logowania, blok z tabelą - 20px, komórki nagłówkowe tabeli i komórki tabeli - 10px, marginesy zewnętrzne (margin) : blok główny na stronie logowania, tabela - auto, blok nagłówkowy, blok z tabelą - 40px, formularz - 10px
R.4.6 obramowanie dowolną, pojedynczą linią ciągłą o szerokości - 1px i kolorze #189AB4 dla tabeli i wiersza tabeli; obramowanie połączone dla tabeli (border-collapse: collapse;)
ok. 2 min
Polecenie z arkusza
Dla tabeli: marginesy zewnętrzne automatycznie wyliczone przez przeglądarkę, obramowanie połączone. Dla tabeli i wiersza tabeli: obramowanie linią ciągłą 1 px o kolorze #189AB4. Dla komórek nagłówkowych tabeli i komórek tabeli: marginesy wewnętrzne 10 px.
margin: auto centruje tabelę w bloku, border-collapse: collapse łączy sąsiednie linie w jedną,
obramowanie dla table i tr (nie dla komórek): widać obwódkę i poziome linie między wierszami, bez pionowych - jak na ilustracji 3 (R.4.6),
padding: 10px w komórkach domyka kryterium marginesów: wewnętrzne okna, bloku z tabelą i komórek oraz zewnętrzne okna, tabeli, nagłówka, bloku z tabelą i formularza (R.4.5).
Typowe błędy
Ramka wiersza tr rysuje się tylko przy border-collapse: collapse.
Krok 25: Odnośniki bez podkreślenia, wiersz pod kursorem
Kryteria: ,
R.4.9 brak podkreślenia dla odnośnika
R.4.10 gdy kursor znajdzie się na wierszu tabeli kolor tła zmienia się na biały o przezroczystości 0.8
ok. 2 min
Polecenie z arkusza
Dla odnośnika: brak podkreślenia. Gdy kursor znajdzie się na wierszu tabeli kolor tła zmienia się na biały o przeźroczystości 0.8.
text-decoration: none dla wszystkich odnośników (R.4.9),
rgb(255 255 255 / 0.8) - nowa składnia koloru: składowe oddzielone spacjami, krycie po ukośniku. To ten sam kolor co rgba(255, 255, 255, 0.8); 80% bieli, przez które prześwituje turkus bloku (R.4.10),
selektor tbody tr:hover podświetla tylko wiersze z danymi - wiersz nagłówków w <thead> zostaje bez zmian.
Typowe błędy
opacity: 0.8 robi półprzezroczystym cały wiersz razem z tekstem - arkusz mówi o przezroczystości koloru tła.
Kroki 26-38 · po tym etapie masz 41 z 41 kryteriów (pkt)
Krok 26: Skrypt 1: start sesji na początku pliku
Kryteria:
R.5.1 Co najmniej jeden ze skryptów realizuje obsługę sesji w języku PHP. Przynajmniej raz użyte polecenia session_start() na początku pliku PHP
ok. 2 min
Polecenie z arkusza
Napisane w języku PHP. Skrypty wykorzystują mechanizm sesji. Efekt działania skryptu 1.
session_start() w pierwszej linii login.php, przed <!DOCTYPE html>: serwer zaczyna pamiętać dane tego użytkownika między stronami (rozpoznaje go po ciasteczku z identyfikatorem sesji). Ciasteczko idzie w nagłówkach odpowiedzi, a nagłówki muszą wyjść przed jakimkolwiek HTML-em (R.5.1). Od tego kroku podgląd to wynik prawdziwego uruchomienia w PHP 8.2.
Typowe błędy
Spacja albo pusta linia przed <?php to już wypisana treść - session_start() kończy się ostrzeżeniem „headers already sent”.
R.5.10 Zastosowana przynajmniej jedna zmienna oraz nazewnictwo jest w języku polskim lub angielskim i odzwierciedla cel zastosowania
ok. 3 min
Polecenie z arkusza
Jeżeli dane zostały wysłane z formularza.
$_SERVER['REQUEST_METHOD'] === 'POST' - formularz był wysłany (pierwsze wejście na stronę to żądanie GET),
filter_input(INPUT_POST, 'login') czyta pole z formularza, a gdy pola nie ma, zwraca null zamiast ostrzeżenia „Undefined array key”, jak przy $_POST['login'],
(string) zamienia ewentualne null na pusty napis, a trim obcina spacje z brzegów loginu (często wklejane razem z nazwą). hasło zostaje bez zmian - spacja może być jego częścią,
$login i $error dostają wartości początkowe przed if, bo szablon HTML będzie ich używał także przy pierwszym wejściu.
Nazwy zmiennych po angielsku, mówią, co przechowują (R.5.10).
Krok 28: Skrypt 1: sprawdzenie loginu i hasła, zmienna sesji
Kryteria:
R.5.3 Skrypt 1 (login.php) pobiera dane z formularza. Jeżeli w formularzu podany login admin oraz hasło TajneHaslo321* to ustawiana jest zmienna sesji zawierająca aktualną nazwę użytkownika. Wykorzystana tablica $_SESSION
ok. 3 min
Polecenie z arkusza
Jeżeli dane zostały wysłane z formularza i login to admin, a hasło to TajneHaslo321* ustawiana jest zmienna sesji zawierająca aktualną nazwę użytkownika.
$login === 'admin' - porównanie ścisłe: ten sam typ i ta sama wartość,
hash_equals('TajneHaslo321*', $password) porównuje napisy zawsze w tym samym czasie, niezależnie od tego, na którym znaku się różnią. Zwykłe == kończy przy pierwszej różnicy, więc z czasu odpowiedzi da się (przy wielu próbach) zgadywać hasło znak po znaku. Hasło z arkusza zostaje w kodzie dosłownie - egzaminator sprawdza R.5.3 w kodzie źródłowym,
session_regenerate_id(true) - po zalogowaniu sesja dostaje nowy identyfikator, a stary jest usuwany. Jeśli ktoś podsunął użytkownikowi swój identyfikator przed logowaniem, po zalogowaniu nic mu on nie da,
$_SESSION['user'] = $login - nazwa użytkownika w sesji (R.5.3).
Typowe błędy
Hasło jest TajneHaslo321* - z gwiazdką na końcu i wielkimi literami T i H. Bez gwiazdki egzaminator nie zaloguje się na stronę.
R.5.4 Skrypt 1 (login.php) ładuje stronę wyniki.php (np. header("location: wyniki.php"))
ok. 2 min
Polecenie z arkusza
Oraz zostaje załadowana strona wyniki.php.
header('Location: wyniki.php') wysyła przeglądarce polecenie przejścia na stronę wyników (R.5.4), a exit kończy skrypt. Bez exit PHP wykonuje plik dalej i wysyła cały formularz, choć nikt go nie zobaczy - przy stronie wyników to samo zaniechanie wysłałoby dane pacjentów (kolejne kroki). Zasada: po każdym header('Location: ...') stoi exit.
Krok 30: Skrypt 1: komunikat przy złym haśle i zapamiętany login
ok. 4 min
Polecenie z arkusza
Jeżeli dane zostały wysłane z formularza i login to admin, a hasło to TajneHaslo321* ustawiana jest zmienna sesji zawierająca aktualną nazwę użytkownika oraz zostaje załadowana strona wyniki.php.
Arkusz nie mówi, co ma się stać przy złym haśle - wersja podstawowa po prostu pokazuje pusty formularz. Tu:
po nieudanej próbie $error dostaje komunikat, a szablon wypisuje go pod przyciskiem (role="alert" - czytnik ekranu przeczyta go od razu). Komunikat nie zdradza, czy błędny był login, czy hasło,
pole loginu dostaje value="<?= htmlspecialchars($login) ?>" - wpisany login zostaje, trzeba poprawić tylko hasło,
htmlspecialchars jest tu konieczne: login pochodzi od użytkownika. Sprawdzone: login "><script>alert(1)</script> wraca jako zwykły tekst w polu, a bez funkcji zamknąłby atrybut value i dopisał do strony skrypt,
<?= ... ?> to skrót od <?php echo ... ?>, a if (...): ... endif; - składnia alternatywna, czytelniejsza w środku HTML-u niż nawiasy klamrowe. Komunikat zajmuje cały wiersz siatki formularza (reguła form p w stylach).
Podgląd: stan po wpisaniu loginu ze spacjami i złego hasła - login przycięty, komunikat pod przyciskiem.
Jeżeli zmienna sesji zawierająca aktualną nazwę użytkownika nie jest ustawiona następuje przekierowanie do strony login.php.
Na początku wyniki.php: session_start(), a potem !isset($_SESSION['user']) - „w sesji nie ma użytkownika”. Wtedy header('Location: login.php') i exit.
Sprawdzone uruchomieniem: wejście na wyniki.php bez sesji daje odpowiedź 302 z przekierowaniem na login.php i pustą treścią. Wersja podstawowa (bez exit) w tej samej sytuacji wysyła całą tabelę z danymi pacjentów - przeglądarka i tak przejdzie na logowanie, ale program, który nie słucha przekierowań (np. curl), dostaje dane bez logowania. Podgląd od teraz to strona po prawdziwym zalogowaniu formularzem.
R.2.8 Na stronie z wynikami nazwa użytkownika jest zapisana w znaczniku <strong> (w skrypcie lub HTML)
R.5.5 Skrypt 2 (wyniki.php): jeżeli nie jest ustawiona zmienna sesji zawierająca nazwę użytkownika, następuje przekierowanie do strony logowania. Napis w bloku nagłówkowym to: "Witaj <użytkownik>” gdzie <użytkownik> to nazwa użytkownika pobrana ze zmiennej sesji
ok. 3 min
Polecenie z arkusza
W bloku nagłówkowym, w akapicie wyświetlany jest napis „Witaj, <użytkownik>” gdzie <użytkownik> to nazwa użytkownika pobrana ze zmiennej sesji. Nazwa użytkownika jest zapisana w znaczniku semantycznym oznaczającym tekst ważny, formatowany domyślnie jako pogrubiony.
Akapit „Witaj, ” z nazwą z sesji w <strong> - tekst ważny, pogrubiony (R.2.8, R.5.5). HTML stoi w szablonie, a PHP wstawia tylko wartość.
Funkcja e() to krótka nazwa dla htmlspecialchars($text ?? '', ENT_QUOTES): zamienia <, >, & i oba cudzysłowy na encje, a null na pusty napis. Będzie potrzebna przy każdej wartości z bazy, więc warto ją mieć pod ręką - e(...) łatwo dopisać i łatwo zauważyć, gdy go brakuje. ?string w nagłówku funkcji mówi, że przyjmuje napis albo null.
Krok 33: Skrypt 3: połączenie PDO, zamknięcie i obsługa awarii
Kryteria:
R.5.2 Skrypt 3 (wyniki.php) realizuje połączenie z serwerem bazy danych i wybór bazy laboratorium oraz ostatnią operacją na bazie jest jej zamknięcie
ok. 4 min
Polecenie z arkusza
Łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie laboratorium. Ostatnią operacją skryptu jest zamknięcie połączenia z bazą danych.
Skrypt 3 stoi na górze pliku, pod sprawdzeniem sesji - najpierw dane, potem HTML. Połączenie przez PDO (klucz R.5 dopuszcza MySQLi i PDO):
'mysql:host=localhost;dbname=laboratorium;charset=utf8mb4' - serwer, baza i kodowanie w jednym napisie (DSN); utf8mb4 to pełny UTF-8,
'root', '' - użytkownik i puste hasło z arkusza,
PDO::ERRMODE_EXCEPTION - każdy błąd bazy rzuca wyjątek, zamiast po cichu zwracać false; PDO::FETCH_ASSOC - wiersze jako tablice z nazwami kolumn.
PDO nie ma funkcji „zamknij” - połączenie kończy się, gdy nie wskazuje na nie już żadna zmienna. $pdo = null zamyka je jawnie i to jest ostatnia operacja na bazie (R.5.2); zapytanie trafi między te dwie linie.
try / catch (PDOException $exception): gdy baza nie odpowiada, wyjątek trafia do catch. Szczegóły (z nazwą serwera i użytkownika) idą tylko do dziennika serwera przez error_log, a użytkownik dostanie ogólny komunikat z $dbError. Wartości początkowe $orders = [] i $dbError = '' stoją przed try, żeby szablon miał co wypisać w każdym przypadku.
R.5.6 Skrypt 3 (wyniki.php) zawiera instrukcję wysyłającą do bazy danych zapytanie 4
ok. 2 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie 4.
Zapytanie 4 z aliasami, rozpisane na kilka linii w apostrofach. $pdo->query(...) wysyła je do bazy, a ->fetchAll() od razu odbiera wszystkie wiersze do tablicy $orders - 12 tablic z kluczami imie, nazwisko, pesel, data_zlecenia, data_wyniku, plik (R.5.6). Po tej linii połączenie nie jest już potrzebne, więc zaraz za nią stoi $pdo = null.
query wystarcza, bo w zapytaniu nie ma żadnej wartości od użytkownika. Gdyby była (np. nazwisko z wyszukiwarki), byłoby prepare i execute z parametrem - nigdy sklejanie napisu.
wyniki.phpPHPzmienione: 6 linii
<?phpsession_start();if (!isset($_SESSION['user'])) {header('Location: login.php');exit;}functione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$orders = [];$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=laboratorium;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$orders = $pdo->query('SELECT p.imie, p.nazwisko, p.pesel, z.data AS data_zlecenia, w.data AS data_wyniku, w.plik FROM Zlecenie AS z JOIN Pacjent AS p ON p.id = z.idPacjenta LEFT JOIN Wynik AS w ON w.idZlecenia = z.id' )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać wyników. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Przeglądaj wyniki</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Portal laboratorium</h1> <p>Witaj, <strong><?= e($_SESSION['user']) ?></strong></p> <p><ahref="wyloguj.php">Wyloguj</a></p> </header> <main> <h2>Wyniki pacjentów</h2> <divid="results"> <table> <thead> <tr> <th>Imię pacjenta</th> <th>Nazwisko pacjenta</th> <th>PESEL</th> <th>Data zlecenia</th> <th>Data wykonania</th> <th>Wynik</th> </tr> </thead> <tbody> </tbody> </table> </div> </main> <footer> <p>Wykonał: Bartosz Bryniarski</p> </footer></body></html>
R.2.6 Strona z wynikami zawiera tabelę składającą się z 6 kolumn. Zastosowano znaczniki <table>, <tr>, <td> (w skrypcie lub HTML)
R.5.7 Skrypt 3 (wyniki.php) dla każdego zwróconego zapytaniem rekordu tworzy nowy wiersz tabeli i wypełnia go danymi pobranymi z bazy
ok. 3 min
Polecenie z arkusza
Wyniki zapytania wyświetlane są jako kolejne wiersze tabeli (zgodnie z ilustracją 3).
W <tbody> pętla foreach ($orders as $order): ... endforeach; w składni alternatywnej: dla każdego zlecenia wiersz <tr> z komórkami <td> (R.2.6, R.5.7). Wartości po nazwach kolumn ($order['pesel']), nie po numerach - kolejność kolumn w SELECT może się zmienić, a kod nadal trafi w dobrą.
Każda wartość przez e(): dane z bazy też mogą zawierać znaki < czy & (wpisane kiedyś przez inny program), a e(null) dla zleceń bez wyniku daje pustą komórkę bez ostrzeżenia. Ostatnia komórka jest na razie pusta. Podgląd: 12 wierszy w kolejności z ilustracji 3 (tak oddała je MariaDB; bez ORDER BY kolejność nie jest gwarantowana).
wyniki.phpPHPzmienione: 10 linii
<?phpsession_start();if (!isset($_SESSION['user'])) {header('Location: login.php');exit;}functione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$orders = [];$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=laboratorium;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$orders = $pdo->query('SELECT p.imie, p.nazwisko, p.pesel, z.data AS data_zlecenia, w.data AS data_wyniku, w.plik FROM Zlecenie AS z JOIN Pacjent AS p ON p.id = z.idPacjenta LEFT JOIN Wynik AS w ON w.idZlecenia = z.id' )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać wyników. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Przeglądaj wyniki</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Portal laboratorium</h1> <p>Witaj, <strong><?= e($_SESSION['user']) ?></strong></p> <p><ahref="wyloguj.php">Wyloguj</a></p> </header> <main> <h2>Wyniki pacjentów</h2> <divid="results"> <table> <thead> <tr> <th>Imię pacjenta</th> <th>Nazwisko pacjenta</th> <th>PESEL</th> <th>Data zlecenia</th> <th>Data wykonania</th> <th>Wynik</th> </tr> </thead> <tbody><?phpforeach ($ordersas$order): ?> <tr> <td><?= e($order['imie']) ?></td> <td><?= e($order['nazwisko']) ?></td> <td><?= e($order['pesel']) ?></td> <td><?= e($order['data_zlecenia']) ?></td> <td><?= e($order['data_wyniku']) ?></td> <td></td> </tr><?phpendforeach; ?> </tbody> </table> </div> </main> <footer> <p>Wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wyniki.php)
Krok 36: Skrypt 3: odnośnik do pliku z wynikiem
Kryteria: ,
R.3.7 Tabela zawiera przynajmniej jeden odnośnik, który otwiera plik PDF w nowej karcie przeglądarki
R.5.8 W ostatniej kolumnie tabeli jest tworzony odnośnik prowadzący do pliku o nazwie zwróconej z bazy tylko wtedy, gdy nazwa jest niepusta
ok. 3 min
Polecenie z arkusza
W ostatniej kolumnie tabeli, gdy z bazy została zwrócona niepusta nazwa pliku wstawiany jest odnośnik o treści „wynik” prowadzący do pliku o nazwie zwróconej z bazy. Odnośnik otwiera się w nowej karcie przeglądarki.
!empty($order['plik']) jest fałszywe dla NULL (zlecenie bez wyniku) i dla pustego napisu - jeden warunek na oba przypadki; komórka zostaje wtedy pusta, a wiersz ma 6 kolumn,
href z nazwą pliku przez e() - nazwa trafia do atrybutu, więc cudzysłów w nazwie nie może go zamknąć,
target="_blank" - nowa karta (R.3.7, R.5.8); rel="noopener" odcina otwartą kartę od strony, z której ją otwarto (nowe przeglądarki robią to same, ale atrybut mówi to wprost).
Podgląd: trzy odnośniki „wynik”. Pliki PDF z archiwum muszą leżeć obok stron.
wyniki.phpPHPzmienione: 5 linii
<?phpsession_start();if (!isset($_SESSION['user'])) {header('Location: login.php');exit;}functione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$orders = [];$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=laboratorium;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$orders = $pdo->query('SELECT p.imie, p.nazwisko, p.pesel, z.data AS data_zlecenia, w.data AS data_wyniku, w.plik FROM Zlecenie AS z JOIN Pacjent AS p ON p.id = z.idPacjenta LEFT JOIN Wynik AS w ON w.idZlecenia = z.id' )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać wyników. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Przeglądaj wyniki</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Portal laboratorium</h1> <p>Witaj, <strong><?= e($_SESSION['user']) ?></strong></p> <p><ahref="wyloguj.php">Wyloguj</a></p> </header> <main> <h2>Wyniki pacjentów</h2> <divid="results"> <table> <thead> <tr> <th>Imię pacjenta</th> <th>Nazwisko pacjenta</th> <th>PESEL</th> <th>Data zlecenia</th> <th>Data wykonania</th> <th>Wynik</th> </tr> </thead> <tbody><?phpforeach ($ordersas$order): ?> <tr> <td><?= e($order['imie']) ?></td> <td><?= e($order['nazwisko']) ?></td> <td><?= e($order['pesel']) ?></td> <td><?= e($order['data_zlecenia']) ?></td> <td><?= e($order['data_wyniku']) ?></td> <td><?phpif (!empty($order['plik'])): ?> <ahref="<?= e($order['plik']) ?>"target="_blank"rel="noopener">wynik</a><?phpendif; ?> </td> </tr><?phpendforeach; ?> </tbody> </table> </div> </main> <footer> <p>Wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wyniki.php)
Krok 37: Skrypt 3: komunikat, gdy baza nie odpowiada
ok. 3 min
Polecenie z arkusza
Wyniki zapytania wyświetlane są jako kolejne wiersze tabeli (zgodnie z ilustracją 3).
Pod pętlą jeden wiersz na całą szerokość tabeli (colspan="6") z komunikatem z $dbError - tylko wtedy, gdy catch coś do niego wpisał.
Sprawdzone uruchomieniem z błędną nazwą bazy: strona się wyświetla, w tabeli stoi „Nie udało się pobrać wyników. Spróbuj ponownie za chwilę.”, a w treści strony nie ma ani nazwy bazy, ani kodu błędu SQL. Bez try/catch PHP przerwałby stronę w połowie tabeli komunikatem „Fatal error: Uncaught PDOException” z pełną ścieżką pliku i nazwą serwera - informacją dla włamywacza, nie dla pracownika laboratorium. W normalnym działaniu (podgląd) wiersza komunikatu nie ma.
wyniki.phpPHPzmienione: 5 linii
<?phpsession_start();if (!isset($_SESSION['user'])) {header('Location: login.php');exit;}functione(?string $text): string{returnhtmlspecialchars($text ?? '', ENT_QUOTES);}$orders = [];$dbError = '';try {$pdo = new PDO('mysql:host=localhost;dbname=laboratorium;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$orders = $pdo->query('SELECT p.imie, p.nazwisko, p.pesel, z.data AS data_zlecenia, w.data AS data_wyniku, w.plik FROM Zlecenie AS z JOIN Pacjent AS p ON p.id = z.idPacjenta LEFT JOIN Wynik AS w ON w.idZlecenia = z.id' )->fetchAll();$pdo = null;} catch (PDOException $exception) {error_log($exception->getMessage());$dbError = 'Nie udało się pobrać wyników. Spróbuj ponownie za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Przeglądaj wyniki</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Portal laboratorium</h1> <p>Witaj, <strong><?= e($_SESSION['user']) ?></strong></p> <p><ahref="wyloguj.php">Wyloguj</a></p> </header> <main> <h2>Wyniki pacjentów</h2> <divid="results"> <table> <thead> <tr> <th>Imię pacjenta</th> <th>Nazwisko pacjenta</th> <th>PESEL</th> <th>Data zlecenia</th> <th>Data wykonania</th> <th>Wynik</th> </tr> </thead> <tbody><?phpforeach ($ordersas$order): ?> <tr> <td><?= e($order['imie']) ?></td> <td><?= e($order['nazwisko']) ?></td> <td><?= e($order['pesel']) ?></td> <td><?= e($order['data_zlecenia']) ?></td> <td><?= e($order['data_wyniku']) ?></td> <td><?phpif (!empty($order['plik'])): ?> <ahref="<?= e($order['plik']) ?>"target="_blank"rel="noopener">wynik</a><?phpendif; ?> </td> </tr><?phpendforeach; ?><?phpif ($dbError !== ''): ?> <tr> <tdcolspan="6"><?= e($dbError) ?></td> </tr><?phpendif; ?> </tbody> </table> </div> </main> <footer> <p>Wykonał: Bartosz Bryniarski</p> </footer></body></html>
Podgląd strony po tym kroku (wyniki.php)
Krok 38: Skrypt 4: wylogowanie
Kryteria:
R.5.9 Skrypt 4 (wyloguj.php) po kliknięciu na odnośnik "Wyloguj" usuwa zmienną sesji i przekierowuje do strony login.php
ok. 3 min
Polecenie z arkusza
Skrypt 4 (wyloguj.php): Jeżeli jest ustawiona zmienna sesji zawierająca nazwę użytkownika to jest ona usuwana (dane sesji są czyszczone). Następuje przekierowanie do strony login.php.
wyloguj.php to sam skrypt, bez HTML-u - i bez zamykającego ?> na końcu pliku: w pliku z samym PHP brak zamknięcia jest zalecany, bo przypadkowa pusta linia za ?> byłaby wypisaną treścią i blokowała header.
$_SESSION = [] czyści wszystkie dane sesji naraz - także nazwę użytkownika (warunek „jeżeli jest ustawiona” nie jest potrzebny: wyczyszczenie pustej sesji niczego nie psuje),
session_destroy() usuwa sesję z serwera,
przekierowanie na logowanie i exit (R.5.9).
Sprawdzone uruchomieniem w jednej „przeglądarce”: logowanie, wyloguj.php (302 na login.php), potem wyniki.php - znowu 302 na login.php, czyli sesji już nie ma. Podgląd pokazuje stronę, na którą trafia użytkownik.
header + exit (bez tego tabela idzie do niezalogowanych)
dane na stronie
wprost
e() = htmlspecialchars(..., ENT_QUOTES)
formularz
$_POST + isset, pusty formularz po błędzie
filter_input, trim, komunikat, zapamiętany login
hasło
==
hash_equals + session_regenerate_id
awaria bazy
biała strona z błędem
komunikat w tabeli, szczegóły w error_log
układ formularza
<br>
siatka CSS
Na egzaminie wersja podstawowa jest szybsza do napisania i trzyma się funkcji z tabel arkusza. Z nowoczesnej warto przenieść choćby exit po header - jedno słowo, a zamyka realną dziurę. Czego tu świadomie nie ma: hasła jako skrótu password_hash (klucz R.5.3 sprawdza w kodzie, że hasło to TajneHaslo321* - pokazuje to wersja podstawowa w etapie „Dobre praktyki”), osobnego pliku config.php z danymi logowania do bazy (R.5.2 każe szukać połączenia w wyniki.php) i tokenu CSRF w formularzu - w prawdziwej aplikacji formularze zmieniające dane go potrzebują, a klucz go nie wymaga.
Krok 40 · po tym etapie masz 41 z 41 kryteriów (pkt)
Krok 40: Plik przeglądarka.txt i nagranie płyty
ok. 5 min
Polecenie z arkusza
Po zakończeniu pracy utwórz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Umieść go w folderze z numerem zdającego. Nagraj płytę z rezultatami pracy.
W folderze z numerem zdającego: login.php, wyniki.php, wyloguj.php, styl.css, kwerendy.txt, import.png, kw1.png … kw4.png, trzy pliki PDF z wynikami i przeglądarka.txt. Płytę sprawdź po nagraniu.
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.