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

Rozwiązanie krok po kroku: Logowanie do portalu osiedlowego (Wersja podstawowa)

Strona logowania do portalu osiedlowego: baza portal z dwiema tabelami połączonymi kluczami głównymi (uzytkownicy z loginem i hasłem zaszyfrowanym SHA-1 oraz dane z rokiem urodzenia, liczbą przyjaciół, hobby i zdjęciem), cztery zapytania - hasło Justyny, liczba wierszy (COUNT), dane Justyny z dwóch tabel (JOIN) i nowa kolumna (ALTER TABLE). Do tego zdjęcie przycięte do 400 × 200 px, strona uzytkownicy.php z dwoma banerami, formularzem logowania i blokiem wizytówki, style w styl5.css oraz dwa skrypty PHP: pierwszy wypisuje liczbę użytkowników portalu, drugi sprawdza login i hasło (funkcja sha1) i pokazuje wizytówkę zalogowanej osoby.

To arkusz EE.09 ze stycznia 2022 - kwalifikacji formuły 2017, którą zastąpiła INF.03. Kroki idą zdanie po zdaniu za treścią arkusza - przy każdym jest polecenie z arkusza, a kod pokazuje dokładnie te linie, które je spełniają. Skrypty są najpierw napisane najprościej, funkcjami mysqli z tabeli 2 arkusza, a na końcu sprawdzone na prawdziwym wstrzyknięciu SQL i zabezpieczone.

To jest wersja podstawowa. W zakładce „Nowoczesny kod (flex, PDO)” jest drugi sposób rozwiązania tego samego arkusza: semantyczne znaczniki z układem flex, etykiety <label>, PDO z zapytaniami przygotowanymi na górze pliku i login, który zostaje w polu po wysłaniu formularza - obie wersje są zgodne z kluczem.

