Przejdź do głównej treści
EE.09-05-22.01-SGStyczeń 2022 · 38 kroków · ok. 83 min · 39 kryteriów z klucza

Rozwiązanie krok po kroku: Logowanie do portalu osiedlowego (Nowoczesny kod (flex, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza portal z czterema zapytaniami, zdjęcie przycięte do 400 × 200 px oraz strona uzytkownicy.php z licznikiem użytkowników, formularzem logowania i wizytówką - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • semantyczne znaczniki i flex zamiast float: oba banery stoją w <header>, blok lewy i prawy w <main>, a każdy z tych dwóch rzędów to kontener display: flex - stopka nie potrzebuje clear,
  • zmienne CSS dla trzech kolorów z arkusza, etykiety <label for> i wiersze formularza w blokach zamiast <br>,
  • PHP na PDO na górze pliku, przed HTML-em - w banerze i w wizytówce zostaje samo wypisanie przygotowanych danych,
  • zapytania przygotowane z loginem z formularza, porównanie skrótów hasła funkcją hash_equals, każdy tekst z bazy przepuszczony przez htmlspecialchars,
  • login zostaje w polu po wysłaniu formularza, a błąd bazy danych kończy się zwykłym komunikatem zamiast „Fatal error”.

To arkusz EE.09 ze stycznia 2022 - kwalifikacji formuły 2017, którą zastąpiła INF.03. Baza i zdjęcia pochodzą z oryginalnego archiwum CKE pliki5 (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). Kroki bazy danych i grafiki są takie same jak w wersji podstawowej.

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

  1. Etap 1: Baza danych

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

    Krok 1: Folder zdającego i archiwum pliki5

    ok. 2 min
    Polecenie z arkusza

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

    Na pulpicie tworzysz folder nazwany numerem PESEL - w tym rozwiązaniu w jego miejscu stoi imię i nazwisko autora, Bartosz Bryniarski. Archiwum pliki5.zip rozpakowujesz do tego folderu, podając hasło *Mieszk4ncY* (z gwiazdkami na początku i na końcu, wielkość liter ma znaczenie). W środku są: baza.sql, zdjęcia o1.jpg - o8.jpg i ikona serca icon-on.png.

    Arkusz podkreśla, że rozpakowane pliki muszą leżeć w tym samym folderze co pliki strony - skrypt wstawia zdjęcia po samej nazwie pliku zapisanej w bazie (o6.jpg), więc przeglądarka szuka ich obok uzytkownicy.php.

    Typowe błędy

    Zdjęcia w podfolderze (np. pliki5/o6.jpg) - nazwa z bazy nie zawiera folderu, więc wizytówka pokaże pusty prostokąt zamiast zdjęcia.

  2. Krok 2: XAMPP i pusta baza portal

    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 portal

    W XAMPP Control Panel klikasz Start przy Apache i MySQL, potem w przeglądarce otwierasz http://localhost/phpmyadmin. Nowa w lewym panelu, nazwa portal, Utwórz. Porównywania napisów arkusz nie narzuca - plik baza.sql i tak tworzy tabele z własnym kodowaniem.

    Typowe błędy

    Nazwa bazy musi brzmieć dokładnie portal - po niej skrypt PHP wybiera bazę (R.5.1), a klucz sprawdza import do bazy o tej nazwie (R.1.1).

  3. Krok 3: Import pliku baza.sql i zrzut egzamin.jpg

    Kryteria:

    R.1.1 Wykonano import tabel do bazy portal , czynność udokumentowano plikiem o nazwie egzamin w formacie JPEG

    ok. 3 min
    Polecenie z arkusza

    Do bazy portal zaimportuj plik baza.sql z rozpakowanego archiwum. Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w folderze opisanym numerem PESEL lub w przypadku jego braku w folderze z innym numerem, którym został podpisany arkusz, w formacie JPEG i nazwij egzamin. 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

    Zaznacz bazę portal, karta Import, Wybierz plik → baza.sql, Wykonaj. Plik sam tworzy obie tabele (dane i uzytkownicy), wstawia po 11 wierszy i ustawia klucze główne. Po imporcie phpMyAdmin pokazuje zielony komunikat o sukcesie, a w lewym panelu pod bazą pojawiają się obie tabele - to są „elementy wskazujące na poprawnie wykonany import”.

    Zrzut całego ekranu z paskiem zadań zapisujesz jako egzamin.jpg (Print Screen, wklejenie do edytora grafiki, eksport do JPEG).

    Typowe błędy

    W tym arkuszu formaty są odwrotne niż zwykle: zrzut importu to JPEG (egzamin.jpg), a zrzuty zapytań - PNG (kw1.png - kw4.png). Zły format to strata punktu.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 3
  4. Krok 4: Zapytanie 1: hasło użytkownika Justyna

    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 wybierające jedynie pole haslo z tabeli uzytkownicy dla użytkownika o loginie Justyna. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT haslo FROM uzytkownicy WHERE login = 'Justyna'; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne jest jedynie jedno hasło wynikające z zapytania

    ok. 3 min
    Polecenie z arkusza

    Zapisz i wykonaj zapytania SQL działające na bazie danych portal. Zapytania zapisz w pliku kwerendy.txt. Wykonaj zrzuty ekranu przedstawiające wyniki działania kwerend. Zrzuty zapisz w formacie PNG o nazwach kw1, kw2, kw3, kw4. Zapytanie 1: wybierające jedynie pole haslo z tabeli uzytkownicy dla użytkownika o loginie Justyna

    Zaznacz bazę portal i otwórz kartę SQL:

    • SELECT haslo - tylko jedna kolumna („jedynie pole haslo”),
    • FROM uzytkownicy,
    • WHERE login = 'Justyna' - warunek z loginem w apostrofach, bo to napis.

    Wynik to jeden wiersz: a056c8d05ae9ac6ca180bc991b93b7ffe37563e0 - 40 znaków szesnastkowych, czyli skrót SHA-1. Hasła Justyny („qqq” z tabeli 1 arkusza) w bazie nie ma, jest tylko jego skrót. Zapytanie kopiujesz do nowego pliku kwerendy.txt w folderze zdającego, a zrzut całego ekranu - do kw1.png.

    To zapytanie wykona później skrypt 2 - z loginem z formularza w miejscu „Justyna”.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT haslo FROM uzytkownicy WHERE login = 'Justyna';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: liczba wierszy w tabeli dane

    Kryteria:

    R.1.4 Utworzono zapytanie 2 liczące liczbę wierszy w tabeli dane. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT COUNT(*) FROM dane; możliwa dowolna nazwa pola zamiast * oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest efekt poprawnie wykonanego zapytania - wartość 11

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: liczące liczbę wierszy w tabeli dane

    SELECT COUNT(*) FROM dane - funkcja COUNT(*) liczy wiersze tabeli (gwiazdka: wszystkie, bez względu na zawartość kolumn). Wynik: 11. Zrzut: kw2.png.

    Wynik przyda się skryptowi 1: każdy wiersz tabeli dane to jeden użytkownik portalu, więc baner pokaże „Liczba użytkowników portalu: 11”.

    Typowe błędy

    SELECT * FROM dane pokaże 11 wierszy, ale niczego nie policzy - klucz wymaga wyniku w postaci wartości 11.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT haslo FROM uzytkownicy WHERE login = 'Justyna';
    
    -- Zapytanie 2
    SELECT COUNT(*) FROM dane;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: dane Justyny z dwóch tabel

    Kryteria:

    R.1.5 Utworzono zapytanie 3 wybierające jedynie pole login z tabeli uzytkownicy i odpowiadające mu pola rok_urodz, przyjaciol, hobby, zdjecie z tabeli dane dla użytkownika o loginie Justyna. Tabele połączone są relacją 1..1, za pomocą kluczy głównych. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = 'Justyna'; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest rekord: Justyna, 1995, 10, moda, o6.jpg

    ok. 4 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pole login z tabeli uzytkownicy i odpowiadające mu pola rok_urodz, przyjaciol, hobby, zdjecie z tabeli dane dla użytkownika o loginie Justyna. Tabele połączone są relacją 1..1, za pomocą kluczy głównych

    Relacja 1..1 przez klucze główne znaczy, że wiersz o id 1 w tabeli uzytkownicy opisuje tę samą osobę co wiersz o id 1 w tabeli dane:

    • SELECT login, rok_urodz, przyjaciol, hobby, zdjecie - pięć kolumn z arkusza, w tej kolejności,
    • FROM dane JOIN uzytkownicy - złączenie dwóch tabel,
    • ON dane.id = uzytkownicy.id - warunek złączenia: równe klucze główne. Nazwa tabeli przed kropką jest konieczna, bo kolumna id jest w obu,
    • WHERE login = 'Justyna'.

    Wynik: Justyna, 1995, 10, moda, o6.jpg - dokładnie rekord z klucza. Zrzut: kw3.png. Kolejność kolumn ma znaczenie dla skryptu 2: mysqli_fetch_row zwróci je pod numerami 0-4.

    Typowe błędy

    Bez warunku ON (samo FROM dane, uzytkownicy WHERE login = ...) baza połączy Justynę z każdym z 11 wierszy tabeli dane - 11 rekordów zamiast jednego.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT haslo FROM uzytkownicy WHERE login = 'Justyna';
    
    -- Zapytanie 2
    SELECT COUNT(*) FROM dane;
    
    -- Zapytanie 3
    SELECT login, rok_urodz, przyjaciol, hobby, zdjecie
    FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id
    WHERE login = 'Justyna';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: nowa kolumna stanowisko

    Kryteria:

    R.1.6 Utworzono zapytanie 4 dodające do tabeli dane kolumnę stanowisko dowolnego typu napisowego. W pliku z kwerendami lub na zrzucie istnieje zapis np.: ALTER TABLE dane ADD stanowisko TEXT; możliwe ADD COLUMN oraz dowolny typ napisowy oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest efekt poprawnie wykonanego zapytania

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: dodające do tabeli dane kolumnę stanowisko dowolnego typu tekstowego

    • ALTER TABLE dane - zmiana struktury istniejącej tabeli,
    • ADD stanowisko TEXT - nowa kolumna typu TEXT (klucz przyjmuje dowolny typ napisowy, np. VARCHAR(50); słowo COLUMN po ADD jest opcjonalne).

    phpMyAdmin odpowiada „MySQL zwrócił pusty wynik” - ALTER TABLE niczego nie wybiera. Zrzut: kw4.png; można też pokazać kartę Struktura z nową kolumną. Nowa kolumna jest pusta (NULL) we wszystkich wierszach i nie przeszkadza skryptom - żadne zapytanie nie używa SELECT *. Plik kwerendy.txt ma komplet czterech zapytań.

    Typowe błędy

    Zapytanie 4 wykonuj raz - drugie wykonanie zgłosi błąd #1060 („Zduplikowana nazwa kolumny”). Na zrzucie ma być efekt udanego wykonania.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT haslo FROM uzytkownicy WHERE login = 'Justyna';
    
    -- Zapytanie 2
    SELECT COUNT(*) FROM dane;
    
    -- Zapytanie 3
    SELECT login, rok_urodz, przyjaciol, hobby, zdjecie
    FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id
    WHERE login = 'Justyna';
    
    -- Zapytanie 4
    ALTER TABLE dane ADD stanowisko TEXT;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

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

    Krok 8: Grafika: przycięcie o1.jpg do 400 × 200 px

    Kryteria:

    R.2.1 Wysokość obrazu o1.jpg wynosi dokładnie 200 px, a szerokość 400 px

    ok. 3 min
    Polecenie z arkusza

    Plik o1.jpg, wypakowany z archiwum, należy przyciąć tak, aby jego wysokość wynosiła dokładnie 200 px, szerokość pozostaje niezmieniona – 400 px, obraz należy zapisać w pliku o tej samej nazwie. Pozostałe obrazy zostały przygotowane i nie należy ich zmieniać.

    Oryginał ma 400 × 266 px, a wszystkie pozostałe zdjęcia - 400 × 200. Przycięcie to wycięcie fragmentu obrazu, a nie skalowanie: w GIMP-ie Obraz → Rozmiar płótna (szerokość 400, wysokość 200, przesunięcie 0) i Obraz → Przytnij do zawartości, albo narzędzie Kadrowanie z wpisanym rozmiarem 400 × 200. W tym rozwiązaniu odcięty jest dolny pas z błotem - głowy zawodników zostają w kadrze.

    Zapis pod tą samą nazwą: Plik → Nadpisz o1.jpg. To zdjęcie zobaczy w wizytówce Krzysiek (w tabeli dane ma o1.jpg) - po przycięciu pasuje do bloku wizytówki tak samo jak pozostałe.

    Typowe błędy

    Skaluj obraz do 400 × 200 przy rozpiętym łańcuchu spłaszczy zdjęcie - wymiary się zgodzą, ale to nie jest przycięcie. Zmiana o2.jpg - o8.jpg jest wbrew arkuszowi.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Plik dane.html

    ok. 1 min
    Polecenie z arkusza

    Składa się ze stron: uzytkownicy.php, dane.html. W pliku o nazwie dane.html jest wpisany jedynie napis „strona w trakcie budowy”. Pozostałe wymagania dotyczą jedynie pliku uzytkownicy.php

    Plik dane.html z jednym zdaniem: strona w trakcie budowy. Arkusz mówi „jedynie napis”, więc bez szkieletu HTML. Do tego pliku poprowadzi przycisk „Więcej informacji” z wizytówki.

    dane.html HTML nowy plik
    strona w trakcie budowy
    Podgląd strony po tym kroku (dane.html)
  10. Krok 10: Plik uzytkownicy.php i kodowanie znaków

    Kryteria: ,

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

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

    ok. 2 min
    Polecenie z arkusza

    Witrynę zapisano w pliku uzytkownicy.php. Zastosowany właściwy standard kodowania polskich znaków

    Plik uzytkownicy.php w folderze zdającego, obok zdjęć. <!DOCTYPE html> to HTML5, lang="pl" - język treści, a <meta charset="UTF-8"> jako pierwszy element <head> mówi przeglądarce, jak czytać polskie litery. Sam plik zapisujesz w UTF-8 (w Notepad++ menu Kodowanie).

    uzytkownicy.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  11. Krok 11: Tytuł strony

    Kryteria:

    R.2.4 Nadano tytuł strony: "Portal społecznościowy”

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Portal społecznościowy”

    <title> w <head>. Obok znacznik viewport - telefon nie będzie udawał ekranu komputera i pokaże stronę w swojej szerokości (klucz go nie ocenia).

    uzytkownicy.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  12. Krok 12: Podpięcie arkusza stylów styl5.css

    Kryteria:

    R.3.5 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl5.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 styl5.css prawidłowo połączony z kodem strony

    <link rel="stylesheet" href="styl5.css">. Całe formatowanie trafi do tego pliku - żadnych atrybutów style w HTML-u.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  13. Krok 13: Bloki: nagłówek z dwoma banerami, część główna, stopka

    Kryteria:

    R.2.5 Wygląd strony zdefiniowano dzieląc ją na bloki: dwa bloki banera, blok lewy i prawy, stopka. Zastosowano znaczniki sekcji

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: obok siebie dwa bloki banera, poniżej obok siebie blok lewy i prawy, poniżej stopka. Podział zrealizowany za pomocą znaczników sekcji

    Strona ma trzy poziome pasy i każdy dostaje znacznik, który mówi, czym jest:

    • <header> - nagłówek strony, w nim dwa bloki banera: <section id="banner-left"> i <section id="banner-right">,
    • <main> - główna treść, w niej blok lewy i prawy, też jako <section>,
    • <footer> - stopka.

    Arkusz wymaga znaczników sekcji dla bloków - pięć bloków z arkusza to cztery <section> i <footer> (R.2.5). <header> i <main> to dodatkowe kontenery: każdy obejmuje jeden rząd bloków, więc w CSS wystarczy ustawić flex na kontenerze.

    uzytkownicy.php PHP zmienione: 14 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
            </section>
            <section id="banner-right">
            </section>
        </header>
        <main>
            <section id="left">
            </section>
            <section id="right">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  14. Krok 14: Nagłówki: „Nasze osiedle”, „Logowanie”, „Wizytówka”

    Kryteria:

    R.2.6 W lewym bloku banera zapisano nagłówek h2: "Nasze osiedle", w bloku lewym h3 "Logowanie", w bloku prawym h3 "Wizytówka"

    ok. 1 min
    Polecenie z arkusza

    Zawartość banera lewego: nagłówek drugiego stopnia o treści „Nasze osiedle”. Zawartość bloku lewego: Nagłówek trzeciego stopnia o treści „Logowanie”. Zawartość bloku prawego: Nagłówek trzeciego stopnia o treści „Wizytówka”

    Trzy zdania arkusza, trzy nagłówki: <h2> w banerze lewym oraz <h3> w bloku lewym i prawym. Baner prawy na razie pusty - liczbę użytkowników wypisze w nim PHP.

    uzytkownicy.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  15. Krok 15: Formularz wysyłający dane metodą POST

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Formularz wysyłający dane do tego samego pliku metodą post z elementami:

    <form method="post"> bez atrybutu action wysyła dane pod ten sam adres, czyli do uzytkownicy.php (R.3.3). Metoda POST - hasło nie trafi do paska adresu ani do historii przeglądarki.

    uzytkownicy.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  16. Krok 16: Etykieta „login”, pod nią pole edycyjne

    ok. 2 min
    Polecenie z arkusza

    Napis login, poniżej pole edycyjne

    Napis to <label for="login"> połączony z polem o id="login": kliknięcie w napis ustawia kursor w polu, a czytnik ekranu odczyta nazwę pola. Etykieta i pole stoją w jednym bloku <div class="field"> - „poniżej” zrobi CSS, bez <br>.

    • name="login" - klucz w $_POST,
    • maxlength="50" - tyle znaków mieści kolumna login w bazie (varchar(50)),
    • autocomplete="username" - menedżer haseł przeglądarki rozpozna pole loginu.
    uzytkownicy.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  17. Krok 17: Etykieta „hasło”, pod nią pole na hasło

    ok. 1 min
    Polecenie z arkusza

    Poniżej napis hasło, poniżej pole edycyjne pozwalające na niejawne wpisanie hasła

    Ten sam układ co login. Niejawne wpisanie to type="password", a autocomplete="current-password" podpowiada przeglądarce, że to hasło do istniejącego konta.

    uzytkownicy.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  18. Krok 18: Przycisk „Zaloguj”

    Kryteria: ,

    R.2.7 W bloku lewym umieszczono formularz z dwoma polami edycyjnymi i przyciskiem

    R.3.4 Pole edycyjne do wpisywania hasła jest typu password oraz przycisk wysyła dane z formularza (input z typem submit lub button)

    ok. 1 min
    Polecenie z arkusza

    Poniżej przycisk o treści „Zaloguj” wysyłający dane z formularza

    <button type="submit"> - przycisk z treścią między znacznikami, jawnie określonym typem wysyłającym formularz. Klucz przyjmuje input type="submit" albo button (R.3.4). Formularz ma dwa pola i przycisk (R.2.7).

    Typowe błędy

    Przycisk w formularzu bez type też wysyła dane, ale jawne type="submit" nie zostawia wątpliwości przy czytaniu kodu.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  19. Krok 19: Blok wizytówki

    ok. 1 min
    Polecenie z arkusza

    Blok wizytówki, w którym umieszczony jest efekt działania skryptu 2

    Wizytówka to samodzielna porcja treści o jednej osobie, więc znacznik <article> z identyfikatorem card. Wypełni ją PHP po zalogowaniu.

    uzytkownicy.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
                </article>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  20. Krok 20: Stopka z autorem strony

    Kryteria:

    R.2.9 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 w jego miejscu stoi imię i nazwisko autora: Bartosz Bryniarski.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
                </article>
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  21. Etap 3: Styl CSS

    Kroki 21-26 · po tym etapie masz 28 z 39 kryteriów (pkt)

    Krok 21: Plik styl5.css: kolory jako zmienne i krój Tahoma

    Kryteria:

    R.4.1 Ustawiono domyślny krój czcionki dla całej strony na Tahoma (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 Tahoma

    Trzy kolory z arkusza powtarzają się w kilku regułach, więc dostają nazwy w :root: --dark (#003d33), --main (#26a69a) i --hover (#004c40). W regułach będzie var(--dark) - zmiana koloru w jednym miejscu zmienia go wszędzie.

    * z font-family: Tahoma obejmuje też pola formularza i przyciski (R.4.1). body bez marginesu - pasy dochodzą do krawędzi okna.

    styl5.css CSS nowy plik
    :root {
        --dark: #003d33;
        --main: #26a69a;
        --hover: #004c40;
    }
    
    * {
        font-family: Tahoma;
    }
    
    body {
        margin: 0;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  22. Krok 22: Banery obok siebie: tło, kolor tekstu, wysokość, szerokość

    ok. 3 min
    Polecenie z arkusza

    Wspólne dla obu banerów: kolor tła #003d33, biały kolor czcionki, wyrównanie tekstu do środka, wysokość 50 px. Dodatkowo dla banera lewego: szerokość 70%. Dodatkowo dla banera prawego: szerokość 30%

    • display: flex na <header> i <main> - dzieci kontenera stają obok siebie w jednym rzędzie. Banery w nagłówku, blok lewy i prawy w części głównej; stopka jest poza oboma kontenerami, więc stoi pod nimi bez clear,
    • wspólne cechy obu banerów w jednej regule, szerokości 70% i 30% osobno - razem 100% rzędu.

    Szerokości są na samych blokach, bo tak opisuje je klucz („ustawiono szerokość banera lewego… 70%”).

    styl5.css CSS zmienione: 21 linii
    :root {
        --dark: #003d33;
        --main: #26a69a;
        --hover: #004c40;
    }
    
    * {
        font-family: Tahoma;
    }
    
    body {
        margin: 0;
    }
    
    header,
    main {
        display: flex;
    }
    
    #banner-left,
    #banner-right {
        background-color: var(--dark);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    #banner-left {
        width: 70%;
    }
    
    #banner-right {
        width: 30%;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  23. Krok 23: Blok lewy, prawy i stopka

    Kryteria: , , , ,

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

    R.4.2 Ustawiono kolor tła #003d33 dla obu bloków banera i stopki, #26a69a dla bloku lewego i prawego

    R.4.3 Ustawiono biały kolor czcionki dla obu bloków banera i stopki

    R.4.4 Ustawiono wysokość bloków: 50px dla obu bloków banera, 550px dla bloku lewego i prawego

    R.4.5 Ustawiono szerokość banera lewego i bloku prawego 70% oraz banera prawego i bloku lewego 30%

    ok. 3 min
    Polecenie z arkusza

    Dla bloku lewego: kolor tła #26a69a, wysokość 550 px, szerokość 30%. Dla bloku prawego: kolor tła #26a69a, wysokość 550 px, szerokość 70%. Dla bloku stopki: kolor tła #003d33, biały kolor czcionki

    Wspólne tło i wysokość bloków w jednej regule, szerokości osobno. W stopce paragraf bez marginesów - pasek ma wysokość jednej linii tekstu, jak na obrazie 2.

    Komplet cech bloków: tła (R.4.2), biały tekst banerów i stopki (R.4.3), wysokości (R.4.4) i szerokości (R.4.5). Układ jest zgodny z obrazem 2 (R.3.1) - klucz przyjmuje flex obok float i grid.

    Typowe błędy

    flex domyślnie zwęża dzieci, które się nie mieszczą (flex-shrink). Przy szerokościach sumujących się do 100% i bez marginesów nic się nie zwęża - ale padding na bloku bez box-sizing: border-box już by to zrobił.

    styl5.css CSS zmienione: 23 linie
    :root {
        --dark: #003d33;
        --main: #26a69a;
        --hover: #004c40;
    }
    
    * {
        font-family: Tahoma;
    }
    
    body {
        margin: 0;
    }
    
    header,
    main {
        display: flex;
    }
    
    #banner-left,
    #banner-right {
        background-color: var(--dark);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    #banner-left {
        width: 70%;
    }
    
    #banner-right {
        width: 30%;
    }
    
    #left,
    #right {
        background-color: var(--main);
        height: 550px;
    }
    
    #left {
        width: 30%;
    }
    
    #right {
        width: 70%;
    }
    
    footer {
        background-color: var(--dark);
        color: white;
    }
    
    footer p {
        margin: 0;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  24. Krok 24: Formularz: pole pod etykietą

    ok. 1 min
    Polecenie z arkusza

    Napis login, poniżej pole edycyjne

    Etykieta i pole są elementami liniowymi i stanęłyby obok siebie. display: block daje każdemu własną linię - pole jest poniżej napisu, jak na obrazie 2, bez znaczników <br> w HTML-u.

    styl5.css CSS zmienione: 5 linii
    :root {
        --dark: #003d33;
        --main: #26a69a;
        --hover: #004c40;
    }
    
    * {
        font-family: Tahoma;
    }
    
    body {
        margin: 0;
    }
    
    header,
    main {
        display: flex;
    }
    
    #banner-left,
    #banner-right {
        background-color: var(--dark);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    #banner-left {
        width: 70%;
    }
    
    #banner-right {
        width: 30%;
    }
    
    #left,
    #right {
        background-color: var(--main);
        height: 550px;
    }
    
    #left {
        width: 30%;
    }
    
    #right {
        width: 70%;
    }
    
    footer {
        background-color: var(--dark);
        color: white;
    }
    
    footer p {
        margin: 0;
    }
    
    .field label,
    .field input {
        display: block;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  25. Krok 25: Blok wizytówki: szerokość, marginesy, wyśrodkowanie, ramka

    Kryteria: , ,

    R.4.6 Ustawiono wyrównanie tekstu do środka dla obu bloków banera i bloku wizytówki

    R.4.7 Ustawiono marginesy zewnętrzne (margin) dla bloku wizytówki 30px

    R.4.8 Ustawiono pozostałe cechy bloku wizytówki: szerokość 400px, obramowanie linią ciągłą 1 px koloru #003d33

    ok. 2 min
    Polecenie z arkusza

    Dla bloku wizytówki: szerokość 400 px, marginesy zewnętrzne 30 px, wyrównanie tekstu do środka, obramowanie linią ciągłą 1 px koloru #003d33

    Wszystkie cechy z arkusza w jednej regule #card (R.4.6, R.4.7, R.4.8). Zdjęcie osoby (bezpośredni potomek wizytówki, #card > img - serce w nagłówku <h1> zostaje w linii z liczbą) dostaje display: block: obraz liniowy zostawia pod sobą kilka pikseli odstępu na „ogonki” liter, a blokowy przylega do tego, co pod nim.

    styl5.css CSS zmienione: 11 linii
    :root {
        --dark: #003d33;
        --main: #26a69a;
        --hover: #004c40;
    }
    
    * {
        font-family: Tahoma;
    }
    
    body {
        margin: 0;
    }
    
    header,
    main {
        display: flex;
    }
    
    #banner-left,
    #banner-right {
        background-color: var(--dark);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    #banner-left {
        width: 70%;
    }
    
    #banner-right {
        width: 30%;
    }
    
    #left,
    #right {
        background-color: var(--main);
        height: 550px;
    }
    
    #left {
        width: 30%;
    }
    
    #right {
        width: 70%;
    }
    
    footer {
        background-color: var(--dark);
        color: white;
    }
    
    footer p {
        margin: 0;
    }
    
    .field label,
    .field input {
        display: block;
    }
    
    #card {
        width: 400px;
        margin: 30px;
        text-align: center;
        border: 1px solid var(--dark);
    }
    
    #card > img {
        display: block;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  26. Krok 26: Przycisk w wizytówce i jego tło po najechaniu

    Kryteria: ,

    R.4.9 Ustawiono styl dla przycisku w bloku wizytówki (dowolną metodą, tak aby tylko ten przycisk był formatowany stylem) kolor tła #003d33, biały kolor czcionki, szerokość 100%, wysokość 30 px, bez obramowania

    R.4.10 Gdy kursor myszy znajdzie się na przycisku w bloku wizytówki jego tło zamienia się na #004c40, np. za pomocą pseudoklasy hover

    ok. 2 min
    Polecenie z arkusza

    Dla przycisku w wizytówce: kolor tła #003d33, biały kolor czcionki, szerokość 100%, wysokość 30 px, bez obramowania. Po najechaniu na przycisk w wizytówce jego tło zamienia się na #004c40

    Selektor #card button trafia tylko w przycisk wewnątrz wizytówki - „Zaloguj” jest w bloku lewym i zostaje bez stylu (R.4.9). cursor: pointer pokazuje rączkę jak nad odnośnikiem (klucz tego nie ocenia), a :hover zmienia tło na var(--hover) (R.4.10).

    Typowe błędy

    Samo button sformatowałoby też przycisk „Zaloguj” - od tego arkusza w tej wersji jest on znacznikiem <button>, więc selektor musi wskazać wizytówkę.

    styl5.css CSS zmienione: 13 linii
    :root {
        --dark: #003d33;
        --main: #26a69a;
        --hover: #004c40;
    }
    
    * {
        font-family: Tahoma;
    }
    
    body {
        margin: 0;
    }
    
    header,
    main {
        display: flex;
    }
    
    #banner-left,
    #banner-right {
        background-color: var(--dark);
        color: white;
        text-align: center;
        height: 50px;
    }
    
    #banner-left {
        width: 70%;
    }
    
    #banner-right {
        width: 30%;
    }
    
    #left,
    #right {
        background-color: var(--main);
        height: 550px;
    }
    
    #left {
        width: 30%;
    }
    
    #right {
        width: 70%;
    }
    
    footer {
        background-color: var(--dark);
        color: white;
    }
    
    footer p {
        margin: 0;
    }
    
    .field label,
    .field input {
        display: block;
    }
    
    #card {
        width: 400px;
        margin: 30px;
        text-align: center;
        border: 1px solid var(--dark);
    }
    
    #card > img {
        display: block;
    }
    
    #card button {
        background-color: var(--dark);
        color: white;
        width: 100%;
        height: 30px;
        border: none;
        cursor: pointer;
    }
    
    #card button:hover {
        background-color: var(--hover);
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  27. Etap 4: Skrypty PHP

    Kroki 27-36 · po tym etapie masz 39 z 39 kryteriów (pkt)

    Krok 27: PHP na górze pliku i połączenie z bazą portal

    Kryteria:

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

    ok. 4 min
    Polecenie z arkusza

    Skrypt łączy się z serwerem bazy danych na localhost, użytkownik root bez hasła, baza danych o nazwie portal

    Kod PHP stoi na samej górze uzytkownicy.php, przed <!DOCTYPE html>: najpierw przygotowuje wszystkie dane, a HTML niżej tylko je wypisuje. Arkusz mówi o dwóch skryptach - oba są w tym jednym bloku, opisane komentarzami.

    • e() - krótka funkcja z htmlspecialchars: każdy tekst z bazy i z formularza wypisywany na stronie zamienia <, >, & i cudzysłowy na encje,
    • new PDO(...) - serwer localhost, baza portal 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, w wizytówce pojawi się zwykłe zdanie, a nie komunikat PHP z danymi serwera.

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

    uzytkownicy.php PHP zmienione: 17 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $usersCount = null;
    $message = null;
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=portal;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]);
    } catch (PDOException $exception) {
        $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
                </article>
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  28. Krok 28: Skrypt 1: liczba użytkowników w nagłówku h5

    Kryteria: ,

    R.5.3 Skrypt 1 wysyła do bazy zapytanie 2 lub skrypt 2 wysyła zapytanie 1 i 3 zmodyfikowane tak, że w klauzuli warunku sprawdzony jest login przesłany z formularza

    R.5.4 Skrypt 1 wyświetla liczbę użytkowników znajdujących się w bazie (rozwiązanie ma charakter uniwersalny) według wzoru: „Liczba użytkowników portalu: <zwrocona_wartosc>”

    ok. 3 min
    Polecenie z arkusza

    Działanie skryptu 1: Wysyła do bazy danych zapytanie 2. Wartość zwróconą zapytaniem wypisuje w nagłówku piątego stopnia, według wzoru: „Liczba użytkowników portalu: <zwrocona_wartosc>”

    • $pdo->query('SELECT COUNT(*) FROM dane;') - zapytanie 2 bez żadnych danych z zewnątrz, więc wystarczy zwykłe query (R.5.3),
    • fetchColumn() - pierwsza kolumna pierwszego wiersza, czyli sama liczba; (int) zamienia napis z bazy na liczbę całkowitą,
    • w banerze prawym: jeśli liczba jest, wypisz <h5> według wzoru z arkusza. <?= … ?> to skrót od <?php echo … ?>.

    Podgląd: „Liczba użytkowników portalu: 11”. Liczba pochodzi z bazy, więc rozwiązanie jest uniwersalne (R.5.4).

    uzytkownicy.php PHP zmienione: 6 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $usersCount = null;
    $message = null;
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=portal;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]);
    
        // skrypt 1 - zapytanie 2
        $usersCount = (int) $pdo->query('SELECT COUNT(*) FROM dane;')->fetchColumn();
    } catch (PDOException $exception) {
        $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
    <?php if ($usersCount !== null): ?>
                <h5>Liczba użytkowników portalu: <?= $usersCount ?></h5>
    <?php endif; ?>
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
                </article>
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  29. Krok 29: Skrypt 2: dane z formularza i login w polu

    ok. 3 min
    Polecenie z arkusza

    Działanie skryptu 2: Skrypt wykonuje akcje tylko wtedy, jeśli dane wysłane z formularza są niepuste

    • $_SERVER['REQUEST_METHOD'] === 'POST' - skrypt 2 pracuje tylko po wysłaniu formularza,
    • $_POST['login'] ?? '' - operator ?? daje pusty napis, gdy pola w ogóle nie ma w żądaniu (bez ostrzeżenia PHP),
    • trim obcina spacje z brzegów loginu - „ Heniek ” to ten sam użytkownik. Hasła się nie obcina: spacja może być jego częścią,
    • pole loginu dostaje value="<?= e($login) ?>" - po wysłaniu formularza wpisany login zostaje w polu, a e() pilnuje, żeby cudzysłów w loginie nie zamknął atrybutu.

    Podgląd: formularz wysłany z danymi z obrazu 2 (Heniek, hasło iii z tabeli 1 arkusza) - login stoi w polu, wizytówka jeszcze pusta.

    uzytkownicy.php PHP zmienione: 8 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $usersCount = null;
    $message = null;
    $login = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=portal;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]);
    
        // skrypt 1 - zapytanie 2
        $usersCount = (int) $pdo->query('SELECT COUNT(*) FROM dane;')->fetchColumn();
    
        // skrypt 2 - tylko dla niepustych danych z formularza
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $login = trim($_POST['login'] ?? '');
            $password = $_POST['password'] ?? '';
        }
    } catch (PDOException $exception) {
        $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
    <?php if ($usersCount !== null): ?>
                <h5>Liczba użytkowników portalu: <?= $usersCount ?></h5>
    <?php endif; ?>
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
                </article>
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  30. Krok 30: Skrypt 2: zapytanie 1 przygotowane i „login nie istnieje”

    Kryteria:

    R.5.5 Skrypt 2 wyświetla komunikat "login nie istnieje", jeżeli podany formularzem login nie istnieje w bazie

    ok. 3 min
    Polecenie z arkusza

    Skrypt wysyła do bazy zmodyfikowane zapytanie 1, tak, że w klauzuli warunku jest sprawdzony login przesłany przez formularz. Jeżeli login nie istnieje w bazie, wyświetlony jest komunikat „login nie istnieje”

    • $login !== '' && $password !== '' - oba pola niepuste. Porównanie z pustym napisem, a nie empty(): empty('0') jest prawdziwe, więc hasło „0” byłoby uznane za puste,
    • zapytanie 1 jako zapytanie przygotowane: w miejscu loginu stoi nazwany parametr :login, a wartość przychodzi osobno w execute. Login z apostrofem albo próba wstrzyknięcia SQL zostają zwykłym tekstem,
    • fetchColumn() zwraca skrót hasła albo false, gdy żaden wiersz nie pasuje - wtedy komunikat „login nie istnieje” (R.5.5),
    • w wizytówce: jeśli jest komunikat, wypisz go w paragrafie.

    Podgląd: login Henio - „login nie istnieje”.

    uzytkownicy.php PHP zmienione: 13 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $usersCount = null;
    $message = null;
    $login = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=portal;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]);
    
        // skrypt 1 - zapytanie 2
        $usersCount = (int) $pdo->query('SELECT COUNT(*) FROM dane;')->fetchColumn();
    
        // skrypt 2 - tylko dla niepustych danych z formularza
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $login = trim($_POST['login'] ?? '');
            $password = $_POST['password'] ?? '';
    
            if ($login !== '' && $password !== '') {
                $statement = $pdo->prepare('SELECT haslo FROM uzytkownicy WHERE login = :login;');
                $statement->execute(['login' => $login]);
                $hash = $statement->fetchColumn();
    
                if ($hash === false) {
                    $message = 'login nie istnieje';
                }
            }
        }
    } catch (PDOException $exception) {
        $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
    <?php if ($usersCount !== null): ?>
                <h5>Liczba użytkowników portalu: <?= $usersCount ?></h5>
    <?php endif; ?>
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
    <?php if ($message !== null): ?>
                    <p><?= e($message) ?></p>
    <?php endif; ?>
                </article>
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  31. Krok 31: Skrypt 2: sprawdzenie hasła funkcją sha1

    Kryteria:

    R.5.6 Skrypt 2 wyświetla komunikat "hasło nieprawidłowe", jeżeli dla loginu nie zgadza się hasło podane formularzem z hasłem w bazie - zastosowano funkcję sha1 do porównania szyfrowanego hasła

    ok. 2 min
    Polecenie z arkusza

    Jeżeli login istnieje jest sprawdzana zgodność przesłanego formularzem hasła z hasłem przechowywanym w bazie. Należy zastosować funkcję szyfrującą dane algorytmem SHA-1. Jeżeli hasła nie zgadzają się, wyświetlony jest komunikat „hasło nieprawidłowe”

    W bazie jest skrót SHA-1, więc sha1($password) liczy skrót hasła z formularza (R.5.6). Do porównania dwóch skrótów służy hash_equals: zwykłe != kończy porównanie na pierwszym różnym znaku, więc czas odpowiedzi zdradza, ile początkowych znaków się zgadza. hash_equals porównuje zawsze cały napis w tym samym czasie.

    Podgląd: Heniek z hasłem abc - „hasło nieprawidłowe”.

    uzytkownicy.php PHP zmienione: 2 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $usersCount = null;
    $message = null;
    $login = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=portal;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]);
    
        // skrypt 1 - zapytanie 2
        $usersCount = (int) $pdo->query('SELECT COUNT(*) FROM dane;')->fetchColumn();
    
        // skrypt 2 - tylko dla niepustych danych z formularza
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $login = trim($_POST['login'] ?? '');
            $password = $_POST['password'] ?? '';
    
            if ($login !== '' && $password !== '') {
                $statement = $pdo->prepare('SELECT haslo FROM uzytkownicy WHERE login = :login;');
                $statement->execute(['login' => $login]);
                $hash = $statement->fetchColumn();
    
                if ($hash === false) {
                    $message = 'login nie istnieje';
                } elseif (!hash_equals($hash, sha1($password))) {
                    $message = 'hasło nieprawidłowe';
                }
            }
        }
    } catch (PDOException $exception) {
        $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
    <?php if ($usersCount !== null): ?>
                <h5>Liczba użytkowników portalu: <?= $usersCount ?></h5>
    <?php endif; ?>
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
    <?php if ($message !== null): ?>
                    <p><?= e($message) ?></p>
    <?php endif; ?>
                </article>
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  32. Krok 32: Skrypt 2: zapytanie 3 i zdjęcie z tekstem „osoba”

    ok. 3 min
    Polecenie z arkusza

    Jeżeli hasła się zgadzają to skrypt wysyła do bazy zmodyfikowane zapytanie 3 tak, że w klauzuli warunku podany jest login przesłany formularzem. Następnie wyświetla dane w bloku wizytówki: Obraz na podstawie nazwy pliku z tekstem alternatywnym „osoba”

    • zapytanie 3 też przygotowane, z tym samym parametrem :login,
    • fetch(PDO::FETCH_ASSOC) - wiersz jako tablica z nazwami kolumn: $profile['zdjecie'] zamiast $row[4]. Kod mówi, co wypisuje, i nie psuje się po zmianie kolejności kolumn w zapytaniu,
    • $profile = null na początku pliku - wizytówka pokazuje dane tylko wtedy, gdy skrypt je ustawił,
    • w wizytówce gałąź elseif: zdjęcie z src z bazy (przez e()) i alt="osoba".

    Podgląd: Heniek z hasłem iii - jego zdjęcie o3.jpg.

    uzytkownicy.php PHP zmienione: 7 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $usersCount = null;
    $message = null;
    $login = '';
    $profile = null;
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=portal;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]);
    
        // skrypt 1 - zapytanie 2
        $usersCount = (int) $pdo->query('SELECT COUNT(*) FROM dane;')->fetchColumn();
    
        // skrypt 2 - tylko dla niepustych danych z formularza
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $login = trim($_POST['login'] ?? '');
            $password = $_POST['password'] ?? '';
    
            if ($login !== '' && $password !== '') {
                $statement = $pdo->prepare('SELECT haslo FROM uzytkownicy WHERE login = :login;');
                $statement->execute(['login' => $login]);
                $hash = $statement->fetchColumn();
    
                if ($hash === false) {
                    $message = 'login nie istnieje';
                } elseif (!hash_equals($hash, sha1($password))) {
                    $message = 'hasło nieprawidłowe';
                } else {
                    $statement = $pdo->prepare('SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = :login;');
                    $statement->execute(['login' => $login]);
                    $profile = $statement->fetch(PDO::FETCH_ASSOC);
                }
            }
        }
    } catch (PDOException $exception) {
        $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
    <?php if ($usersCount !== null): ?>
                <h5>Liczba użytkowników portalu: <?= $usersCount ?></h5>
    <?php endif; ?>
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
    <?php if ($message !== null): ?>
                    <p><?= e($message) ?></p>
    <?php elseif ($profile !== null): ?>
                    <img src="<?= e($profile['zdjecie']) ?>" alt="osoba">
    <?php endif; ?>
                </article>
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  33. Krok 33: Skrypt 2: login i wiek w nagłówku h4

    Kryteria:

    R.5.8 Skrypt 2 oblicza wiek na podstawie roku urodzenia z bazy i aktualnej daty. Np. $wiek = date('Y') - $wiersz[1]; (dopuszcza się jawne użycie roku 2022)

    ok. 2 min
    Polecenie z arkusza

    Poniżej w nagłówku czwartego stopnia login oraz w nawiasie wiek danej osoby obliczony na podstawie roku urodzenia (pole rok_urodz)

    Wiek liczy skrypt na górze, zanim zacznie się HTML: date('Y') (bieżący rok) minus rok urodzenia z bazy, oba zamienione na liczby całkowite (R.5.8). Wynik trafia do tej samej tablicy pod kluczem wiek. W wizytówce <h4> z loginem i wiekiem w nawiasie.

    Podgląd: Heniek (36). Na obrazie 2 jest „Heniek (29)”, ale Heniek ma w bazie rok 1990 - w 2022 roku miał 32 lata, więc ilustracja nie zgadza się z danymi z archiwum.

    uzytkownicy.php PHP zmienione: 2 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $usersCount = null;
    $message = null;
    $login = '';
    $profile = null;
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=portal;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]);
    
        // skrypt 1 - zapytanie 2
        $usersCount = (int) $pdo->query('SELECT COUNT(*) FROM dane;')->fetchColumn();
    
        // skrypt 2 - tylko dla niepustych danych z formularza
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $login = trim($_POST['login'] ?? '');
            $password = $_POST['password'] ?? '';
    
            if ($login !== '' && $password !== '') {
                $statement = $pdo->prepare('SELECT haslo FROM uzytkownicy WHERE login = :login;');
                $statement->execute(['login' => $login]);
                $hash = $statement->fetchColumn();
    
                if ($hash === false) {
                    $message = 'login nie istnieje';
                } elseif (!hash_equals($hash, sha1($password))) {
                    $message = 'hasło nieprawidłowe';
                } else {
                    $statement = $pdo->prepare('SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = :login;');
                    $statement->execute(['login' => $login]);
                    $profile = $statement->fetch(PDO::FETCH_ASSOC);
                    $profile['wiek'] = (int) date('Y') - (int) $profile['rok_urodz'];
                }
            }
        }
    } catch (PDOException $exception) {
        $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
    <?php if ($usersCount !== null): ?>
                <h5>Liczba użytkowników portalu: <?= $usersCount ?></h5>
    <?php endif; ?>
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
    <?php if ($message !== null): ?>
                    <p><?= e($message) ?></p>
    <?php elseif ($profile !== null): ?>
                    <img src="<?= e($profile['zdjecie']) ?>" alt="osoba">
                    <h4><?= e($profile['login']) ?> (<?= $profile['wiek'] ?>)</h4>
    <?php endif; ?>
                </article>
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  34. Krok 34: Skrypt 2: hobby i serce z liczbą przyjaciół

    Kryteria: ,

    R.2.8 W bloku prawym umieszczono dwa obrazy. Pierwszy ma tekst alternatywny o treści „osoba”, drugi wyświetla obraz serca (za pomocą skryptu lub w HTML)

    R.5.9 W skrypcie 1 komunikat jest zapisany w nagłówku h5 oraz w skrypcie 2 login zapisany jest w nagłówku h4, hobby w paragrafie, liczba przyjaciół w nagłówku h1

    ok. 2 min
    Polecenie z arkusza

    Poniżej w paragrafie hobby, poprzedzone napisem „hobby: ”. Poniżej w nagłówku pierwszego stopnia obraz serca wypakowany z archiwum (icon-on.png) oraz liczbę przyjaciół pobraną z bazy (pole przyjaciol)

    Paragraf z napisem „hobby: ” i nagłówek <h1> z obrazem serca (tekst alternatywny „serce”) i liczbą przyjaciół. W bloku prawym są dwa obrazy (R.2.8), a napisy mają znaczniki z arkusza: <h5>, <h4>, <p> i <h1> (R.5.9).

    Podgląd: „hobby: film” i serce z liczbą 20.

    uzytkownicy.php PHP zmienione: 2 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $usersCount = null;
    $message = null;
    $login = '';
    $profile = null;
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=portal;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]);
    
        // skrypt 1 - zapytanie 2
        $usersCount = (int) $pdo->query('SELECT COUNT(*) FROM dane;')->fetchColumn();
    
        // skrypt 2 - tylko dla niepustych danych z formularza
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $login = trim($_POST['login'] ?? '');
            $password = $_POST['password'] ?? '';
    
            if ($login !== '' && $password !== '') {
                $statement = $pdo->prepare('SELECT haslo FROM uzytkownicy WHERE login = :login;');
                $statement->execute(['login' => $login]);
                $hash = $statement->fetchColumn();
    
                if ($hash === false) {
                    $message = 'login nie istnieje';
                } elseif (!hash_equals($hash, sha1($password))) {
                    $message = 'hasło nieprawidłowe';
                } else {
                    $statement = $pdo->prepare('SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = :login;');
                    $statement->execute(['login' => $login]);
                    $profile = $statement->fetch(PDO::FETCH_ASSOC);
                    $profile['wiek'] = (int) date('Y') - (int) $profile['rok_urodz'];
                }
            }
        }
    } catch (PDOException $exception) {
        $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
    <?php if ($usersCount !== null): ?>
                <h5>Liczba użytkowników portalu: <?= $usersCount ?></h5>
    <?php endif; ?>
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
    <?php if ($message !== null): ?>
                    <p><?= e($message) ?></p>
    <?php elseif ($profile !== null): ?>
                    <img src="<?= e($profile['zdjecie']) ?>" alt="osoba">
                    <h4><?= e($profile['login']) ?> (<?= $profile['wiek'] ?>)</h4>
                    <p>hobby: <?= e($profile['hobby']) ?></p>
                    <h1><img src="icon-on.png" alt="serce"> <?= e($profile['przyjaciol']) ?></h1>
    <?php endif; ?>
                </article>
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  35. Krok 35: Skrypt 2: przycisk „Więcej informacji”

    Kryteria: ,

    R.3.2 Na stronie umieszczono odnośnik zawierający przycisk o treści „Więcej informacji”, którego kliknięcie powoduje przejście do strony dane.html

    R.5.7 Skrypt 2, tylko jeżeli login i hasło się zgadzają wyświetla dane odpowiadające loginowi w bloku wizytówka: obraz, login, hobby, liczba przyjaciół

    ok. 2 min
    Polecenie z arkusza

    Poniżej przycisk o treści „Więcej informacji” prowadzący do pliku dane.html.

    Klucz opisuje ten element dokładnie: odnośnik zawierający przycisk (R.3.2) - więc <a href="dane.html"> z <button> w środku, jak w wersji podstawowej. Przycisk ma type="button", żeby nigdy nie wysyłał formularza; przejście do dane.html robi odnośnik.

    Specyfikacja HTML nie pozwala wkładać przycisku do odnośnika (dwa elementy interaktywne jeden w drugim). Poza egzaminem wystarczy sam odnośnik ostylowany jak przycisk: <a class="button" href="dane.html"> - tu jednak kryterium wymaga przycisku w odnośniku, więc zostaje forma z klucza.

    Wizytówka jest kompletna (R.5.7) i pokazuje się tylko po poprawnym logowaniu.

    uzytkownicy.php PHP zmienione: 1 linia
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $usersCount = null;
    $message = null;
    $login = '';
    $profile = null;
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=portal;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]);
    
        // skrypt 1 - zapytanie 2
        $usersCount = (int) $pdo->query('SELECT COUNT(*) FROM dane;')->fetchColumn();
    
        // skrypt 2 - tylko dla niepustych danych z formularza
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $login = trim($_POST['login'] ?? '');
            $password = $_POST['password'] ?? '';
    
            if ($login !== '' && $password !== '') {
                $statement = $pdo->prepare('SELECT haslo FROM uzytkownicy WHERE login = :login;');
                $statement->execute(['login' => $login]);
                $hash = $statement->fetchColumn();
    
                if ($hash === false) {
                    $message = 'login nie istnieje';
                } elseif (!hash_equals($hash, sha1($password))) {
                    $message = 'hasło nieprawidłowe';
                } else {
                    $statement = $pdo->prepare('SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = :login;');
                    $statement->execute(['login' => $login]);
                    $profile = $statement->fetch(PDO::FETCH_ASSOC);
                    $profile['wiek'] = (int) date('Y') - (int) $profile['rok_urodz'];
                }
            }
        }
    } catch (PDOException $exception) {
        $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
    <?php if ($usersCount !== null): ?>
                <h5>Liczba użytkowników portalu: <?= $usersCount ?></h5>
    <?php endif; ?>
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
    <?php if ($message !== null): ?>
                    <p><?= e($message) ?></p>
    <?php elseif ($profile !== null): ?>
                    <img src="<?= e($profile['zdjecie']) ?>" alt="osoba">
                    <h4><?= e($profile['login']) ?> (<?= $profile['wiek'] ?>)</h4>
                    <p>hobby: <?= e($profile['hobby']) ?></p>
                    <h1><img src="icon-on.png" alt="serce"> <?= e($profile['przyjaciol']) ?></h1>
                    <a href="dane.html"><button type="button">Więcej informacji</button></a>
    <?php endif; ?>
                </article>
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  36. Krok 36: 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 w bloku finally, który wykonuje się zawsze po try - po udanym logowaniu, po błędzie i po wyjątku. Zamknięcie jest więc ostatnią operacją na bazie w każdej gałęzi (R.5.2), a cały HTML niżej korzysta już tylko ze zmiennych.

    Podgląd: stan z obrazu 2.

    uzytkownicy.php PHP zmienione: 3 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $usersCount = null;
    $message = null;
    $login = '';
    $profile = null;
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=portal;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]);
    
        // skrypt 1 - zapytanie 2
        $usersCount = (int) $pdo->query('SELECT COUNT(*) FROM dane;')->fetchColumn();
    
        // skrypt 2 - tylko dla niepustych danych z formularza
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $login = trim($_POST['login'] ?? '');
            $password = $_POST['password'] ?? '';
    
            if ($login !== '' && $password !== '') {
                $statement = $pdo->prepare('SELECT haslo FROM uzytkownicy WHERE login = :login;');
                $statement->execute(['login' => $login]);
                $hash = $statement->fetchColumn();
    
                if ($hash === false) {
                    $message = 'login nie istnieje';
                } elseif (!hash_equals($hash, sha1($password))) {
                    $message = 'hasło nieprawidłowe';
                } else {
                    $statement = $pdo->prepare('SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = :login;');
                    $statement->execute(['login' => $login]);
                    $profile = $statement->fetch(PDO::FETCH_ASSOC);
                    $profile['wiek'] = (int) date('Y') - (int) $profile['rok_urodz'];
                }
            }
        }
    } catch (PDOException $exception) {
        $message = 'nie udało się połączyć z bazą danych, spróbuj ponownie za chwilę';
    } finally {
        $statement = null;
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h2>Nasze osiedle</h2>
            </section>
            <section id="banner-right">
    <?php if ($usersCount !== null): ?>
                <h5>Liczba użytkowników portalu: <?= $usersCount ?></h5>
    <?php endif; ?>
            </section>
        </header>
        <main>
            <section id="left">
                <h3>Logowanie</h3>
                <form method="post">
                    <div class="field">
                        <label for="login">login</label>
                        <input type="text" id="login" name="login" maxlength="50" autocomplete="username" value="<?= e($login) ?>">
                    </div>
                    <div class="field">
                        <label for="password">hasło</label>
                        <input type="password" id="password" name="password" autocomplete="current-password">
                    </div>
                    <button type="submit">Zaloguj</button>
                </form>
            </section>
            <section id="right">
                <h3>Wizytówka</h3>
                <article id="card">
    <?php if ($message !== null): ?>
                    <p><?= e($message) ?></p>
    <?php elseif ($profile !== null): ?>
                    <img src="<?= e($profile['zdjecie']) ?>" alt="osoba">
                    <h4><?= e($profile['login']) ?> (<?= $profile['wiek'] ?>)</h4>
                    <p>hobby: <?= e($profile['hobby']) ?></p>
                    <h1><img src="icon-on.png" alt="serce"> <?= e($profile['przyjaciol']) ?></h1>
                    <a href="dane.html"><button type="button">Więcej informacji</button></a>
    <?php endif; ?>
                </article>
            </section>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  37. Etap 5: Porównanie z wersją podstawową

    Krok 37 · po tym etapie masz 39 z 39 kryteriów (pkt)

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

    ok. 3 min

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

    wersja podstawowawersja nowoczesna
    blokicztery <section> i <footer>to samo w <header> i <main>
    układfloat: left sekcji, clear stopkidisplay: flex na <header> i <main>
    kolorywpisane w każdej regulezmienne --dark, --main, --hover
    formularznapis, pole, <br>label for, wiersze w <div>, maxlength, autocomplete
    bibliotekamysqli (tabela 2 arkusza)PDO z wyjątkami
    gdzie jest skryptdwa bloki w banerze i w wizytówcejeden blok na górze pliku, w HTML-u tylko wypisanie
    login w zapytaniachwklejony w treść zapytaniaparametr :login
    porównanie hasłasha1($password) != $row[0]hash_equals
    dane w wizytówce$row[4], wprost$profile['zdjecie'] przez e()
    po wysłaniupuste polalogin zostaje w polu
    awaria bazy„Fatal error” z danymi serwerazdanie w wizytówce, reszta strony działa

    Obie wersje zostawiają rzeczy wymagane przez arkusz, których prawdziwy serwis by nie zrobił: SHA-1 zamiast password_hash (szybki skrót bez soli), dwa różne komunikaty zdradzające, które loginy istnieją, i przycisk wewnątrz odnośnika. Po udanym logowaniu prawdziwa strona zapamiętałaby też użytkownika w sesji (session_start, $_SESSION) - tu wizytówka znika po odświeżeniu, bo arkusz każe tylko wyświetlić dane.

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 2 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie wywraca się na apostrofie w loginie, nie przepuszcza wstrzyknięcia SQL i nie gubi loginu po błędzie - a klucz przyjmuje obie.

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

  38. Etap 6: Oddanie pracy

    Krok 38 · po tym etapie masz 39 z 39 kryteriów (pkt)

    Krok 38: 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: dane.html, egzamin.jpg, kw1.png - kw4.png, kwerendy.txt, o1.jpg - o8.jpg, icon-on.png, przeglądarka.txt, styl5.css i uzytkownicy.php. Nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

Podgląd

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

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

Potwierdzenie

Informacja