INF.03-09-25.06-SGCzerwiec 2025 · 59 kroków · ok. 105 min · 42 kryteria z klucza
Rozwiązanie krok po kroku: Portal remontowy z bazą (Wersja podstawowa)
Rozwiązanie buduje portal dla remontujących mieszkania: baza remonty z czterema tabelami i pięcioma zapytaniami (funkcje grupujące z aliasami, WHERE z porównaniem, DISTINCT z sortowaniem, złączenie dwóch i czterech tabel), grafika wycięta z tapety i jej lustrzane odbicie, strona zlecenia.php z dwoma formularzami (pole liczbowe oraz lista rozwijana z polami opcji), style w styl.css z tłem obrazkowym i układem na pływających blokach oraz trzy skrypty PHP: lista firm od podanej liczby pracowników, lista miast do listy rozwijanej i lista zleceń dla wybranego miasta i rodzaju remontu.
Kroki idą zdanie po zdaniu za treścią arkusza - każdy zaczyna się poleceniem z arkusza, a kod pokazuje dokładnie te linie, które je spełniają. Kod PHP jest najpierw pisany funkcjami z tabeli 1 arkusza (proceduralne mysqli), a na końcu zostaje przerobiony na wersję z prawdziwych projektów: zapytania przygotowane z bindowaniem, escapowanie danych i walidacja tego, co przychodzi z formularza. W tym arkuszu nie ma JavaScriptu.
To jest wersja podstawowa. W zakładce „Nowoczesny kod (grid, PDO)” jest drugi sposób rozwiązania tego samego arkusza: układ na siatce CSS i PHP na PDO z zapytaniami przygotowanymi od pierwszej linii - obie wersje są zgodne z kluczem.
Baza i tapeta pochodzą z prawdziwego archiwum pliki5 (załącznik do pobrania na stronie arkusza, hasło w treści zadania). Zrzuty wyników zapytań są odtworzone z tej bazy - na egzaminie robisz je sam, z całego ekranu.
Jak liczą się punkty. Klucz ma 42 kryteria (R.1.1 - R.5.10), 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-8 · po tym etapie masz 7 z 42 kryteriów (pkt)
Krok 1: Baza danych remonty
Kryteria:
R.1.1 Wykonany import tabeli do bazy danych remonty , czynność udokumentowana w pliku import w formacie JPEG
ok. 2 min
Polecenie z arkusza
Utwórz bazę danych o nazwie remonty, z zestawem polskich znaków (np. utf8_unicode_ci).
W przeglądarce otwierasz http://localhost/phpmyadmin, klikasz Nowa w lewym panelu, wpisujesz nazwę remonty i z listy metod porównywania wybierasz utf8_unicode_ci (albo utf8_polish_ci - plik remonty.sql i tak ustawia polskie porównywanie na tabelach). Ma to znaczenie w zapytaniu 3: sortowanie miast ma ustawić „Łeba” i „Łódź” po „Lesko”, nie na końcu listy.
Typowe błędy
Nazwa bazy musi być dokładnie remonty - skrypty PHP łączą się z bazą po nazwie, więc literówka odetnie punkty także za skrypty (R.5.1).
Krok 2: Import tabel z pliku remonty.sql
Kryteria:
R.1.1 Wykonany import tabeli do bazy danych remonty , czynność udokumentowana w pliku import w formacie JPEG
ok. 2 min
Polecenie z arkusza
Z rozpakowanego archiwum zaimportuj tabele z pliku remonty.sql do utworzonej bazy.
Wejdź do bazy remonty (musi być zaznaczona w lewym panelu), karta Import, Wybierz plik → remonty.sql z rozpakowanego archiwum, Wykonaj.
Po imporcie są cztery tabele z ilustracji 1: klienci (id_klienta, imie, miasto - 32 wiersze), wykonawcy (id_wykonawcy, nazwa_firmy, liczba_pracownikow - 18 firm), zlecenia (id_zlecenia, id_klienta, cena, rodzaj - 46 zleceń; rodzaj to malowanie albo gipsowanie) i wykonanie (id_wykonawcy, id_zlecenia - 52 pary). Tabela wykonanie jest tabelą łączącą: jedno zlecenie może wykonywać kilka firm, dlatego nie ma własnego identyfikatora, a jej klucz to para dwóch kluczy obcych.
Typowe błędy
Import bez wcześniejszego wejścia do bazy remonty tworzy tabele w innej bazie (np. test) - skrypty nie znajdą wtedy tabeli wykonawcy.
Krok 3: Zrzut ekranu import.jpg
Kryteria:
R.1.1 Wykonany import tabeli do bazy danych remonty , czynność udokumentowana w pliku import w formacie JPEG
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie JPEG pod nazwą import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań. Na zrzucie powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel.
Klawisz Print Screen (cały ekran, nie Alt+PrtScr, który łapie tylko aktywne okno), wklejasz do edytora grafiki i zapisujesz jako import.jpg w folderze z numerem zdającego. Uwaga na formaty w tym arkuszu: zrzut importu w JPEG, zrzuty kwerend w PNG. Na zrzucie ma być widać komunikat phpMyAdmin o pomyślnym imporcie albo cztery tabele w bazie.
Typowe błędy
Wykadrowany zrzut (samo okno przeglądarki, bez paska zadań) nie jest oceniany - klucz mówi to wprost. Zły format pliku (PNG zamiast JPEG) to też strata punktu.
Krok 4: Zapytanie 1: średnia, minimum i maksimum pracowników
Kryteria: ,
R.1.2 Zapisany plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Wykonane zapytanie 1 wybierające jedynie średnią liczbę pracowników w firmach, minimalną liczbę pracowników i maksymalną liczbę pracowników. Kolumnom nadano aliasy, kolejno: „średnia”, „najmniej”, „najwięcej”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT AVG(liczba_pracownikow) AS "średnia", MIN(liczba_pracownikow) AS "najmniej", MAX(liczba_pracownikow) AS "najwięcej" FROM wykonawcy; (AS opcjonalnie) oraz wynik działania zapytania udokumentowany zrzutem, na którym widoczne są wartości: 27.3333 (dowolne zaokrąglenie) , 11, 55
ok. 3 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie średnią liczbę pracowników w firmach, minimalną liczbę pracowników i maksymalną liczbę pracowników. Kolumnom należy nadać nazwy (aliasy), kolejno: „średnia”, „najmniej”, „najwięcej”.
Zakładka SQL w phpMyAdmin (przy zaznaczonej bazie remonty). Trzy funkcje grupujące liczone na całej tabeli, więc bez GROUP BY - wynik to jeden wiersz:
AVG(liczba_pracownikow) AS "średnia" - średnia; AS nadaje kolumnie wyniku nazwę (alias). Cudzysłów przy aliasie z polską literą jest najbezpieczniejszy, choć MySQL przyjąłby też AS średnia bez niego - litery spoza ASCII są dozwolone w nazwach.
MIN(...) AS "najmniej", MAX(...) AS "najwięcej" - najmniejsza i największa wartość.
FROM wykonawcy - liczba pracowników jest w tabeli firm.
Wynik: 27.3333, 11, 55 - dokładnie wartości z klucza. Zapytanie kopiujesz do pliku kwerendy.txt i robisz zrzut: kw1.png.
Typowe błędy
Kolejność aliasów jest sprawdzana: średnia, najmniej, najwięcej. Zamiana miejscami kolumn to niespełnione kryterium R.1.3.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT AVG(liczba_pracownikow) AS "średnia", MIN(liczba_pracownikow) AS "najmniej", MAX(liczba_pracownikow) AS "najwięcej" FROM wykonawcy;
Podgląd strony po tym kroku
Krok 5: Zapytanie 2: firmy z co najmniej 40 pracownikami
Kryteria:
R.1.4 Wykonane zapytanie 2 wybierające jedynie nazwy firm i liczbę pracowników dla firm, w których liczba pracowników wynosi 40 i więcej. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= 40; oraz wynik działania zapytania udokumentowany zrzutem, na którym widocznych jest dokładnie 5 rekordów. Nazwy firm: BBud, GBud, KBud, RBud, SBud
ok. 1 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie nazwy firm i liczbę pracowników dla firm, w których liczba pracowników wynosi 40 i więcej.
SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= 40 - dwie kolumny i warunek „40 i więcej”, czyli >=, nie >. Wynik: dokładnie 5 firm: BBud, GBud, KBud, RBud, SBud. Zrzut: kw2.png. To zapytanie wyśle skrypt 1, podstawiając w miejsce 40 liczbę z formularza.
Typowe błędy
> 40 pominie firmę z dokładnie 40 pracownikami. „40 i więcej” to >= 40.
kwerendy.txtTekstzmienione: 3 linie
-- Zapytanie 1SELECT AVG(liczba_pracownikow) AS "średnia", MIN(liczba_pracownikow) AS "najmniej", MAX(liczba_pracownikow) AS "najwięcej" FROM wykonawcy;-- Zapytanie 2SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= 40;
Podgląd strony po tym kroku
Krok 6: Zapytanie 3: miasta klientów bez powtórzeń
Kryteria:
R.1.5 Wykonane zapytanie 3 wybierające bez powtórzeń jedynie nazwy miast, z których pochodzą klienci, posortowane rosnąco według nazwy miasta. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT DISTINCT miasto FROM klienci ORDER BY miasto; (opcjonalnie ASC; możliwe również GROUP BY, wtedy ORDER BY opcjonalne) oraz wynik działania zapytania udokumentowany zrzutem, na którym widocznych jest dokładnie 16 rekordów, pierwszy rekord to Darłowo, ostatni - Wrocław
ok. 2 min
Polecenie z arkusza
Zapytanie 3: wybierające bez powtórzeń jedynie nazwy miast, z których pochodzą klienci, posortowane rosnąco według nazwy miasta.
SELECT DISTINCT miasto - „bez powtórzeń” to słowo DISTINCT: każde miasto raz, choć w Gdańsku mieszka trzech klientów.
FROM klienci - miasto jest w tabeli klientów.
ORDER BY miasto - alfabetycznie, rosnąco (domyślne ASC).
Wynik: dokładnie 16 miast, od Darłowa do Wrocławia (klucz podaje pierwszy i ostatni rekord). Zrzut: kw3.png. To zapytanie wyśle skrypt 2 do listy rozwijanej.
Typowe błędy
Bez DISTINCT wynik ma 32 wiersze z powtórzeniami - a lista rozwijana w skrypcie 2 pokazałaby Gdańsk trzy razy.
kwerendy.txtTekstzmienione: 3 linie
-- Zapytanie 1SELECT AVG(liczba_pracownikow) AS "średnia", MIN(liczba_pracownikow) AS "najmniej", MAX(liczba_pracownikow) AS "najwięcej" FROM wykonawcy;-- Zapytanie 2SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= 40;-- Zapytanie 3SELECT DISTINCT miasto FROM klienci ORDER BY miasto;
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: malowanie u klientów z Poznania
Kryteria:
R.1.6 Wykonane zapytanie 4 wybierające jedynie imiona klientów oraz ceny ich zleceń tylko dla klientów z Poznania, którzy zamówili malowanie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT imie, cena FROM klienci JOIN zlecenia USING(id_klienta) WHERE miasto = "Poznań" AND rodzaj = "malowanie"; (możliwe również INNER JOIN lub porównanie kluczy po ON lub WHERE) oraz wynik działania zapytania udokumentowany zrzutem, na którym są widoczne dokładnie 4 rekordy: Paweł, 2500, Paweł 6500, Adam 777, Paulina 1200
ok. 3 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie imiona klientów oraz ceny ich zleceń tylko dla klientów z Poznania, którzy zamówili malowanie. Należy posłużyć się relacją.
Imię jest w klienci, cena i rodzaj w zlecenia - „posłużyć się relacją” znaczy złączyć tabele po id_klienta.
SELECT imie, cena - jedynie te dwie kolumny.
FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta - do każdego zlecenia dokładasz jego klienta; kolumna nazywa się tak samo w obu tabelach, więc poprzedzasz ją nazwą tabeli (klucz CKE pokazuje też skrót USING(id_klienta)).
WHERE miasto = "Poznań" AND rodzaj = "malowanie" - dwa warunki naraz, więc AND.
Wynik: dokładnie 4 wiersze: Paweł 2500, Paweł 6500, Adam 777, Paulina 1200. Zrzut: kw4.png. To zapytanie wyśle skrypt 3 z miastem i rodzajem z formularza.
Typowe błędy
OR zamiast AND zwróci wszystkie zlecenia z Poznania ORAZ wszystkie malowania z całej Polski - kilkanaście wierszy zamiast czterech.
kwerendy.txtTekstzmienione: 3 linie
-- Zapytanie 1SELECT AVG(liczba_pracownikow) AS "średnia", MIN(liczba_pracownikow) AS "najmniej", MAX(liczba_pracownikow) AS "najwięcej" FROM wykonawcy;-- Zapytanie 2SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= 40;-- Zapytanie 3SELECT DISTINCT miasto FROM klienci ORDER BY miasto;-- Zapytanie 4SELECT imie, cena FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta WHERE miasto = "Poznań" AND rodzaj = "malowanie";
Podgląd strony po tym kroku
Krok 8: Zapytanie 5: klienci i firmy wykonujące ich zlecenia
Kryteria:
R.1.7 Wykonane zapytanie 5 wybierające jedynie imiona klientów oraz nazwy firm wykonujących dla nich zlecenia. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT imie, nazwa_firmy FROM klienci JOIN zlecenia USING (id_klienta) JOIN wykonanie USING (id_zlecenia)JOIN wykonawcy USING (id_wykonawcy); (możliwe również INNER JOIN lub porównanie kluczy po ON lub WHERE) oraz wynik działania zapytania udokumentowany zrzutem informującym o wybranych 52 rekordach
ok. 4 min
Polecenie z arkusza
Zapytanie 5: wybierające jedynie imiona klientów oraz nazwy firm wykonujących dla nich zlecenia. Należy posłużyć się relacją.
Od imienia klienta do nazwy firmy prowadzi łańcuch czterech tabel z ilustracji 1: klienci → zlecenia (po id_klienta) → wykonanie (po id_zlecenia) → wykonawcy (po id_wykonawcy). Każdy JOIN ... ON dokłada jedno ogniwo; warunek złączenia zawsze zrównuje klucz główny jednej tabeli z kluczem obcym drugiej.
Wynik: 52 wiersze - tyle, ile par w tabeli wykonanie, bo to ona decyduje, która firma robiła które zlecenie (klient z dwoma firmami przy jednym zleceniu pojawia się dwa razy). Zrzut: kw5.png - w phpMyAdmin wystarczy komunikat „Pokazano 52 rekordów”. Plik kwerendy.txt ma komplet pięciu zapytań.
Typowe błędy
Pominięcie tabeli wykonanie (złączenie zlecenia bezpośrednio z wykonawcy) nie ma po czym złączyć - te tabele nie mają wspólnej kolumny. Łańcuch musi przejść przez tabelę łączącą.
kwerendy.txtTekstzmienione: 3 linie
-- Zapytanie 1SELECT AVG(liczba_pracownikow) AS "średnia", MIN(liczba_pracownikow) AS "najmniej", MAX(liczba_pracownikow) AS "najwięcej" FROM wykonawcy;-- Zapytanie 2SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= 40;-- Zapytanie 3SELECT DISTINCT miasto FROM klienci ORDER BY miasto;-- Zapytanie 4SELECT imie, cena FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta WHERE miasto = "Poznań" AND rodzaj = "malowanie";-- Zapytanie 5SELECT imie, nazwa_firmy FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta JOIN wykonanie ON zlecenia.id_zlecenia = wykonanie.id_zlecenia JOIN wykonawcy ON wykonanie.id_wykonawcy = wykonawcy.id_wykonawcy;
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 9-34 · po tym etapie masz 21 z 42 kryteriów (pkt)
Krok 9: Grafika cegla.jpg: cała cegła z pierwszego wiersza, wysokość 60 px
Kryteria:
R.2.1 Grafika cegla.jpg zawiera jedną całą cegłę. Wysokość grafiki to 60 px. Grafika tapeta_prawa.png jest odbiciem poziomym grafiki tapeta_lewa.png
ok. 4 min
Polecenie z arkusza
Cechy grafiki cegla.jpg: wycięta z pliku tapeta_lewa.png, zawiera całą cegłę z pierwszego od góry wiersza cegieł. Po wycięciu cegły należy ją przeskalować z zachowaniem proporcji do wysokości 60 px.
Otwórz tapeta_lewa.png (330 × 380 px) w GIMP-ie. W pierwszym wierszu są trzy cegły, ale tylko środkowa jest cała - lewa i prawa są ucięte krawędzią obrazu. Zaznacz ją prostokątem po fugach (ok. 154 × 54 px), Obraz → Przytnij do zaznaczenia, potem Obraz → Skaluj obraz z wysokością 60 px i zamkniętym łańcuchem proporcji - szerokość wychodzi sama (ok. 171 px). Eksportuj jakocegla.jpg. Ta grafika będzie tłem nagłówka i stopki o wysokości 60 px, dlatego arkusz wymaga dokładnie takiej wysokości.
Typowe błędy
Cegła ucięta krawędzią tapety albo wycinek z fragmentem sąsiedniej cegły nie spełnia „jedna cała cegła”. Skalowanie bez proporcji rozciąga cegłę.
R.2.1 Grafika cegla.jpg zawiera jedną całą cegłę. Wysokość grafiki to 60 px. Grafika tapeta_prawa.png jest odbiciem poziomym grafiki tapeta_lewa.png
ok. 2 min
Polecenie z arkusza
Cechy grafiki tapeta_prawa.png: grafika jest odbitą poziomo wersją grafiki tapeta_lewa.png.
Ponownie otwórz oryginalną tapeta_lewa.png, Obraz → Przekształć → Odbij poziomo (lustro: czerwona cegła i czerwone kółko przechodzą na drugą stronę) i wyeksportuj jako tapeta_prawa.png. Obie tapety trafią do bloku bocznego, jedna pod drugą.
Typowe błędy
„Odbij pionowo” (góra-dół) to nie to samo co „odbij poziomo” (lustro). Arkusz mówi „poziomo”.
Podgląd strony po tym kroku
Krok 11: Podstrona kontakt.html
Kryteria:
R.3.3 Wybranie odnośnika "Kontakt" otwiera podstronę kontakt.html , wybranie odnośnika "Partnerzy" inicjuje otwarcie strony https://remonty.pl w nowym oknie
ok. 1 min
Polecenie z arkusza
Składa się ze stron o nazwie zlecenia.php, kontakt.html. W pliku kontakt.html należy umieścić tekst „Strona w budowie”.
Najprostszy plik z samym napisem „Strona w budowie” - arkusz wymaga tylko tego. Istnieje po to, żeby odnośnik „Kontakt” miał dokąd prowadzić (klucz R.3.3 sprawdza, czy się otwiera).
kontakt.htmlHTMLnowy plik
Strona w budowie
Podgląd strony po tym kroku (kontakt.html)
Krok 12: Plik zlecenia.php w HTML5
Kryteria:
R.2.2 W pliku zlecenia.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków
ok. 2 min
Polecenie z arkusza
Zapisana w języku HTML5.
W edytorze zaznaczającym składnię tworzysz plik zlecenia.php w folderze zdającego, obok grafik i podstrony. Rozszerzenie .php, bo dojdą skrypty PHP - na razie to czysty HTML.
Pierwsza linia <!DOCTYPE html> mówi przeglądarce, że dokument jest w HTML5. Dalej szkielet każdej strony: <html>, a w nim <head> (informacje o stronie) i <body> (to, co widać w oknie).
R.2.2 W pliku zlecenia.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków
ok. 1 min
Polecenie z arkusza
Zadeklarowany polski język zawartości witryny.
Atrybut lang="pl" w znaczniku <html>. Czytnik ekranu czyta wtedy tekst polską wymową, a przeglądarka dobiera słownik. Na stronie nic się nie zmienia - to informacja dla programów.
R.2.2 W pliku zlecenia.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków
ok. 1 min
Polecenie z arkusza
Jawnie zastosowany właściwy standard kodowania polskich znaków.
<meta charset="UTF-8"> jako pierwsza rzecz w <head>. „Jawnie” znaczy: wpisane w kodzie, nie zdane na domysł przeglądarki. Bez tej linii „Łódź” z bazy i „Stronę wykonał” ze stopki mogłyby wyświetlić się jako krzaki. Plik trzeba też zapisać w UTF-8.
R.3.2 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.css formatowanie CSS pochodzi jedynie z tego arkusza
ok. 1 min
Polecenie z arkusza
Arkusz stylów w pliku o nazwie styl.css prawidłowo połączony z kodem strony.
<link rel="stylesheet" href="styl.css"> w <head>. W tym arkuszu nazwa to styl.css - sprawdź ją w treści zadania, bo w innych arkuszach bywa style.css albo styles.css. Plik napiszesz po HTML.
Typowe błędy
Zła nazwa pliku w href to brak stylów i cały rezultat R.4 przepada, bo „formatowanie CSS pochodzi jedynie z tego arkusza”.
R.2.4 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML 5: header, main -> nav+aside+section+section, footer
ok. 3 min
Polecenie z arkusza
Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 4. Zawartość bloku głównego: bloki nawigacyjny, dwie sekcje, boczny.
Z ilustracji 4: blok nagłówkowy na górze, pod nim blok główny, a w nim nawigacja, dwie sekcje i blok boczny, który zajmuje prawą kolumnę na wysokości nawigacji i sekcji razem; na dole stopka. Klucz R.2.4 wymienia znaczniki wprost: <header>, <main> z <nav>, <aside>, <section>, <section>, oraz <footer> (nazwy w tabeli 2 arkusza).
Blok boczny wpisujesz przed sekcjami, choć arkusz wymienia go na końcu: układ zrobisz na pływających blokach (float), a pływający element nigdy nie wjeżdża wyżej niż elementy przed nim w kodzie. Blok boczny ma sięgać od góry nawigacji, więc w kodzie musi stać zaraz po niej. Klucz podaje tę samą kolejność: nav, aside, section, section.
Dwie sekcje nie potrzebują identyfikatorów - dostaną te same wymiary. Nazwy identyfikatorów i zmiennych piszesz w tym rozwiązaniu po angielsku (arkusz dopuszcza polski lub angielski); nazwy narzucone przez arkusz (styl.css, zlecenia.php, kolumny bazy) zostają.
Typowe błędy
<nav> albo <aside> poza <main> nie spełnia R.2.4 - klucz mówi „main → nav+aside+section+section”.
Krok 18: Blok nagłówkowy: nagłówek pierwszego stopnia
Kryteria:
R.2.5 Umieszczone w bloku nagłówkowym <h1>: „Malowanie i gipsowanie”, w sekcji lewej <h2> „Dla klientów", w sekcji środkowej <h2>: „Dla wykonawców” oraz paragraf z numerem zdającego w stopce
ok. 1 min
Polecenie z arkusza
Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści: „Malowanie i gipsowanie”.
Pierwszego stopnia to <h1>, w <header>. Jedyny <h1> na stronie - sekcje dostaną <h2>.
R.3.3 Wybranie odnośnika "Kontakt" otwiera podstronę kontakt.html , wybranie odnośnika "Partnerzy" inicjuje otwarcie strony https://remonty.pl w nowym oknie
ok. 1 min
Polecenie z arkusza
Zawartość bloku nawigacyjnego: odnośnik do podstrony kontakt.html o treści „Kontakt”.
<a href="kontakt.html">Kontakt</a> - ścieżka względna, sama nazwa pliku z tego samego folderu. Klucz R.3.3 sprawdza, czy kliknięcie otwiera podstronę. W podglądzie kliknięcia są zablokowane.
Krok 20: Blok nawigacyjny: odnośnik „Partnerzy” w nowej karcie
Kryteria:
R.3.3 Wybranie odnośnika "Kontakt" otwiera podstronę kontakt.html , wybranie odnośnika "Partnerzy" inicjuje otwarcie strony https://remonty.pl w nowym oknie
ok. 1 min
Polecenie z arkusza
Odnośnik do strony o adresie „https://remonty.pl” i treści „Partnerzy”. Odnośnik otwiera się w nowej karcie przeglądarki.
Adres zewnętrzny wpisujesz w całości, z https://. „W nowej karcie” to atrybut target="_blank" - bez niego strona partnerów zastąpiłaby portal w tej samej karcie. Klucz R.3.3 sprawdza oba odnośniki.
Typowe błędy
href="remonty.pl" bez https:// to ścieżka względna - przeglądarka szuka pliku remonty.pl w folderze strony.
R.2.6 W bloku bocznym umieszczona przynajmniej jedna grafika z tapetą, z tekstem alternatywnym "usługi"
ok. 1 min
Polecenie z arkusza
Zawartość bloku bocznego: obraz tapeta_lewa.png z tekstem alternatywnym "usługi".
<img> ze src wskazującym plik obok strony i tekstem alternatywnym z arkusza. alt jest sprawdzany w kluczu R.2.6 - czytnik ekranu i wyszukiwarka widzą tylko ten tekst. Obraz jest na razie w pełnym rozmiarze (330 px) - skaluje go dopiero CSS.
R.2.6 W bloku bocznym umieszczona przynajmniej jedna grafika z tapetą, z tekstem alternatywnym "usługi"
ok. 1 min
Polecenie z arkusza
Poniżej obraz tapeta_prawa.png z tekstem alternatywnym "usługi". Poniżej obraz tapeta_lewa.png z tekstem alternatywnym "usługi".
Dwa kolejne obrazy, każdy z tym samym alt. <img> to element liniowy, ale trzy obrazy o szerokości 100% bloku (ustawisz ją w CSS) i tak ustawią się jeden pod drugim - stąd „poniżej” bez żadnych <br>. Naprzemienne lustro daje ciągły wzór muru, jak na ilustracji 3.
R.2.5 Umieszczone w bloku nagłówkowym <h1>: „Malowanie i gipsowanie”, w sekcji lewej <h2> „Dla klientów", w sekcji środkowej <h2>: „Dla wykonawców” oraz paragraf z numerem zdającego w stopce
ok. 1 min
Polecenie z arkusza
Zawartość sekcji lewej: nagłówek drugiego stopnia o treści „Dla klientów”.
Drugiego stopnia to <h2>, w pierwszej <section>. Wyśrodkuje go reguła h2 w CSS - arkusz każe ją napisać właśnie selektorem znacznika.
R.2.8 Na stronie umieszczony przynajmniej jeden formularz
R.3.4 Przynajmniej jeden formularz wysyła dane metodą POST do tego samego pliku (poprawne również bez atrybutu action) oraz przynajmniej jeden przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu)
ok. 1 min
Polecenie z arkusza
Formularz wysyłający dane do tego samego pliku metodą bezpieczną, z elementami: etykieta, pole edycyjne, przycisk.
„Metoda bezpieczna” w rozumieniu arkusza to POST - dane nie pojawiają się w adresie strony. W specyfikacji HTTP „bezpieczne” są co prawda metody, które niczego nie zmieniają na serwerze (GET i HEAD), ale arkusz używa tego słowa w innym sensie, a klucz wymaga POST. „Do tego samego pliku” to brak atrybutu action (przeglądarka wysyła formularz na adres bieżącej strony) albo action="zlecenia.php" - klucz R.3.4 przyjmuje oba warianty. Na stronie będą dwa formularze; każdy wysyła tylko własne pola.
Typowe błędy
method="get" (albo brak method) wyśle dane w adresie - arkusz i klucz wymagają tu POST, a skrypt czytający $_POST nic nie dostanie.
R.2.9 Umieszczone kontrolki: pole edycyjne, przycisk "Szukaj firm" lub "Szukaj klientów", lista rozwijana, dwa pola opcji (radio)
R.3.5 Etykieta jest powiązana z polem edycyjnym, pole edycyjne jest typu number, pierwsze pole radio zawiera atrybut checked oraz w jednym momencie można wybrać tylko jedno pole radio
ok. 1 min
Polecenie z arkusza
Etykieta dla pola edycyjnego o treści „Ilu co najmniej pracowników potrzebujesz?”.
<label for="workers"> - atrybut for wskazuje id pola, które dopiszesz za chwilę; to jest „powiązanie” sprawdzane w kluczu R.3.5 (kliknięcie etykiety ustawia kursor w polu). Na ilustracji 5 pole stoi pod etykietą - stąd <br>.
zlecenia.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> </form> </section> <section> </section> </main> <footer> </footer></body></html>
Podgląd strony po tym kroku (zlecenia.php)
Krok 26: Formularz 1: pole liczbowe
Kryteria: ,
R.2.9 Umieszczone kontrolki: pole edycyjne, przycisk "Szukaj firm" lub "Szukaj klientów", lista rozwijana, dwa pola opcji (radio)
R.3.5 Etykieta jest powiązana z polem edycyjnym, pole edycyjne jest typu number, pierwsze pole radio zawiera atrybut checked oraz w jednym momencie można wybrać tylko jedno pole radio
ok. 1 min
Polecenie z arkusza
Poniżej pole edycyjne przeznaczone do wpisywania jedynie liczb całkowitych.
„Jedynie liczby całkowite” to type="number" - przeglądarka nie pozwoli wpisać liter i pokazuje strzałki jak na ilustracji 5 (klucz R.3.5: pole typu number). id="workers" łączy pole z etykietą, name="workers" to klucz, pod którym wartość trafi do $_POST w PHP.
Typowe błędy
Pole bez name nie jest wysyłane z formularzem - skrypt 1 nie dostanie liczby, choć pole wygląda poprawnie.
zlecenia.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> <inputtype="number"id="workers"name="workers"> </form> </section> <section> </section> </main> <footer> </footer></body></html>
Podgląd strony po tym kroku (zlecenia.php)
Krok 27: Formularz 1: przycisk „Szukaj firm”
Kryteria: ,
R.2.9 Umieszczone kontrolki: pole edycyjne, przycisk "Szukaj firm" lub "Szukaj klientów", lista rozwijana, dwa pola opcji (radio)
R.3.4 Przynajmniej jeden formularz wysyła dane metodą POST do tego samego pliku (poprawne również bez atrybutu action) oraz przynajmniej jeden przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu)
ok. 1 min
Polecenie z arkusza
Przycisk „Szukaj firm” wysyłający dane z formularza do skryptu 1.
<button type="submit">Szukaj firm</button> wewnątrz formularza wysyła go po kliknięciu. Klucz R.3.4 przyjmuje też <input type="submit" value="Szukaj firm">. „Efekt działania skryptu 1” (lista firm) pojawi się pod formularzem - dopisze go PHP.
zlecenia.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> <inputtype="number"id="workers"name="workers"> <buttontype="submit">Szukaj firm</button> </form> </section> <section> </section> </main> <footer> </footer></body></html>
R.2.5 Umieszczone w bloku nagłówkowym <h1>: „Malowanie i gipsowanie”, w sekcji lewej <h2> „Dla klientów", w sekcji środkowej <h2>: „Dla wykonawców” oraz paragraf z numerem zdającego w stopce
ok. 1 min
Polecenie z arkusza
Zawartość sekcji środkowej: nagłówek drugiego stopnia o treści „Dla wykonawców”.
Drugi <h2>, w drugiej <section>. Klucz R.2.5 sprawdza oba nagłówki drugiego stopnia razem z <h1> i paragrafem stopki.
R.2.8 Na stronie umieszczony przynajmniej jeden formularz
R.2.9 Umieszczone kontrolki: pole edycyjne, przycisk "Szukaj firm" lub "Szukaj klientów", lista rozwijana, dwa pola opcji (radio)
R.3.4 Przynajmniej jeden formularz wysyła dane metodą POST do tego samego pliku (poprawne również bez atrybutu action) oraz przynajmniej jeden przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu)
ok. 2 min
Polecenie z arkusza
Formularz wysyłający dane do tego samego pliku metodą bezpieczną, z elementami: lista rozwijana wypełniona skryptem 2.
Drugi formularz, też method="post" bez action. <select name="city"> to lista rozwijana; pod nazwą city wybrane miasto trafi do $_POST. Opcje zostawiasz puste - wstawi je skrypt 2, w pętli po wyniku zapytania 3. Na ilustracji 6 pola opcji są pod listą, stąd <br>.
Krok 30: Formularz 2: pole opcji „malowanie”, domyślnie zaznaczone
Kryteria: ,
R.2.9 Umieszczone kontrolki: pole edycyjne, przycisk "Szukaj firm" lub "Szukaj klientów", lista rozwijana, dwa pola opcji (radio)
R.3.5 Etykieta jest powiązana z polem edycyjnym, pole edycyjne jest typu number, pierwsze pole radio zawiera atrybut checked oraz w jednym momencie można wybrać tylko jedno pole radio
ok. 2 min
Polecenie z arkusza
Poniżej pole opcji o treści „malowanie”, pole jest domyślnie zaznaczone.
Pole opcji to type="radio". Trzy atrybuty robią tu robotę: name="kind" (pod tą nazwą PHP dostanie wybór), value="malowanie" (co dokładnie zostanie wysłane - tę wartość wstawisz do zapytania, więc musi być równa zawartości kolumny rodzaj) i checked - „domyślnie zaznaczone” (klucz R.3.5 sprawdza checked na pierwszym polu). Treść „malowanie” to osobna etykieta powiązana przez for.
Typowe błędy
Pole radio bez value wysyła napis „on” - skrypt 3 szukałby zleceń o rodzaju „on” i nic nie znajdzie.
Krok 31: Formularz 2: pole opcji „gipsowanie”, jeden wybór naraz
Kryteria: ,
R.2.9 Umieszczone kontrolki: pole edycyjne, przycisk "Szukaj firm" lub "Szukaj klientów", lista rozwijana, dwa pola opcji (radio)
R.3.5 Etykieta jest powiązana z polem edycyjnym, pole edycyjne jest typu number, pierwsze pole radio zawiera atrybut checked oraz w jednym momencie można wybrać tylko jedno pole radio
ok. 1 min
Polecenie z arkusza
Poniżej pole opcji o treści „gipsowanie”. W jednym momencie można wybrać tylko jedno pole opcji.
Drugie pole radio z tym samym name="kind" - i to właśnie wspólna nazwa sprawia, że „w jednym momencie można wybrać tylko jedno”: przeglądarka traktuje pola o tej samej nazwie jako jedną grupę i zaznaczenie jednego odznacza drugie (klucz R.3.5). Różne id i value, bo to różne pola. Sprawdź w podglądzie: klik na „gipsowanie” zdejmuje zaznaczenie z „malowanie”.
Typowe błędy
Różne name w dwóch polach radio (name="painting", name="plastering") pozwalają zaznaczyć oba naraz - kryterium „tylko jedno” pada.
R.2.9 Umieszczone kontrolki: pole edycyjne, przycisk "Szukaj firm" lub "Szukaj klientów", lista rozwijana, dwa pola opcji (radio)
ok. 1 min
Polecenie z arkusza
Przycisk „Szukaj klientów” wysyłający dane z formularza do skryptu 3.
Drugi przycisk type="submit", wewnątrz drugiego formularza - wysyła miasto i rodzaj remontu. Każdy formularz ma własny przycisk, bo wysyła się tylko ten formularz, w którym przycisk stoi.
R.2.7 W sekcji środkowej umieszczona jedna lista punktowana z elementami, zastosowane znaczniki <ul>, <li> (w skrypcie lub HTML)
ok. 1 min
Polecenie z arkusza
Lista punktowana (nieuporządkowana) wypełniona za pomocą skryptu 3.
<ul> pod formularzem, na razie pusta - elementy <li> wypisze skrypt 3. Klucz R.2.7 przyjmuje znaczniki listy zarówno w HTML, jak i w skrypcie; sam <ul> w HTML, a <li> w PHP to najczytelniejszy podział.
R.2.5 Umieszczone w bloku nagłówkowym <h1>: „Malowanie i gipsowanie”, w sekcji lewej <h2> „Dla klientów", w sekcji środkowej <h2>: „Dla wykonawców” oraz paragraf z numerem zdającego w stopce
R.2.10 Zastosowany znacznik <strong> do treści w stopce
ok. 1 min
Polecenie z arkusza
Zawartość stopki: paragraf o treści „Stronę wykonał: ”, dalej wstawiony numer zdającego. Tekst w paragrafie zapisany jest za pomocą znacznika semantycznego oznaczającego tekst ważny, formatowany domyślnie jako pogrubiony.
Paragraf <p> w <footer>, a w nim <strong> - semantyczny znacznik tekstu ważnego, który przeglądarka domyślnie pogrubia (klucz R.2.10 sprawdza właśnie strong). <b> daje ten sam wygląd, ale nie niesie znaczenia i nie spełnia kryterium. W tym rozwiązaniu po dwukropku stoi nazwisko autora - na egzaminie wpisujesz swój numer zdającego (PESEL). Struktura HTML jest kompletna - dalej style.
Typowe błędy
<b> zamiast <strong> wygląda tak samo, ale klucz R.2.10 wymaga znacznika semantycznego.
Kroki 35-45 · po tym etapie masz 32 z 42 kryteriów (pkt)
Krok 35: Plik styl.css: krój czcionki
Kryteria:
R.4.1 Ustawione domyślnie dla wszystkich selektorów (*): krój czcionki "Century Gothic", Verdana (tylko taka kolejność)
ok. 1 min
Polecenie z arkusza
Domyślnie dla wszystkich selektorów: krój czcionki Century Gothic, w przypadku braku Verdana.
Utwórz plik styl.css obok zlecenia.php. Selektor uniwersalny * obejmuje każdy element. font-family to lista: nazwa z dwóch słów w cudzysłowie ("Century Gothic"), potem zapasowa Verdana - klucz R.4.1 mówi: tylko taka kolejność. Podgląd od razu zmienia krój.
styl.cssCSSnowy plik
* {font-family: "Century Gothic", Verdana;}
Podgląd strony po tym kroku (zlecenia.php)
Krok 36: Ciało strony: kolor tła
Kryteria:
R.4.2 Ustawiony kolor tła dla bloku nawigacyjnego #824555 oraz dla selektora body #F0EAE2, dla bloku nagłówkowego i stopki tło w postaci obrazu z cegłą
ok. 1 min
Polecenie z arkusza
Dla selektora ciała strony: kolor tła #F0EAE2.
Selektor body - arkusz podkreśla w uwadze, że ciało strony, h2 i a formatujesz wyłącznie selektorem znacznika (bez klas i identyfikatorów), bo witryna ma być później rozbudowywana. Kolor szesnastkowy przepisujesz z arkusza: jasny beż.
Krok 37: Nagłówek i stopka: tło z cegły, biały tekst, wysokość, wyśrodkowanie
Kryteria: , , ,
R.4.2 Ustawiony kolor tła dla bloku nawigacyjnego #824555 oraz dla selektora body #F0EAE2, dla bloku nagłówkowego i stopki tło w postaci obrazu z cegłą
R.4.3 Ustawiony kolor czcionki: dla bloku nagłówkowego i stopki biały oraz dla selektora a #F0EAE2
R.4.4 Ustawiona wysokość bloku nagłówkowego, nawigacyjnego i stopki 60px, bocznego 560px, obu sekcji 500px
R.4.6 Ustawione wyrównanie tekstu do środka dla bloku nagłówkowego, stopki i selektora h2 oraz do prawej strony dla bloku bocznego
ok. 2 min
Polecenie z arkusza
Dla bloku nagłówkowego i stopki: tło, które stanowi obraz cegla.jpg, biały kolor czcionki, wysokość 60 px, wyrównanie tekstu do środka.
Wspólne cechy dwóch bloków piszesz raz - header, footer. Tło z obrazu to background-image: url("cegla.jpg") - ścieżka względem pliku CSS, który leży w tym samym folderze co grafika. Obraz jest mniejszy niż blok, więc przeglądarka domyślnie go powtarza w obu kierunkach - stąd mur z cegieł na ilustracji 3; wysokość bloku 60 px równa wysokości cegły daje dokładnie jeden rząd. Biały tekst (color: white) i wyśrodkowanie dopełniają regułę.
Typowe błędy
background: url(cegla.jpg) też działa, ale background-color: url(...) - nie: kolor i obraz to różne właściwości.
Krok 38: Blok nawigacyjny: tło, szerokość, wysokość
Kryteria: , ,
R.4.2 Ustawiony kolor tła dla bloku nawigacyjnego #824555 oraz dla selektora body #F0EAE2, dla bloku nagłówkowego i stopki tło w postaci obrazu z cegłą
R.4.4 Ustawiona wysokość bloku nagłówkowego, nawigacyjnego i stopki 60px, bocznego 560px, obu sekcji 500px
R.4.5 Ustawiona szerokość bloku nawigacyjnego 80%, bocznego 20%, obu sekcji 40% (width lub grid-template-columns lub flex) , oraz jedynie dla obrazów w bloku bocznym 100%
ok. 1 min
Polecenie z arkusza
Dla bloku nawigacyjnego: kolor tła #824555, szerokość 80%, wysokość 60 px.
Selektor znacznika nav. Bordowe tło z arkusza, szerokość 80% strony (pozostałe 20% zajmie blok boczny) i wysokość 60 px, taka sama jak nagłówka.
R.4.4 Ustawiona wysokość bloku nagłówkowego, nawigacyjnego i stopki 60px, bocznego 560px, obu sekcji 500px
R.4.5 Ustawiona szerokość bloku nawigacyjnego 80%, bocznego 20%, obu sekcji 40% (width lub grid-template-columns lub flex) , oraz jedynie dla obrazów w bloku bocznym 100%
R.4.6 Ustawione wyrównanie tekstu do środka dla bloku nagłówkowego, stopki i selektora h2 oraz do prawej strony dla bloku bocznego
R.4.9 Ustawiona właściwość overflow dla bloku bocznego - hidden, dla obu sekcji - auto
ok. 2 min
Polecenie z arkusza
Dla bloku bocznego: szerokość 20%, wysokość 560 px, wyrównanie tekstu do prawej strony, w przypadku przepełnienia bloku zawartość poza blokiem jest ukryta.
Selektor aside. Wysokość 560 px to dokładnie nawigacja (60 px) plus sekcje (500 px) - blok boczny ma sięgać od góry nawigacji do dołu sekcji. „Zawartość poza blokiem jest ukryta” to overflow: hidden (klucz R.4.9): trzy tapety jedna pod drugą są wyższe niż 560 px i dolna zostanie po prostu obcięta, bez paska przewijania. text-align: right dla wyrównania zawartości do prawej.
R.4.5 Ustawiona szerokość bloku nawigacyjnego 80%, bocznego 20%, obu sekcji 40% (width lub grid-template-columns lub flex) , oraz jedynie dla obrazów w bloku bocznym 100%
ok. 1 min
Polecenie z arkusza
Jedynie dla obrazów w bloku bocznym: szerokość 100%.
„Jedynie dla obrazów w bloku bocznym” to selektor potomkaaside img - obraz, który leży wewnątrz aside. Sam selektor img objąłby też ewentualne obrazy w innych blokach, a tego arkusz nie chce. Tapeta 330 px rozciąga się na całe 20% szerokości strony, a wysokość skaluje się proporcjonalnie.
Typowe błędy
Sam selektor img nie spełnia „jedynie dla obrazów w bloku bocznym” - klucz R.4.5 sprawdza zawężenie.
R.4.4 Ustawiona wysokość bloku nagłówkowego, nawigacyjnego i stopki 60px, bocznego 560px, obu sekcji 500px
R.4.5 Ustawiona szerokość bloku nawigacyjnego 80%, bocznego 20%, obu sekcji 40% (width lub grid-template-columns lub flex) , oraz jedynie dla obrazów w bloku bocznym 100%
R.4.9 Ustawiona właściwość overflow dla bloku bocznego - hidden, dla obu sekcji - auto
ok. 1 min
Polecenie z arkusza
Dla obu sekcji: szerokość 40%, wysokość 500 px, paski przewijania tylko w przypadku przepełnienia bloku.
Selektor section obejmuje obie sekcje naraz. Dwa razy 40% plus 20% bloku bocznego daje 100%. „Paski tylko przy przepełnieniu” to overflow: auto (klucz R.4.9: hidden dla bocznego, auto dla sekcji) - gdy skrypt 1 wypisze długą listę firm, pojawi się pasek; przy pustej sekcji go nie ma.
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 4 w arkuszu egzaminacyjnym (prawidłowo zastosowane właściwości CSS decydujące o układzie bloków na stronie, np. float i clear albo display flex albo grid albo position)
ok. 3 min
Polecenie z arkusza
Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 4.
Cztery dopiski robią układ z ilustracji 4:
nav dostaje float: left - pływa w lewo i zajmuje 80% szerokości,
aside dostaje float: right - pływa w prawo, w 20% obok nawigacji, i dzięki wysokości 560 px schodzi aż do dołu sekcji,
section dostaje float: left - sekcje nie mieszczą się obok nawigacji (zostało 20%, zajęte przez aside), więc spadają pod nią i ustawiają się obok siebie, na lewo od bloku bocznego,
footer dostaje osobną regułę clear: both, żeby wrócił pod wszystkie pływające bloki (wspólna reguła header, footer zostaje).
Klucz R.3.1 przyjmuje też flex i grid - float jest najprostszy przy tej ilustracji.
Typowe błędy
Bez clear: both stopka wjeżdża pod pływające bloki, a <main> ma zerową wysokość. Blok boczny z float: left wylądowałby na lewo od sekcji.
R.4.3 Ustawiony kolor czcionki: dla bloku nagłówkowego i stopki biały oraz dla selektora a #F0EAE2
R.4.7 Ustawiony jedynie lewy margines wewnętrzny (padding-left) dla odnośników 100 px
R.4.8 Ustawiony rozmiar czcionki dla odnośników 180%
ok. 1 min
Polecenie z arkusza
Dla selektora odnośnika: kolor tekstu #F0EAE2, tylko lewy margines wewnętrzny 100 px, rozmiar czcionki 180%.
Selektor a. „Tylko lewy margines wewnętrzny” to padding-left: 100px (klucz R.4.7 sprawdza właśnie tę właściwość, nie skrót padding), a nie margin-left - margines zewnętrzny to inna rzecz. Rozmiar 180% liczy się względem czcionki rodzica, więc odnośniki są prawie dwa razy większe niż zwykły tekst, jak na ilustracji 3. Kolor tekstu taki jak tło strony.
Typowe błędy
margin-left zamiast padding-left nie spełnia R.4.7 - arkusz mówi o marginesie wewnętrznym.
Kroki 46-55 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 46: Połączenie z bazą danych
Kryteria: ,
R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy remonty
R.5.10 W skryptach zastosowana przynajmniej jedna zmienna oraz nazwy wszystkich zmiennych są w języku polskim lub angielskim i odzwierciedlają cel zastosowania
ok. 3 min
Polecenie z arkusza
Napisane w języku PHP. Łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie remonty.
Kod PHP piszesz między <?php a ?> - serwer wykonuje go, zanim strona trafi do przeglądarki. Połączenie otwierasz raz, na samym początku pliku, i trzymasz w zmiennej $connection (znacząca, angielska nazwa - R.5.10), z której skorzystają trzy skrypty. mysqli_connect z tabeli 1 arkusza przyjmuje kolejno: serwer, użytkownika, hasło (puste) i nazwę bazy.
Od tego kroku podgląd to wynik działania strony po stronie serwera (PHP nie wykonuje się w przeglądarce) - na razie wygląda jak wcześniej.
Typowe błędy
Kolejność argumentów ma znaczenie: mysqli_connect('localhost', 'root', 'remonty') podaje „remonty” jako HASŁO. Nazwa bazy jest czwarta. Plik sprawdzaj przez http://localhost/…, nie otwierając go z dysku.
R.5.9 Przynajmniej jeden skrypt obsługujący formularz realizuje czynności tylko wtedy, gdy formularz wypełniono
ok. 2 min
Polecenie z arkusza
Skrypt 1, wykonywany tylko gdy wypełniono formularz.
Skrypt 1 piszesz w sekcji lewej, pod formularzem - tam ma się pojawić lista firm. !empty($_POST['workers']) jest prawdziwe tylko wtedy, gdy wysłano pierwszy formularz z wpisaną liczbą: przy wejściu na stronę tablica $_POST jest pusta, a po wysłaniu drugiego formularza nie ma w niej klucza workers. Cały blok if jest wtedy pomijany (klucz R.5.9: „tylko gdy wypełniono formularz”).
Samo isset z tabeli 1 tu nie wystarczy: po kliknięciu „Szukaj firm” z pustym polem klucz workers istnieje (z pustym napisem), zapytanie miałoby postać ... >= ;, a PHP 8 przerwałby stronę błędem SQL.
Typowe błędy
isset($_POST['workers']) przepuszcza puste pole - zapytanie z pustym warunkiem kończy się wyjątkiem mysqli_sql_exception i zamiast strony widać komunikat błędu.
Krok 48: Skrypt 1: zapytanie 2 z liczbą z formularza
Kryteria: ,
R.5.3 Przynajmniej jeden skrypt zawiera instrukcję wysyłającą do bazy danych zapytanie
R.5.4 Skrypt 1 wyświetla każdy wiersz zwrócony zmodyfikowanym zapytaniem 2, tak że w warunku podana jest wartość wysłana z formularza
ok. 2 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie 2, zmodyfikowane tak, że w warunku podana jest wartość wysłana z formularza.
Liczbę z pola workers przepisujesz do zmiennej $workers, a zapytanie 2 z kwerendy.txt dostaje ją w miejsce 40 - cały napis w cudzysłowie, żeby PHP podstawił zmienną. mysqli_query($connection, ...) wysyła zapytanie i zwraca wynik, który zapisujesz w $companies (klucz R.5.3).
R.5.4 Skrypt 1 wyświetla każdy wiersz zwrócony zmodyfikowanym zapytaniem 2, tak że w warunku podana jest wartość wysłana z formularza
R.5.5 Dane są wyświetlone w postaci „<firma>, <liczbaPracownikow> pracowników”
ok. 2 min
Polecenie z arkusza
Dla każdego zwróconego wiersza wypisuje paragraf o treści <firma>, <liczbaPracownikow> pracowników, gdzie pola <> są pobrane z bazy.
„Dla każdego wiersza” to pętla. mysqli_fetch_row($companies) (tabela 1) oddaje kolejny wiersz jako tablicę od zera, a gdy wierszy zabraknie - false, co kończy while. Kolejność kolumn jak w SELECT: $row[0] nazwa firmy, $row[1] liczba pracowników. echo składa paragraf dokładnie w formacie z arkusza: nazwa, przecinek, liczba, słowo „pracowników” (klucz R.5.5 sprawdza ten format). Podgląd pokazuje stan po wpisaniu 30 - siedem firm, jak na ilustracji 5.
Typowe błędy
echo '<p>$row[0], $row[1] pracowników</p>' w apostrofach wypisze dosłownie „$row[0]” - PHP nie podstawia zmiennych w apostrofach.
R.5.3 Przynajmniej jeden skrypt zawiera instrukcję wysyłającą do bazy danych zapytanie
ok. 1 min
Polecenie z arkusza
Skrypt 2: wysyła do bazy danych zapytanie 3.
Skrypt 2 piszesz wewnątrz<select>, bo tam mają trafić opcje. Działa zawsze, bez if - lista miast ma być widoczna od pierwszego wejścia na stronę. Treść to dokładnie zapytanie 3 z kwerendy.txt: miasta bez powtórzeń, alfabetycznie. Wynik w $cities.
zlecenia.phpPHPzmienione: 3 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'remonty');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> <inputtype="number"id="workers"name="workers"> <buttontype="submit">Szukaj firm</button> </form><?phpif (!empty($_POST['workers'])) {$workers = $_POST['workers'];$companies = mysqli_query($connection, "SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= $workers;");while ($row = mysqli_fetch_row($companies)) {echo"<p>$row[0], $row[1] pracowników</p>"; }}?> </section> <section> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"><?php$cities = mysqli_query($connection, 'SELECT DISTINCT miasto FROM klienci ORDER BY miasto;');?> </select><br> <inputtype="radio"id="painting"name="kind"value="malowanie"checked> <labelfor="painting">malowanie</label><br> <inputtype="radio"id="plastering"name="kind"value="gipsowanie"> <labelfor="plastering">gipsowanie</label><br> <buttontype="submit">Szukaj klientów</button> </form> <ul> </ul> </section> </main> <footer> <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p> </footer></body></html>
R.5.6 Skrypt 2 wyświetla nazwy miast zwrócone zapytaniem 3 jako opcje listy rozwijanej
ok. 2 min
Polecenie z arkusza
Każde zwrócone zapytaniem miasto jest treścią opcji listy rozwijanej.
Ta sama pętla co w skrypcie 1, tylko wypisuje <option>. Bez atrybutu value formularz wysyła treść opcji, czyli nazwę miasta - dokładnie to, czego skrypt 3 potrzebuje w warunku miasto = .... Podgląd: lista ma 16 miast, od Darłowa, jak na ilustracji 6.
zlecenia.phpPHPzmienione: 3 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'remonty');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> <inputtype="number"id="workers"name="workers"> <buttontype="submit">Szukaj firm</button> </form><?phpif (!empty($_POST['workers'])) {$workers = $_POST['workers'];$companies = mysqli_query($connection, "SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= $workers;");while ($row = mysqli_fetch_row($companies)) {echo"<p>$row[0], $row[1] pracowników</p>"; }}?> </section> <section> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"><?php$cities = mysqli_query($connection, 'SELECT DISTINCT miasto FROM klienci ORDER BY miasto;');while ($row = mysqli_fetch_row($cities)) {echo"<option>$row[0]</option>";}?> </select><br> <inputtype="radio"id="painting"name="kind"value="malowanie"checked> <labelfor="painting">malowanie</label><br> <inputtype="radio"id="plastering"name="kind"value="gipsowanie"> <labelfor="plastering">gipsowanie</label><br> <buttontype="submit">Szukaj klientów</button> </form> <ul> </ul> </section> </main> <footer> <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p> </footer></body></html>
Podgląd strony po tym kroku (zlecenia.php)
Krok 52: Skrypt 3: tylko po wysłaniu drugiego formularza
Kryteria:
R.5.9 Przynajmniej jeden skrypt obsługujący formularz realizuje czynności tylko wtedy, gdy formularz wypełniono
ok. 2 min
Polecenie z arkusza
Skrypt 3, wykonywany tylko gdy wypełniono formularz.
Skrypt 3 piszesz wewnątrz<ul> - elementy listy mają trafić do środka. Warunek sprawdza klucz city, który wysyła tylko drugi formularz (lista rozwijana zawsze ma jakąś wartość wybraną, więc klucz jest, gdy formularz poszedł). Od tego kroku podgląd pokazuje stan po wysłaniu drugiego formularza (Wrocław, malowanie), więc lista firm z pierwszego formularza znika - oba formularze nie wysyłają się naraz.
zlecenia.phpPHPzmienione: 4 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'remonty');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> <inputtype="number"id="workers"name="workers"> <buttontype="submit">Szukaj firm</button> </form><?phpif (!empty($_POST['workers'])) {$workers = $_POST['workers'];$companies = mysqli_query($connection, "SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= $workers;");while ($row = mysqli_fetch_row($companies)) {echo"<p>$row[0], $row[1] pracowników</p>"; }}?> </section> <section> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"><?php$cities = mysqli_query($connection, 'SELECT DISTINCT miasto FROM klienci ORDER BY miasto;');while ($row = mysqli_fetch_row($cities)) {echo"<option>$row[0]</option>";}?> </select><br> <inputtype="radio"id="painting"name="kind"value="malowanie"checked> <labelfor="painting">malowanie</label><br> <inputtype="radio"id="plastering"name="kind"value="gipsowanie"> <labelfor="plastering">gipsowanie</label><br> <buttontype="submit">Szukaj klientów</button> </form> <ul><?phpif (isset($_POST['city'])) {}?> </ul> </section> </main> <footer> <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p> </footer></body></html>
Podgląd strony po tym kroku (zlecenia.php)
Krok 53: Skrypt 3: zapytanie 4 z miastem i rodzajem z formularza
Kryteria:
R.5.7 Skrypt 3 wyświetla każdy wiersz zwrócony zmodyfikowanym zapytaniem 4 , tak że w warunku podane jest miasto i rodzaj remontu wysłane z formularza
ok. 3 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie 4, zmodyfikowane tak, że w warunku podane są nazwa miasta i rodzaj remontu wysłane z formularza.
Dwie wartości z $_POST: city z listy rozwijanej i kind z zaznaczonego pola radio (jego value, czyli „malowanie” albo „gipsowanie”). Zapytanie 4 z kwerendy.txt dostaje je w miejsce „Poznań” i „malowanie”; wartości w SQL w apostrofach, cały napis PHP w cudzysłowie. Wynik w $orders.
zlecenia.phpPHPzmienione: 3 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'remonty');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> <inputtype="number"id="workers"name="workers"> <buttontype="submit">Szukaj firm</button> </form><?phpif (!empty($_POST['workers'])) {$workers = $_POST['workers'];$companies = mysqli_query($connection, "SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= $workers;");while ($row = mysqli_fetch_row($companies)) {echo"<p>$row[0], $row[1] pracowników</p>"; }}?> </section> <section> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"><?php$cities = mysqli_query($connection, 'SELECT DISTINCT miasto FROM klienci ORDER BY miasto;');while ($row = mysqli_fetch_row($cities)) {echo"<option>$row[0]</option>";}?> </select><br> <inputtype="radio"id="painting"name="kind"value="malowanie"checked> <labelfor="painting">malowanie</label><br> <inputtype="radio"id="plastering"name="kind"value="gipsowanie"> <labelfor="plastering">gipsowanie</label><br> <buttontype="submit">Szukaj klientów</button> </form> <ul><?phpif (isset($_POST['city'])) {$city = $_POST['city'];$kind = $_POST['kind'];$orders = mysqli_query($connection, "SELECT imie, cena FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta WHERE miasto = '$city' AND rodzaj = '$kind';");}?> </ul> </section> </main> <footer> <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p> </footer></body></html>
R.5.7 Skrypt 3 wyświetla każdy wiersz zwrócony zmodyfikowanym zapytaniem 4 , tak że w warunku podane jest miasto i rodzaj remontu wysłane z formularza
R.5.8 Dane są wyświetlane w postaci elementów listy
ok. 2 min
Polecenie z arkusza
Każde zwrócone zapytaniem imię i cenę wypisuje w elementach listy punktowanej, oddzielone od siebie znakiem myślnika.
Pętla po wyniku wypisuje <li> z imieniem ($row[0]), myślnikiem i ceną ($row[1]). Ponieważ <ul> jest już w HTML, każdy <li> ląduje w środku listy (klucz R.5.8: dane jako elementy listy). Podgląd: siedem zleceń malowania we Wrocławiu, dokładnie jak na ilustracji 6, z cenami z dwoma miejscami po kropce - tak oddaje je kolumna decimal(10,2).
zlecenia.phpPHPzmienione: 3 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'remonty');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> <inputtype="number"id="workers"name="workers"> <buttontype="submit">Szukaj firm</button> </form><?phpif (!empty($_POST['workers'])) {$workers = $_POST['workers'];$companies = mysqli_query($connection, "SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= $workers;");while ($row = mysqli_fetch_row($companies)) {echo"<p>$row[0], $row[1] pracowników</p>"; }}?> </section> <section> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"><?php$cities = mysqli_query($connection, 'SELECT DISTINCT miasto FROM klienci ORDER BY miasto;');while ($row = mysqli_fetch_row($cities)) {echo"<option>$row[0]</option>";}?> </select><br> <inputtype="radio"id="painting"name="kind"value="malowanie"checked> <labelfor="painting">malowanie</label><br> <inputtype="radio"id="plastering"name="kind"value="gipsowanie"> <labelfor="plastering">gipsowanie</label><br> <buttontype="submit">Szukaj klientów</button> </form> <ul><?phpif (isset($_POST['city'])) {$city = $_POST['city'];$kind = $_POST['kind'];$orders = mysqli_query($connection, "SELECT imie, cena FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta WHERE miasto = '$city' AND rodzaj = '$kind';");while ($row = mysqli_fetch_row($orders)) {echo"<li>$row[0] - $row[1]</li>"; }}?> </ul> </section> </main> <footer> <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p> </footer></body></html>
Podgląd strony po tym kroku (zlecenia.php)
Krok 55: Zamknięcie połączenia z bazą
Kryteria:
R.5.2 Ostatnią operacją na bazie jest jej zamknięcie
ok. 1 min
Polecenie z arkusza
Na końcu działania zamykane jest połączenie z bazą.
mysqli_close($connection) na końcu pliku, po wszystkich trzech skryptach. Klucz R.5.2 sprawdza, że zamknięcie jest ostatnią operacją na bazie. Strona spełnia wszystkie kryteria arkusza - dalej trzy kroki „jak w prawdziwym projekcie”.
Typowe błędy
mysqli_close po skrypcie 1 sprawia, że skrypty 2 i 3 idą po zamkniętym połączeniu - lista miast zostaje pusta.
zlecenia.phpPHPzmienione: 3 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'remonty');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> <inputtype="number"id="workers"name="workers"> <buttontype="submit">Szukaj firm</button> </form><?phpif (!empty($_POST['workers'])) {$workers = $_POST['workers'];$companies = mysqli_query($connection, "SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= $workers;");while ($row = mysqli_fetch_row($companies)) {echo"<p>$row[0], $row[1] pracowników</p>"; }}?> </section> <section> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"><?php$cities = mysqli_query($connection, 'SELECT DISTINCT miasto FROM klienci ORDER BY miasto;');while ($row = mysqli_fetch_row($cities)) {echo"<option>$row[0]</option>";}?> </select><br> <inputtype="radio"id="painting"name="kind"value="malowanie"checked> <labelfor="painting">malowanie</label><br> <inputtype="radio"id="plastering"name="kind"value="gipsowanie"> <labelfor="plastering">gipsowanie</label><br> <buttontype="submit">Szukaj klientów</button> </form> <ul><?phpif (isset($_POST['city'])) {$city = $_POST['city'];$kind = $_POST['kind'];$orders = mysqli_query($connection, "SELECT imie, cena FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta WHERE miasto = '$city' AND rodzaj = '$kind';");while ($row = mysqli_fetch_row($orders)) {echo"<li>$row[0] - $row[1]</li>"; }}?> </ul> </section> </main> <footer> <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p> </footer><?phpmysqli_close($connection);?></body></html>
Kroki 56-58 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 56: Refaktor: zapytania przygotowane w skryptach 1 i 3
Kryteria: ,
R.5.4 Skrypt 1 wyświetla każdy wiersz zwrócony zmodyfikowanym zapytaniem 2, tak że w warunku podana jest wartość wysłana z formularza
R.5.7 Skrypt 3 wyświetla każdy wiersz zwrócony zmodyfikowanym zapytaniem 4 , tak że w warunku podane jest miasto i rodzaj remontu wysłane z formularza
ok. 5 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie 2, zmodyfikowane tak, że w warunku podana jest wartość wysłana z formularza.
Wartości z formularzy szły prosto do zapytań - to SQL injection: lista rozwijana i pola radio tylko podpowiadają, w żądaniu POST można wysłać dowolny napis, np. x' OR '1'='1. W prawdziwych projektach dane trafiają do zapytania przez zapytanie przygotowane: mysqli_prepare z ? w miejscu wartości, mysqli_stmt_bind_param z typami ('i' - liczba całkowita w skrypcie 1, 'ss' - dwa napisy w skrypcie 3), mysqli_stmt_execute i mysqli_stmt_get_result, z którego czytasz pętlą jak dotąd. Stare linie zostają w komentarzu. Podgląd bez zmian.
Typowe błędy
Znaki ? w zapytaniu przygotowanym stoją bez apostrofów (miasto = ?, nie miasto = '?') - w apostrofach byłyby zwykłym napisem ze znakiem zapytania i mysqli_stmt_bind_param zgłosiłby błąd liczby parametrów.
zlecenia.phpPHPzmienione: 10 linii
<?php$connection = mysqli_connect('localhost', 'root', '', 'remonty');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> <inputtype="number"id="workers"name="workers"> <buttontype="submit">Szukaj firm</button> </form><?phpif (!empty($_POST['workers'])) {$workers = $_POST['workers'];// $companies = mysqli_query($connection, "SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= $workers;");$statement = mysqli_prepare($connection, 'SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= ?'); mysqli_stmt_bind_param($statement, 'i', $workers); mysqli_stmt_execute($statement);$companies = mysqli_stmt_get_result($statement);while ($row = mysqli_fetch_row($companies)) {echo"<p>$row[0], $row[1] pracowników</p>"; }}?> </section> <section> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"><?php$cities = mysqli_query($connection, 'SELECT DISTINCT miasto FROM klienci ORDER BY miasto;');while ($row = mysqli_fetch_row($cities)) {echo"<option>$row[0]</option>";}?> </select><br> <inputtype="radio"id="painting"name="kind"value="malowanie"checked> <labelfor="painting">malowanie</label><br> <inputtype="radio"id="plastering"name="kind"value="gipsowanie"> <labelfor="plastering">gipsowanie</label><br> <buttontype="submit">Szukaj klientów</button> </form> <ul><?phpif (isset($_POST['city'])) {$city = $_POST['city'];$kind = $_POST['kind'];// $orders = mysqli_query($connection, "SELECT imie, cena FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta WHERE miasto = '$city' AND rodzaj = '$kind';");$statement = mysqli_prepare($connection, 'SELECT imie, cena FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta WHERE miasto = ? AND rodzaj = ?'); mysqli_stmt_bind_param($statement, 'ss', $city, $kind); mysqli_stmt_execute($statement);$orders = mysqli_stmt_get_result($statement);while ($row = mysqli_fetch_row($orders)) {echo"<li>$row[0] - $row[1]</li>"; }}?> </ul> </section> </main> <footer> <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p> </footer><?phpmysqli_close($connection);?></body></html>
Krok 57: Refaktor: escapowanie danych przed wypisaniem
Kryteria: , ,
R.5.5 Dane są wyświetlone w postaci „<firma>, <liczbaPracownikow> pracowników”
R.5.6 Skrypt 2 wyświetla nazwy miast zwrócone zapytaniem 3 jako opcje listy rozwijanej
R.5.8 Dane są wyświetlane w postaci elementów listy
ok. 3 min
Polecenie z arkusza
Dla każdego zwróconego wiersza wypisuje paragraf o treści <firma>, <liczbaPracownikow> pracowników, gdzie pola <> są pobrane z bazy.
Wszystkie trzy skrypty wypisują do HTML napisy z bazy: nazwy firm, miasta, imiona. Dziś są niewinne, ale wpisuje je kiedyś człowiek - a napis <script>…</script> w nazwie firmy wykonałby się u każdego, kto otworzy stronę (XSS). htmlspecialchars() zamienia <, >, ", & na encje, więc tekst wyświetla się dosłownie i nie staje się kodem. Wartości przechodzą przez funkcję do zmiennych o angielskich nazwach, a echo używa już tylko ich. Liczby ($row[1]) zostają - nie niosą znaczników. Podgląd bez zmian.
Typowe błędy
htmlspecialchars przy zapisie do bazy zamiast przy wypisywaniu to częsty błąd - baza dostaje zniekształcone dane. Escapujesz na wyjściu.
zlecenia.phpPHPzmienione: 6 linii
<?php$connection = mysqli_connect('localhost', 'root', '', 'remonty');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> <inputtype="number"id="workers"name="workers"> <buttontype="submit">Szukaj firm</button> </form><?phpif (!empty($_POST['workers'])) {$workers = $_POST['workers'];// $companies = mysqli_query($connection, "SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= $workers;");$statement = mysqli_prepare($connection, 'SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= ?'); mysqli_stmt_bind_param($statement, 'i', $workers); mysqli_stmt_execute($statement);$companies = mysqli_stmt_get_result($statement);while ($row = mysqli_fetch_row($companies)) {$company = htmlspecialchars($row[0]);echo"<p>$company, $row[1] pracowników</p>"; }}?> </section> <section> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"><?php$cities = mysqli_query($connection, 'SELECT DISTINCT miasto FROM klienci ORDER BY miasto;');while ($row = mysqli_fetch_row($cities)) {$city = htmlspecialchars($row[0]);echo"<option>$city</option>";}?> </select><br> <inputtype="radio"id="painting"name="kind"value="malowanie"checked> <labelfor="painting">malowanie</label><br> <inputtype="radio"id="plastering"name="kind"value="gipsowanie"> <labelfor="plastering">gipsowanie</label><br> <buttontype="submit">Szukaj klientów</button> </form> <ul><?phpif (isset($_POST['city'])) {$city = $_POST['city'];$kind = $_POST['kind'];// $orders = mysqli_query($connection, "SELECT imie, cena FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta WHERE miasto = '$city' AND rodzaj = '$kind';");$statement = mysqli_prepare($connection, 'SELECT imie, cena FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta WHERE miasto = ? AND rodzaj = ?'); mysqli_stmt_bind_param($statement, 'ss', $city, $kind); mysqli_stmt_execute($statement);$orders = mysqli_stmt_get_result($statement);while ($row = mysqli_fetch_row($orders)) {$name = htmlspecialchars($row[0]);echo"<li>$name - $row[1]</li>"; }}?> </ul> </section> </main> <footer> <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p> </footer><?phpmysqli_close($connection);?></body></html>
Krok 58: Refaktor: walidacja liczby i rodzaju remontu
ok. 3 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie 4, zmodyfikowane tak, że w warunku podane są nazwa miasta i rodzaj remontu wysłane z formularza.
Dwie poprawki na to, co przychodzi z formularzy:
skrypt 1: (int) rzutuje wartość na liczbę całkowitą (puste pole albo litery dają 0, nie błąd), a max(0, …) odcina liczby ujemne - formularz z type="number" to tylko wygoda w przeglądarce, nie gwarancja,
skrypt 3: rodzaj remontu może mieć tylko dwie wartości, więc sprawdzasz go białą listąin_array($kind, ['malowanie', 'gipsowanie']) i każdą inną wartość zamieniasz na domyślne „malowanie”. Miasta nie walidujesz listą - jest ich 16 i mogą dojść nowe; przed wstrzyknięciem chroni je już zapytanie przygotowane.
Na egzaminie wystarczy wersja z kroków wcześniejszych - klucz tego nie sprawdza. To już kod gotowy do prawdziwego użycia.
Typowe błędy
Walidacja po stronie przeglądarki (type="number", pola radio) nie zastępuje walidacji w PHP - żądanie POST można wysłać bez przeglądarki.
zlecenia.phpPHPzmienione: 4 linie
<?php$connection = mysqli_connect('localhost', 'root', '', 'remonty');?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Remonty</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Malowanie i gipsowanie</h1> </header> <main> <nav> <ahref="kontakt.html">Kontakt</a> <ahref="https://remonty.pl"target="_blank">Partnerzy</a> </nav> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> <section> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label><br> <inputtype="number"id="workers"name="workers"> <buttontype="submit">Szukaj firm</button> </form><?phpif (!empty($_POST['workers'])) {$workers = max(0, (int) $_POST['workers']);// $companies = mysqli_query($connection, "SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= $workers;");$statement = mysqli_prepare($connection, 'SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= ?'); mysqli_stmt_bind_param($statement, 'i', $workers); mysqli_stmt_execute($statement);$companies = mysqli_stmt_get_result($statement);while ($row = mysqli_fetch_row($companies)) {$company = htmlspecialchars($row[0]);echo"<p>$company, $row[1] pracowników</p>"; }}?> </section> <section> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"><?php$cities = mysqli_query($connection, 'SELECT DISTINCT miasto FROM klienci ORDER BY miasto;');while ($row = mysqli_fetch_row($cities)) {$city = htmlspecialchars($row[0]);echo"<option>$city</option>";}?> </select><br> <inputtype="radio"id="painting"name="kind"value="malowanie"checked> <labelfor="painting">malowanie</label><br> <inputtype="radio"id="plastering"name="kind"value="gipsowanie"> <labelfor="plastering">gipsowanie</label><br> <buttontype="submit">Szukaj klientów</button> </form> <ul><?phpif (isset($_POST['city'])) {$city = $_POST['city'];$kind = $_POST['kind'];if (!in_array($kind, ['malowanie', 'gipsowanie'])) {$kind = 'malowanie'; }// $orders = mysqli_query($connection, "SELECT imie, cena FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta WHERE miasto = '$city' AND rodzaj = '$kind';");$statement = mysqli_prepare($connection, 'SELECT imie, cena FROM klienci JOIN zlecenia ON klienci.id_klienta = zlecenia.id_klienta WHERE miasto = ? AND rodzaj = ?'); mysqli_stmt_bind_param($statement, 'ss', $city, $kind); mysqli_stmt_execute($statement);$orders = mysqli_stmt_get_result($statement);while ($row = mysqli_fetch_row($orders)) {$name = htmlspecialchars($row[0]);echo"<li>$name - $row[1]</li>"; }}?> </ul> </section> </main> <footer> <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p> </footer><?phpmysqli_close($connection);?></body></html>
Krok 59 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 59: Plik przeglądarka.txt i nagranie płyty
ok. 5 min
Polecenie z arkusza
Po zakończeniu pracy utwórz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. 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ć: cegla.jpg, import.jpg, kontakt.html, kw1.png … kw5.png, kwerendy.txt, przeglądarka.txt, styl.css, tapeta_lewa.png, tapeta_prawa.png, zlecenia.php. Nagraj płytę, sprawdź jej odczyt i opisz 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.