Serwis używa plików cookies do analizy ruchu i poprawy działania. Szczegóły
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.
Suwak i strzałki obok niego - albo ←→ na klawiaturze - przechodzą krok po kroku; wyjaśnienie, pliki i podgląd zmieniają się razem z krokiem.
Lista „Etap” przenosi od razu do wybranego etapu.
„Rozwiązuję samodzielnie” chowa rozwiązanie kroku - najpierw własna próba, potem odsłonięcie.
„Tryb prezentacji” pokazuje same kroki, bez menu strony - na rzutnik.
Etap 1: Baza danych
Kroki 1-7 · po tym etapie masz 6 z 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.
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).
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
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.txtTekstnowy plik
-- Zapytanie 1INSERT 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
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.txtTekstzmienione: 5 linii
-- Zapytanie 1INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz)VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.');-- Zapytanie 2SELECT nick, YEAR(CURDATE()) - rejestracja AS latNaForumFROM kontaWHERE nick IN ('Jacek', 'Tomek', 'Monika');
Podgląd strony po tym kroku
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.txtTekstzmienione: 5 linii
-- Zapytanie 1INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz)VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.');-- Zapytanie 2SELECT nick, YEAR(CURDATE()) - rejestracja AS latNaForumFROM kontaWHERE nick IN ('Jacek', 'Tomek', 'Monika');-- Zapytanie 3SELECT nick, postow, pytanieFROM konta JOIN pytania ON konta.id = pytania.konta_idWHERE pytania.id = 1;
Podgląd strony po tym kroku
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.txtTekstzmienione: 5 linii
-- Zapytanie 1INSERT INTO odpowiedzi (Pytania_id, konta_id, odpowiedz)VALUES (1, 5, 'Przyda Ci się także wysoki stół, na którym postawisz pieska.');-- Zapytanie 2SELECT nick, YEAR(CURDATE()) - rejestracja AS latNaForumFROM kontaWHERE nick IN ('Jacek', 'Tomek', 'Monika');-- Zapytanie 3SELECT nick, postow, pytanieFROM konta JOIN pytania ON konta.id = pytania.konta_idWHERE pytania.id = 1;-- Zapytanie 4SELECT odpowiedzi.id, odpowiedz, nickFROM odpowiedzi JOIN konta ON odpowiedzi.konta_id = konta.idWHERE odpowiedzi.Pytania_id = 1;
Podgląd strony po tym kroku
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
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
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.phpPHPnowy plik
<html><head></head><body></body></html>
Podgląd strony po tym kroku (index.php)
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.
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.
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).
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title></head><body></body></html>
Podgląd strony po tym kroku (index.php)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body></body></html>
Podgląd strony po tym kroku (index.php)
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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].
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.
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.
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.phpPHPzmienione: 6 linii
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <sectionid="left"> <imgsrc="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>";?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"></textarea><br> <buttontype="submit">Dodaj odpowiedź</button> </form><?phpif (!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 <ahref="http://mojestrony.pl/"target="_blank">Zobacz nasze realizacje</a> </footer></body></html>
Podgląd strony po tym kroku (index.php)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <sectionid="left"> <imgsrc="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>";?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"></textarea><br> <buttontype="submit">Dodaj odpowiedź</button> </form><?phpif (!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 <ahref="http://mojestrony.pl/"target="_blank">Zobacz nasze realizacje</a> </footer></body></html>
Podgląd strony po tym kroku (index.php)
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <sectionid="left"> <imgsrc="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>";?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"></textarea><br> <buttontype="submit">Dodaj odpowiedź</button> </form><?phpif (!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 <ahref="http://mojestrony.pl/"target="_blank">Zobacz nasze realizacje</a> </footer></body></html>
Podgląd strony po tym kroku (index.php)
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 (< 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 <) i nie chroni tekstów, które trafiły do bazy inną drogą, np. importem.
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.phpPHPzmienione: 4 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <sectionid="left"> <imgsrc="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>';?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"></textarea><br> <buttontype="submit">Dodaj odpowiedź</button> </form><?phpif (!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 <ahref="http://mojestrony.pl/"target="_blank">Zobacz nasze realizacje</a> </footer></body></html>
Podgląd strony po tym kroku (index.php)
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.phpPHPzmienione: 4 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Forum o psach</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Forum miłośników psów</h1> </header> <sectionid="left"> <imgsrc="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>';?> <videosrc="video.mp4"controlsloop></video> </section> <sectionid="right"> <formmethod="post"> <textareaname="odpowiedz"rows="4"cols="40"></textarea><br> <buttontype="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 <ahref="http://mojestrony.pl/"target="_blank">Zobacz nasze realizacje</a> </footer></body></html>
Podgląd strony po tym kroku (index.php)
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.
Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.
To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.
Autor rozwiązania: Bartosz Bryniarski. Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.