Przejdź do głównej treści
EE.09-01-22.06-SGCzerwiec 2022 · 47 kroków · ok. 91 min · 42 kryteria z klucza

Rozwiązanie krok po kroku: Forum miłośników psów (Wersja podstawowa)

Fragment forum o psach: baza forumpsy zaimportowana z pliku baza.sql (tabele konta, pytania, odpowiedzi) i cztery zapytania - dopisanie odpowiedzi (INSERT), liczba lat na forum liczona od bieżącego roku (YEAR(CURDATE()) z aliasem), autor pytania nr 1 i odpowiedzi na nie (JOIN). Do tego avatar z przezroczystym tłem 100 × 100 px w formacie PNG, strona index.php z dwoma blokami obok siebie, filmem w pętli i formularzem z polem wielowierszowym, arkusz styl.css z gradientami i efektem :hover oraz trzy skrypty PHP: autor pytania, dodanie odpowiedzi i lista odpowiedzi.

To arkusz EE.09 z czerwca 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 1 arkusza, a na końcu sprawdzone na prawdziwych atakach (skrypt wpisany jako odpowiedź, apostrof, wstrzyknięcie SQL) i zabezpieczone.

To jest wersja podstawowa. W zakładce „Nowoczesny kod (grid, PDO)” jest drugi sposób rozwiązania tego samego arkusza: siatka CSS, zmienne CSS, PDO z zapytaniami przygotowanymi na górze pliku, htmlspecialchars przy każdym wypisaniu i przekierowanie po dodaniu odpowiedzi - obie wersje są zgodne z kluczem.

Baza, avatar i film pochodzą z oryginalnego archiwum CKE zad1.zip (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.1.2 - użyte funkcje działają w obu wersjach tak samo).

