Przejdź do głównej treści
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.

  1. 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).

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

  3. 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.

  4. 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.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT AVG(liczba_pracownikow) AS `średnia`,
           MIN(liczba_pracownikow) AS `najmniej`,
           MAX(liczba_pracownikow) AS `najwięcej`
    FROM wykonawcy;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. 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.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT AVG(liczba_pracownikow) AS `średnia`,
           MIN(liczba_pracownikow) AS `najmniej`,
           MAX(liczba_pracownikow) AS `najwięcej`
    FROM wykonawcy;
    
    -- Zapytanie 2
    SELECT nazwa_firmy, liczba_pracownikow
    FROM wykonawcy
    WHERE liczba_pracownikow >= 40;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. 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.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT AVG(liczba_pracownikow) AS `średnia`,
           MIN(liczba_pracownikow) AS `najmniej`,
           MAX(liczba_pracownikow) AS `najwięcej`
    FROM wykonawcy;
    
    -- Zapytanie 2
    SELECT nazwa_firmy, liczba_pracownikow
    FROM wykonawcy
    WHERE liczba_pracownikow >= 40;
    
    -- Zapytanie 3
    SELECT DISTINCT miasto
    FROM klienci
    ORDER BY miasto ASC;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. 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.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT AVG(liczba_pracownikow) AS `średnia`,
           MIN(liczba_pracownikow) AS `najmniej`,
           MAX(liczba_pracownikow) AS `najwięcej`
    FROM wykonawcy;
    
    -- Zapytanie 2
    SELECT nazwa_firmy, liczba_pracownikow
    FROM wykonawcy
    WHERE liczba_pracownikow >= 40;
    
    -- Zapytanie 3
    SELECT DISTINCT miasto
    FROM klienci
    ORDER BY miasto ASC;
    
    -- Zapytanie 4
    SELECT k.imie, z.cena
    FROM klienci AS k
    JOIN zlecenia AS z ON z.id_klienta = k.id_klienta
    WHERE k.miasto = 'Poznań' AND z.rodzaj = 'malowanie';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. 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.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT AVG(liczba_pracownikow) AS `średnia`,
           MIN(liczba_pracownikow) AS `najmniej`,
           MAX(liczba_pracownikow) AS `najwięcej`
    FROM wykonawcy;
    
    -- Zapytanie 2
    SELECT nazwa_firmy, liczba_pracownikow
    FROM wykonawcy
    WHERE liczba_pracownikow >= 40;
    
    -- Zapytanie 3
    SELECT DISTINCT miasto
    FROM klienci
    ORDER BY miasto ASC;
    
    -- Zapytanie 4
    SELECT k.imie, z.cena
    FROM klienci AS k
    JOIN zlecenia AS z ON z.id_klienta = k.id_klienta
    WHERE k.miasto = 'Poznań' AND z.rodzaj = 'malowanie';
    
    -- Zapytanie 5
    SELECT k.imie, w.nazwa_firmy
    FROM klienci AS k
    JOIN zlecenia AS z ON z.id_klienta = k.id_klienta
    JOIN wykonanie AS wy ON wy.id_zlecenia = z.id_zlecenia
    JOIN wykonawcy AS w ON w.id_wykonawcy = wy.id_wykonawcy;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. 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 jako cegla.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”.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 9
  10. Krok 10: Grafika tapeta_prawa.png: odbicie poziome

    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. 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
    Podgląd strony po kroku 10
  11. 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.html HTML nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Kontakt</title>
    </head>
    <body>
        <p>Strona w budowie</p>
    </body>
    </html>
    Podgląd strony po tym kroku (kontakt.html)
  12. 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.

    zlecenia.php PHP nowy plik
    <!DOCTYPE html>
    <html>
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  13. Krok 13: Język strony

    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

    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.

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  14. Krok 14: Kodowanie polskich znaków

    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

    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.

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  15. Krok 15: Tytuł strony

    Kryteria:

    R.2.3 Stronie nadany tytuł "Remonty”

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony „Remonty”.

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

    zlecenia.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  16. Krok 16: Podpięcie arkusza stylów

    Kryteria:

    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"> - nazwa dokładnie z arkusza. Cały wygląd będzie w tym pliku, w HTML nie ma ani jednego atrybutu style.

    Typowe błędy

    Zła nazwa w href to brak stylów, a wtedy cały rezultat R.4 przepada.

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  17. Krok 17: Bloki semantyczne

    Kryteria:

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

    zlecenia.php PHP zmienione: 14 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
        </header>
        <main>
            <nav>
            </nav>
            <section id="clients">
            </section>
            <section id="contractors">
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  18. 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>.

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
            </nav>
            <section id="clients">
            </section>
            <section id="contractors">
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  19. Krok 19: Blok nawigacyjny: odnośnik „Kontakt”

    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

    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.

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
            </nav>
            <section id="clients">
            </section>
            <section id="contractors">
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  20. 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.

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
            </section>
            <section id="contractors">
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  21. Krok 21: Sekcja lewa: nagłówek „Dla klientów”

    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ść sekcji lewej: nagłówek drugiego stopnia o treści „Dla klientów”.

    <h2> w sekcji clients.

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
            </section>
            <section id="contractors">
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  22. Krok 22: Sekcja lewa: formularz

    Kryteria: ,

    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.

    zlecenia.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                </form>
            </section>
            <section id="contractors">
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  23. Krok 23: Formularz 1: etykieta pola

    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

    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.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                </form>
            </section>
            <section id="contractors">
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  24. 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.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                    </div>
                </form>
            </section>
            <section id="contractors">
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  25. 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.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  26. Krok 26: Sekcja środkowa: nagłówek „Dla wykonawców”

    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ść sekcji środkowej: nagłówek drugiego stopnia o treści „Dla wykonawców”.

    Drugi <h2>, w sekcji contractors (R.2.5).

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  27. Krok 27: Formularz 2: lista rozwijana

    Kryteria: , ,

    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.

    zlecenia.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                </form>
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  28. 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.

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                </form>
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  29. 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.

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                </form>
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  30. Krok 30: Formularz 2: przycisk „Szukaj klientów”

    Kryteria:

    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.

    Przycisk wysyła tylko formularz, w którym stoi - miasto i rodzaj remontu (R.2.9).

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  31. Krok 31: Sekcja środkowa: lista punktowana

    Kryteria:

    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; elementy <li> wypisze szablon w kroku skryptu 3 (R.2.7).

    zlecenia.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  32. Krok 32: Blok boczny: pierwsza tapeta

    Kryteria:

    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> z tekstem alternatywnym z arkusza (R.2.6). Rozmiar dopasuje CSS.

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  33. Krok 33: Blok boczny: tapeta prawa i znów lewa

    Kryteria:

    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 z tym samym alt. Przy szerokości 100% bloku (CSS) staną jeden pod drugim bez <br>.

    zlecenia.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="tapeta_lewa.png" alt="usługi">
            </aside>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (zlecenia.php)
  34. Krok 34: Stopka: paragraf z tekstem ważnym

    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

    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.

    zlecenia.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="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)
  35. Etap 3: Styl CSS

    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.

    styl.css CSS nowy plik
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    Podgląd strony po tym kroku (zlecenia.php)
  36. 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 znacznika body (uwaga arkusza), kolor ze zmiennej (R.4.2).

    styl.css CSS zmienione: 4 linie
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    
    body {
        background-color: var(--background);
    }
    Podgląd strony po tym kroku (zlecenia.php)
  37. 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.

    styl.css CSS zmienione: 13 linii
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    
    body {
        background-color: var(--background);
    }
    
    header,
    footer {
        background-image: url("cegla.jpg");
        color: white;
        height: 60px;
        text-align: center;
    }
    
    header h1,
    footer p {
        margin: 0;
    }
    Podgląd strony po tym kroku (zlecenia.php)
  38. 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ść.

    styl.css CSS zmienione: 8 linii
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    
    body {
        background-color: var(--background);
    }
    
    header,
    footer {
        background-image: url("cegla.jpg");
        color: white;
        height: 60px;
        text-align: center;
    }
    
    header h1,
    footer p {
        margin: 0;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 40% 20%;
        grid-template-areas:
            "nav     nav         aside"
            "clients contractors aside";
    }
    Podgląd strony po tym kroku (zlecenia.php)
  39. Krok 39: Blok nawigacyjny: obszar, tło, 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

    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.

    styl.css CSS zmienione: 6 linii
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    
    body {
        background-color: var(--background);
    }
    
    header,
    footer {
        background-image: url("cegla.jpg");
        color: white;
        height: 60px;
        text-align: center;
    }
    
    header h1,
    footer p {
        margin: 0;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 40% 20%;
        grid-template-areas:
            "nav     nav         aside"
            "clients contractors aside";
    }
    
    nav {
        grid-area: nav;
        background-color: var(--nav);
        height: 60px;
    }
    Podgląd strony po tym kroku (zlecenia.php)
  40. Krok 40: Blok boczny: obszar, wysokość, wyrównanie, ukryte przepełnienie

    Kryteria: , ,

    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).

    styl.css CSS zmienione: 7 linii
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    
    body {
        background-color: var(--background);
    }
    
    header,
    footer {
        background-image: url("cegla.jpg");
        color: white;
        height: 60px;
        text-align: center;
    }
    
    header h1,
    footer p {
        margin: 0;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 40% 20%;
        grid-template-areas:
            "nav     nav         aside"
            "clients contractors aside";
    }
    
    nav {
        grid-area: nav;
        background-color: var(--nav);
        height: 60px;
    }
    
    aside {
        grid-area: aside;
        height: 560px;
        text-align: right;
        overflow: hidden;
    }
    Podgląd strony po tym kroku (zlecenia.php)
  41. Krok 41: Obrazy w bloku bocznym: szerokość 100%

    Kryteria:

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

    styl.css CSS zmienione: 4 linie
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    
    body {
        background-color: var(--background);
    }
    
    header,
    footer {
        background-image: url("cegla.jpg");
        color: white;
        height: 60px;
        text-align: center;
    }
    
    header h1,
    footer p {
        margin: 0;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 40% 20%;
        grid-template-areas:
            "nav     nav         aside"
            "clients contractors aside";
    }
    
    nav {
        grid-area: nav;
        background-color: var(--nav);
        height: 60px;
    }
    
    aside {
        grid-area: aside;
        height: 560px;
        text-align: right;
        overflow: hidden;
    }
    
    aside img {
        width: 100%;
    }
    Podgląd strony po tym kroku (zlecenia.php)
  42. 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.

    styl.css CSS zmienione: 13 linii
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    
    body {
        background-color: var(--background);
    }
    
    header,
    footer {
        background-image: url("cegla.jpg");
        color: white;
        height: 60px;
        text-align: center;
    }
    
    header h1,
    footer p {
        margin: 0;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 40% 20%;
        grid-template-areas:
            "nav     nav         aside"
            "clients contractors aside";
    }
    
    nav {
        grid-area: nav;
        background-color: var(--nav);
        height: 60px;
    }
    
    aside {
        grid-area: aside;
        height: 560px;
        text-align: right;
        overflow: hidden;
    }
    
    aside img {
        width: 100%;
    }
    
    section {
        height: 500px;
        overflow: auto;
    }
    
    #clients {
        grid-area: clients;
    }
    
    #contractors {
        grid-area: contractors;
    }
    Podgląd strony po tym kroku (zlecenia.php)
  43. Krok 43: Nagłówki drugiego stopnia: wyśrodkowanie

    Kryteria:

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

    Dla selektora nagłówka drugiego stopnia: wyrównanie tekstu do środka.

    Selektor znacznika h2 (uwaga arkusza, R.4.6).

    styl.css CSS zmienione: 4 linie
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    
    body {
        background-color: var(--background);
    }
    
    header,
    footer {
        background-image: url("cegla.jpg");
        color: white;
        height: 60px;
        text-align: center;
    }
    
    header h1,
    footer p {
        margin: 0;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 40% 20%;
        grid-template-areas:
            "nav     nav         aside"
            "clients contractors aside";
    }
    
    nav {
        grid-area: nav;
        background-color: var(--nav);
        height: 60px;
    }
    
    aside {
        grid-area: aside;
        height: 560px;
        text-align: right;
        overflow: hidden;
    }
    
    aside img {
        width: 100%;
    }
    
    section {
        height: 500px;
        overflow: auto;
    }
    
    #clients {
        grid-area: clients;
    }
    
    #contractors {
        grid-area: contractors;
    }
    
    h2 {
        text-align: center;
    }
    Podgląd strony po tym kroku (zlecenia.php)
  44. Krok 44: Odnośniki: kolor, lewy margines wewnętrzny, rozmiar czcionki

    Kryteria: , ,

    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%.

    Kolor ze zmiennej tła strony (R.4.3), padding-left: 100px - tylko lewy margines wewnętrzny (R.4.7), font-size: 180% względem rodzica (R.4.8).

    Typowe błędy

    margin-left to margines zewnętrzny, a skrót padding: 0 0 0 100px ustawia też pozostałe strony.

    styl.css CSS zmienione: 6 linii
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    
    body {
        background-color: var(--background);
    }
    
    header,
    footer {
        background-image: url("cegla.jpg");
        color: white;
        height: 60px;
        text-align: center;
    }
    
    header h1,
    footer p {
        margin: 0;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 40% 20%;
        grid-template-areas:
            "nav     nav         aside"
            "clients contractors aside";
    }
    
    nav {
        grid-area: nav;
        background-color: var(--nav);
        height: 60px;
    }
    
    aside {
        grid-area: aside;
        height: 560px;
        text-align: right;
        overflow: hidden;
    }
    
    aside img {
        width: 100%;
    }
    
    section {
        height: 500px;
        overflow: auto;
    }
    
    #clients {
        grid-area: clients;
    }
    
    #contractors {
        grid-area: contractors;
    }
    
    h2 {
        text-align: center;
    }
    
    a {
        color: var(--background);
        padding-left: 100px;
        font-size: 180%;
    }
    Podgląd strony po tym kroku (zlecenia.php)
  45. Krok 45: Odnośnik pod kursorem

    Kryteria:

    R.4.10 Gdy kursor znajdzie się na odnośniku, jego kolor czcionki zmienia się na biały

    ok. 1 min
    Polecenie z arkusza

    Gdy kursor znajdzie się na odnośniku, jego kolor czcionki zmienia się na biały.

    a:hover - biały tylko pod kursorem (R.4.10).

    styl.css CSS zmienione: 4 linie
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    
    body {
        background-color: var(--background);
    }
    
    header,
    footer {
        background-image: url("cegla.jpg");
        color: white;
        height: 60px;
        text-align: center;
    }
    
    header h1,
    footer p {
        margin: 0;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 40% 20%;
        grid-template-areas:
            "nav     nav         aside"
            "clients contractors aside";
    }
    
    nav {
        grid-area: nav;
        background-color: var(--nav);
        height: 60px;
    }
    
    aside {
        grid-area: aside;
        height: 560px;
        text-align: right;
        overflow: hidden;
    }
    
    aside img {
        width: 100%;
    }
    
    section {
        height: 500px;
        overflow: auto;
    }
    
    #clients {
        grid-area: clients;
    }
    
    #contractors {
        grid-area: contractors;
    }
    
    h2 {
        text-align: center;
    }
    
    a {
        color: var(--background);
        padding-left: 100px;
        font-size: 180%;
    }
    
    a:hover {
        color: white;
    }
    Podgląd strony po tym kroku (zlecenia.php)
  46. Krok 46: Formularze: elementy jeden pod drugim

    ok. 2 min
    Polecenie z arkusza

    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.

    styl.css CSS zmienione: 6 linii
    :root {
        --background: #F0EAE2;
        --nav: #824555;
    }
    
    * {
        font-family: "Century Gothic", Verdana;
    }
    
    body {
        background-color: var(--background);
    }
    
    header,
    footer {
        background-image: url("cegla.jpg");
        color: white;
        height: 60px;
        text-align: center;
    }
    
    header h1,
    footer p {
        margin: 0;
    }
    
    main {
        display: grid;
        grid-template-columns: 40% 40% 20%;
        grid-template-areas:
            "nav     nav         aside"
            "clients contractors aside";
    }
    
    nav {
        grid-area: nav;
        background-color: var(--nav);
        height: 60px;
    }
    
    aside {
        grid-area: aside;
        height: 560px;
        text-align: right;
        overflow: hidden;
    }
    
    aside img {
        width: 100%;
    }
    
    section {
        height: 500px;
        overflow: auto;
    }
    
    #clients {
        grid-area: clients;
    }
    
    #contractors {
        grid-area: contractors;
    }
    
    h2 {
        text-align: center;
    }
    
    a {
        color: var(--background);
        padding-left: 100px;
        font-size: 180%;
    }
    
    a:hover {
        color: white;
    }
    
    form {
        display: grid;
        justify-items: start;
        gap: 4px;
    }
    Podgląd strony po tym kroku (zlecenia.php)
  47. Etap 4: Skrypty PHP

    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.php PHP nowy plik
    <?php
    // Dane połączenia z bazą - w jednym miejscu, poza plikiem strony
    return [
        'dsn' => 'mysql:host=localhost;dbname=remonty;charset=utf8mb4',
        'user' => 'root',
        'password' => '',
    ];

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

  48. Krok 48: Połączenie z bazą przez PDO

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

    zlecenia.php PHP zmienione: 13 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,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="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)
  49. 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.

    zlecenia.php PHP zmienione: 5 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,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="tapeta_lewa.png" alt="usługi">
            </aside>
        </main>
        <footer>
            <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>

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

  50. Krok 50: Skrypt 1: tylko gdy wypełniono formularz

    Kryteria:

    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.

    zlecenia.php PHP zmienione: 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)) {
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1" value="<?= e($_POST['workers'] ?? '') ?>">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="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)
  51. 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).

    zlecenia.php PHP zmienione: 5 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();
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1" value="<?= e($_POST['workers'] ?? '') ?>">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="tapeta_lewa.png" alt="usługi">
            </aside>
        </main>
        <footer>
            <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>

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

  52. Krok 52: Skrypt 1: paragraf dla każdej firmy

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

    zlecenia.php PHP zmienione: 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();
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1" value="<?= e($_POST['workers'] ?? '') ?>">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
    <?php foreach ($companies as $company): ?>
                <p><?= e($company['nazwa_firmy']) ?>, <?= e($company['liczba_pracownikow']) ?> pracowników</p>
    <?php endforeach; ?>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="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)
  53. Krok 53: Skrypt 2: zapytanie 3 do bazy

    Kryteria:

    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.

    zlecenia.php PHP zmienione: 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);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1" value="<?= e($_POST['workers'] ?? '') ?>">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
    <?php foreach ($companies as $company): ?>
                <p><?= e($company['nazwa_firmy']) ?>, <?= e($company['liczba_pracownikow']) ?> pracowników</p>
    <?php endforeach; ?>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="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)
  54. Krok 54: Skrypt 2: miasta jako opcje listy

    Kryteria:

    R.5.6 Skrypt 2 wyświetla nazwy miast zwrócone zapytaniem 3 jako opcje listy rozwijanej

    ok. 1 min
    Polecenie z arkusza

    Każde zwrócone zapytaniem miasto jest treścią opcji listy rozwijanej.

    Jeden <option> na miasto; bez value formularz wysyła treść opcji, czyli nazwę miasta (R.5.6). Lista ma 16 miast od Darłowa.

    zlecenia.php PHP zmienione: 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);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1" value="<?= e($_POST['workers'] ?? '') ?>">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
    <?php foreach ($companies as $company): ?>
                <p><?= e($company['nazwa_firmy']) ?>, <?= e($company['liczba_pracownikow']) ?> pracowników</p>
    <?php endforeach; ?>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
    <?php foreach ($cities as $city): ?>
                        <option><?= e($city) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="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)
  55. 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).

    zlecenia.php PHP zmienione: 7 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)) {
        }
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1" value="<?= e($_POST['workers'] ?? '') ?>">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
    <?php foreach ($companies as $company): ?>
                <p><?= e($company['nazwa_firmy']) ?>, <?= e($company['liczba_pracownikow']) ?> pracowników</p>
    <?php endforeach; ?>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
    <?php foreach ($cities as $city): ?>
                        <option<?= $city === $selectedCity ? ' selected' : '' ?>><?= e($city) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="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)
  56. 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.php PHP zmienione: 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.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1" value="<?= e($_POST['workers'] ?? '') ?>">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
    <?php foreach ($companies as $company): ?>
                <p><?= e($company['nazwa_firmy']) ?>, <?= e($company['liczba_pracownikow']) ?> pracowników</p>
    <?php endforeach; ?>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
    <?php foreach ($cities as $city): ?>
                        <option<?= $city === $selectedCity ? ' selected' : '' ?>><?= e($city) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="tapeta_lewa.png" alt="usługi">
            </aside>
        </main>
        <footer>
            <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>

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

  57. Krok 57: Skrypt 3: imię i cena w elementach listy

    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

    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.php PHP zmienione: 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.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1" value="<?= e($_POST['workers'] ?? '') ?>">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
    <?php foreach ($companies as $company): ?>
                <p><?= e($company['nazwa_firmy']) ?>, <?= e($company['liczba_pracownikow']) ?> pracowników</p>
    <?php endforeach; ?>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
    <?php foreach ($cities as $city): ?>
                        <option<?= $city === $selectedCity ? ' selected' : '' ?>><?= e($city) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
    <?php foreach ($orders as $order): ?>
                    <li><?= e($order['imie']) ?> - <?= e($order['cena']) ?></li>
    <?php endforeach; ?>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="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)
  58. 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.php PHP zmienione: 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.');
    }
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Remonty</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Malowanie i gipsowanie</h1>
        </header>
        <main>
            <nav>
                <a href="kontakt.html">Kontakt</a>
                <a href="https://remonty.pl" target="_blank" rel="noopener">Partnerzy</a>
            </nav>
            <section id="clients">
                <h2>Dla klientów</h2>
                <form method="post">
                    <label for="workers">Ilu co najmniej pracowników potrzebujesz?</label>
                    <div>
                        <input type="number" id="workers" name="workers" min="0" step="1" value="<?= e($_POST['workers'] ?? '') ?>">
                        <button type="submit">Szukaj firm</button>
                    </div>
                </form>
    <?php foreach ($companies as $company): ?>
                <p><?= e($company['nazwa_firmy']) ?>, <?= e($company['liczba_pracownikow']) ?> pracowników</p>
    <?php endforeach; ?>
            </section>
            <section id="contractors">
                <h2>Dla wykonawców</h2>
                <form method="post">
                    <select name="city" aria-label="Miasto">
    <?php foreach ($cities as $city): ?>
                        <option<?= $city === $selectedCity ? ' selected' : '' ?>><?= e($city) ?></option>
    <?php endforeach; ?>
                    </select>
                    <label><input type="radio" name="kind" value="malowanie" checked> malowanie</label>
                    <label><input type="radio" name="kind" value="gipsowanie"> gipsowanie</label>
                    <button type="submit">Szukaj klientów</button>
                </form>
                <ul>
    <?php foreach ($orders as $order): ?>
                    <li><?= e($order['imie']) ?> - <?= e($order['cena']) ?></li>
    <?php endforeach; ?>
                </ul>
            </section>
            <aside>
                <img src="tapeta_lewa.png" alt="usługi">
                <img src="tapeta_prawa.png" alt="usługi">
                <img src="tapeta_lewa.png" alt="usługi">
            </aside>
        </main>
        <footer>
            <p><strong>Stronę wykonał: Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>

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

  59. Etap 5: Porównanie z wersją podstawową

    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 podstawowawersja nowoczesna
    układfloat + clear, aside przed sekcjamigrid-template-areas, kolejność bloków jak w arkuszu
    formularze<br> między elementamisiatka w CSS
    bazamysqli_* z tabeli 1, dane sklejone z SQLPDO, zapytania przygotowane, config.php
    dane z formularzyisset / !emptyfilter_input i biała lista
    wypisywanieecho w pętli whileszablon foreach … endforeach, e()
    po wyszukaniuformularze pustepole 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.

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

  60. Etap 6: Oddanie pracy

    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.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

Podgląd

To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.

Autor rozwiązania: . Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.