INF.03-09-25.06-SGCzerwiec 2025 · 60 kroków · ok. 106 min · 42 kryteria z klucza
Rozwiązanie krok po kroku: Portal remontowy z bazą (Nowoczesny kod (grid, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza remonty z pięcioma zapytaniami, cegła wycięta z tapety i jej lustrzane odbicie, strona zlecenia.php z dwoma formularzami i trzema skryptami - i tak samo spełnia każde kryterium klucza. Inna jest droga:
układ na siatce CSS z nazwanymi obszarami (grid-template-areas) zamiast pływających bloków float i clear - blok boczny może stać w kodzie tam, gdzie wymienia go arkusz, a siatka i tak postawi go po prawej,
formularze ułożone siatką, bez znaczników <br>, kolory z arkusza w zmiennych CSS,
PHP na PDO: wszystkie operacje na bazie na początku pliku, zapytania przygotowane z parametrami nazwanymi, filter_input dla liczby pracowników, biała lista dla miasta i rodzaju remontu, funkcja e() przy każdym wypisaniu i dane logowania w config.php,
formularze pamiętają wybór - po wyszukaniu w polu zostaje wpisana liczba, a na liście wybrane miasto, jak na ilustracjach 5 i 6.
Wersja podstawowa (zakładka obok) używa najprostszych funkcji z tabeli 1 arkusza i poprawia kod dopiero na końcu; tu jest on od razu taki, jak w prawdziwym projekcie. Na końcu krótkie porównanie obu wersji.
Baza i tapeta pochodzą z prawdziwego archiwum pliki5 (załącznik na stronie arkusza, hasło w treści zadania), a podgląd każdego kroku z PHP to wynik prawdziwego uruchomienia strony na serwerze z tą bazą.
Jak liczą się punkty. Klucz ma 42 kryteria (R.1.1 - R.5.10), każde za 1 punkt. Próg zdania to 75%, czyli 32 punkty. Przy każdym kroku jest napisane, które kryteria spełnia.
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).
phpMyAdmin → Nowa → nazwa remonty, porównywanie utf8_unicode_ci. To samo jednym poleceniem:
CREATE DATABASE remonty CHARACTER SET utf8 COLLATE utf8_unicode_ci;
Kolejność w zapytaniu 3 ustala porównywanie zapisane w definicji tabel pliku remonty.sql (utf8_polish_ci), a nie ustawienie bazy - dzięki niemu „Łeba” i „Łódź” stają po „Lesko”, a nie na końcu listy.
Typowe błędy
Literówka w nazwie bazy odcina też punkty za skrypty - połączenie podaje nazwę bazy (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.
Zaznacz bazę remonty, karta Import, plik remonty.sql, Wykonaj. Cztery tabele z ilustracji 1: klienci (32), wykonawcy (18 firm), zlecenia (46, kolumna rodzaj = malowanie albo gipsowanie) i tabela łącząca wykonanie (52 pary firma - zlecenie, klucz złożony z dwóch kluczy obcych).
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.
Cały ekran z paskiem zadań, komunikat o imporcie albo lista czterech tabel, zapis jako import.jpg. Formaty w tym arkuszu: import w JPEG, zrzuty zapytań w PNG.
Typowe błędy
Wykadrowany zrzut nie jest oceniany; 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”.
Trzy funkcje grupujące na całej tabeli - jeden wiersz wyniku, bez GROUP BY. Aliasy w odwrotnych apostrofach (`średnia`): to w MySQL właściwy znak dla nazw kolumn i tabel. Cudzysłów "średnia" też zadziała, ale formalnie jest napisem, który MySQL tylko przyjmuje jako alias. Każda kolumna w osobnej linii, wyrównana - widać od razu, że są trzy i w jakiej kolejności.
Wynik: 27.3333, 11, 55 (R.1.3). Zapytanie do kwerendy.txt, zrzut kw1.png.
Typowe błędy
Kolejność aliasów jest sprawdzana: średnia, najmniej, najwięcej.
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.
„40 i więcej” to >= 40. Wynik: BBud, GBud, KBud, RBud, SBud (R.1.4), zrzut kw2.png. Skrypt 1 wyśle to zapytanie z liczbą z formularza w miejscu 40.
Typowe błędy
> 40 pominie firmę z dokładnie 40 pracownikami.
kwerendy.txtTekstzmienione: 5 linii
-- 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_pracownikowFROM wykonawcyWHERE 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.
DISTINCT usuwa powtórzenia, ORDER BY miasto ASC sortuje rosnąco (kierunek wpisany jawnie). Wynik: 16 miast od Darłowa do Wrocławia (R.1.5), zrzut kw3.png. Ta lista będzie też białą listą dozwolonych miast w skrypcie 3.
kwerendy.txtTekstzmienione: 5 linii
-- 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_pracownikowFROM wykonawcyWHERE liczba_pracownikow >= 40;-- Zapytanie 3SELECT DISTINCT miastoFROM klienciORDER BY miasto ASC;
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ą.
Złączenie z aliasami tabel: klienci AS k, zlecenia AS z. Każda kolumna ma przedrostek - k.miasto, z.rodzaj - więc od razu widać, z której tabeli pochodzi, a ON z.id_klienta = k.id_klienta łączy klucz obcy z kluczem głównym. Napisy w SQL w apostrofach (standard SQL; cudzysłów działa tylko w MySQL).
Wynik: Paweł 2500, Paweł 6500, Adam 777, Paulina 1200 (R.1.6), zrzut kw4.png.
Typowe błędy
OR zamiast AND zwróci wszystkie zlecenia z Poznania i wszystkie malowania z innych miast.
kwerendy.txtTekstzmienione: 6 linii
-- 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_pracownikowFROM wykonawcyWHERE liczba_pracownikow >= 40;-- Zapytanie 3SELECT DISTINCT miastoFROM klienciORDER BY miasto ASC;-- Zapytanie 4SELECT k.imie, z.cenaFROM klienci AS kJOIN zlecenia AS z ON z.id_klienta = k.id_klientaWHERE k.miasto = 'Poznań' AND z.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ą.
Łańcuch czterech tabel, każdy JOIN w osobnej linii: klient → jego zlecenia → pary w tabeli łączącej → firmy. Krótkie aliasy (k, z, wy, w) skracają warunki ON, a ich układ jeden pod drugim pokazuje drogę od imienia do nazwy firmy.
Wynik: 52 wiersze - tyle, ile par w wykonanie (R.1.7), zrzut kw5.png z komunikatem o liczbie rekordów.
Typowe błędy
zlecenia i wykonawcy nie mają wspólnej kolumny - łańcuch musi przejść przez tabelę łączącą wykonanie.
kwerendy.txtTekstzmienione: 7 linii
-- 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_pracownikowFROM wykonawcyWHERE liczba_pracownikow >= 40;-- Zapytanie 3SELECT DISTINCT miastoFROM klienciORDER BY miasto ASC;-- Zapytanie 4SELECT k.imie, z.cenaFROM klienci AS kJOIN zlecenia AS z ON z.id_klienta = k.id_klientaWHERE k.miasto = 'Poznań' AND z.rodzaj = 'malowanie';-- Zapytanie 5SELECT k.imie, w.nazwa_firmyFROM klienci AS kJOIN zlecenia AS z ON z.id_klienta = k.id_klientaJOIN wykonanie AS wy ON wy.id_zlecenia = z.id_zleceniaJOIN wykonawcy AS w ON w.id_wykonawcy = wy.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.
W pierwszym wierszu tapeta_lewa.png cała jest tylko środkowa cegła (boczne ucina krawędź). W GIMP-ie zaznacz ją prostokątem po fugach, Obraz → Przytnij do zaznaczenia, potem Skaluj obraz do wysokości 60 px przy zamkniętym łańcuchu proporcji i Eksportuj jakocegla.jpg (R.2.1). Wysokość 60 px równa wysokości nagłówka daje w tle dokładnie jeden rząd cegieł.
Typowe błędy
Cegła ucięta krawędzią tapety albo z kawałkiem sąsiedniej nie spełnia „jedna cała cegła”.
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.
Oryginalna tapeta_lewa.png, Obraz → Przekształć → Odbij poziomo (lustro lewo-prawo), eksport jako tapeta_prawa.png (R.2.1).
Typowe błędy
„Odbij pionowo” to odbicie góra-dół - 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. 2 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”.
Arkusz wymaga tylko napisu, ale podstrona też jest stroną: dostaje ten sam szkielet HTML5 z językiem, kodowaniem i tytułem, a napis stoi w paragrafie. Plik bez <meta charset> mógłby pokazać „Strona w budowie” z krzakami w miejscu „ę”. Odnośnik „Kontakt” ma dokąd prowadzić (R.3.3).
kontakt.htmlHTMLnowy plik
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <title>Kontakt</title></head><body> <p>Strona w budowie</p></body></html>
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. 1 min
Polecenie z arkusza
Zapisana w języku HTML5.
Plik zlecenia.php w folderze zdającego. <!DOCTYPE html> włącza tryb standardów HTML5, dalej szkielet: <html>, <head> i <body>. Rozszerzenie .php, bo na początku pliku stanie kod PHP.
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.
lang="pl" w <html> - czytnik ekranu czyta polską wymową, przeglądarka dobiera słownik i dzielenie wyrazó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 pierwszy element <head>. Ten sam standard ustawi połączenie z bazą (charset=utf8mb4 w PDO), więc polskie litery przejdą bez zmian całą drogę: baza → PHP → przeglądarka.
<title>Remonty</title> - napis na karcie przeglądarki. Obok <meta name="viewport">: bez niego telefon pokazałby stronę pomniejszoną jak na monitorze. Arkusz tego nie wymaga, a w każdym współczesnym szablonie ta linia jest.
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.
Znaczniki z klucza R.2.4: <header>, <main> z <nav>, dwiema <section> i <aside>, <footer>. Kolejność w <main> jest taka jak w zdaniu arkusza - nawigacja, sekcje, blok boczny. Przy układzie na float blok boczny musiałby stać zaraz po nawigacji (pływający element nie wjeżdża wyżej niż elementy przed nim); siatka CSS rozmieszcza bloki według nazwanych obszarów, więc kolejność w kodzie może iść za treścią. Czytnik ekranu czyta wtedy najpierw nawigację i formularze, a tapety na końcu.
Sekcje dostają identyfikatory clients i contractors - siatka musi wiedzieć, która stoi po lewej.
Typowe błędy
<nav> albo <aside> poza <main> nie spełnia R.2.4. Klucz wymienia bloki w kolejności nav, aside, section, section, bo tak wymusza float - ta sama czwórka bloków w innej kolejności to nadal „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”.
<h1> w <header> - jedyny 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”.
Ścieżka względna - sam plik z tego samego folderu (R.3.3). 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.
target="_blank" otwiera nową kartę (R.3.3). Obok rel="noopener": strona otwarta w nowej karcie nie dostaje wtedy dostępu do karty, z której ją otwarto (bez tego obca strona mogłaby przez window.opener podmienić adres karty z portalem na fałszywy). Nowe przeglądarki robią to już domyślnie, ale atrybut wpisany jawnie chroni też w starszych.
Typowe błędy
href="remonty.pl" bez https:// to ścieżka do pliku w folderze strony.
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”.
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.
method="post" bez action - dane w treści żądania, wysyłane na adres bieżącej strony (R.2.8, R.3.4).
Typowe błędy
Brak method to GET - skrypt czytający dane 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"> - powiązanie z polem o id="workers", które stanie pod etykietą (R.3.5). Bez <br>: każdy element formularza dostanie własny wiersz siatki w CSS.
zlecenia.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <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"rel="noopener">Partnerzy</a> </nav> <sectionid="clients"> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label> </form> </section> <sectionid="contractors"> </section> <aside> </aside> </main> <footer> </footer></body></html>
Podgląd strony po tym kroku (zlecenia.php)
Krok 24: 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.
type="number" (R.3.5) z step="1" - przeglądarka uzna „2.5” za błędną wartość, czyli pole przyjmie jedynie liczby całkowite, a min="0" odrzuci ujemne. Pole stoi w <div> razem z przyciskiem z następnego kroku: na ilustracji 5 pole i przycisk są w jednym wierszu, a <div> to jeden wiersz siatki formularza.
Typowe błędy
Pole bez name nie jest wysyłane - skrypt 1 nie dostanie liczby.
zlecenia.phpPHPzmienione: 3 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <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"rel="noopener">Partnerzy</a> </nav> <sectionid="clients"> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label> <div> <inputtype="number"id="workers"name="workers"min="0"step="1"> </div> </form> </section> <sectionid="contractors"> </section> <aside> </aside> </main> <footer> </footer></body></html>
Podgląd strony po tym kroku (zlecenia.php)
Krok 25: 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"> obok pola (R.2.9, R.3.4). Efekt skryptu 1 - paragrafy z firmami - stanie pod formularzem.
zlecenia.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <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"rel="noopener">Partnerzy</a> </nav> <sectionid="clients"> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label> <div> <inputtype="number"id="workers"name="workers"min="0"step="1"> <buttontype="submit">Szukaj firm</button> </div> </form> </section> <sectionid="contractors"> </section> <aside> </aside> </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”.
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 POST. Arkusz nie przewiduje etykiety dla listy, więc lista dostaje aria-label="Miasto" - niewidoczny opis, który czytnik ekranu przeczyta przy wejściu na listę (bez niego usłyszałby tylko „lista rozwijana”). Opcje wstawi skrypt 2.
Krok 28: 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 radio wewnątrz<label> - to też powiązanie etykiety z polem, bez id i for: kliknięcie w słowo „malowanie” zaznacza pole. value="malowanie" to dokładnie wartość kolumny rodzaj, którą skrypt 3 wstawi do zapytania, a checked zaznacza pole domyślnie (R.3.5).
Typowe błędy
Pole radio bez value wysyła napis „on” - skrypt 3 nie znalazłby takiego rodzaju remontu.
Krok 29: 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.
Ta sama nazwa name="kind" łączy oba pola w jedną grupę - zaznaczenie jednego odznacza drugie (R.3.5). Sprawdź w podglądzie.
Typowe błędy
Różne name w polach radio pozwalają zaznaczyć oba naraz.
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.
<strong> - znacznik ważności, nie tylko pogrubienia (R.2.10). Tu stoi nazwisko autora rozwiązania; na egzaminie wpisujesz swój numer zdającego (PESEL), bo tego sprawdza klucz R.2.5.
Typowe błędy
<b> wygląda tak samo, ale nie jest znacznikiem semantycznym.
Kroki 35-46 · po tym etapie masz 32 z 42 kryteriów (pkt)
Krok 35: Plik styl.css: zmienne kolorów i krój czcionki
Kryteria:
R.4.1 Ustawione domyślnie dla wszystkich selektorów (*): krój czcionki "Century Gothic", Verdana (tylko taka kolejność)
ok. 2 min
Polecenie z arkusza
Domyślnie dla wszystkich selektorów: krój czcionki Century Gothic, w przypadku braku Verdana.
Na początku zmienne CSS z kolorami arkusza: beż #F0EAE2 występuje dwa razy (tło strony i kolor odnośników), bordo #824555 - w nawigacji. Potem selektor uniwersalny * z krojem i zapasową Verdana (R.4.1 - tylko ta kolejność).
Typowe błędy
Inspektor przeglądarki pokazuje wartość obliczoną zmiennej (rgb(240, 234, 226) = #F0EAE2). Jeśli wolisz nie ryzykować sporu przy czytaniu samego pliku, wpisz kolory wprost - jak w wersji podstawowej.
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 znacznika body (uwaga arkusza), kolor ze zmiennej (R.4.2).
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.
background-image: url("cegla.jpg") - obraz powtarza się w poziomie i tworzy mur; przy wysokości 60 px równej wysokości cegły to dokładnie jeden rząd (R.4.2, R.4.3, R.4.4, R.4.6). Druga reguła zeruje domyślne marginesy <h1> i paragrafu stopki: bez niej margines „wypływa” poza blok i nad murem z cegieł zostaje pasek tła strony, którego nie ma na ilustracji 3.
Typowe błędy
background-color: url(...) nie działa - kolor i obraz tła to różne właściwości.
Krok 38: Układ bloków: siatka z nazwanymi obszarami
Kryteria: ,
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)
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. 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.
Ilustracja 4 przepisana wprost na CSS. <main> to siatka o trzech kolumnach 40% 40% 20%, a grid-template-areas rysuje plan bloków jak w arkuszu - każdy napis to jeden wiersz siatki:
pierwszy wiersz: nav nav aside - nawigacja zajmuje dwie kolumny, czyli 80%, blok boczny 20%,
drugi wiersz: clients contractors aside - dwie sekcje po 40%, a aside powtórzone w obu wierszach rozciąga blok boczny na całą wysokość, od góry nawigacji do dołu sekcji.
Szerokości z arkusza są więc zapisane w jednym miejscu (klucz R.4.5 przyjmuje grid-template-columns zamiast width). Stopka jest poza <main>, więc stoi pod spodem bez żadnego clear (R.3.1). Bloki trafią do obszarów w następnych krokach.
Typowe błędy
Obszar w grid-template-areas musi być prostokątem - aside musi stać w obu wierszach w tej samej kolumnie, inaczej przeglądarka odrzuci całą właściwość.
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
ok. 1 min
Polecenie z arkusza
Dla bloku nawigacyjnego: kolor tła #824555, szerokość 80%, wysokość 60 px.
grid-area: nav stawia nawigację w obszarze nav - dwie kolumny po 40%, razem 80%. Do tego tło i wysokość 60 px (R.4.2, R.4.4).
Typowe błędy
Dodatkowe width: 80% w bloku siatki liczyłoby się od obszaru (80% z 80%) - szerokość daje tu siatka.
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
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.
Obszar aside (kolumna 20%, oba wiersze). Wysokość 560 px = 60 px nawigacji + 500 px sekcji. overflow: hidden obcina trzecią tapetę bez paska przewijania (R.4.9), text-align: right wyrównuje do prawej (R.4.6).
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%.
Selektor potomka aside img - tylko obrazy wewnątrz aside (R.4.5).
Typowe błędy
Sam selektor img nie spełnia „jedynie dla obrazów w bloku bocznym”.
Krok 42: Obie sekcje: obszary, wysokość, paski przewijania
Kryteria: ,
R.4.4 Ustawiona wysokość bloku nagłówkowego, nawigacyjnego i stopki 60px, bocznego 560px, obu sekcji 500px
R.4.9 Ustawiona właściwość overflow dla bloku bocznego - hidden, dla obu sekcji - auto
ok. 2 min
Polecenie z arkusza
Dla obu sekcji: szerokość 40%, wysokość 500 px, paski przewijania tylko w przypadku przepełnienia bloku.
Wspólna reguła section: wysokość 500 px i overflow: auto - pasek pojawi się dopiero przy długiej liście (R.4.4, R.4.9). Szerokość 40% dają kolumny siatki; identyfikatory wskazują obszary clients i contractors. Układ jest kompletny.
Etykieta dla pola edycyjnego o treści „Ilu co najmniej pracowników potrzebujesz?”. Poniżej pole edycyjne przeznaczone do wpisywania jedynie liczb całkowitych.
„Poniżej” bez <br>: oba formularze są siatkami o jednej kolumnie - każde dziecko formularza (etykieta, <div> z polem i przyciskiem, lista, etykiety pól opcji) dostaje własny wiersz, justify-items: start dosuwa je do lewej jak na ilustracjach 5 i 6, a gap daje równe odstępy bez marginesów przy każdym elemencie. Klucz tego nie ocenia - to wygląd z ilustracji.
Kroki 47-58 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 47: Dane połączenia w pliku config.php
Kryteria:
R.5.1 Skrypt realizuje połączenie z serwerem bazy danych i wybór bazy remonty
ok. 2 min
Polecenie z arkusza
Łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie remonty.
Dane logowania do bazy w osobnym pliku config.php, który tylko zwraca tablicę (return [...]). W prawdziwym projekcie hasło do bazy jest inne na każdym serwerze i nie trafia do repozytorium razem z kodem strony - wystarczy podmienić jeden plik. Arkusz dopuszcza dodatkowe pliki („ewentualnie inne przygotowane pliki”).
DSN (mysql:host=localhost;dbname=remonty;charset=utf8mb4) to adres bazy dla PDO w jednym napisie: rodzaj serwera, host, nazwa bazy i kodowanie połączenia. Plik bez HTML-u nie ma końcowego ?> - nic po nim nie wypisze się przypadkiem.
Typowe błędy
Plik config.php musi trafić do folderu z rezultatami - bez niego strona się nie połączy, a egzaminator nie znajdzie nazwy bazy.
config.phpPHPnowy plik
<?php// Dane połączenia z bazą - w jednym miejscu, poza plikiem stronyreturn ['dsn' => 'mysql:host=localhost;dbname=remonty;charset=utf8mb4','user' => 'root','password' => '',];
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 stoi na samym początku pliku, przed <!DOCTYPE html>: najpierw wszystkie operacje na bazie, potem szablon strony, który tylko wypisuje gotowe dane. Klucz R.5 przyjmuje PDO tak samo jak MySQLi.
require 'config.php' - dane połączenia trafiają do $config,
new PDO(...) - połączenie i wybór bazy; opcja ERRMODE_EXCEPTION sprawia, że każdy błąd SQL zatrzymuje skrypt wyjątkiem, zamiast po cichu zwracać false, a FETCH_ASSOC - że wiersze przychodzą jako tablice z nazwami kolumn ($row['nazwa'] zamiast $row[1]),
try … catch (PDOException $error) - gdy baza nie działa, użytkownik widzi zwykły komunikat, a nie treść błędu z nazwą serwera i ścieżkami; http_response_code(500) mówi przeglądarce, że to błąd serwera.
Od tego kroku podgląd to prawdziwy wynik strony z serwera - na razie bez zmian.
Typowe błędy
catch wypisujący $error->getMessage() pokazałby użytkownikowi szczegóły serwera - to wskazówka dla atakującego.
Krok 49: Funkcja e(): bezpieczne wypisywanie tekstu
ok. 2 min
Polecenie z arkusza
Należy stosować znaczące nazewnictwo zmiennych i funkcji w języku polskim lub angielskim.
Każdy napis z bazy albo z formularza, który trafi do HTML-u, przejdzie przez e() (od escape). htmlspecialchars zamienia <, >, & i - dzięki ENT_QUOTES - oba cudzysłowy na encje, więc napis <script> zapisany kiedyś w bazie wyświetli się jako tekst, a nie wykona (ochrona przed XSS). Escapowanie robi się na wyjściu, tuż przed wypisaniem - w bazie zostają dane surowe.
Krótka nazwa to świadomy wyjątek od „znaczącego nazewnictwa”: e() występuje w szablonie wiele razy i jest powszechnie znaną konwencją (tak samo nazywa się w Laravelu). Typy string przy parametrze i wyniku mówią, czego funkcja oczekuje; liczby PHP zamieni na tekst sam.
R.5.9 Przynajmniej jeden skrypt obsługujący formularz realizuje czynności tylko wtedy, gdy formularz wypełniono
ok. 3 min
Polecenie z arkusza
Skrypt 1, wykonywany tylko gdy wypełniono formularz.
W bloku try, zaraz po połączeniu. filter_input czyta pole workers z danych POST i od razu je sprawdza filtrem FILTER_VALIDATE_INT z zakresem od 0: wynik to liczba całkowita, false (wpisano „abc”, „2.5”, „-3”) albo null (pola nie wysłano - pierwsze wejście albo drugi formularz). is_int($workers) przepuszcza tylko pierwszy przypadek, więc skrypt działa wyłącznie po wypełnieniu formularza poprawną liczbą (R.5.9). $companies zaczyna jako pusta tablica - szablon wypisze ją zawsze, a bez wyników nie wypisze nic.
W polu pojawia się value z wysłaną liczbą (przez e(), bo to dane od użytkownika) - po wyszukaniu „30” zostaje w polu, jak na ilustracji 5. Podgląd: stan po wpisaniu 30.
Typowe błędy
Samo isset($_POST['workers']) przepuszcza puste pole - zapytanie dostałoby pusty warunek i stronę przerwałby wyjątek.
Krok 51: Skrypt 1: zapytanie 2 przygotowane 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. 3 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie 2, zmodyfikowane tak że w warunku podana jest wartość wysłana z formularza.
Zapytanie 2 jako zapytanie przygotowane: w miejscu 40 parametr :workers, a liczba idzie osobno w execute(['workers' => $workers]). Baza dostaje treść zapytania i wartość oddzielnie, więc wartość nigdy nie staje się częścią polecenia SQL. Tu liczba jest już sprawdzona przez filter_input, ale zapytanie przygotowane to zasada, nie wyjątek - działa tak samo przy każdej wartości. fetchAll() odbiera wszystkie wiersze do $companies (R.5.3, R.5.4).
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.
Pod formularzem pętla w składni szablonu (foreach … : i endforeach;): HTML paragrafu zostaje HTML-em, a PHP wstawia tylko wartości przez <?= ... ?>. Kolumny po nazwach - nazwa_firmy, liczba_pracownikow - format „nazwa, liczba pracowników” jak w arkuszu (R.5.4, R.5.5).
Podgląd: siedem firm od 30 pracowników, jak na ilustracji 5.
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.
Zapytanie 3 bez danych od użytkownika, więc wystarczy query. PDO::FETCH_COLUMN zwraca od razu listę nazw miast, a nie listę wierszy (R.5.3). Ta lista przyda się dwa razy: w liście rozwijanej i jako biała lista w skrypcie 3.
Krok 55: Skrypt 3: tylko gdy wypełniono formularz - biała lista
ok. 3 min
Polecenie z arkusza
Skrypt 3 (ilustracja 6), wykonywany tylko gdy wypełniono formularz.
Miasto i rodzaj remontu przychodzą z listy i z pól opcji, ale żądanie POST można wysłać z dowolnymi wartościami. Dlatego biała lista: in_array(..., true) przepuszcza tylko wartości, które są na liście dozwolonych - miasto musi być jednym z 16 miast z bazy (lista $cities ze skryptu 2), a rodzaj jednym z dwóch słów. Trzeci argument true porównuje ściśle (typ i wartość). Bez wysłanego formularza obie wartości są puste, więc skrypt się nie wykonuje (R.5.9 spełnia już skrypt 1, tu ta sama zasada).
Na liście miast opcja równa wysłanemu miastu dostaje selected - po wyszukaniu lista pokazuje Wrocław, jak na ilustracji 6. Od tego kroku podgląd pokazuje stan po wysłaniu drugiego formularza (Wrocław, malowanie).
Krok 56: Skrypt 3: zapytanie 4 przygotowane z miastem i rodzajem
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.
Zapytanie 4 z kwerendy.txt z dwoma parametrami nazwanymi :city i :kind w miejscu „Poznań” i „malowanie” (R.5.7). Biała lista i zapytanie przygotowane to dwie warstwy ochrony: lista odrzuca wartości bez sensu, a parametry gwarantują, że nawet przepuszczona wartość nie zmieni treści zapytania.
ORDER BY z.id_zlecenia ustala kolejność zleceń: bez niego baza może zwrócić wiersze złączenia w dowolnej kolejności, a tak zawsze wychodzi kolejność z ilustracji 6.
zlecenia.phpPHPzmienione: 6 linii
<?php$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$companies = [];$workers = filter_input(INPUT_POST, 'workers', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);if (is_int($workers)) {$query = $db->prepare('SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= :workers' );$query->execute(['workers' => $workers]);$companies = $query->fetchAll(); }$cities = $db->query('SELECT DISTINCT miasto FROM klienci ORDER BY miasto')->fetchAll(PDO::FETCH_COLUMN);$orders = [];$selectedCity = $_POST['city'] ?? '';$kind = $_POST['kind'] ?? '';if (in_array($selectedCity, $cities, true) && in_array($kind, ['malowanie', 'gipsowanie'], true)) {$query = $db->prepare('SELECT k.imie, z.cena FROM klienci AS k JOIN zlecenia AS z ON z.id_klienta = k.id_klienta WHERE k.miasto = :city AND z.rodzaj = :kind ORDER BY z.id_zlecenia' );$query->execute(['city' => $selectedCity, 'kind' => $kind]);$orders = $query->fetchAll(); }} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <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"rel="noopener">Partnerzy</a> </nav> <sectionid="clients"> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label> <div> <inputtype="number"id="workers"name="workers"min="0"step="1"value="<?= e($_POST['workers'] ?? '') ?>"> <buttontype="submit">Szukaj firm</button> </div> </form><?phpforeach ($companiesas$company): ?> <p><?= e($company['nazwa_firmy']) ?>, <?= e($company['liczba_pracownikow']) ?> pracowników</p><?phpendforeach; ?> </section> <sectionid="contractors"> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"aria-label="Miasto"><?phpforeach ($citiesas$city): ?> <option<?= $city === $selectedCity ? ' selected' : ''?>><?= e($city) ?></option><?phpendforeach; ?> </select> <label><inputtype="radio"name="kind"value="malowanie"checked> malowanie</label> <label><inputtype="radio"name="kind"value="gipsowanie"> gipsowanie</label> <buttontype="submit">Szukaj klientów</button> </form> <ul> </ul> </section> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> </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.
Jeden <li> na zlecenie: imię, myślnik, cena (R.5.7, R.5.8). Cena przychodzi z kolumny decimal(10,2) jako napis „600.00” i tak zostaje - dokładnie jak na ilustracji 6.
zlecenia.phpPHPzmienione: 3 linie
<?php$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$companies = [];$workers = filter_input(INPUT_POST, 'workers', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);if (is_int($workers)) {$query = $db->prepare('SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= :workers' );$query->execute(['workers' => $workers]);$companies = $query->fetchAll(); }$cities = $db->query('SELECT DISTINCT miasto FROM klienci ORDER BY miasto')->fetchAll(PDO::FETCH_COLUMN);$orders = [];$selectedCity = $_POST['city'] ?? '';$kind = $_POST['kind'] ?? '';if (in_array($selectedCity, $cities, true) && in_array($kind, ['malowanie', 'gipsowanie'], true)) {$query = $db->prepare('SELECT k.imie, z.cena FROM klienci AS k JOIN zlecenia AS z ON z.id_klienta = k.id_klienta WHERE k.miasto = :city AND z.rodzaj = :kind ORDER BY z.id_zlecenia' );$query->execute(['city' => $selectedCity, 'kind' => $kind]);$orders = $query->fetchAll(); }} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <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"rel="noopener">Partnerzy</a> </nav> <sectionid="clients"> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label> <div> <inputtype="number"id="workers"name="workers"min="0"step="1"value="<?= e($_POST['workers'] ?? '') ?>"> <buttontype="submit">Szukaj firm</button> </div> </form><?phpforeach ($companiesas$company): ?> <p><?= e($company['nazwa_firmy']) ?>, <?= e($company['liczba_pracownikow']) ?> pracowników</p><?phpendforeach; ?> </section> <sectionid="contractors"> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"aria-label="Miasto"><?phpforeach ($citiesas$city): ?> <option<?= $city === $selectedCity ? ' selected' : ''?>><?= e($city) ?></option><?phpendforeach; ?> </select> <label><inputtype="radio"name="kind"value="malowanie"checked> malowanie</label> <label><inputtype="radio"name="kind"value="gipsowanie"> gipsowanie</label> <buttontype="submit">Szukaj klientów</button> </form> <ul><?phpforeach ($ordersas$order): ?> <li><?= e($order['imie']) ?> - <?= e($order['cena']) ?></li><?phpendforeach; ?> </ul> </section> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> </main> <footer> <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p> </footer></body></html>
Podgląd strony po tym kroku (zlecenia.php)
Krok 58: 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ą.
W PDO połączenie zamyka $db = null; - znika ostatnie odwołanie do obiektu (R.5.2). Stoi na końcu bloku try, po wszystkich zapytaniach; szablon niżej używa już tylko gotowych tablic.
Typowe błędy
$db->close() nie istnieje w PDO.
zlecenia.phpPHPzmienione: 2 linie
<?php$config = require'config.php';try {$db = new PDO($config['dsn'], $config['user'], $config['password'], [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);$companies = [];$workers = filter_input(INPUT_POST, 'workers', FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]);if (is_int($workers)) {$query = $db->prepare('SELECT nazwa_firmy, liczba_pracownikow FROM wykonawcy WHERE liczba_pracownikow >= :workers' );$query->execute(['workers' => $workers]);$companies = $query->fetchAll(); }$cities = $db->query('SELECT DISTINCT miasto FROM klienci ORDER BY miasto')->fetchAll(PDO::FETCH_COLUMN);$orders = [];$selectedCity = $_POST['city'] ?? '';$kind = $_POST['kind'] ?? '';if (in_array($selectedCity, $cities, true) && in_array($kind, ['malowanie', 'gipsowanie'], true)) {$query = $db->prepare('SELECT k.imie, z.cena FROM klienci AS k JOIN zlecenia AS z ON z.id_klienta = k.id_klienta WHERE k.miasto = :city AND z.rodzaj = :kind ORDER BY z.id_zlecenia' );$query->execute(['city' => $selectedCity, 'kind' => $kind]);$orders = $query->fetchAll(); }$db = null;} catch (PDOException $error) {http_response_code(500);exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');}functione(string $text): string{returnhtmlspecialchars($text, ENT_QUOTES, 'UTF-8');}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <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"rel="noopener">Partnerzy</a> </nav> <sectionid="clients"> <h2>Dla klientów</h2> <formmethod="post"> <labelfor="workers">Ilu co najmniej pracowników potrzebujesz?</label> <div> <inputtype="number"id="workers"name="workers"min="0"step="1"value="<?= e($_POST['workers'] ?? '') ?>"> <buttontype="submit">Szukaj firm</button> </div> </form><?phpforeach ($companiesas$company): ?> <p><?= e($company['nazwa_firmy']) ?>, <?= e($company['liczba_pracownikow']) ?> pracowników</p><?phpendforeach; ?> </section> <sectionid="contractors"> <h2>Dla wykonawców</h2> <formmethod="post"> <selectname="city"aria-label="Miasto"><?phpforeach ($citiesas$city): ?> <option<?= $city === $selectedCity ? ' selected' : ''?>><?= e($city) ?></option><?phpendforeach; ?> </select> <label><inputtype="radio"name="kind"value="malowanie"checked> malowanie</label> <label><inputtype="radio"name="kind"value="gipsowanie"> gipsowanie</label> <buttontype="submit">Szukaj klientów</button> </form> <ul><?phpforeach ($ordersas$order): ?> <li><?= e($order['imie']) ?> - <?= e($order['cena']) ?></li><?phpendforeach; ?> </ul> </section> <aside> <imgsrc="tapeta_lewa.png"alt="usługi"> <imgsrc="tapeta_prawa.png"alt="usługi"> <imgsrc="tapeta_lewa.png"alt="usługi"> </aside> </main> <footer> <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p> </footer></body></html>
Krok 59 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 59: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 2 min
Obie wersje dają komplet 42 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ
float + clear, aside przed sekcjami
grid-template-areas, kolejność bloków jak w arkuszu
formularze
<br> między elementami
siatka w CSS
baza
mysqli_* z tabeli 1, dane sklejone z SQL
PDO, zapytania przygotowane, config.php
dane z formularzy
isset / !empty
filter_input i biała lista
wypisywanie
echo w pętli while
szablon foreach … endforeach, e()
po wyszukaniu
formularze puste
pole i lista pamiętają wybór
Na egzaminie:float z clear wymaga przemyślenia kolejności bloków w HTML, siatka z nazwanymi obszarami - nie, więc przy złożonym układzie jak ilustracja 4 to siatka bywa szybsza i bezpieczniejsza. PHP z tabeli 1 jest krótsze i podpowiedziane w arkuszu; PDO wybierz, jeśli znasz je na pamięć. Formularze wyszukiwania, które niczego nie zapisują, w prawdziwej aplikacji wysyłałoby się metodą GET (wynik da się wtedy zapisać w zakładce), ale arkusz wprost wymaga POST.
Krok 60 · po tym etapie masz 42 z 42 kryteriów (pkt)
Krok 60: 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 folderze z numerem zdającego: cegla.jpg, config.php, import.jpg, kontakt.html, kw1.png … kw5.png, kwerendy.txt, przeglądarka.txt, styl.css, tapeta_lewa.png, tapeta_prawa.png, zlecenia.php. Płytę sprawdź po nagraniu.
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.