Jak liczą się punkty. Klucz ma 42 kryteria (R.1.1 - R.5.8), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 32 punkty - można stracić 10 kryteriów i nadal zdać. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.

  1. Etap 1: Baza danych

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

    Krok 1: Folder zdającego i archiwum zad1.zip

    ok. 2 min
    Polecenie z arkusza

    Na pulpicie znajduje się archiwum o nazwie zad1.zip zabezpieczone hasłem: 3&PsY#@ Archiwum należy rozpakować. Na pulpicie konta Egzamin należy utworzyć folder. Jako nazwy folderu należy użyć numeru zdającego, którym został podpisany arkusz. Rozpakowane pliki należy umieścić w tym folderze.

    Na pulpicie tworzysz folder nazwany numerem PESEL - w tym rozwiązaniu w jego miejscu stoi imię i nazwisko autora, Bartosz Bryniarski. Archiwum zad1.zip rozpakowujesz do tego folderu z hasłem _3&PsY#@_ (podkreślenia na początku i na końcu też należą do hasła, wielkość liter ma znaczenie). W środku są trzy pliki: baza.sql (tabele z danymi), Avatar.jpg (rysunek do obróbki) i video.mp4 (film do strony).

    Do tego folderu trafia wszystko, co powstanie: zrzuty, kwerendy.txt, grafika i pliki strony. Strona będzie uruchamiana przez XAMPP, więc folder kopiujesz też do C:\xampp\htdocs - albo pracujesz w htdocs, a na koniec kopiujesz wyniki do folderu zdającego.

    Typowe błędy

    Pliki zapisane tylko w htdocs nie trafią na płytę - a ocenia się wyłącznie to, co jest na płycie.

  2. Krok 2: XAMPP i pusta baza forumpsy

    ok. 2 min
    Polecenie z arkusza

    Za pomocą narzędzia phpMyAdmin należy wykonać operacje na bazie danych: Utworzenie bazy danych o nazwie: forumpsy z zestawem polskich znaków (np. utf8_unicode_ci)

    W XAMPP Control Panel klikasz Start przy Apache i MySQL, potem w przeglądarce otwierasz http://localhost/phpmyadmin. Nowa w lewym panelu, nazwa forumpsy, porównywanie napisów utf8_unicode_ci (to podpowiada arkusz; każde porównywanie z rodziny utf8 lub utf8mb4 z obsługą polskich liter się nada), Utwórz.

    Porównywanie decyduje o tym, jak baza zapisuje i sortuje polskie litery - w odpowiedziach na forum są „ł”, „ę” i „ś”.

    Typowe błędy

    Nazwa bazy musi brzmieć dokładnie forumpsy - skrypt PHP wybiera bazę po nazwie (R.5.1).

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

    Kryteria:

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

    ok. 3 min
    Polecenie z arkusza

    Import tabel do bazy forumpsy z pliku baza.sql. Czynność należy udokumentować zrzutem ekranu, na którym powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel. Zrzut należy zapisać w formacie JPEG, pod nazwą import. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań.

    Zaznacz bazę forumpsy w lewym panelu, karta Import, Wybierz plik → baza.sql, Wykonaj. Plik sam tworzy trzy tabele (CREATE TABLE), wypełnia je danymi i ustawia klucze główne z AUTO_INCREMENT.

    „Elementy wskazujące na poprawny import” to zielony komunikat „Import zakończył się sukcesem” i lista tabel w lewym panelu - na karcie Struktura bazy widać: konta (10 rekordów), odpowiedzi (7) i pytania (4). Zrzut całego ekranu z paskiem zadań zapisujesz jako import.jpg (Print Screen, wklejenie do edytora grafiki, eksport do JPEG).

    Typowe błędy

    Ten zrzut ma format JPEG, a zrzuty zapytań - PNG (odwrotnie niż w wielu innych arkuszach). Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany (uwaga w kluczu przy R.1).

    Podgląd strony po tym kroku
    Podgląd strony po kroku 3
  4. Krok 4: Zapytanie 1: nowa odpowiedź na pytanie 1

    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 wstawiające do tabeli odpowiedzi wiersz, o treści „Przyda Ci się także wysoki stół, na którym postawisz pieska.”, pytania_id = 1, konta_id = 5. W pliku z kwerendami lub na zrzucie istnieje zapis np.: INSERT INTO `odpowiedzi` (`Pytania_id`, `konta_id`, `odpowiedz`) VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.'); możliwe inne formy zapytania INSERT INTO oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest komunikat o poprawnie wykonanym zapytaniu lub tabela ze wstawionym rekordem

    ok. 3 min
    Polecenie z arkusza

    Zapytania SQL na bazie forumpsy zapisane do pliku kwerendy.txt. Zapytanie 1: wstawiające do tabeli odpowiedzi wiersz, o treści „Przyda Ci się także wysoki stół, na którym postawisz pieska.”. Jest on odpowiedzią na pytanie o id równym 1, udzieloną przez użytkownika o id równym 5.

    Zaznacz bazę forumpsy i otwórz kartę SQL. Zapytanie:

    • INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) - tabela i kolumny, które wypełniasz; id pomijasz, bo nada go AUTO_INCREMENT,
    • VALUES (1, 5, '…') - wartości w tej samej kolejności co kolumny: pytanie nr 1, użytkownik nr 5 (to Gosia) i treść odpowiedzi w apostrofach, razem z kropką na końcu.

    Komunikat „1 wiersz wstawiony”, identyfikator nowego wiersza 8 (tabela miała 7 odpowiedzi). Zapytanie kopiujesz do nowego pliku kwerendy.txt (R.1.2), a cały ekran zapisujesz jako kw1.png.

    To zapytanie jest wzorem dla skryptu 2: w miejsce stałej treści trafi odpowiedź wpisana w formularz.

    Typowe błędy

    Kolumna nazywa się Pytania_id - z wielką literą, jak na obrazie 1 (MariaDB w Windows nie rozróżnia wielkości liter w nazwach kolumn, ale lepiej pisać dosłownie). Zapytanie wykonane dwa razy doda dwie takie same odpowiedzi - drugą trzeba wtedy usunąć.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz)
    VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.');
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: ile lat na forum

    Kryteria:

    R.1.4 Utworzono zapytanie 2 wybierające z tabeli konta jedynie pole nick oraz liczące ile lat dany użytkownik jest zarejestrowany na forum – alias latNaForum, dla rekordów, których nick jest równy Jacek, Tomek, Monika. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nick, YEAR(CURRENT_DATE())-rejestracja AS latNaForum FROM konta WHERE nick IN ('Jacek', 'Tomek', 'Monika'); (możliwe inne funkcje zamiast CURRENT_DATE()aktualny rok zwracany funkcją lub warunek z OR) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są jedynie 3 rekordy, jedynie pola nick i latNaForum wartości w kolumnie latNaForum są obliczone dla Jacka 3, Tomka 6, Moniki 2

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 2: z tabeli konta dla rekordów, których nick to Jacek, Tomek, Monika wybierające jedynie pole nick oraz liczące ile lat dany użytkownik jest zarejestrowany na forum – kolumnę należy nazwać (alias) latNaForum. Zapytanie jest uniwersalne, gdy zmieni się aktualny rok, też powinno być liczone poprawnie

    Klauzula po klauzuli:

    • SELECT nick, - pierwsza kolumna wprost z tabeli,
    • YEAR(CURDATE()) - rejestracja AS latNaForum - CURDATE() zwraca dzisiejszą datę, a YEAR() wyciąga z niej rok (obie funkcje są w tabeli 2 arkusza). Od bieżącego roku odejmujesz rok rejestracji, a AS latNaForum nadaje wyliczonej kolumnie nazwę. Rok nie jest wpisany na sztywno - dlatego zapytanie jest „uniwersalne”,
    • FROM konta,
    • WHERE nick IN ('Jacek', 'Tomek', 'Monika') - IN to krótszy zapis trzech warunków połączonych OR.

    Wynik w 2026 roku: Jacek 7, Tomek 10, Monika 6. Za rok każda liczba będzie o 1 większa i to jest poprawne. Zrzut: kw2.png.

    ⚠️ Rozbieżność arkusz-klucz: z danych w 2026 roku wychodzi 7, 10 i 6 (i to pokazuje zrzut), a klucz CKE podaje 3, 6 i 2 - wynik z czerwca 2022, roku egzaminu. Klucz sam wymaga zapytania uniwersalnego, więc punkt daje zapytanie z YEAR(CURDATE()), a liczby zależą od roku, w którym się je wykonuje.

    Typowe błędy

    2022 - rejestracja daje dobre liczby tylko w 2022 roku - to nie jest zapytanie „uniwersalne” i klucz go nie przyjmie. Bez AS latNaForum kolumna nazywa się YEAR(CURDATE()) - rejestracja.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz)
    VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.');
    
    -- Zapytanie 2
    SELECT nick, YEAR(CURDATE()) - rejestracja AS latNaForum
    FROM konta
    WHERE nick IN ('Jacek', 'Tomek', 'Monika');
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: autor pytania nr 1

    Kryteria:

    R.1.5 Utworzono zapytanie 3 wybierające jedynie nick i liczbę postów użytkownika z tabeli konta oraz odpowiadające im pole pytanie z tabeli pytania dla pytania, którego id jest równe 1. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = konta_id WHERE pytania.id = 1; (lub bez JOIN i porównanie kluczy po WHERE) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest dokładnie jeden rekord o danych Jacek, 50, "Mam pytanie w jaki sposób dbać ..."

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie nick i liczbę postów użytkownika z tabeli konta oraz odpowiadające im pole pytanie z tabeli pytania dla pytania, którego id jest równe 1. Należy posłużyć się relacją.

    „Posłużyć się relacją” znaczy połączyć tabele po kluczach. Relacja Rel_01 z obrazu 1: pole konta_id w tabeli pytania wskazuje id w tabeli konta.

    • SELECT nick, postow, pytanie - dwie kolumny z konta, jedna z pytania,
    • FROM konta JOIN pytania ON konta.id = pytania.konta_id - JOIN … ON łączy każde pytanie z kontem jego autora,
    • WHERE pytania.id = 1 - tylko pytanie nr 1. Nazwa tabeli przed kropką jest konieczna: kolumnę id mają obie tabele.

    Wynik: jeden wiersz - Jacek, 50 postów i pytanie „Mam pytanie w jaki sposób dbać o sierść sznaucera? …”, jak w kluczu. Zrzut: kw3.png. To zapytanie wykona skrypt 1.

    Typowe błędy

    Samo WHERE id = 1 kończy się błędem #1052 „Column id in where clause is ambiguous” - baza nie wie, o które id chodzi.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz)
    VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.');
    
    -- Zapytanie 2
    SELECT nick, YEAR(CURDATE()) - rejestracja AS latNaForum
    FROM konta
    WHERE nick IN ('Jacek', 'Tomek', 'Monika');
    
    -- Zapytanie 3
    SELECT nick, postow, pytanie
    FROM konta JOIN pytania ON konta.id = pytania.konta_id
    WHERE pytania.id = 1;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: odpowiedzi na pytanie nr 1

    Kryteria:

    R.1.6 Utworzono zapytanie 4 wybierające jedynie id odpowiedzi oraz odpowiedź z tabeli odpowiedzi oraz odpowiadające im pole nick z tabeli konta dla id pytania równego 1. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON konta_id = konta.id WHERE Pytania_id = 1; (lub bez JOIN i porównanie kluczy po WHERE) oraz wynik działania zapytania udokumentowano zrzutem, na którym widać tylko rekordy z id równym 1, 2, 3; pola id, odpowiedz i nick lub dodatkowo inne rekordy powstałe wyłącznie na skutek działania zapytania 1

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie id odpowiedzi oraz odpowiedź z tabeli odpowiedzi oraz odpowiadające im pole nick z tabeli konta dla id pytania równego 1. Należy posłużyć się relacją.

    Relacja Rel_03: pole konta_id w tabeli odpowiedzi wskazuje autora odpowiedzi.

    • SELECT odpowiedzi.id, odpowiedz, nick - id z nazwą tabeli (znowu dwie tabele mają tę kolumnę),
    • FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id,
    • WHERE odpowiedzi.Pytania_id = 1 - odpowiedzi na pytanie nr 1.

    Wynik: odpowiedzi 1, 2 i 3 (Anna, Gosia, Tomek) oraz 8 - Gosia z odpowiedzią dodaną zapytaniem 1. Klucz przyjmuje ten czwarty wiersz wprost („lub dodatkowo inne rekordy powstałe wyłącznie na skutek działania zapytania 1”). Zrzut: kw4.png. Plik kwerendy.txt ma komplet czterech zapytań, a to zapytanie wykona skrypt 3.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz)
    VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.');
    
    -- Zapytanie 2
    SELECT nick, YEAR(CURDATE()) - rejestracja AS latNaForum
    FROM konta
    WHERE nick IN ('Jacek', 'Tomek', 'Monika');
    
    -- Zapytanie 3
    SELECT nick, postow, pytanie
    FROM konta JOIN pytania ON konta.id = pytania.konta_id
    WHERE pytania.id = 1;
    
    -- Zapytanie 4
    SELECT odpowiedzi.id, odpowiedz, nick
    FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id
    WHERE odpowiedzi.Pytania_id = 1;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

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

    Krok 8: Avatar: przezroczyste tło

    ok. 3 min
    Polecenie z arkusza

    Cechy grafiki z pliku Avatar: Tło przezroczyste

    Otwórz Avatar.jpg w GIMP-ie. Format JPEG nie ma przezroczystości, więc najpierw Warstwa → Przezroczystość → Dodaj kanał alfa. Potem narzędzie Zaznaczanie rozmytego obszaru (różdżka), kliknięcie w białe tło i Edycja → Wyczyść (klawisz Delete) - w miejscu tła pojawia się szachownica, czyli przezroczystość. Jeśli kosmyki włosów odcinają kawałki tła przy górnej krawędzi, kliknij różdżką z wciśniętym Shift także w te kawałki.

    Różdżka zaznacza tylko obszar połączony z klikniętym punktem, dlatego białe odblaski w oczach zostają nietknięte. Narzędzie Kolory → Kolor na alfę z białym kolorem zrobiłoby przezroczystymi także oczy i rozjaśnione miejsca twarzy.

    Typowe błędy

    Zapis do JPEG zamieni przezroczystość z powrotem na biel - format zapisu wybierasz w kolejnym kroku.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Avatar: 100 × 100 px w formacie PNG

    Kryteria:

    R.2.1 Obraz w pliku Avatar ma wymiary 100 px na 100 px, tło przezroczyste i jest zapisany w formacie obsługującym przezroczystość

    ok. 2 min
    Polecenie z arkusza

    Rozmiary 100 px na 100 px. Grafika zapisana pod nazwą Avatar w formacie obsługującym przezroczystość

    Oryginał ma 640 × 640 px, czyli jest kwadratem - Obraz → Skaluj obraz, szerokość 100 px, a wysokość przy zamkniętym łańcuchu sama wyjdzie 100 px.

    „Format obsługujący przezroczystość” to PNG (albo GIF, WebP). Plik → Eksportuj jako, nazwa Avatar.png - nazwa z wielką literą A, tak jak w arkuszu. Plik Avatar.jpg może zostać w folderze; strona użyje wersji PNG (R.2.1).

    Typowe błędy

    Plik → Zapisz w GIMP-ie zapisuje format .xcf, który nie jest grafiką dla przeglądarki - do PNG służy Eksportuj jako.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 9
  10. Krok 10: Plik index.php

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Cechy witryny: Składa się ze strony o nazwie index.php

    W edytorze zaznaczającym składnię (np. Notepad++, Visual Studio Code) tworzysz plik index.php w folderze zdającego. Rozszerzenie .php, bo dojdą skrypty - na razie to szkielet HTML: <html>, w nim nagłówek dokumentu <head> i treść <body>.

    Dopóki w pliku nie ma kodu PHP, przeglądarka pokaże go jak zwykłą stronę. Gdy pojawią się skrypty, stronę otwierasz przez http://localhost/…/index.php - serwer Apache z XAMPP wykona PHP, zanim wyśle stronę.

    index.php PHP nowy plik
    <html>
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  11. Krok 11: Deklaracja HTML5

    ok. 1 min
    Polecenie z arkusza

    Zapisana w języku HTML5

    <!DOCTYPE html> w pierwszej linii pliku mówi przeglądarce, że to HTML5. Bez tej linii przeglądarka przechodzi w tryb zgodności ze starymi stronami (quirks mode) i inaczej liczy np. wysokości bloków.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html>
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  12. Krok 12: Kodowanie polskich znaków

    ok. 1 min
    Polecenie z arkusza

    Jawnie zastosowany właściwy standard kodowania polskich znaków

    <meta charset="UTF-8"> jako pierwszy element <head>. Plik trzeba też zapisać w UTF-8 (w Notepad++ menu Kodowanie) - inaczej „miłośników” i „psów” zamienią się w krzaki.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  13. Krok 13: Język strony: polski

    Kryteria:

    R.2.3 Zapisano jawnie standard kodowania polskich znaków i deklarację HTML5 <!DOCTYPE HTML> oraz zadeklarowano dla witryny język polski (np. lang = 'pl')

    ok. 1 min
    Polecenie z arkusza

    Zadeklarowany język zawartości witryny: polski

    Atrybut lang="pl" w znaczniku <html>. Przeglądarka wie wtedy, jak dzielić wyrazy i jakiego słownika użyć, a czytnik ekranu czyta tekst polskim głosem. Deklaracja HTML5, kodowanie i język są gotowe (R.2.3).

    index.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 (index.php)
  14. Krok 14: Tytuł strony

    Kryteria:

    R.2.4 Nadano tytuł strony: Forum o psach

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Forum o psach”

    <title> w <head> - napis na karcie przeglądarki (R.2.4). Uwaga na dwa podobne teksty: tytuł to „Forum o psach”, a nagłówek banera - „Forum miłośników psów”.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Podpięcie arkusza stylów styl.css

    Kryteria:

    R.3.8 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.css , formatowanie pochodzi jedynie z tego arkusza Uwaga: kryterium to nie jest spełnione również, gdy zdający źle przypisał co najmniej jeden selektor do bloku (np. kropka zamiast #)

    ok. 1 min
    Polecenie z arkusza

    Arkusz stylów w pliku o nazwie styl.css prawidłowo połączony z kodem strony

    <link rel="stylesheet" href="styl.css"> w <head>. Plik stylów powstanie po HTML-u; połączenie działa, gdy plik leży w tym samym folderze co index.php. Od tego zależy całe formatowanie strony (R.3.8).

    Typowe błędy

    Klucz uznaje R.3.8 tylko wtedy, gdy formatowanie pochodzi wyłącznie z tego pliku i każdy selektor trafia w swój blok - kropka zamiast # przy jednym bloku odbiera punkt.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Bloki: baner, lewy, prawy i stopka

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

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

    Cztery bloki w kolejności z obrazu 2: baner <header>, dwa bloki treści jako <section> i stopka <footer> (R.2.5). Bloki lewy i prawy mają różne style, więc dostają identyfikatory left i right (nazwy po angielsku - arkusz ich nie narzuca). Na razie stoją jeden pod drugim; obok siebie ustawi je CSS.

    index.php PHP zmienione: 8 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
        </header>
        <section id="left">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  17. Krok 17: Baner: nagłówek pierwszego stopnia

    ok. 1 min
    Polecenie z arkusza

    Zawartość banera: nagłówek pierwszego stopnia o treści „Forum miłośników psów”

    <h1> w <header> - jedyny nagłówek pierwszego stopnia na stronie. Kryterium R.2.6 sprawdza go razem z nagłówkiem <h2> i paragrafem - punkt przyjdzie, gdy skrypt 1 wypisze paragrafy.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Blok lewy: avatar z tekstem alternatywnym

    Kryteria:

    R.2.8 W bloku lewym umieszczono obraz Avatar z tekstem alternatywnym "Użytkownik forum"

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku lewego: Obraz o nazwie Avatar i tekście alternatywnym „Użytkownik forum”

    <img> z atrybutami src - plik Avatar.png z poprzednich kroków (z rozszerzeniem!) - i alt, czyli tekst, który czytnik ekranu przeczyta zamiast obrazu (R.2.8). W HTML5 <img> nie ma znacznika zamykającego ani ukośnika na końcu.

    Typowe błędy

    W Windows avatar.png i Avatar.png to ten sam plik, ale na serwerze z Linuksem już nie - nazwę w src pisz dokładnie tak, jak nazywa się plik.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  19. Krok 19: Blok lewy: film w pętli z przyciskami

    Kryteria: , ,

    R.2.10 W bloku lewym umieszczono film video.mp4 (np. znaczniki video i source lub jedynie video z atrybutem src lub znacznik object lub embed)

    R.3.5 Video odtwarza się w pętli

    R.3.6 Na filmie widoczne są przyciski sterujące

    ok. 2 min
    Polecenie z arkusza

    Film o nazwie video.mp4 z przyciskami do nawigacji, odtwarzający się w pętli

    Znacznik <video> z HTML5, plik w atrybucie src:

    • controls - przeglądarka pokazuje przyciski: odtwarzanie, pasek postępu, głośność, pełny ekran (R.3.6),
    • loop - po dojściu do końca (film ma 17 sekund) odtwarzanie zaczyna się od początku (R.3.5).

    To atrybuty logiczne: sama ich obecność je włącza, bez ="…". Według arkusza między avatarem a filmem stoi jeszcze „efekt działania skryptu 1” - wstawisz go w części o skryptach. Film na razie jest duży (350 × 196 px w oryginale); szerokość ustawi CSS (R.2.10).

    Typowe błędy

    <video> bez controls nie ma żadnych przycisków - film da się uruchomić tylko z menu kontekstowego, a punkt R.3.6 przepada.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  20. Krok 20: Blok prawy: formularz wysyłany metodą POST

    Kryteria:

    R.3.3 Zdefiniowano wysyłanie danych z formularza do tego samego pliku metodą POST (poprawne również bez atrybutu action)

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku prawego: Formularz wysyłający dane metodą bezpieczną do tego samego pliku

    <form method="post"> - bez atrybutu action formularz wysyła dane do tego samego adresu, z którego został wczytany, czyli do index.php (klucz R.3.3 przyjmuje to wprost). „Metoda bezpieczna” w rozumieniu arkusza to POST: dane jadą w treści żądania, a nie w adresie strony, więc długa odpowiedź nie trafi do paska adresu ani do historii przeglądarki. W PHP odczytasz je z tablicy $_POST.

    Typowe błędy

    Przy method="get" treść odpowiedzi doklei się do adresu (index.php?odpowiedz=…), a skrypt czytający $_POST niczego nie dostanie.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
            </form>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  21. Krok 21: Pole wielowierszowe 4 × 40

    ok. 1 min
    Polecenie z arkusza

    Pole wieloliniowe o 4 wierszach i 40 kolumnach

    Pole wielowierszowe to <textarea> - w odróżnieniu od <input> ma znacznik zamykający, a tekst między znacznikami byłby domyślną treścią pola (tu pole jest puste). rows="4" to wysokość w wierszach tekstu, cols="40" - szerokość w znakach. Atrybut name="odpowiedz" to klucz, pod którym treść trafi do PHP: $_POST['odpowiedz']. <br> przenosi przycisk pod pole, jak na obrazie 2.

    Typowe błędy

    Spacja albo enter między <textarea> a </textarea> staje się treścią pola - pole nie jest wtedy puste, a uczeń zaczyna pisać od drugiego wiersza.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
            </form>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Przycisk „Dodaj odpowiedź”

    Kryteria: ,

    R.2.9 W bloku prawym umieszczono formularz z polem wielowierszowym (<textarea>) i przyciskiem, zastosowano znacznik <form>

    R.3.4 Pole wielowierszowe ma 4 wiersze (rows) i 40 kolumn (cols) oraz przycisk wysyła dane z formularza (input lub button z typem submit lub button bez typu)

    ok. 1 min
    Polecenie z arkusza

    Przycisk o treści „Dodaj odpowiedź”, którego wciśnięcie powoduje wysłanie danych do skryptu 2

    <button type="submit"> - przycisk, który wysyła formularz; napis jest treścią znacznika. Formularz ma komplet: znacznik <form>, pole <textarea> i przycisk (R.2.9), a pole ma 4 wiersze i 40 kolumn, przycisk zaś wysyła dane (R.3.4). Skrypt 2, który je odbierze, powstanie w części o PHP.

    Znacznik button przyda się też w stylach: arkusz każe pokolorować przycisk, a selektor button nie obejmie przy okazji innych pól.

    Typowe błędy

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

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  23. Krok 23: Nagłówek „Odpowiedzi na pytanie”

    ok. 1 min
    Polecenie z arkusza

    Nagłówek drugiego stopnia o treści „Odpowiedzi na pytanie”

    Drugiego stopnia to <h2>, pod formularzem w bloku prawym - tak jak na obrazie 2.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <h2>Odpowiedzi na pytanie</h2>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Lista numerowana na odpowiedzi

    ok. 1 min
    Polecenie z arkusza

    Lista numerowana (uporządkowana), której elementy generowane są skryptem 3

    Lista uporządkowana to <ol>. W HTML-u zostaje pusta - jej elementy <li> wypisze skrypt 3, po jednym na każdą odpowiedź z bazy. Numery 1, 2, 3… dopisze przeglądarka.

    Typowe błędy

    <ul> to lista wypunktowana - kropki zamiast numerów, a klucz wymaga znaczników <ol> i <li> (R.2.7).

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
            </ol>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  25. Krok 25: Stopka: autor

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: Tekst o treści „Autor: ”, dalej wstawiony numer zdającego

    Na egzaminie po dwukropku wpisujesz swój numer PESEL. W tym rozwiązaniu w jego miejscu stoi imię i nazwisko autora: Bartosz Bryniarski.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  26. Krok 26: Stopka: odnośnik otwierany w nowej karcie

    Kryteria:

    R.3.2 Na stronie umieszczono odnośnik, którego kliknięcie inicjuje otwarcie strony http://mojestrony.pl/ w nowej karcie przeglądarki

    ok. 1 min
    Polecenie z arkusza

    Za numerem zdającego wstawiony odnośnik do strony http://mojestrony.pl/ otwierający się w osobnej karcie przeglądarki i podpisany „Zobacz nasze realizacje”

    <a href="http://mojestrony.pl/"> z pełnym adresem - z http:// na początku. Bez protokołu przeglądarka szukałaby pliku mojestrony.pl w folderze strony. Atrybut target="_blank" otwiera stronę w nowej karcie (R.3.2).

    Typowe błędy

    target="blank" bez podkreślenia też otworzy nową kartę, ale zawsze tę samą (nazwaną „blank”) - poprawna wartość to _blank.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
            <a href="http://mojestrony.pl/" target="_blank">Zobacz nasze realizacje</a>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  27. Etap 3: Styl CSS

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

    Krok 27: Plik styl.css: krój Garamond dla wszystkich elementów

    Kryteria:

    R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (*): krój czcionki: Garamond, serif (czcionka Garamond jako pierwsza)

    ok. 1 min
    Polecenie z arkusza

    Styl CSS zdefiniowany w całości w zewnętrznym pliku o nazwie styl.css. Ustawione domyślne wartości dla wszystkich selektorów stylu CSS: krój czcionki Garamond, w przypadku jego braku – serif

    Nowy plik styl.css w folderze zdającego - od teraz każda reguła trafia tylko tutaj, bez atrybutów style w HTML-u. Pierwsza reguła: selektor uniwersalny * („wszystkie selektory” z arkusza - tak też zapisuje to klucz R.4.1).

    font-family: Garamond, serif - lista krojów czytana od lewej: Garamond, a gdy go nie ma w systemie - dowolny krój szeryfowy. Selektor * obejmuje także pole tekstowe i przycisk, które kroju po body nie dziedziczą - dlatego napis „Dodaj odpowiedź” też jest pisany Garamondem.

    Typowe błędy

    font-family: serif, Garamond - przeglądarka weźmie pierwszy dostępny krój, czyli zawsze serif; klucz wymaga Garamonda na pierwszym miejscu.

    styl.css CSS nowy plik
    * {
        font-family: Garamond, serif;
    }
    Podgląd strony po tym kroku (index.php)
  28. Krok 28: Baner: gradient, biały tekst, wyśrodkowanie, marginesy wewnętrzne

    Kryteria:

    R.4.7 Ustawiono wyrównanie tekstu do środka dla banera

    ok. 2 min
    Polecenie z arkusza

    Dla bloku banera: tło będące pionowym gradientem liniowym od koloru #97b498 do #c8e6c9, biały kolor czcionki, wyrównanie tekstu do środka, marginesy wewnętrzne 5 px

    • linear-gradient(#97b498, #c8e6c9) - gradient liniowy od pierwszego koloru do drugiego. Bez podanego kierunku biegnie z góry na dół, czyli jest pionowy, jak w arkuszu. Gradient to obraz, a nie kolor, dlatego stoi we właściwości background (albo background-image), a nie w background-color,
    • color: white - biały tekst,
    • text-align: center - nagłówek na środku (R.4.7),
    • padding: 5px - margines wewnętrzny, czyli odstęp tekstu od krawędzi banera.
    Typowe błędy

    background-color: linear-gradient(…) jest błędne - przeglądarka odrzuci całą deklarację i baner zostanie bez tła.

    styl.css CSS zmienione: 6 linii
    * {
        font-family: Garamond, serif;
    }
    header {
        background: linear-gradient(#97b498, #c8e6c9);
        color: white;
        text-align: center;
        padding: 5px;
    }
    Podgląd strony po tym kroku (index.php)
  29. Krok 29: Blok lewy: tło, kolor tekstu i wymiary

    ok. 1 min
    Polecenie z arkusza

    Dla bloku lewego: kolor tła #c8e6c9, kolor czcionki #005005, szerokość 40%, wysokość 600 px

    Selektor identyfikatora #left - z krzyżykiem, bo w HTML-u jest id="left". Jasnozielone tło i ciemnozielony tekst; szerokość procentowa liczy się od szerokości okna, więc blok rośnie razem z oknem.

    styl.css CSS zmienione: 6 linii
    * {
        font-family: Garamond, serif;
    }
    header {
        background: linear-gradient(#97b498, #c8e6c9);
        color: white;
        text-align: center;
        padding: 5px;
    }
    #left {
        background-color: #c8e6c9;
        color: #005005;
        width: 40%;
        height: 600px;
    }
    Podgląd strony po tym kroku (index.php)
  30. Krok 30: Blok prawy: kolor tekstu, wymiary i paski przewijania

    Kryteria: ,

    R.4.5 Ustawiono wysokość 600px dla bloków lewego i prawego

    R.4.9 Ustawiono zawsze widoczne paski przewijana dla bloku prawego np. overflow: scroll;

    ok. 2 min
    Polecenie z arkusza

    Dla bloku prawego: kolor czcionki #005005, szerokość 60%, wysokość 600 px, zawsze widoczne paski przewijana

    Blok prawy nie ma koloru tła - na obrazie 2 jest biały. overflow: scroll pokazuje oba paski przewijania zawsze, nawet gdy treść się mieści (R.4.9). Wartość auto pokazałaby je dopiero wtedy, gdy odpowiedzi przestaną się mieścić - a arkusz mówi „zawsze widoczne”.

    Oba bloki mają po 600 px wysokości (R.4.5) i biorą razem 40% + 60% = 100% szerokości.

    Typowe błędy

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

    styl.css CSS zmienione: 6 linii
    * {
        font-family: Garamond, serif;
    }
    header {
        background: linear-gradient(#97b498, #c8e6c9);
        color: white;
        text-align: center;
        padding: 5px;
    }
    #left {
        background-color: #c8e6c9;
        color: #005005;
        width: 40%;
        height: 600px;
    }
    #right {
        color: #005005;
        width: 60%;
        height: 600px;
        overflow: scroll;
    }
    Podgląd strony po tym kroku (index.php)
  31. Krok 31: Stopka: gradient odwrotny, biały tekst, marginesy wewnętrzne

    Kryteria:

    R.4.3 Ustawiono gradient liniowy pionowy od koloru #97b498 do #c8e6c9 dla banera np. background: linear-gradient(#97b498, #c8e6c9) i odwrotnej kolejności dla stopki np. background: linear-gradient(#c8e6c9, #97b498)

    ok. 1 min
    Polecenie z arkusza

    Dla stopki: tło będące pionowym gradientem liniowym od koloru #c8e6c9 do #97b498, biały kolor czcionki, marginesy wewnętrzne 12 px

    Te same dwa kolory co w banerze, ale w odwrotnej kolejności - stopka jest jasna u góry i ciemnieje ku dołowi. Gradienty banera i stopki są gotowe (R.4.3).

    styl.css CSS zmienione: 5 linii
    * {
        font-family: Garamond, serif;
    }
    header {
        background: linear-gradient(#97b498, #c8e6c9);
        color: white;
        text-align: center;
        padding: 5px;
    }
    #left {
        background-color: #c8e6c9;
        color: #005005;
        width: 40%;
        height: 600px;
    }
    #right {
        color: #005005;
        width: 60%;
        height: 600px;
        overflow: scroll;
    }
    footer {
        background: linear-gradient(#c8e6c9, #97b498);
        color: white;
        padding: 12px;
    }
    Podgląd strony po tym kroku (index.php)
  32. Krok 32: 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). Nie należy brać pod uwagę umiejscowienia filmu

    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

    Obie sekcje dostają float: left: blok lewy (40%) staje przy lewej krawędzi, prawy (60%) zaraz obok niego. Stopka z clear: both zaczyna się dopiero pod oboma pływającymi blokami - tuż pod nimi, bo oba mają 600 px wysokości.

    Klucz R.3.1 przyjmuje float z clear, flex albo grid; położenia filmu nie ocenia. Wersja nowoczesna pokazuje ten sam układ na siatce.

    Typowe błędy

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

    styl.css CSS zmienione: 6 linii
    * {
        font-family: Garamond, serif;
    }
    header {
        background: linear-gradient(#97b498, #c8e6c9);
        color: white;
        text-align: center;
        padding: 5px;
    }
    #left {
        background-color: #c8e6c9;
        color: #005005;
        width: 40%;
        height: 600px;
    }
    #right {
        color: #005005;
        width: 60%;
        height: 600px;
        overflow: scroll;
    }
    footer {
        background: linear-gradient(#c8e6c9, #97b498);
        color: white;
        padding: 12px;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    Podgląd strony po tym kroku (index.php)
  33. Krok 33: Film: szerokość 100%

    Kryteria:

    R.4.6 Ustawiono szerokość 40% dla bloku lewego i 60% dla prawego (width lub grid- template-columns lub flex) oraz 100% dla selektora osadzającego film (np. video, object, embed)

    ok. 1 min
    Polecenie z arkusza

    Dla selektora filmu: szerokość 100%

    Selektor znacznika video - arkusz zastrzega, że styl filmu ma być zapisany selektorem znacznika, a nie klasą (uwaga w kluczu przy R.4). 100% liczy się od szerokości rodzica, czyli bloku lewego: film wypełnia blok od krawędzi do krawędzi, a wysokość przelicza się sama z proporcji filmu. Szerokości bloków i filmu są ustawione (R.4.6).

    Typowe błędy

    .film { width: 100%; } z klasą działa tak samo, ale klucz go nie przyjmie - kryteria R.4.6 i R.4.8 wymagają selektorów znaczników.

    styl.css CSS zmienione: 3 linie
    * {
        font-family: Garamond, serif;
    }
    header {
        background: linear-gradient(#97b498, #c8e6c9);
        color: white;
        text-align: center;
        padding: 5px;
    }
    #left {
        background-color: #c8e6c9;
        color: #005005;
        width: 40%;
        height: 600px;
    }
    #right {
        color: #005005;
        width: 60%;
        height: 600px;
        overflow: scroll;
    }
    footer {
        background: linear-gradient(#c8e6c9, #97b498);
        color: white;
        padding: 12px;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    video {
        width: 100%;
    }
    Podgląd strony po tym kroku (index.php)
  34. Krok 34: Formularz: marginesy wewnętrzne

    Kryteria:

    R.4.8 Ustawiono marginesy wewnętrzne (padding) 5px dla banera, 12px dla stopki, 10px (górny i dolny) 50px (lewy i prawy) dla selektora form

    ok. 1 min
    Polecenie z arkusza

    Dla selektora formularza: marginesy wewnętrzne górny i dolny 10 px, lewy i prawy 50 px

    Selektor znacznika form. Skrót padding: 10px 50px - pierwsza wartość to góra i dół, druga - lewa i prawa strona. Dlatego pole odpowiedzi na obrazie 2 jest odsunięte od lewej krawędzi bloku. Marginesy wewnętrzne banera, stopki i formularza są gotowe (R.4.8).

    Typowe błędy

    padding: 50px 10px odwraca kierunki. Kolejność w skrócie z dwiema wartościami: najpierw pion, potem poziom.

    styl.css CSS zmienione: 3 linie
    * {
        font-family: Garamond, serif;
    }
    header {
        background: linear-gradient(#97b498, #c8e6c9);
        color: white;
        text-align: center;
        padding: 5px;
    }
    #left {
        background-color: #c8e6c9;
        color: #005005;
        width: 40%;
        height: 600px;
    }
    #right {
        color: #005005;
        width: 60%;
        height: 600px;
        overflow: scroll;
    }
    footer {
        background: linear-gradient(#c8e6c9, #97b498);
        color: white;
        padding: 12px;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    video {
        width: 100%;
    }
    form {
        padding: 10px 50px;
    }
    Podgląd strony po tym kroku (index.php)
  35. Krok 35: Przycisk: tło i biały tekst

    Kryteria: ,

    R.4.2 Ustawiono kolor tła: #c8e6c9 dla bloku lewego i #97b498 dla przycisku

    R.4.4 Ustawiono biały kolor czcionki dla bloku banera, stopki i przycisku oraz #005005 dla bloków lewego i prawego

    ok. 1 min
    Polecenie z arkusza

    Dla przycisku: kolor tła #97b498, biały kolor czcionki

    Selektor znacznika button - na stronie jest jeden przycisk. Kolor tła bloku lewego i przycisku (R.4.2) oraz kolory tekstu banera, stopki, przycisku i obu bloków (R.4.4) są ustawione.

    styl.css CSS zmienione: 4 linie
    * {
        font-family: Garamond, serif;
    }
    header {
        background: linear-gradient(#97b498, #c8e6c9);
        color: white;
        text-align: center;
        padding: 5px;
    }
    #left {
        background-color: #c8e6c9;
        color: #005005;
        width: 40%;
        height: 600px;
    }
    #right {
        color: #005005;
        width: 60%;
        height: 600px;
        overflow: scroll;
    }
    footer {
        background: linear-gradient(#c8e6c9, #97b498);
        color: white;
        padding: 12px;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    video {
        width: 100%;
    }
    form {
        padding: 10px 50px;
    }
    button {
        background-color: #97b498;
        color: white;
    }
    Podgląd strony po tym kroku (index.php)
  36. Krok 36: Przycisk po najechaniu kursorem

    Kryteria:

    R.4.10 W momencie, gdy kursor myszy znajdzie się na przycisku jego kolory są zmienione na: biały kolor tła, kolor czcionki #97b498 (np. za pomocą pseudoklasy :hover)

    ok. 1 min
    Polecenie z arkusza

    W momencie, gdy kursor myszy znajdzie się na przycisku kolory tego przycisku są zmienione na: biały kolor tła, kolor czcionki #97b498

    Pseudoklasa :hover dopisana do selektora (bez spacji!) działa tylko wtedy, gdy kursor jest nad elementem - kolory zamieniają się miejscami. Reguła stoi pod zwykłą regułą button: przy równej sile selektorów wygrywa ta, która jest później w pliku, a button:hover i tak jest silniejszy od samego button (R.4.10).

    Typowe błędy

    button :hover ze spacją znaczy „element pod kursorem wewnątrz przycisku” - przycisk nie ma takich elementów i nic się nie zmienia.

    styl.css CSS zmienione: 4 linie
    * {
        font-family: Garamond, serif;
    }
    header {
        background: linear-gradient(#97b498, #c8e6c9);
        color: white;
        text-align: center;
        padding: 5px;
    }
    #left {
        background-color: #c8e6c9;
        color: #005005;
        width: 40%;
        height: 600px;
    }
    #right {
        color: #005005;
        width: 60%;
        height: 600px;
        overflow: scroll;
    }
    footer {
        background: linear-gradient(#c8e6c9, #97b498);
        color: white;
        padding: 12px;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    video {
        width: 100%;
    }
    form {
        padding: 10px 50px;
    }
    button {
        background-color: #97b498;
        color: white;
    }
    button:hover {
        background-color: white;
        color: #97b498;
    }
    Podgląd strony po tym kroku (index.php)
  37. Etap 4: Skrypty PHP

    Kroki 37-43 · po tym etapie masz 42 z 42 kryteriów (pkt)

    Krok 37: Połączenie z bazą forumpsy

    Kryteria:

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

    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 forumpsy

    Trzy skrypty arkusza pracują na jednej bazie, więc połączenie wystarczy otworzyć raz - przed pierwszym z nich. Skrypt 1 stoi w bloku lewym pod avatarem (tam ma być jego „efekt działania”), dlatego blok PHP z połączeniem zaczyna się właśnie tam, między <?php a ?>.

    mysqli_connect z tabeli 1 arkusza - kolejno serwer localhost, użytkownik root, puste hasło '' i nazwa bazy forumpsy: jedno wywołanie łączy z serwerem i wybiera bazę (R.5.1). Połączenie trafia do zmiennej $connection, z której skorzystają wszystkie trzy skrypty - zmienne PHP żyją przez cały plik, także w kolejnych blokach <?php … ?>.

    Od tego kroku podgląd to wynik działania strony po stronie serwera (prawdziwe uruchomienie w PHP 8.2) - na razie wygląda jak wcześniej.

    index.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'forumpsy');
            ?>
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
            <a href="http://mojestrony.pl/" target="_blank">Zobacz nasze realizacje</a>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  38. Krok 38: Skrypt 1: zapytanie 3 i nagłówek z nickiem

    Kryteria:

    R.5.3 Skrypt 1 wysyła do bazy zapytanie 3 lub skrypt 3 wysyła do bazy zapytanie 4

    ok. 3 min
    Polecenie z arkusza

    Działanie skryptu 1: Wysyła do bazy zapytanie 3. Dane zwrócone zapytaniem wyświetlane są według wzoru, gdzie w nawiasach <> zapisano wartości pobrane z bazy: „Użytkownik: <nick>” w nagłówku czwartego stopnia

    • mysqli_query($connection, '…') - zapytanie 3 z kwerendy.txt, w jednej linii (R.5.3); wynik trafia do $result,
    • mysqli_fetch_row($result) - pierwszy (i jedyny) wiersz wyniku jako tablica numerowana od zera: $row[0] to nick, $row[1] - liczba postów, $row[2] - treść pytania, w kolejności kolumn z SELECT,
    • echo "<h4>Użytkownik: $row[0]</h4>"; - nagłówek czwartego stopnia. Napis jest w cudzysłowie, więc PHP podstawia wartość $row[0] w jego środku.

    Zapytanie zwraca jeden wiersz (jedno pytanie o id 1), dlatego wystarczy jedno pobranie bez pętli. Podgląd: „Użytkownik: Jacek” pod avatarem.

    Typowe błędy

    W apostrofach '<h4>Użytkownik: $row[0]</h4>' PHP nie podstawia zmiennych - na stronie pojawi się dosłownie $row[0].

    index.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'forumpsy');
            $result = mysqli_query($connection, 'SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;');
            $row = mysqli_fetch_row($result);
            echo "<h4>Użytkownik: $row[0]</h4>";
            ?>
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
            <a href="http://mojestrony.pl/" target="_blank">Zobacz nasze realizacje</a>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  39. Krok 39: Skrypt 1: liczba postów i treść pytania w paragrafach

    Kryteria: ,

    R.5.4 Skrypt 1 wyświetla dane wg wzoru: w h4: "Użytkownik: <nick>", w paragrafie: "<liczba postów> postów na forum”, w paragrafie <pytanie>, gdzie w nawiasach <> zapisano wartości pobrane z bazy

    R.2.6 W bloku banera zapisano nagłówek h1: "Forum miłośników psów", w bloku prawym h2: "Odpowiedzi na pytanie" oraz zapisano przynajmniej jeden paragraf (w skrypcie lub HTML)

    ok. 2 min
    Polecenie z arkusza

    „<liczba postów> postów na forum” w paragrafie. <pytanie> w paragrafie

    Dwa kolejne echo, każde z paragrafem <p>: liczba postów z dopiskiem „postów na forum” ($row[1]) i sama treść pytania ($row[2]). Skrypt 1 wypisuje dane według wzoru z arkusza (R.5.4). Strona ma już nagłówki <h1> i <h2> oraz paragrafy (R.2.6).

    Podgląd: „Użytkownik: Jacek”, „50 postów na forum” i pytanie o sierść sznaucera - jak na obrazie 2.

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'forumpsy');
            $result = mysqli_query($connection, 'SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;');
            $row = mysqli_fetch_row($result);
            echo "<h4>Użytkownik: $row[0]</h4>";
            echo "<p>$row[1] postów na forum</p>";
            echo "<p>$row[2]</p>";
            ?>
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
            <a href="http://mojestrony.pl/" target="_blank">Zobacz nasze realizacje</a>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  40. Krok 40: Skrypt 2: dodanie odpowiedzi z formularza

    Kryteria: ,

    R.5.5 Skrypt 2 wysyła do bazy zmodyfikowane zapytanie 1 ten sposób, że w polu odpowiedz znajduje się wartość z pola tekstowego, należy sprawdzić w phpMyAdmin

    R.5.6 Jeżeli pole tekstowe jest puste, skrypt 2 nie wysyła danych do bazy (w skrypcie 2 znajduje się warunek sprawdzający czy pole jest puste)

    ok. 4 min
    Polecenie z arkusza

    Działanie skryptu 2: Jeżeli wpisano dane do pola wielowierszowego i dane nie są puste, skrypt wysyła do bazy zmodyfikowane zapytanie 1, w ten sposób, że w polu odpowiedz znajduje się wartość z pola wielowierszowego

    Blok PHP zaraz pod formularzem - przed listą odpowiedzi, żeby nowa odpowiedź od razu się na niej pojawiła:

    • !empty($_POST['odpowiedz']) - empty jest prawdziwe, gdy klucza w tablicy nie ma (pierwsze wejście na stronę, formularz nie był wysłany) albo gdy pole jest puste. Wykrzyknik odwraca warunek: dalej skrypt idzie tylko wtedy, gdy coś wpisano (R.5.6),
    • $answer = $_POST['odpowiedz']; - treść z pola do zmiennej,
    • mysqli_query(…, "INSERT INTO … VALUES (1, 5, '$answer');") - zapytanie 1 z kwerendy.txt, w którym w miejsce stałej treści stoi $answer (R.5.5). Pytanie nr 1 i użytkownik nr 5 zostają jak w zapytaniu 1 - arkusz każe zmienić tylko pole odpowiedz.

    Sprawdzenie, jak każe klucz: po wysłaniu formularza w phpMyAdmin jest nowy wiersz 9 z treścią z formularza. Podgląd: wpisana odpowiedź „Do sznaucera kup trymer…” została wysłana - skrypt 3 jeszcze jej nie wypisuje, lista jest pusta.

    Typowe błędy

    Zapytanie w apostrofach 'INSERT … VALUES (1, 5, '$answer');' to błąd składni PHP - apostrof przed $answer kończy napis. Zewnętrzne ograniczniki muszą być cudzysłowem.

    index.php PHP zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'forumpsy');
            $result = mysqli_query($connection, 'SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;');
            $row = mysqli_fetch_row($result);
            echo "<h4>Użytkownik: $row[0]</h4>";
            echo "<p>$row[1] postów na forum</p>";
            echo "<p>$row[2]</p>";
            ?>
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <?php
            if (!empty($_POST['odpowiedz'])) {
                $answer = $_POST['odpowiedz'];
                mysqli_query($connection, "INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, '$answer');");
            }
            ?>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
            <a href="http://mojestrony.pl/" target="_blank">Zobacz nasze realizacje</a>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  41. Krok 41: Skrypt 3: zapytanie 4 i elementy listy

    Kryteria: ,

    R.5.8 Skrypt 3 wyświetla dane w elementach listy

    R.2.7 W bloku prawym umieszczono listę numerowaną, zastosowano znaczniki <ol>, <li> (w skrypcie lub HTML)

    ok. 3 min
    Polecenie z arkusza

    Działanie skryptu 3: Wysyła do bazy zapytanie 4. Każdy wiersz zwrócony zapytaniem jest wyświetlany w elemencie listy

    Blok PHP wewnątrz <ol> - skrypt wypisuje same elementy <li>, a lista dookoła jest w HTML-u:

    • mysqli_query z zapytaniem 4 - odpowiedzi na pytanie nr 1 z nickami autorów,
    • while ($row = mysqli_fetch_row($result)) - pętla bierze kolejne wiersze wyniku. Gdy wierszy zabraknie, funkcja zwraca false i pętla się kończy,
    • echo "<li>$row[1]</li>"; - treść odpowiedzi ($row[1], druga kolumna zapytania 4) w elemencie listy (R.5.8, R.2.7).

    Podgląd: cztery ponumerowane odpowiedzi - trzy z obrazu 2 i czwarta, dodana zapytaniem 1 (obraz 2 pokazuje stronę sprzed tego zapytania).

    index.php PHP zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'forumpsy');
            $result = mysqli_query($connection, 'SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;');
            $row = mysqli_fetch_row($result);
            echo "<h4>Użytkownik: $row[0]</h4>";
            echo "<p>$row[1] postów na forum</p>";
            echo "<p>$row[2]</p>";
            ?>
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <?php
            if (!empty($_POST['odpowiedz'])) {
                $answer = $_POST['odpowiedz'];
                mysqli_query($connection, "INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, '$answer');");
            }
            ?>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
                <?php
                $result = mysqli_query($connection, 'SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id WHERE odpowiedzi.Pytania_id = 1;');
                while ($row = mysqli_fetch_row($result)) {
                    echo "<li>$row[1]</li>";
                }
                ?>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
            <a href="http://mojestrony.pl/" target="_blank">Zobacz nasze realizacje</a>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  42. Krok 42: Skrypt 3: nick w znaczniku em i linia pozioma

    Kryteria: ,

    R.5.7 Skrypt 3 wyświetla pola odpowiedz i nick każdego zwróconego wiersza

    R.3.7 W bloku prawym nicki osób publikujących odpowiedzi są wyróżnione znacznikiem <em> </em> (w skrypcie lub HTML)

    ok. 2 min
    Polecenie z arkusza

    Wyświetlane jest pole odpowiedz, pole nick oraz linia pozioma. Nick jest zapisany za pomocą znacznika semantycznego pochylającego tekst i oznaczającego tekst wyróżniony

    Do elementu listy dochodzą nick i linia:

    • <em> (emphasis) to znacznik semantyczny: oznacza tekst wyróżniony w znaczeniu, a przeglądarka domyślnie pisze go kursywą. <i> też pochyla tekst, ale nie niesie znaczenia - arkusz opisuje dokładnie <em> (R.3.7),
    • <hr> - linia pozioma pod każdą odpowiedzią, jak na obrazie 2.

    Skrypt 3 wypisuje dla każdego wiersza odpowiedź i nick (R.5.7). Podgląd: „… nie zraził się do tego zabiegu. Anna”, pod spodem linia.

    Typowe błędy

    <strong> też jest semantyczne, ale pogrubia (oznacza tekst ważny) - klucz wymaga <em>.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'forumpsy');
            $result = mysqli_query($connection, 'SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;');
            $row = mysqli_fetch_row($result);
            echo "<h4>Użytkownik: $row[0]</h4>";
            echo "<p>$row[1] postów na forum</p>";
            echo "<p>$row[2]</p>";
            ?>
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <?php
            if (!empty($_POST['odpowiedz'])) {
                $answer = $_POST['odpowiedz'];
                mysqli_query($connection, "INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, '$answer');");
            }
            ?>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
                <?php
                $result = mysqli_query($connection, 'SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id WHERE odpowiedzi.Pytania_id = 1;');
                while ($row = mysqli_fetch_row($result)) {
                    echo "<li>$row[1] <em>$row[2]</em><hr></li>";
                }
                ?>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
            <a href="http://mojestrony.pl/" target="_blank">Zobacz nasze realizacje</a>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  43. Krok 43: 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) po pętli skryptu 3 - to ostatnia operacja na bazie w całym pliku: skrypt 1 i skrypt 2 są wcześniej (R.5.2).

    Strona spełnia wszystkie kryteria arkusza. Podgląd: formularz wysłany z odpowiedzią „Do sznaucera kup trymer…” - pojawia się na liście jako piąta, podpisana nickiem Gosia (użytkownik nr 5 z zapytania 1). Dalej trzy kroki o zabezpieczeniach - klucz ich nie wymaga.

    Typowe błędy

    mysqli_close w bloku skryptu 1 zamknie połączenie, zanim skrypty 2 i 3 z niego skorzystają - PHP zgłosi błąd „mysqli object is already closed”.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'forumpsy');
            $result = mysqli_query($connection, 'SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;');
            $row = mysqli_fetch_row($result);
            echo "<h4>Użytkownik: $row[0]</h4>";
            echo "<p>$row[1] postów na forum</p>";
            echo "<p>$row[2]</p>";
            ?>
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <?php
            if (!empty($_POST['odpowiedz'])) {
                $answer = $_POST['odpowiedz'];
                mysqli_query($connection, "INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, '$answer');");
            }
            ?>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
                <?php
                $result = mysqli_query($connection, 'SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id WHERE odpowiedzi.Pytania_id = 1;');
                while ($row = mysqli_fetch_row($result)) {
                    echo "<li>$row[1] <em>$row[2]</em><hr></li>";
                }
                mysqli_close($connection);
                ?>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
            <a href="http://mojestrony.pl/" target="_blank">Zobacz nasze realizacje</a>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  44. Etap 5: Dobre praktyki (poza kluczem)

    Kroki 44-46 · po tym etapie masz 42 z 42 kryteriów (pkt)

    Krok 44: Zabezpieczenie: htmlspecialchars przy wypisywaniu

    ok. 4 min
    Polecenie z arkusza

    Każdy wiersz zwrócony zapytaniem jest wyświetlany w elemencie listy, w ten sposób, że wyświetlane jest pole odpowiedz, pole nick oraz linia pozioma.

    Odpowiedź z formularza trafia do bazy, a skrypt 3 wypisuje ją każdemu, kto otworzy stronę. Sprawdzone na wersji z poprzednich kroków: odpowiedź

    <script>document.title = "Strona przejęta";</script>Polecam szczotkę z naturalnego włosia.

    zapisała się w bazie bez zmian, a przy każdym kolejnym wejściu przeglądarka wykonywała ten skrypt - zmieniał tytuł karty. Zamiast niewinnej zmiany tytułu mógłby podmienić stronę albo wysłać komuś ciasteczka zalogowanego użytkownika. To atak XSS (cross-site scripting), w tej odmianie zapisany w bazie, więc trafia w każdego odwiedzającego.

    Lekarstwo: htmlspecialchars() na każdej wartości z bazy w chwili wypisywania. Funkcja zamienia <, >, & i cudzysłów na encje (&lt; itd.), więc przeglądarka pokazuje znaczniki jako zwykły tekst, a nie je wykonuje. Napis w apostrofach i kropki . łączą tekst z wynikami funkcji. Zabezpieczone są oba skrypty, które wypisują dane z bazy: 1 i 3 (liczba postów to liczba, więc zostaje bez zmian). Stare linie zostają w komentarzu.

    Podgląd: ta sama „odpowiedź” wyświetla się jako tekst ze znacznikami, a tytuł karty się nie zmienia.

    Typowe błędy

    htmlspecialchars przy zapisie do bazy zamiast przy wypisywaniu psuje dane (w bazie lądują encje &lt;) i nie chroni tekstów, które trafiły do bazy inną drogą, np. importem.

    index.php PHP zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'forumpsy');
            $result = mysqli_query($connection, 'SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;');
            $row = mysqli_fetch_row($result);
            // echo "<h4>Użytkownik: $row[0]</h4>";
            echo '<h4>Użytkownik: ' . htmlspecialchars($row[0]) . '</h4>';
            echo "<p>$row[1] postów na forum</p>";
            // echo "<p>$row[2]</p>";
            echo '<p>' . htmlspecialchars($row[2]) . '</p>';
            ?>
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <?php
            if (!empty($_POST['odpowiedz'])) {
                $answer = $_POST['odpowiedz'];
                mysqli_query($connection, "INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, '$answer');");
            }
            ?>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
                <?php
                $result = mysqli_query($connection, 'SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id WHERE odpowiedzi.Pytania_id = 1;');
                while ($row = mysqli_fetch_row($result)) {
                    // echo "<li>$row[1] <em>$row[2]</em><hr></li>";
                    echo '<li>' . htmlspecialchars($row[1]) . ' <em>' . htmlspecialchars($row[2]) . '</em><hr></li>';
                }
                mysqli_close($connection);
                ?>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
            <a href="http://mojestrony.pl/" target="_blank">Zobacz nasze realizacje</a>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  45. Krok 45: Zabezpieczenie: zapytanie przygotowane dla INSERT

    ok. 4 min
    Polecenie z arkusza

    Skrypt wysyła do bazy zmodyfikowane zapytanie 1, w ten sposób, że w polu odpowiedz znajduje się wartość z pola wielowierszowego

    Treść z formularza jest wklejona wprost w zapytanie SQL. Sprawdzone na wersji z poprzednich kroków:

    • odpowiedź z apostrofem, np. Nie wiem, co to 'trymowanie'., rozbija składnię zapytania - PHP 8.1 i nowsze zgłaszają wyjątek mysqli_sql_exception, strona urywa się w połowie, a zamiast listy odpowiedzi widać „Fatal error”,
    • odpowiedź Uważaj na pchły.'), (1, 1, 'Jacek poleca sklep zoo-spam.pl zamienia zapytanie w VALUES (1, 5, 'Uważaj na pchły.'), (1, 1, 'Jacek poleca…') - do bazy trafiają dwie odpowiedzi, w tym jedna podpisana jako Jacek, choć Jacek niczego nie pisał. To SQL injection.

    Lekarstwo to zapytanie przygotowane: mysqli_prepare ze znakiem ? w miejscu treści, mysqli_stmt_bind_param z typem 's' (napis) i mysqli_stmt_execute. Baza dostaje treść zapytania i dane osobno, więc dane nigdy nie staną się kodem SQL. Pytanie nr 1 i użytkownik nr 5 są stałe, więc zostają w treści zapytania. Stara linia zostaje w komentarzu.

    Podgląd: odpowiedź z apostrofem zapisuje się bez błędu i stoi na liście jako piąta.

    Typowe błędy

    Liczba liter w 's' musi równać się liczbie znaków ? i liczbie zmiennych - inaczej mysqli_stmt_bind_param zgłosi błąd i odpowiedź nie zostanie zapisana.

    index.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'forumpsy');
            $result = mysqli_query($connection, 'SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;');
            $row = mysqli_fetch_row($result);
            // echo "<h4>Użytkownik: $row[0]</h4>";
            echo '<h4>Użytkownik: ' . htmlspecialchars($row[0]) . '</h4>';
            echo "<p>$row[1] postów na forum</p>";
            // echo "<p>$row[2]</p>";
            echo '<p>' . htmlspecialchars($row[2]) . '</p>';
            ?>
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <?php
            if (!empty($_POST['odpowiedz'])) {
                $answer = $_POST['odpowiedz'];
                // mysqli_query($connection, "INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, '$answer');");
                $statement = mysqli_prepare($connection, 'INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, ?);');
                mysqli_stmt_bind_param($statement, 's', $answer);
                mysqli_stmt_execute($statement);
            }
            ?>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
                <?php
                $result = mysqli_query($connection, 'SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id WHERE odpowiedzi.Pytania_id = 1;');
                while ($row = mysqli_fetch_row($result)) {
                    // echo "<li>$row[1] <em>$row[2]</em><hr></li>";
                    echo '<li>' . htmlspecialchars($row[1]) . ' <em>' . htmlspecialchars($row[2]) . '</em><hr></li>';
                }
                mysqli_close($connection);
                ?>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
            <a href="http://mojestrony.pl/" target="_blank">Zobacz nasze realizacje</a>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  46. Krok 46: Zabezpieczenie: odpowiedź z samych spacji

    ok. 3 min
    Polecenie z arkusza

    Jeżeli wpisano dane do pola wielowierszowego i dane nie są puste, skrypt wysyła do bazy zmodyfikowane zapytanie 1

    Warunek !empty() przepuszcza odpowiedź złożoną z samych spacji albo enterów - sprawdzone: po wysłaniu trzech spacji na liście pojawił się pusty element z samym nickiem Gosia. Takie „odpowiedzi” zaśmiecają forum.

    • trim() zdejmuje spacje, tabulatory i entery z początku i końca tekstu, a operator ?? '' daje pusty napis, gdy formularza nie wysłano (zamiast ostrzeżenia o brakującym kluczu),
    • porównanie !== '' zamiast empty() - empty('0') jest prawdziwe, więc odpowiedź „0” była odrzucana jako puste pole.

    Odpowiedź po trim trafia do zapytania przygotowanego z poprzedniego kroku. Podgląd: wysłane same spacje - lista bez zmian, w bazie nic nie przybyło.

    index.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Forum o psach</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Forum miłośników psów</h1>
        </header>
        <section id="left">
            <img src="Avatar.png" alt="Użytkownik forum">
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'forumpsy');
            $result = mysqli_query($connection, 'SELECT nick, postow, pytanie FROM konta JOIN pytania ON konta.id = pytania.konta_id WHERE pytania.id = 1;');
            $row = mysqli_fetch_row($result);
            // echo "<h4>Użytkownik: $row[0]</h4>";
            echo '<h4>Użytkownik: ' . htmlspecialchars($row[0]) . '</h4>';
            echo "<p>$row[1] postów na forum</p>";
            // echo "<p>$row[2]</p>";
            echo '<p>' . htmlspecialchars($row[2]) . '</p>';
            ?>
            <video src="video.mp4" controls loop></video>
        </section>
        <section id="right">
            <form method="post">
                <textarea name="odpowiedz" rows="4" cols="40"></textarea><br>
                <button type="submit">Dodaj odpowiedź</button>
            </form>
            <?php
            // if (!empty($_POST['odpowiedz'])) {
            $answer = trim($_POST['odpowiedz'] ?? '');
            if ($answer !== '') {
                // $answer = $_POST['odpowiedz'];
                // mysqli_query($connection, "INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, '$answer');");
                $statement = mysqli_prepare($connection, 'INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz) VALUES (1, 5, ?);');
                mysqli_stmt_bind_param($statement, 's', $answer);
                mysqli_stmt_execute($statement);
            }
            ?>
            <h2>Odpowiedzi na pytanie</h2>
            <ol>
                <?php
                $result = mysqli_query($connection, 'SELECT odpowiedzi.id, odpowiedz, nick FROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.id WHERE odpowiedzi.Pytania_id = 1;');
                while ($row = mysqli_fetch_row($result)) {
                    // echo "<li>$row[1] <em>$row[2]</em><hr></li>";
                    echo '<li>' . htmlspecialchars($row[1]) . ' <em>' . htmlspecialchars($row[2]) . '</em><hr></li>';
                }
                mysqli_close($connection);
                ?>
            </ol>
        </section>
        <footer>
            Autor: Bartosz Bryniarski
            <a href="http://mojestrony.pl/" target="_blank">Zobacz nasze realizacje</a>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  47. Etap 6: Oddanie pracy

    Krok 47 · po tym etapie masz 42 z 42 kryteriów (pkt)

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

    ok. 5 min
    Polecenie z arkusza

    UWAGA: po zakończeniu pracy utwórz w folderze z numerem, którym został podpisany arkusz, plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowałeś 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ć: Avatar.png, import.jpg, index.php, kw1.png, kw2.png, kw3.png, kw4.png, kwerendy.txt, przeglądarka.txt, styl.css i video.mp4 (pliki Avatar.jpg i baza.sql z archiwum mogą zostać). Nagraj płytę, sprawdź jej odczyt 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 46.

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