Baza i zdjęcia pochodzą z oryginalnego archiwum CKE pliki5 (załącznik do pobrania na stronie arkusza, hasło w treści zadania). Zapytania i zrzuty są policzone na MariaDB 10.4 - tej samej, która jest w XAMPP. Podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 (klucz każe sprawdzać w XAMPP 8.0.7 - użyte funkcje działają w obu wersjach tak samo). Wiek w wizytówce liczy się od bieżącego roku, więc w podglądzie jest inny niż na obrazie 2 arkusza.

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 (75% z 39 to 29,25, zaokrąglone w górę) - można stracić 9 kryteriów i nadal zdać. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.

  1. Etap 1: Baza danych

    Kroki 1-7 · po tym etapie masz 6 z 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-23 · 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

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Witrynę zapisano w pliku uzytkownicy.php

    Plik uzytkownicy.php w folderze zdającego, obok zdjęć. Rozszerzenie .php, bo dojdą dwa skrypty - na razie to czysty HTML. <!DOCTYPE html> oznacza HTML5, lang="pl" mówi przeglądarce, że treść jest po polsku. Nazwę pliku sprawdza klucz (R.2.2).

    uzytkownicy.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  11. Krok 11: Kodowanie polskich znaków

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Zastosowany właściwy standard kodowania polskich znaków

    <meta charset="UTF-8"> jako pierwsza rzecz w <head>, a sam plik zapisany w UTF-8 (w Notepad++ menu Kodowanie). Bez tego „społecznościowy” i „Wizytówka” mogą wyświetlić się z krzakami.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  12. Krok 12: 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> - napis na karcie przeglądarki. Nie myl go z nagłówkiem banera („Nasze osiedle”).

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  13. Krok 13: 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"> w <head>. Nazwa z cyfrą 5 - tak jak numer zadania. Plik stylów powstanie po HTML-u.

    Typowe błędy

    Klucz uznaje R.3.5 tylko przy działającym połączeniu, w którym każdy selektor trafia w swój blok - kropka zamiast # przy identyfikatorze odbiera punkt.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  14. Krok 14: Bloki: dwa banery, lewy, prawy i 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

    Pięć bloków w kolejności, w jakiej ułoży je CSS: dwa banery, blok lewy, blok prawy i stopka. Cztery pierwsze to znaczniki <section> z identyfikatorami banner-left, banner-right, left i right (nazwy po angielsku - arkusz ich nie narzuca), stopka - <footer>. Identyfikatory są potrzebne, bo każdy blok ma inną szerokość. Na razie bloki stoją jeden pod drugim; obok siebie ustawi je CSS.

    uzytkownicy.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
        </section>
        <section id="banner-right">
        </section>
        <section id="left">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  15. Krok 15: Baner lewy: nagłówek drugiego stopnia

    ok. 1 min
    Polecenie z arkusza

    Zawartość banera lewego: nagłówek drugiego stopnia o treści „Nasze osiedle”

    Drugiego stopnia to <h2>. W banerze prawym na razie nic - wstawi tam liczbę użytkowników skrypt 1.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
        </section>
        <section id="left">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  16. Krok 16: Blok lewy: nagłówek „Logowanie”

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku lewego: Nagłówek trzeciego stopnia o treści „Logowanie”

    Trzeciego stopnia to <h3>.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
        </section>
        <section id="left">
            <h3>Logowanie</h3>
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  17. Krok 17: 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 formularz wysyła dane pod ten sam adres, z którego został wczytany, czyli do uzytkownicy.php (klucz R.3.3 przyjmuje to wprost). Metoda POST przesyła dane w treści żądania - hasło nie pojawi się w pasku adresu. W PHP odczytasz je z tablicy $_POST.

    Typowe błędy

    method="get" dopisze login i hasło do adresu strony - a skrypt czytający $_POST niczego nie dostanie.

    uzytkownicy.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
            </form>
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  18. Krok 18: Napis „login”, pod nim pole edycyjne

    ok. 1 min
    Polecenie z arkusza

    Napis login, poniżej pole edycyjne

    Napis login, a poniżej pole - stąd <br> po napisie i po polu (na obrazie 2 każdy element formularza stoi w osobnej linii). Atrybut name="login" to klucz, pod którym wartość trafi do PHP: $_POST['login'].

    uzytkownicy.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
            </form>
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  19. Krok 19: Napis „hasło”, pod nim 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

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

    uzytkownicy.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
            </form>
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  20. Krok 20: 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

    <input type="submit" value="Zaloguj"> wysyła formularz. Formularz ma komplet: dwa pola edycyjne i przycisk (R.2.7), pole hasła jest typu password, a przycisk wysyła dane (R.3.4).

    Typowe błędy

    <input type="button"> wygląda tak samo, ale niczego nie wysyła - brak punktu R.3.4, a skrypt 2 nigdy nie dostanie danych.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  21. Krok 21: Blok prawy: nagłówek „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ść bloku prawego: Nagłówek trzeciego stopnia o treści „Wizytówka”

    Drugi <h3>. Komplet nagłówków z klucza: <h2> w banerze lewym oraz <h3> w bloku lewym i prawym (R.2.6).

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  22. Krok 22: 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 blok wewnątrz bloku prawego, więc wystarczy zwykły <div> - znaczniki sekcji są dla głównych bloków strony. Identyfikator card przyda się w CSS (szerokość, ramka, styl przycisku tylko w tym bloku). Do środka skrypt 2 wypisze zdjęcie, login z wiekiem, hobby, serce z liczbą przyjaciół i przycisk.

    uzytkownicy.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
            </div>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  23. Krok 23: 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">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  24. Etap 3: Styl CSS

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

    Krok 24: Plik styl5.css: krój Tahoma dla całej strony

    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. 1 min
    Polecenie z arkusza

    Cechy formatowania CSS: Są zawarte wyłącznie w zewnętrznym arkuszu stylów. Domyślne dla całej strony: krój czcionki Tahoma

    Nowy plik styl5.css w folderze zdającego - od teraz każda reguła trafia tylko tutaj. Selektor uniwersalny * z font-family: Tahoma obejmuje też pola formularza i przyciski, które kroju po body nie dziedziczą (klucz R.4.1 przyjmuje *, body i html).

    styl5.css CSS nowy plik
    * {
        font-family: Tahoma;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  25. Krok 25: Oba banery: tło, kolor tekstu, wyśrodkowanie, wysokość

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

    Dwa selektory identyfikatorów rozdzielone przecinkiem dostają jedną wspólną regułę. #003d33 to ciemna zieleń zapisana szesnastkowo (czerwony 00, zielony 3d, niebieski 33).

    styl5.css CSS zmienione: 6 linii
    * {
        font-family: Tahoma;
    }
    #banner-left, #banner-right {
        background-color: #003d33;
        color: white;
        text-align: center;
        height: 50px;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  26. Krok 26: Szerokości banerów: 70% i 30%

    ok. 1 min
    Polecenie z arkusza

    Dodatkowo dla banera lewego: szerokość 70%. Dodatkowo dla banera prawego: szerokość 30%

    Cechy „dodatkowe” to osobne reguły dla każdego banera. 70% + 30% = 100% szerokości okna - oba banery zmieszczą się obok siebie, gdy CSS ustawi je w jednym rzędzie.

    styl5.css CSS zmienione: 6 linii
    * {
        font-family: Tahoma;
    }
    #banner-left, #banner-right {
        background-color: #003d33;
        color: white;
        text-align: center;
        height: 50px;
    }
    #banner-left {
        width: 70%;
    }
    #banner-right {
        width: 30%;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  27. Krok 27: Blok lewy: tło, wysokość, szerokość

    ok. 1 min
    Polecenie z arkusza

    Dla bloku lewego: kolor tła #26a69a, wysokość 550 px, szerokość 30%

    Selektor #left. Pod banerem prawym (30%) stoi blok lewy (30%) - szerokości „krzyżują się”: wąski baner jest po prawej, wąski blok po lewej.

    styl5.css CSS zmienione: 5 linii
    * {
        font-family: Tahoma;
    }
    #banner-left, #banner-right {
        background-color: #003d33;
        color: white;
        text-align: center;
        height: 50px;
    }
    #banner-left {
        width: 70%;
    }
    #banner-right {
        width: 30%;
    }
    #left {
        background-color: #26a69a;
        height: 550px;
        width: 30%;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  28. Krok 28: Blok prawy: tło, wysokość, szerokość

    Kryteria: ,

    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. 1 min
    Polecenie z arkusza

    Dla bloku prawego: kolor tła #26a69a, wysokość 550 px, szerokość 70%

    Ten sam kolor i wysokość co blok lewy. Wszystkie wysokości (R.4.4) i szerokości (R.4.5) bloków są ustawione: 50 px dla banerów, 550 px dla bloków; 70/30 w górnym rzędzie i 30/70 w dolnym.

    Typowe błędy

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

    styl5.css CSS zmienione: 5 linii
    * {
        font-family: Tahoma;
    }
    #banner-left, #banner-right {
        background-color: #003d33;
        color: white;
        text-align: center;
        height: 50px;
    }
    #banner-left {
        width: 70%;
    }
    #banner-right {
        width: 30%;
    }
    #left {
        background-color: #26a69a;
        height: 550px;
        width: 30%;
    }
    #right {
        background-color: #26a69a;
        height: 550px;
        width: 70%;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  29. Krok 29: Stopka: tło i kolor tekstu

    Kryteria: ,

    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

    ok. 1 min
    Polecenie z arkusza

    Dla bloku stopki: kolor tła #003d33, biały kolor czcionki

    Ten sam kolor co banery. Kolory tła wszystkich bloków (R.4.2) i biały tekst banerów i stopki (R.4.3) są ustawione.

    styl5.css CSS zmienione: 4 linie
    * {
        font-family: Tahoma;
    }
    #banner-left, #banner-right {
        background-color: #003d33;
        color: white;
        text-align: center;
        height: 50px;
    }
    #banner-left {
        width: 70%;
    }
    #banner-right {
        width: 30%;
    }
    #left {
        background-color: #26a69a;
        height: 550px;
        width: 30%;
    }
    #right {
        background-color: #26a69a;
        height: 550px;
        width: 70%;
    }
    footer {
        background-color: #003d33;
        color: white;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  30. Krok 30: Układ bloków zgodny z obrazem 2

    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)

    ok. 3 min
    Polecenie z arkusza

    Podział zrealizowany za pomocą znaczników sekcji tak, aby wygląd układu bloków strony po uruchomieniu w przeglądarce, był zgodny z Obrazem 2

    Wszystkie cztery sekcje dostają float: left - każda staje przy lewej krawędzi albo tuż za poprzednią, dopóki starcza miejsca w rzędzie. Baner lewy (70%) i prawy (30%) wypełniają pierwszy rząd, więc blok lewy (30%) zaczyna drugi rząd, a blok prawy (70%) staje obok niego. Stopka z clear: both zaczyna się dopiero pod wszystkimi pływającymi blokami.

    Klucz R.3.1 przyjmuje float z clear, flex albo grid. Wersja nowoczesna pokazuje ten sam układ na flex.

    Typowe błędy

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

    styl5.css CSS zmienione: 6 linii
    * {
        font-family: Tahoma;
    }
    #banner-left, #banner-right {
        background-color: #003d33;
        color: white;
        text-align: center;
        height: 50px;
    }
    #banner-left {
        width: 70%;
    }
    #banner-right {
        width: 30%;
    }
    #left {
        background-color: #26a69a;
        height: 550px;
        width: 30%;
    }
    #right {
        background-color: #26a69a;
        height: 550px;
        width: 70%;
    }
    footer {
        background-color: #003d33;
        color: white;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  31. Krok 31: 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

    Selektor #card. margin: 30px to margines zewnętrzny z każdej strony (R.4.7), border: 1px solid #003d33 - grubość, rodzaj linii (solid = ciągła) i kolor (R.4.8). Wyrównanie do środka dotyczy treści wizytówki - razem z banerami daje R.4.6.

    Szerokość 400 px to dokładnie szerokość zdjęć o1.jpg - o8.jpg, więc zdjęcie wypełni wizytówkę od ramki do ramki.

    styl5.css CSS zmienione: 6 linii
    * {
        font-family: Tahoma;
    }
    #banner-left, #banner-right {
        background-color: #003d33;
        color: white;
        text-align: center;
        height: 50px;
    }
    #banner-left {
        width: 70%;
    }
    #banner-right {
        width: 30%;
    }
    #left {
        background-color: #26a69a;
        height: 550px;
        width: 30%;
    }
    #right {
        background-color: #26a69a;
        height: 550px;
        width: 70%;
    }
    footer {
        background-color: #003d33;
        color: white;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    #card {
        width: 400px;
        margin: 30px;
        text-align: center;
        border: 1px solid #003d33;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  32. Krok 32: Przycisk w wizytówce

    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

    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

    Selektor potomka #card button - tylko przyciski wewnątrz wizytówki. Przycisk „Zaloguj” w formularzu jest typu <input> i leży w bloku lewym, więc ta reguła go nie dotyczy (klucz R.4.9: „tak aby tylko ten przycisk był formatowany”). border: none usuwa obramowanie, a width: 100% rozciąga przycisk na całą szerokość wizytówki.

    Typowe błędy

    Selektor samego button albo input sformatuje też przycisk logowania (albo nie trafi w żaden) - punkt R.4.9 przepada.

    styl5.css CSS zmienione: 7 linii
    * {
        font-family: Tahoma;
    }
    #banner-left, #banner-right {
        background-color: #003d33;
        color: white;
        text-align: center;
        height: 50px;
    }
    #banner-left {
        width: 70%;
    }
    #banner-right {
        width: 30%;
    }
    #left {
        background-color: #26a69a;
        height: 550px;
        width: 30%;
    }
    #right {
        background-color: #26a69a;
        height: 550px;
        width: 70%;
    }
    footer {
        background-color: #003d33;
        color: white;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    #card {
        width: 400px;
        margin: 30px;
        text-align: center;
        border: 1px solid #003d33;
    }
    #card button {
        background-color: #003d33;
        color: white;
        width: 100%;
        height: 30px;
        border: none;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  33. Krok 33: Przycisk w wizytówce po najechaniu kursorem

    Kryteria:

    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. 1 min
    Polecenie z arkusza

    Po najechaniu na przycisk w wizytówce jego tło zamienia się na #004c40

    Pseudoklasa :hover obowiązuje, dopóki kursor jest nad elementem. Reguła stoi za regułą przycisku, ale ma też wyższą szczegółowość (pseudoklasa liczy się jak klasa), więc nowe tło wygrywa (R.4.10). Przycisk pojawi się na stronie dopiero po zalogowaniu - wypisze go skrypt 2.

    styl5.css CSS zmienione: 3 linie
    * {
        font-family: Tahoma;
    }
    #banner-left, #banner-right {
        background-color: #003d33;
        color: white;
        text-align: center;
        height: 50px;
    }
    #banner-left {
        width: 70%;
    }
    #banner-right {
        width: 30%;
    }
    #left {
        background-color: #26a69a;
        height: 550px;
        width: 30%;
    }
    #right {
        background-color: #26a69a;
        height: 550px;
        width: 70%;
    }
    footer {
        background-color: #003d33;
        color: white;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    #card {
        width: 400px;
        margin: 30px;
        text-align: center;
        border: 1px solid #003d33;
    }
    #card button {
        background-color: #003d33;
        color: white;
        width: 100%;
        height: 30px;
        border: none;
    }
    #card button:hover {
        background-color: #004c40;
    }
    Podgląd strony po tym kroku (uzytkownicy.php)
  34. Etap 4: Skrypty PHP

    Kroki 34-45 · po tym etapie masz 39 z 39 kryteriów (pkt)

    Krok 34: Skrypt 1: połączenie z bazą portal

    Kryteria:

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

    ok. 3 min
    Polecenie z arkusza

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

    Pierwszy skrypt ma działać w banerze prawym („Zawartość banera prawego: efekt działania skryptu 1”) - tam więc piszesz blok PHP, między <?php a ?>. Serwer wykona go, zanim strona trafi do przeglądarki.

    Pierwsza instrukcja: mysqli_connect z tabeli 2 arkusza - kolejno serwer localhost, użytkownik root, puste hasło '' i nazwa bazy portal. Połączenie trafia do zmiennej $connection. Z tego samego połączenia skorzysta później skrypt 2 w bloku wizytówki - zmienne PHP z wcześniejszego bloku są dostępne w kolejnych blokach tego samego pliku (R.5.1).

    Od tego kroku podgląd to wynik działania strony (prawdziwe uruchomienie w PHP 8.2). Stronę sprawdzasz przez http://localhost/…, a nie otwierając plik z dysku: przeglądarka sama nie wykona PHP.

    uzytkownicy.php PHP zmienione: 9 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                }
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  35. Krok 35: Skrypt 1: zapytanie 2

    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

    ok. 2 min
    Polecenie z arkusza

    Działanie skryptu 1: Wysyła do bazy danych zapytanie 2

    • mysqli_query($connection, 'SELECT COUNT(*) FROM dane;') - zapytanie 2 z kwerendy.txt (R.5.3), wynik w $result,
    • mysqli_fetch_row($result) - pierwszy (i jedyny) wiersz wyniku jako tablica numerowana; $row[0] to liczba wierszy.

    Podgląd jeszcze się nie zmienia - liczba jest pobrana, ale nie wypisana.

    uzytkownicy.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                }
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>

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

  36. Krok 36: Skrypt 1: liczba użytkowników w nagłówku h5

    Kryteria:

    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. 2 min
    Polecenie z arkusza

    Wartość zwróconą zapytaniem wypisuje w nagłówku piątego stopnia, według wzoru: „Liczba użytkowników portalu: <zwrocona_wartosc>”

    echo z nagłówkiem <h5>. Napis jest w cudzysłowie "…", więc PHP wstawia w miejsce $row[0] wartość z bazy. Podgląd: „Liczba użytkowników portalu: 11” w banerze prawym, jak na obrazie 2. Liczba pochodzi z bazy, więc po dodaniu nowego wiersza do tabeli dane zmieni się sama - klucz wymaga, żeby rozwiązanie było uniwersalne (R.5.4).

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                }
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  37. Krok 37: Skrypt 2: działa tylko dla niepustych danych

    ok. 3 min
    Polecenie z arkusza

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

    Drugi blok PHP stoi wewnątrz <div id="card"> - wszystko, co wypisze, znajdzie się w wizytówce.

    • empty() jest prawdziwe dla pustego napisu i dla pola, którego w ogóle nie wysłano (pierwsze wejście na stronę). Dwa warunki !empty łączy && („i”): skrypt działa tylko wtedy, gdy oba pola są wypełnione,
    • login i hasło z $_POST trafiają do zmiennych o czytelnych nazwach - klucze to atrybuty name pól.

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

    Typowe błędy

    Bez tego warunku pierwsze wejście na stronę uruchomi zapytania z pustym loginem i od razu wypisze „login nie istnieje”.

    uzytkownicy.php PHP zmienione: 0 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                }
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>

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

  38. Krok 38: Skrypt 2: zapytanie 1 z loginem z formularza

    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”

    Zapytanie 1 z kwerendy.txt z jedną zmianą: w miejsce 'Justyna' stoi '$login'. Całe zapytanie jest w cudzysłowie, więc PHP podstawi login; apostrofy wewnątrz są ogranicznikami napisu w SQL.

    • mysqli_fetch_row($result) zwraca wiersz z hasłem albo null, gdy żaden wiersz nie pasuje,
    • if (!$row) - wiersza nie ma, czyli takiego loginu nie ma w bazie: komunikat „login nie istnieje” (R.5.5). Arkusz nie mówi, w jakim znaczniku - tutaj w paragrafie.

    Podgląd: login Henio (w bazie jest tylko Heniek) - „login nie istnieje”.

    uzytkownicy.php PHP zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                    $result = mysqli_query($connection, "SELECT haslo FROM uzytkownicy WHERE login = '$login';");
                    $row = mysqli_fetch_row($result);
                    if (!$row) {
                        echo '<p>login nie istnieje</p>';
                    }
                }
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  39. Krok 39: 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. 3 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 nie ma haseł, tylko ich skróty SHA-1 ($row[0]). Hasła ze skrótu nie da się odczytać, ale ten sam napis daje zawsze ten sam skrót - dlatego sha1($password) liczy skrót hasła z formularza i porównuje go ze skrótem z bazy (R.5.6). Dla Heńka: sha1('iii') = 425ffc1422dc4f32528bd9fd5af355fdb5c96192 - dokładnie wartość z tabeli uzytkownicy.

    Gałąź elseif wykona się tylko wtedy, gdy login istnieje. Podgląd: login Heniek, hasło abc - „hasło nieprawidłowe”.

    Typowe błędy

    Porównanie $password != $row[0] (bez sha1) odrzuci każde hasło, bo w bazie jest skrót. Odwrotny błąd - sha1($row[0]) - też nigdy się nie zgodzi.

    uzytkownicy.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                    $result = mysqli_query($connection, "SELECT haslo FROM uzytkownicy WHERE login = '$login';");
                    $row = mysqli_fetch_row($result);
                    if (!$row) {
                        echo '<p>login nie istnieje</p>';
                    } elseif (sha1($password) != $row[0]) {
                        echo '<p>hasło nieprawidłowe</p>';
                    }
                }
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  40. Krok 40: Skrypt 2: zapytanie 3 i zdjęcie z tekstem „osoba”

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

    Ostatnia gałąź else - login istnieje i hasło się zgadza:

    • zapytanie 3 z '$login' w miejscu 'Justyna' - dane zalogowanej osoby z obu tabel,
    • mysqli_fetch_row - jeden wiersz: $row[0] login, $row[1] rok urodzenia, $row[2] liczba przyjaciół, $row[3] hobby, $row[4] nazwa pliku zdjęcia,
    • <img> z src zbudowanym z nazwy pliku z bazy i alt="osoba". Cudzysłowy atrybutów są poprzedzone ukośnikiem \", bo cały napis echo też stoi w cudzysłowie.

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

    Typowe błędy

    Zdjęcie wstawione na sztywno (src="o3.jpg") pokaże każdemu to samo - skrypt ma brać nazwę pliku z bazy.

    uzytkownicy.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                    $result = mysqli_query($connection, "SELECT haslo FROM uzytkownicy WHERE login = '$login';");
                    $row = mysqli_fetch_row($result);
                    if (!$row) {
                        echo '<p>login nie istnieje</p>';
                    } elseif (sha1($password) != $row[0]) {
                        echo '<p>hasło nieprawidłowe</p>';
                    } else {
                        $result = mysqli_query($connection, "SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = '$login';");
                        $row = mysqli_fetch_row($result);
                        echo "<img src=\"$row[4]\" alt=\"osoba\">";
                    }
                }
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  41. Krok 41: 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)

    • date('Y') zwraca bieżący rok czterema cyframi, np. 2026. Odejmujesz od niego rok urodzenia z bazy ($row[1]) - PHP zamieni napis „1990” na liczbę (R.5.8),
    • <h4> z loginem i wiekiem w nawiasie.

    Podgląd: Heniek (36). Na obrazie 2 arkusza jest „Heniek (29)” - ilustracja nie zgadza się z bazą: Heniek ma w tabeli dane rok 1990, więc w 2022 roku miał 32 lata. Klucz dopuszcza też wpisanie roku 2022 na sztywno, ale date('Y') liczy poprawnie w każdym roku.

    Typowe błędy

    Wpisanie wieku z obrazu (29) albo samego roku urodzenia zamiast różnicy nie spełnia R.5.8 - wiek ma wynikać z obliczenia.

    uzytkownicy.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                    $result = mysqli_query($connection, "SELECT haslo FROM uzytkownicy WHERE login = '$login';");
                    $row = mysqli_fetch_row($result);
                    if (!$row) {
                        echo '<p>login nie istnieje</p>';
                    } elseif (sha1($password) != $row[0]) {
                        echo '<p>hasło nieprawidłowe</p>';
                    } else {
                        $result = mysqli_query($connection, "SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = '$login';");
                        $row = mysqli_fetch_row($result);
                        echo "<img src=\"$row[4]\" alt=\"osoba\">";
                        $age = date('Y') - $row[1];
                        echo "<h4>$row[0] ($age)</h4>";
                    }
                }
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  42. Krok 42: Skrypt 2: hobby w paragrafie

    ok. 1 min
    Polecenie z arkusza

    Poniżej w paragrafie hobby, poprzedzone napisem „hobby: ”

    Paragraf <p> z napisem „hobby: ” i wartością $row[3]. Podgląd: „hobby: film”.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                    $result = mysqli_query($connection, "SELECT haslo FROM uzytkownicy WHERE login = '$login';");
                    $row = mysqli_fetch_row($result);
                    if (!$row) {
                        echo '<p>login nie istnieje</p>';
                    } elseif (sha1($password) != $row[0]) {
                        echo '<p>hasło nieprawidłowe</p>';
                    } else {
                        $result = mysqli_query($connection, "SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = '$login';");
                        $row = mysqli_fetch_row($result);
                        echo "<img src=\"$row[4]\" alt=\"osoba\">";
                        $age = date('Y') - $row[1];
                        echo "<h4>$row[0] ($age)</h4>";
                        echo "<p>hobby: $row[3]</p>";
                    }
                }
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  43. Krok 43: Skrypt 2: serce i liczba przyjaciół w nagłówku h1

    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 nagłówku pierwszego stopnia obraz serca wypakowany z archiwum (icon-on.png) oraz liczbę przyjaciół pobraną z bazy (pole przyjaciol)

    <h1> z obrazem icon-on.png (50 × 50 px) i liczbą przyjaciół $row[2]. Obraz ma własny tekst alternatywny „serce”. W bloku prawym są teraz dwa obrazy - zdjęcie z tekstem „osoba” i serce (R.2.8). Napisy w wizytówce mają właściwe znaczniki: <h5> w skrypcie 1 oraz <h4>, <p> i <h1> w skrypcie 2 (R.5.9).

    Podgląd: serce i 20, jak na obrazie 2.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                    $result = mysqli_query($connection, "SELECT haslo FROM uzytkownicy WHERE login = '$login';");
                    $row = mysqli_fetch_row($result);
                    if (!$row) {
                        echo '<p>login nie istnieje</p>';
                    } elseif (sha1($password) != $row[0]) {
                        echo '<p>hasło nieprawidłowe</p>';
                    } else {
                        $result = mysqli_query($connection, "SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = '$login';");
                        $row = mysqli_fetch_row($result);
                        echo "<img src=\"$row[4]\" alt=\"osoba\">";
                        $age = date('Y') - $row[1];
                        echo "<h4>$row[0] ($age)</h4>";
                        echo "<p>hobby: $row[3]</p>";
                        echo "<h1><img src=\"icon-on.png\" alt=\"serce\"> $row[2]</h1>";
                    }
                }
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  44. Krok 44: 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.

    Przycisk, który „prowadzi do pliku”, to odnośnik zawierający przycisk: <a href="dane.html"> z <button> w środku - tak opisuje go klucz (R.3.2). Kliknięcie otwiera dane.html z napisem „strona w trakcie budowy”. Napis jest w apostrofach, bo w środku są cudzysłowy atrybutu i nie ma zmiennych.

    Wizytówka jest kompletna (R.5.7): zdjęcie, login z wiekiem, hobby, serce z liczbą przyjaciół i przycisk - pokazane tylko wtedy, gdy login i hasło się zgadzają. Przycisk dostał styl z kroku CSS - pełna szerokość, ciemne tło, a po najechaniu jaśniejsze.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                    $result = mysqli_query($connection, "SELECT haslo FROM uzytkownicy WHERE login = '$login';");
                    $row = mysqli_fetch_row($result);
                    if (!$row) {
                        echo '<p>login nie istnieje</p>';
                    } elseif (sha1($password) != $row[0]) {
                        echo '<p>hasło nieprawidłowe</p>';
                    } else {
                        $result = mysqli_query($connection, "SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = '$login';");
                        $row = mysqli_fetch_row($result);
                        echo "<img src=\"$row[4]\" alt=\"osoba\">";
                        $age = date('Y') - $row[1];
                        echo "<h4>$row[0] ($age)</h4>";
                        echo "<p>hobby: $row[3]</p>";
                        echo "<h1><img src=\"icon-on.png\" alt=\"serce\"> $row[2]</h1>";
                        echo '<a href="dane.html"><button>Więcej informacji</button></a>';
                    }
                }
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  45. Krok 45: Zamknięcie połączenia z bazą

    Kryteria:

    R.5.2 Ostatnią operacją na bazie jest jej zamknięcie (zgodne ze składnią)

    ok. 1 min
    Polecenie z arkusza

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

    mysqli_close($connection) na końcu skryptu 2, poza warunkiem if - wykona się zawsze, także przy pierwszym wejściu na stronę. Skrypt 2 jest ostatnim blokiem PHP w pliku, więc to rzeczywiście ostatnia operacja na bazie (R.5.2).

    Strona spełnia wszystkie wymagania arkusza; podgląd pokazuje stan z obrazu 2. Dalej trzy kroki o zabezpieczeniach - klucz ich nie wymaga.

    Typowe błędy

    mysqli_close w skrypcie 1 (zaraz po liczbie użytkowników) zamknie połączenie, z którego skrypt 2 chce jeszcze korzystać - PHP zgłosi błąd przy pierwszym logowaniu.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                    $result = mysqli_query($connection, "SELECT haslo FROM uzytkownicy WHERE login = '$login';");
                    $row = mysqli_fetch_row($result);
                    if (!$row) {
                        echo '<p>login nie istnieje</p>';
                    } elseif (sha1($password) != $row[0]) {
                        echo '<p>hasło nieprawidłowe</p>';
                    } else {
                        $result = mysqli_query($connection, "SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = '$login';");
                        $row = mysqli_fetch_row($result);
                        echo "<img src=\"$row[4]\" alt=\"osoba\">";
                        $age = date('Y') - $row[1];
                        echo "<h4>$row[0] ($age)</h4>";
                        echo "<p>hobby: $row[3]</p>";
                        echo "<h1><img src=\"icon-on.png\" alt=\"serce\"> $row[2]</h1>";
                        echo '<a href="dane.html"><button>Więcej informacji</button></a>';
                    }
                }
                mysqli_close($connection);
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>

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

  46. Etap 5: Dobre praktyki (poza kluczem)

    Kroki 46-48 · po tym etapie masz 39 z 39 kryteriów (pkt)

    Krok 46: Zabezpieczenie: zapytania przygotowane

    ok. 4 min
    Polecenie z arkusza

    Skrypt wysyła do bazy zmodyfikowane zapytanie 1, tak, że w klauzuli warunku jest sprawdzony login przesłany przez formularz

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

    • login ' OR '1'='1 z hasłem qqq - warunek zamienia się w login = '' OR '1'='1', czyli „każdy wiersz”. Skrypt bierze pierwszy wiersz (Justyna), hasło się zgadza i wizytówka Justyny otwiera się bez podania jej loginu,
    • login x' UNION SELECT SHA1('a') -- z hasłem a - zapytanie 1 zwraca skrót, który podał sam atakujący, więc sprawdzenie hasła przechodzi; strona wywraca się dopiero na zapytaniu 3 („Fatal error”, różna liczba kolumn),
    • login z apostrofem, np. O'Neill, rozbija składnię - PHP 8.1 i nowsze zgłaszają wyjątek mysqli_sql_exception i zamiast strony widać „Fatal error”.

    Lekarstwo to zapytanie przygotowane: mysqli_prepare ze znakiem ? w miejscu loginu, mysqli_stmt_bind_param z typem 's' (napis), mysqli_stmt_execute i mysqli_stmt_get_result, które zwraca wynik dla dotychczasowego mysqli_fetch_row. Baza dostaje treść zapytania i dane osobno, więc dane nigdy nie staną się kodem SQL. Podgląd: ten sam atak UNION - teraz zwykłe „login nie istnieje”. Stare linie zostają w komentarzu.

    Typowe błędy

    mysqli_stmt_get_result wymaga sterownika mysqlnd - w XAMPP jest. Gdyby go nie było, wynik odbiera się przez mysqli_stmt_bind_result i mysqli_stmt_fetch.

    uzytkownicy.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                    // $result = mysqli_query($connection, "SELECT haslo FROM uzytkownicy WHERE login = '$login';");
                    $statement = mysqli_prepare($connection, 'SELECT haslo FROM uzytkownicy WHERE login = ?;');
                    mysqli_stmt_bind_param($statement, 's', $login);
                    mysqli_stmt_execute($statement);
                    $result = mysqli_stmt_get_result($statement);
                    $row = mysqli_fetch_row($result);
                    if (!$row) {
                        echo '<p>login nie istnieje</p>';
                    } elseif (sha1($password) != $row[0]) {
                        echo '<p>hasło nieprawidłowe</p>';
                    } else {
                        // $result = mysqli_query($connection, "SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = '$login';");
                        $statement = mysqli_prepare($connection, 'SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = ?;');
                        mysqli_stmt_bind_param($statement, 's', $login);
                        mysqli_stmt_execute($statement);
                        $result = mysqli_stmt_get_result($statement);
                        $row = mysqli_fetch_row($result);
                        echo "<img src=\"$row[4]\" alt=\"osoba\">";
                        $age = date('Y') - $row[1];
                        echo "<h4>$row[0] ($age)</h4>";
                        echo "<p>hobby: $row[3]</p>";
                        echo "<h1><img src=\"icon-on.png\" alt=\"serce\"> $row[2]</h1>";
                        echo '<a href="dane.html"><button>Więcej informacji</button></a>';
                    }
                }
                mysqli_close($connection);
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  47. Krok 47: Zabezpieczenie: htmlspecialchars na wyjściu

    ok. 3 min
    Polecenie z arkusza

    Następnie wyświetla dane w bloku wizytówki

    Skrypt wypisuje na stronie dane z bazy: login, hobby, nazwę pliku zdjęcia. Te dane wpisują użytkownicy portalu (przy rejestracji albo edycji profilu), więc ktoś może wpisać jako hobby kod HTML lub JavaScript - i wtedy wykona się on u każdego, kto otworzy wizytówkę (XSS).

    array_map('htmlspecialchars', $row) przepuszcza każdą kolumnę wiersza przez htmlspecialchars: znaki <, >, & i cudzysłowy stają się encjami, więc przeglądarka pokaże je jako tekst. Sprawdzone: hobby zmienione w bazie na <b>film wychodzi na stronie jako zwykły napis „<b>film”, a nie pogrubienie reszty wizytówki. Dla zwykłych danych nic się nie zmienia - podgląd jak na obrazie 2.

    Funkcję stosuje się przy wypisywaniu, nie przy zapisie do bazy - w bazie zostaje oryginalny tekst.

    uzytkownicy.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                    // $result = mysqli_query($connection, "SELECT haslo FROM uzytkownicy WHERE login = '$login';");
                    $statement = mysqli_prepare($connection, 'SELECT haslo FROM uzytkownicy WHERE login = ?;');
                    mysqli_stmt_bind_param($statement, 's', $login);
                    mysqli_stmt_execute($statement);
                    $result = mysqli_stmt_get_result($statement);
                    $row = mysqli_fetch_row($result);
                    if (!$row) {
                        echo '<p>login nie istnieje</p>';
                    } elseif (sha1($password) != $row[0]) {
                        echo '<p>hasło nieprawidłowe</p>';
                    } else {
                        // $result = mysqli_query($connection, "SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = '$login';");
                        $statement = mysqli_prepare($connection, 'SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = ?;');
                        mysqli_stmt_bind_param($statement, 's', $login);
                        mysqli_stmt_execute($statement);
                        $result = mysqli_stmt_get_result($statement);
                        $row = mysqli_fetch_row($result);
                        $row = array_map('htmlspecialchars', $row);
                        echo "<img src=\"$row[4]\" alt=\"osoba\">";
                        $age = date('Y') - $row[1];
                        echo "<h4>$row[0] ($age)</h4>";
                        echo "<p>hobby: $row[3]</p>";
                        echo "<h1><img src=\"icon-on.png\" alt=\"serce\"> $row[2]</h1>";
                        echo '<a href="dane.html"><button>Więcej informacji</button></a>';
                    }
                }
                mysqli_close($connection);
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  48. Krok 48: Hasła i komunikaty: co zmienić poza egzaminem

    ok. 2 min
    Polecenie z arkusza

    Jeżeli login nie istnieje w bazie, wyświetlony jest komunikat „login nie istnieje”

    Dwie rzeczy, których klucz wymaga, a których prawdziwy serwis by nie zrobił - w pliku egzaminacyjnym zostają:

    • dwa różne komunikaty („login nie istnieje” i „hasło nieprawidłowe”) zdradzają, które loginy są w bazie - atakujący najpierw zbiera istniejące loginy, a potem zgaduje tylko hasła. Zwykle pokazuje się jeden komunikat: „nieprawidłowy login lub hasło”,
    • SHA-1 do haseł się nie nadaje: liczy się w ułamku mikrosekundy, a te same hasła mają te same skróty (bez losowej „soli”), więc skróty popularnych haseł z tabeli 1 da się odgadnąć w kilka sekund. Do haseł służą funkcje celowo wolne:
    $hash = password_hash($password, PASSWORD_DEFAULT);   // zapis konta
    if (password_verify($password, $hashFromDatabase)) { … }     // logowanie
    

    password_hash zwraca 60 znaków i więcej, a kolumna haslo ma varchar(50) - zmiana wymagałaby też tabeli. Na egzaminie tego nie rób: arkusz każe użyć SHA-1 (R.5.6). Plik strony bez zmian; podgląd: komunikat o złym haśle.

    uzytkownicy.php PHP zmienione: 0 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Portal społecznościowy</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <section id="banner-left">
            <h2>Nasze osiedle</h2>
        </section>
        <section id="banner-right">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'portal');
            $result = mysqli_query($connection, 'SELECT COUNT(*) FROM dane;');
            $row = mysqli_fetch_row($result);
            echo "<h5>Liczba użytkowników portalu: $row[0]</h5>";
            ?>
        </section>
        <section id="left">
            <h3>Logowanie</h3>
            <form method="post">
                login<br>
                <input type="text" name="login"><br>
                hasło<br>
                <input type="password" name="password"><br>
                <input type="submit" value="Zaloguj">
            </form>
        </section>
        <section id="right">
            <h3>Wizytówka</h3>
            <div id="card">
                <?php
                if (!empty($_POST['login']) && !empty($_POST['password'])) {
                    $login = $_POST['login'];
                    $password = $_POST['password'];
                    // $result = mysqli_query($connection, "SELECT haslo FROM uzytkownicy WHERE login = '$login';");
                    $statement = mysqli_prepare($connection, 'SELECT haslo FROM uzytkownicy WHERE login = ?;');
                    mysqli_stmt_bind_param($statement, 's', $login);
                    mysqli_stmt_execute($statement);
                    $result = mysqli_stmt_get_result($statement);
                    $row = mysqli_fetch_row($result);
                    if (!$row) {
                        echo '<p>login nie istnieje</p>';
                    } elseif (sha1($password) != $row[0]) {
                        echo '<p>hasło nieprawidłowe</p>';
                    } else {
                        // $result = mysqli_query($connection, "SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = '$login';");
                        $statement = mysqli_prepare($connection, 'SELECT login, rok_urodz, przyjaciol, hobby, zdjecie FROM dane JOIN uzytkownicy ON dane.id = uzytkownicy.id WHERE login = ?;');
                        mysqli_stmt_bind_param($statement, 's', $login);
                        mysqli_stmt_execute($statement);
                        $result = mysqli_stmt_get_result($statement);
                        $row = mysqli_fetch_row($result);
                        $row = array_map('htmlspecialchars', $row);
                        echo "<img src=\"$row[4]\" alt=\"osoba\">";
                        $age = date('Y') - $row[1];
                        echo "<h4>$row[0] ($age)</h4>";
                        echo "<p>hobby: $row[3]</p>";
                        echo "<h1><img src=\"icon-on.png\" alt=\"serce\"> $row[2]</h1>";
                        echo '<a href="dane.html"><button>Więcej informacji</button></a>';
                    }
                }
                mysqli_close($connection);
                ?>
            </div>
        </section>
        <footer>
            Stronę wykonał: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (uzytkownicy.php)
  49. Etap 6: Oddanie pracy

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

    Krok 49: Plik przeglądarka.txt i nagranie płyty

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz w folderze z numerem PESEL lub w przypadku jego braku w folderze z innym numerem, którym został podpisany arkusz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowano poprawność działania witryny. Nagraj płytę z rezultatami pracy.

    W przeglądarka.txt wpisujesz nazwę przeglądarki, w której sprawdzasz stronę przez http://localhost/….

    W folderze z numerem zdającego muszą być: dane.html, egzamin.jpg, kw1.png - kw4.png, kwerendy.txt, o1.jpg - o8.jpg (oraz icon-on.png - bez niego serce się nie wyświetli), przeglądarka.txt, styl5.css i uzytkownicy.php. Nagraj płytę, sprawdź jej odczyt w obecności przewodniczącego zespołu nadzorującego i opisz ją numerem zdającego. Pusta lub nieczytelna płyta to zero punktów za wszystkie pięć rezultatów.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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