Przejdź do głównej treści
INF.03-11-25.01-SGStyczeń 2025 · 32 kroki · ok. 78 min · 37 kryteriów z klucza

Rozwiązanie krok po kroku: Losowanie nagród wolontariatu (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza konkurs z czterema zapytaniami, puchar z cieniem i strona konkurs.php z tabelą pięciu losowych nagród - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • siatka CSS na całej stronie: grid-template-areas rozkłada nagłówek, blok lewy, blok prawy i stopkę, a szerokości 79% i 20% stoją w grid-template-columns (klucz R.4.6 przyjmuje je wprost) - bez float i clear,
  • kolory w zmiennych CSS: pięć kolorów z arkusza zapisanych raz, w :root,
  • znaczniki od treści: blok lewy to <main> (losowane nagrody), blok prawy to <aside> (puchar i odnośniki),
  • PHP na PDO: zapytanie na górze pliku, połączenie zamknięte, zanim zacznie się HTML, tabela wypisywana pętlą foreach w szablonie,
  • bezpieczeństwo i porządek w danych: escape() przy każdej wartości z bazy, try/catch przy awarii bazy, cena po polsku (1 230,00 zł) i „brak ceny” zamiast „0,00 zł” dla nagród bez ceny,
  • zapytanie 4 przedziałem dat zamiast porównywania daty jak tekstu.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafika pochodzą z prawdziwego archiwum pliki2, zapytania zostały wykonane na tej bazie w MariaDB z XAMPP-a, a podgląd strony od etapu PHP to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2. Zapytanie 1 losuje, więc w podglądzie stoi pięć nagród z ilustracji 2, w tej samej kolejności.

Jak liczą się punkty. Klucz ma 37 kryteriów (R.1.1 - R.5.7), każde za 1 punkt. Próg zdania to 75%, czyli 28 punktów. Przy każdym kroku jest napisane, które kryteria spełnia.

  1. Etap 1: Baza danych

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

    Krok 1: Baza danych konkurs

    ok. 2 min
    Polecenie z arkusza

    Utwórz bazę danych o nazwie konkurs, z zestawem polskich znaków (np. utf8_unicode_ci).

    Archiwum pliki2.7z rozpakuj hasłem ^WolonTari@t (daszek na początku, małpa zamiast „a”) do folderu z numerem zdającego: baza.sql i puchar.bmp. W phpMyAdmin Nowa → konkurs, porównywanie utf8_unicode_ci, albo zapytaniem:

    CREATE DATABASE konkurs CHARACTER SET utf8 COLLATE utf8_unicode_ci;
    
    Typowe błędy

    Nazwa bazy musi być dokładnie konkurs - skrypt łączy się z bazą po nazwie (R.5.2).

  2. Krok 2: Import tabeli z pliku baza.sql

    ok. 2 min
    Polecenie z arkusza

    Z rozpakowanego archiwum zaimportuj tabelę z pliku baza.sql do utworzonej bazy.

    Import → baza.sql. Tabela nagrody z ilustracji 1: 25 nagród z nazwą, opisem, ceną (decimal(19,2), pole może być puste - NULL), liczbą sztuk i datą dodania (typ date).

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonany import tabeli do bazy danych konkurs , co jest udokumentowane w pliku import.png

    ok. 2 min
    Polecenie z arkusza

    Wykonaj zrzut ekranu po imporcie. Zapisz zrzut w formacie PNG 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ń, a na nim komunikat phpMyAdmin o udanym imporcie albo tabela nagrody w drzewku bazy. Zapis jako import.png.

    Typowe błędy

    Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany (uwaga w kluczu przy R.1).

  4. Krok 4: Zapytanie 1: pięć losowych nagród

    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 losowo dokładnie pięć rekordów z tabeli nagrody zawierających jedynie pola nazwa, opis i cena. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, opis, cena FROM nagrody ORDER BY RAND() LIMIT 5; oraz wynik działania zapytania udokumentowany zrzutem, na którym widocznych jest dokładnie 5 dowolnych rekordów zawierających jedynie pola nazwa, opis, cena

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające losowo dokładnie pięć rekordów z tabeli nagrody zawierających jedynie pola nazwa, opis i cena.

    Każda klauzula w osobnej linii: trzy kolumny, ORDER BY RAND() tasuje wiersze (każdy dostaje liczbę losową), LIMIT 5 zostawia pięć pierwszych. Przy każdym wykonaniu inne nagrody - klucz to przewiduje. Zapytanie zapisz w kwerendy.txt, zrzut jako kw1.png. To zapytanie wyśle skrypt na stronie.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT nazwa, opis, cena
    FROM nagrody
    ORDER BY RAND()
    LIMIT 5;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: cena od 100 do 1000 zł i dwie sztuki

    Kryteria:

    R.1.4 Wykonane zapytanie 2 wybierające wszystkie pola z tabeli nagrody , dla których cena mieści się w przedziale <100, 1000>, oraz ilość jest równa 2 sztuki. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT * FROM nagrody WHERE cena BETWEEN 100 AND 1000 AND ilosc_sztuk = 2; (dopuszczalne inne rozwiązania ) oraz wynik działania zapytania udokumentowany zrzutem, na którym widoczne są dokładnie dwa rekordy o id 7 i 14

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: wybierające wszystkie pola z tabeli nagrody, dla których cena mieści się w przedziale <100, 1000>, oraz których ilość jest równa 2 sztuki.

    Nawiasy ostre <100, 1000> w matematyce oznaczają przedział domknięty - i dokładnie tak działa BETWEEN 100 AND 1000 (to samo co cena >= 100 AND cena <= 1000). Drugi warunek w osobnej linii z AND - oba muszą być spełnione. Wynik: dwie nagrody o id 7 i 14, jak w kluczu. Zrzut: kw2.png.

    Typowe błędy

    cena > 100 AND cena < 1000 pomija granice przedziału.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT nazwa, opis, cena
    FROM nagrody
    ORDER BY RAND()
    LIMIT 5;
    
    -- Zapytanie 2
    SELECT *
    FROM nagrody
    WHERE cena BETWEEN 100 AND 1000
      AND ilosc_sztuk = 2;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: usunięcie nagród bez ceny

    Kryteria:

    R.1.5 Wykonane zapytanie 3 usuwające te rekordy, w których cena nie została wpisana lub wynosi 0 zł. W pliku z kwerendami lub na zrzucie istnieje zapis np.: DELETE FROM nagrody WHERE cena IS NULL OR cena = 0; oraz wynik działania zapytania udokumentowany zrzutem, na którym widoczny jest komunikat o zmodyfikowaniu 5 rekordów lub tabela z usuniętymi rekordami

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: usuwające te rekordy, w których cena nie została wpisana lub wynosi 0 zł.

    „Nie została wpisana” to w SQL IS NULL - wartości NULL nie da się porównać znakiem =, bo każde porównanie z nią daje „nieznane”, a nie prawdę. „Wynosi 0 zł” to zwykłe cena = 0. phpMyAdmin pokaże komunikat o 5 usuniętych wierszach (cztery nagrody bez ceny i zatyczki do uszu za 0,00 zł). Zrzut: kw3.png.

    Typowe błędy

    WHERE cena = NULL nie usunie niczego. Przed każdym DELETE warto uruchomić SELECT z tym samym WHERE i zobaczyć, które wiersze znikną.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT nazwa, opis, cena
    FROM nagrody
    ORDER BY RAND()
    LIMIT 5;
    
    -- Zapytanie 2
    SELECT *
    FROM nagrody
    WHERE cena BETWEEN 100 AND 1000
      AND ilosc_sztuk = 2;
    
    -- Zapytanie 3
    DELETE FROM nagrody
    WHERE cena IS NULL
       OR cena = 0;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: nagrody dodane w sierpniu 2021

    Kryteria:

    R.1.6 Wykonane zapytanie 4 wybierające jedynie nazwę, cenę i datę dodania nagród, które zostały dodane w sierpniu 2021 roku. Wyniki powinny być posortowane rosnąco według daty. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, cena, data_dodania FROM nagrody WHERE data_dodania LIKE '2021-08-%' ORDER BY data_dodania ASC; (dopuszczalne inne rozwiązania, ASC opcjonalne ) oraz wynik działania zapytania udokumentowany zrzutem, na którym widoczne są wyłącznie rekordy zawierające nazwę, cenę i datę dodania z sierpnia 2021 posortowane wg daty rosnąco (w zależności od wykonania / niewykonania kwerendy 3 może być różna liczba zwróconych rekordów: 14, 10 lub inna gdy kwerenda 3 jest błędna)

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie nazwę, cenę i datę dodania nagród, które zostały dodane w sierpniu 2021 roku. Wyniki powinny być posortowane rosnąco według daty.

    Sierpień 2021 jako przedział dat: od '2021-08-01' włącznie do '2021-09-01' wyłącznie. Kolumna data_dodania ma typ date, więc porównanie jest porównaniem dat - działałoby też dla typu z godziną (datetime), gdzie <= '2021-08-31' zgubiłoby cały ostatni dzień. Taki warunek może też korzystać z indeksu na kolumnie, a LIKE '2021-08-%' z klucza najpierw zamienia datę na tekst. Klucz przyjmuje inne rozwiązania.

    ORDER BY data_dodania - rosnąco (ASC jest domyślne). Po zapytaniu 3 wychodzi 10 wierszy, przed nim 14 - klucz przyjmuje oba wyniki. Zrzut: kw4.png (po zapytaniu 3).

    Typowe błędy

    Samo MONTH(data_dodania) = 8 łapie sierpień z każdego roku.

    kwerendy.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT nazwa, opis, cena
    FROM nagrody
    ORDER BY RAND()
    LIMIT 5;
    
    -- Zapytanie 2
    SELECT *
    FROM nagrody
    WHERE cena BETWEEN 100 AND 1000
      AND ilosc_sztuk = 2;
    
    -- Zapytanie 3
    DELETE FROM nagrody
    WHERE cena IS NULL
       OR cena = 0;
    
    -- Zapytanie 4
    SELECT nazwa, cena, data_dodania
    FROM nagrody
    WHERE data_dodania >= '2021-08-01'
      AND data_dodania < '2021-09-01'
    ORDER BY data_dodania;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

    Kroki 8-16 · po tym etapie masz 18 z 37 kryteriów (pkt)

    Krok 8: Puchar: przezroczyste tło, cień, 260 × 260 px

    Kryteria: ,

    R.2.1 Grafika puchar.png zawiera cień przesunięty w prawo i w dół oraz tło jest przezroczyste

    R.2.2 Grafika przeskalowana dokładnie do rozmiaru 260 x 260 px

    ok. 8 min
    Polecenie z arkusza

    Cechy grafiki: Nazwa pliku puchar.png. Tło przezroczyste. Puchar rzuca cień przesunięty jednocześnie w prawo i w dół. Przeskalowana do rozmiaru 260 px na 260 px.

    Grafika jest taka sama jak w wersji podstawowej. W GIMP-ie, na puchar.bmp:

    1. Warstwa → Przezroczystość → Dodaj kanał alfa, Różdżka na białym tle i Delete. Białe prześwity w uchwytach nie łączą się z tłem - dodaj je do zaznaczenia z klawiszem Shift,
    2. Filtry → Światło i cień → Cień: przesunięcie X i Y dodatnie (np. 20 i 20) - w prawo i w dół (R.2.1),
    3. Obraz → Skaluj obraz: 260 × 260 - źródło jest kwadratem 745 × 745, więc proporcje zostają (R.2.2),
    4. Eksportuj jako puchar.png.
    Typowe błędy

    Ujemne przesunięcie cienia (w lewo lub w górę) nie spełnia R.2.1. JPG nie ma przezroczystości.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Plik konkurs.php: HTML5, język, kodowanie

    Kryteria:

    R.2.3 Witryna internetowa zapisana w pliku konkurs.php oraz zapisany jawnie standard kodowania polskich znaków, deklaracja HTML5 <!DOCTYPE HTML>, zadeklarowany język witryny: polski np. <html lang="pl">. Tytuł strony „WOLONTARIAT SZKOLNY”. Strona zawiera sekcje head i body

    ok. 2 min
    Polecenie z arkusza

    Cechy witryny: Składa się ze strony o nazwie konkurs.php. Zapisana w języku HTML5. Zadeklarowany polski język zawartości witryny. Jawnie zastosowany właściwy standard kodowania polskich znaków.

    Plik konkurs.php: <!DOCTYPE html>, <html lang="pl"> i <meta charset="UTF-8"> jako pierwszy element <head>. Rozszerzenie .php, bo strona pobierze nagrody z bazy - otwierasz ją przez http://localhost/....

    konkurs.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  10. Krok 10: Tytuł strony i arkusz stylów

    Kryteria: ,

    R.2.3 Witryna internetowa zapisana w pliku konkurs.php oraz zapisany jawnie standard kodowania polskich znaków, deklaracja HTML5 <!DOCTYPE HTML>, zadeklarowany język witryny: polski np. <html lang="pl">. Tytuł strony „WOLONTARIAT SZKOLNY”. Strona zawiera sekcje head i body

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

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony „WOLONTARIAT SZKOLNY” widoczny na karcie przeglądarki. Arkusz stylów w pliku o nazwie styles.css prawidłowo połączony z kodem strony.

    <title> (tekst na karcie przeglądarki) i <link> do styles.css - z „s” na końcu (R.3.2). Do tego viewport, żeby telefon nie pomniejszał strony do szerokości ekranu komputera.

    konkurs.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>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  11. Krok 11: Bloki semantyczne

    Kryteria:

    R.2.4 Zdefiniowany układ strony dzielący ją na bloki: nagłówkowy, lewy, prawy i stopki przy pomocy semantycznych znaczników sekcji języka HTML5 zgodnie z ich znaczeniem, w tym do części nagłówkowej - header, do stopki - footer oraz dopuszcza się dodatkowe użycie bloków div lub section

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: nagłówkowy, poniżej lewy i prawy, na dole blok stopki. Podział zrealizowany za pomocą semantycznych znaczników sekcji języka HTML5 tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z ilustracją 2.

    Znaczniki wybrane od treści bloków:

    • <header> i <footer> - nagłówek i stopka (klucz R.2.4 wymienia je wprost),
    • <main> - blok lewy: losowane nagrody, czyli główna treść strony,
    • <aside> - blok prawy: puchar i odnośniki do zrzutów, dodatki obok treści.

    Każdy występuje raz, więc CSS trafi w nie samym selektorem znacznika. Ułożenie obok siebie zrobi siatka CSS.

    konkurs.php PHP zmienione: 8 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
        </header>
        <main>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  12. Krok 12: Nagłówek strony i bloku lewego

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego: Nagłówek pierwszego stopnia o treści: „KONKURS - WOLONTARIAT SZKOLNY”. Zawartość bloku lewego: Nagłówek trzeciego stopnia o treści: „Konkursowe nagrody”.

    <h1> w nagłówku i <h3> na początku bloku lewego.

    konkurs.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>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
            <h1>KONKURS - WOLONTARIAT SZKOLNY</h1>
        </header>
        <main>
            <h3>Konkursowe nagrody</h3>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  13. Krok 13: Przycisk przeładowujący stronę

    Kryteria: ,

    R.2.9 Utworzony w bloku lewym przycisk o treści „Losuj nowe nagrody”

    R.3.4 Kliknięcie przycisku przeładowuje stronę

    ok. 2 min
    Polecenie z arkusza

    Przycisk o treści „Losuj nowe nagrody”, jego wybranie przeładowuje stronę.

    Formularz bez atrybutów z jednym <button>: przycisk w formularzu bez atrybutu type jest przyciskiem wysyłającym (klucz to przyjmuje), formularz bez action wysyła się pod adres tej samej strony, a bez method - metodą GET. Przeglądarka pobiera więc konkurs.php jeszcze raz, serwer wykonuje PHP i losuje nowe nagrody (R.2.9, R.3.4).

    Bez JavaScriptu działa to także wtedy, gdy skrypty są w przeglądarce wyłączone. <button onclick="location.reload()"> też przeładowałby stronę, ale potrzebuje JavaScriptu do czegoś, co HTML robi sam.

    konkurs.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>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
            <h1>KONKURS - WOLONTARIAT SZKOLNY</h1>
        </header>
        <main>
            <h3>Konkursowe nagrody</h3>
            <form>
                <button>Losuj nowe nagrody</button>
            </form>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  14. Krok 14: Tabela: wiersz nagłówkowy

    Kryteria:

    R.2.6 Tabela zawiera wiersz z komórkami nagłówkowymi, zastosowany znacznik <th>

    ok. 2 min
    Polecenie z arkusza

    Tabela o czterech kolumnach, z wierszem nagłówkowym o komórkach: „Nr”, „Nazwa”, „Opis”, „Wartość”. Wypełniona przez skrypt.

    Pierwszy wiersz z czterema <th> (R.2.6); wiersze z nagrodami dopisze skrypt.

    Tu świadomie nie ma podziału na <thead> i <tbody>, choć w innych tabelach to dobry zwyczaj. Arkusz każe pokolorować parzyste wiersze tabeli, a ilustracja 2 pokazuje żółte pierwszą, trzecią i piątą nagrodę - czyli wiersze liczone razem z nagłówkowym. Z <tbody> reguła tr:nth-child(even) liczyłaby wiersze od pierwszej nagrody i żółte byłyby druga i czwarta.

    konkurs.php PHP zmienione: 8 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
            <h1>KONKURS - WOLONTARIAT SZKOLNY</h1>
        </header>
        <main>
            <h3>Konkursowe nagrody</h3>
            <form>
                <button>Losuj nowe nagrody</button>
            </form>
            <table>
                <tr>
                    <th>Nr</th>
                    <th>Nazwa</th>
                    <th>Opis</th>
                    <th>Wartość</th>
                </tr>
            </table>
        </main>
        <aside>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  15. Krok 15: Blok prawy: puchar, nagłówek i lista odnośników

    Kryteria: , ,

    R.2.7 W bloku prawym umieszczona grafika z pucharem z tekstem alternatywnym "Puchar dla wolontariusza"

    R.2.8 W bloku prawym umieszczona jedna lista punktowana o czterech elementach, zastosowane znaczniki <ul>, <li>

    R.3.3 Utworzone 4 odnośniki nazwane: kwerenda1, kwerenda2, kwerenda3, kwerenda4. Po wybraniu przynajmniej jednego odnośnika otwiera/pobiera się plik graficzny

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku prawego: Grafika puchar.png z tekstem alternatywnym o treści: „Puchar dla wolontariusza”. Nagłówek czwartego stopnia o treści: „Polecane linki”. Lista punktowana (nieuporządkowana), której poszczególne elementy: „Kwerenda1”, „Kwerenda2”, „Kwerenda3”, „Kwerenda4” są odnośnikami do plików zawierających zrzuty ekranu wykonanych kwerend.

    Obraz z alt (R.2.7), <h4> i <ul> z czterema <li>, a w każdym <a> do zrzutu (R.2.8, R.3.3). Zrzuty leżą w tym samym folderze co strona, więc w href wystarczy nazwa pliku. W podglądzie kliknięcia są zablokowane.

    konkurs.php PHP zmienione: 8 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
            <h1>KONKURS - WOLONTARIAT SZKOLNY</h1>
        </header>
        <main>
            <h3>Konkursowe nagrody</h3>
            <form>
                <button>Losuj nowe nagrody</button>
            </form>
            <table>
                <tr>
                    <th>Nr</th>
                    <th>Nazwa</th>
                    <th>Opis</th>
                    <th>Wartość</th>
                </tr>
            </table>
        </main>
        <aside>
            <img src="puchar.png" alt="Puchar dla wolontariusza">
            <h4>Polecane linki</h4>
            <ul>
                <li><a href="kw1.png">Kwerenda1</a></li>
                <li><a href="kw2.png">Kwerenda2</a></li>
                <li><a href="kw3.png">Kwerenda3</a></li>
                <li><a href="kw4.png">Kwerenda4</a></li>
            </ul>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  16. Krok 16: Stopka

    Kryteria:

    R.2.10 Utworzony w bloku nagłówkowym nagłówek <h1> o treści: „KONKURS - WOLONTARIAT SZKOLNY”, w bloku lewym <h3>: „Konkursowe nagrody”, w bloku prawym <h4>: „Polecane linki" oraz w stopce paragraf z numerem zdającego

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: paragraf o treści „Numer zdającego: ”, dalej wstawiony numer zdającego.

    Akapit w <footer>. Na egzaminie po dwukropku wpisujesz swój numer zdającego (PESEL), tu stoi autor rozwiązania. Z nagłówkami h1, h3 i h4 to komplet R.2.10.

    konkurs.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>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
            <h1>KONKURS - WOLONTARIAT SZKOLNY</h1>
        </header>
        <main>
            <h3>Konkursowe nagrody</h3>
            <form>
                <button>Losuj nowe nagrody</button>
            </form>
            <table>
                <tr>
                    <th>Nr</th>
                    <th>Nazwa</th>
                    <th>Opis</th>
                    <th>Wartość</th>
                </tr>
            </table>
        </main>
        <aside>
            <img src="puchar.png" alt="Puchar dla wolontariusza">
            <h4>Polecane linki</h4>
            <ul>
                <li><a href="kw1.png">Kwerenda1</a></li>
                <li><a href="kw2.png">Kwerenda2</a></li>
                <li><a href="kw3.png">Kwerenda3</a></li>
                <li><a href="kw4.png">Kwerenda4</a></li>
            </ul>
        </aside>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  17. Etap 3: Styl CSS

    Kroki 17-24 · po tym etapie masz 29 z 37 kryteriów (pkt)

    Krok 17: Plik styles.css: kolory z arkusza w zmiennych

    ok. 1 min

    Pięć kolorów z arkusza zapisanych raz, w zmiennych CSS na :root. Kremowy #FDFAAA i zielony #5BBB77 występują w kilku blokach i w gradiencie - w regułach stanie var(--cream) i var(--green), a zmiana odcienia to jedna linia. Klucz ocenia kolor, który blok faktycznie dostaje.

    styles.css CSS nowy plik
    :root {
        --cream: #FDFAAA;
        --green: #5BBB77;
        --pink: #FFA6A6;
        --yellow: #FFF555;
        --link: #680808;
    }
    Podgląd strony po tym kroku (konkurs.php)
  18. Krok 18: Ciało strony i układ na siatce

    Kryteria: , ,

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 2 w arkuszu egzaminacyjnym (prawidłowo zastosowane właściwości CSS decydujące o układzie strony, np. float i clear albo display flex albo grid albo position )

    R.4.1 Dla ciała strony (body lub kontenera całej strony) ustawiony krój czcionki Verdana, wyrównanie tekstu do środka, marginesy wewnętrzne górny i dolny 0, prawy i lewy 3%

    R.4.6 Ustawiona szerokość bloku lewego na 79% oraz bloku prawego na 20% (width lub grid- template-columns lub flex) oraz szerokość selektora table na 80%

    ok. 3 min
    Polecenie z arkusza

    Dla ciała strony: krój czcionki Verdana, wyrównanie tekstu do środka, marginesy wewnętrzne górny i dolny 0, prawy i lewy 3%.

    Najpierw cechy z arkusza: Verdana (z zapasowym krojem bezszeryfowym), tekst do środka i padding: 0 3% - góra i dół 0, boki 3% (R.4.1).

    Ta sama reguła robi z <body> siatkę: grid-template-columns: 79% 20% to od razu szerokości bloku lewego i prawego (R.4.6), a grid-template-areas rysuje układ z ilustracji 2 słowami - nagłówek na dwie kolumny, pod nim main i aside, na dole stopka (R.3.1). Procenty liczą się od szerokości treści <body>, czyli bez marginesów 3%; 79% + 20% zostawia 1% luzu po prawej. Bez float i bez clear w stopce.

    styles.css CSS zmienione: 28 linii
    :root {
        --cream: #FDFAAA;
        --green: #5BBB77;
        --pink: #FFA6A6;
        --yellow: #FFF555;
        --link: #680808;
    }
    
    body {
        font-family: Verdana, sans-serif;
        text-align: center;
        padding: 0 3%;
        display: grid;
        grid-template-columns: 79% 20%;
        grid-template-areas:
            "header header"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: header;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (konkurs.php)
  19. Krok 19: Blok nagłówkowy: kolory, odstęp, cień tekstu

    Kryteria: , , ,

    R.4.2 Ustawiony kolor tła dla: bloku nagłówkowego #FDFAAA, bloku lewego #FFA6A6, bloku prawego: linear-gradient(#FDFAAA, #5BBB77), stopki #5BBB77

    R.4.3 Ustawiony kolor czcionki dla: selektora a #680808, dla selektora h4 zielony, bloku nagłówkowego #5BBB77, stopki: #FFF555

    R.4.7 Ustawione marginesy wewnętrzne (padding) dla bloku nagłówkowego 10px, stopki 15px oraz marginesy zewnętrzne (margin) dla bloku lewego górny i dolny 15px, prawy i lewy 0, auto dla tabeli

    R.4.8 Ustawiony cień tekstu w bloku nagłówkowym: text-shadow: 5px 5px 10px black; oraz cień bloku lewego: box-shadow: 5px 6px 10px black;

    ok. 2 min
    Polecenie z arkusza

    Dla bloku nagłówkowego: kolor tła #FDFAAA, kolor czcionki #5BBB77, marginesy wewnętrzne 10 px, cień tekstu o cechach: przesunięcie w poziomie i pionie: 5 px, promień rozmycia: 10 px, czarny kolor cienia.

    Cechy dopisane do reguły header, obok jej pola siatki. text-shadow: 5px 5px 10px black - przesunięcie poziome, pionowe, rozmycie, kolor (kolejność z tabeli 2 arkusza).

    styles.css CSS zmienione: 4 linie
    :root {
        --cream: #FDFAAA;
        --green: #5BBB77;
        --pink: #FFA6A6;
        --yellow: #FFF555;
        --link: #680808;
    }
    
    body {
        font-family: Verdana, sans-serif;
        text-align: center;
        padding: 0 3%;
        display: grid;
        grid-template-columns: 79% 20%;
        grid-template-areas:
            "header header"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: header;
        background-color: var(--cream);
        color: var(--green);
        padding: 10px;
        text-shadow: 5px 5px 10px black;
    }
    
    main {
        grid-area: main;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (konkurs.php)
  20. Krok 20: Blok lewy: tło, wysokość, marginesy, cień bloku

    Kryteria: , , ,

    R.4.2 Ustawiony kolor tła dla: bloku nagłówkowego #FDFAAA, bloku lewego #FFA6A6, bloku prawego: linear-gradient(#FDFAAA, #5BBB77), stopki #5BBB77

    R.4.5 Ustawiona wysokość: bloku lewego 600px, bloku prawego 630px

    R.4.7 Ustawione marginesy wewnętrzne (padding) dla bloku nagłówkowego 10px, stopki 15px oraz marginesy zewnętrzne (margin) dla bloku lewego górny i dolny 15px, prawy i lewy 0, auto dla tabeli

    R.4.8 Ustawiony cień tekstu w bloku nagłówkowym: text-shadow: 5px 5px 10px black; oraz cień bloku lewego: box-shadow: 5px 6px 10px black;

    ok. 3 min
    Polecenie z arkusza

    Dla bloku lewego: kolor tła #FFA6A6, szerokość 79%, wysokość 600 px, marginesy zewnętrzne górny i dolny 15 px, prawy i lewy 0, cień bloku o cechach: przesunięcie w poziomie 5 px, w pionie 6 px, promień rozmycia: 10 px, kolor czarny.

    margin: 15px 0 - góra i dół 15 px, boki 0; w siatce margines odsuwa blok od krawędzi jego pola. box-shadow: 5px 6px 10px black - cień całego bloku (tabela 2). Szerokości 79% tu nie ma, bo daje ją kolumna siatki - dodatkowe width: 79% liczyłoby się od kolumny i zwęziło blok.

    styles.css CSS zmienione: 4 linie
    :root {
        --cream: #FDFAAA;
        --green: #5BBB77;
        --pink: #FFA6A6;
        --yellow: #FFF555;
        --link: #680808;
    }
    
    body {
        font-family: Verdana, sans-serif;
        text-align: center;
        padding: 0 3%;
        display: grid;
        grid-template-columns: 79% 20%;
        grid-template-areas:
            "header header"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: header;
        background-color: var(--cream);
        color: var(--green);
        padding: 10px;
        text-shadow: 5px 5px 10px black;
    }
    
    main {
        grid-area: main;
        background-color: var(--pink);
        height: 600px;
        margin: 15px 0;
        box-shadow: 5px 6px 10px black;
    }
    
    aside {
        grid-area: aside;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (konkurs.php)
  21. Krok 21: Blok prawy: gradient

    Kryteria: ,

    R.4.2 Ustawiony kolor tła dla: bloku nagłówkowego #FDFAAA, bloku lewego #FFA6A6, bloku prawego: linear-gradient(#FDFAAA, #5BBB77), stopki #5BBB77

    R.4.5 Ustawiona wysokość: bloku lewego 600px, bloku prawego 630px

    ok. 2 min
    Polecenie z arkusza

    Dla bloku prawego: tło będące gradientem liniowym od koloru #FDFAAA do koloru #5BBB77, szerokość 20%, wysokość 630 px.

    linear-gradient to obraz tła, więc stoi we właściwości background, nie background-color. Bez kierunku biegnie z góry na dół - od kremowego do zielonego, jak na ilustracji 2. Wysokość 630 px = 600 px bloku lewego + jego marginesy 2 × 15 px, więc oba bloki kończą się równo.

    Typowe błędy

    background-color: linear-gradient(...) jest błędne - przeglądarka pominie całą deklarację.

    styles.css CSS zmienione: 2 linie
    :root {
        --cream: #FDFAAA;
        --green: #5BBB77;
        --pink: #FFA6A6;
        --yellow: #FFF555;
        --link: #680808;
    }
    
    body {
        font-family: Verdana, sans-serif;
        text-align: center;
        padding: 0 3%;
        display: grid;
        grid-template-columns: 79% 20%;
        grid-template-areas:
            "header header"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: header;
        background-color: var(--cream);
        color: var(--green);
        padding: 10px;
        text-shadow: 5px 5px 10px black;
    }
    
    main {
        grid-area: main;
        background-color: var(--pink);
        height: 600px;
        margin: 15px 0;
        box-shadow: 5px 6px 10px black;
    }
    
    aside {
        grid-area: aside;
        background: linear-gradient(var(--cream), var(--green));
        height: 630px;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (konkurs.php)
  22. Krok 22: Stopka

    Kryteria: , , ,

    R.4.2 Ustawiony kolor tła dla: bloku nagłówkowego #FDFAAA, bloku lewego #FFA6A6, bloku prawego: linear-gradient(#FDFAAA, #5BBB77), stopki #5BBB77

    R.4.3 Ustawiony kolor czcionki dla: selektora a #680808, dla selektora h4 zielony, bloku nagłówkowego #5BBB77, stopki: #FFF555

    R.4.7 Ustawione marginesy wewnętrzne (padding) dla bloku nagłówkowego 10px, stopki 15px oraz marginesy zewnętrzne (margin) dla bloku lewego górny i dolny 15px, prawy i lewy 0, auto dla tabeli

    R.4.9 Ustawiona czcionka pogrubiona, w rozmiar 120% dla bloku stopki

    ok. 2 min
    Polecenie z arkusza

    Dla bloku stopki: kolor tła #5BBB77, kolor czcionki #FFF555, czcionka pogrubiona w rozmiarze 120%, marginesy wewnętrzne 15 px.

    Pogrubienie to font-weight: bold.

    styles.css CSS zmienione: 5 linii
    :root {
        --cream: #FDFAAA;
        --green: #5BBB77;
        --pink: #FFA6A6;
        --yellow: #FFF555;
        --link: #680808;
    }
    
    body {
        font-family: Verdana, sans-serif;
        text-align: center;
        padding: 0 3%;
        display: grid;
        grid-template-columns: 79% 20%;
        grid-template-areas:
            "header header"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: header;
        background-color: var(--cream);
        color: var(--green);
        padding: 10px;
        text-shadow: 5px 5px 10px black;
    }
    
    main {
        grid-area: main;
        background-color: var(--pink);
        height: 600px;
        margin: 15px 0;
        box-shadow: 5px 6px 10px black;
    }
    
    aside {
        grid-area: aside;
        background: linear-gradient(var(--cream), var(--green));
        height: 630px;
    }
    
    footer {
        grid-area: footer;
        background-color: var(--green);
        color: var(--yellow);
        font-weight: bold;
        font-size: 120%;
        padding: 15px;
    }
    Podgląd strony po tym kroku (konkurs.php)
  23. Krok 23: Tabela: wyśrodkowanie, komórki nagłówkowe, parzyste wiersze

    Kryteria: , , ,

    R.4.4 Ustawione dla selektora ul: interlinia 1.5, typ punktora kwadrat oraz dla komórki nagłówkowej tabeli: kolor tła #5BBB77, wysokość 40px, rozmiar czcionki 16px

    R.4.6 Ustawiona szerokość bloku lewego na 79% oraz bloku prawego na 20% (width lub grid- template-columns lub flex) oraz szerokość selektora table na 80%

    R.4.7 Ustawione marginesy wewnętrzne (padding) dla bloku nagłówkowego 10px, stopki 15px oraz marginesy zewnętrzne (margin) dla bloku lewego górny i dolny 15px, prawy i lewy 0, auto dla tabeli

    R.4.10 Ustawiony dla parzystych wierszy tabeli (np. tr:nth-child(even)): kolor tła #FDFAAA

    ok. 2 min
    Polecenie z arkusza

    Dla selektora tabeli: marginesy zewnętrzne automatycznie wyliczane przez przeglądarkę, szerokość: 80%. Dla komórek nagłówkowych tabeli: kolor tła #5BBB77, rozmiar czcionki 16 px, wysokość komórki 40 px. Dla parzystych wierszy tabeli: kolor tła #FDFAAA.

    Selektory znaczników table i th, jak każe uwaga arkusza (R.4.4, R.4.6, R.4.7). Pseudoklasa :nth-child(even) wybiera co drugi wiersz - drugi, czwarty, szósty; wiersz nagłówkowy jest pierwszy, więc żółte tło dostają pierwsza, trzecia i piąta nagroda (R.4.10).

    styles.css CSS zmienione: 15 linii
    :root {
        --cream: #FDFAAA;
        --green: #5BBB77;
        --pink: #FFA6A6;
        --yellow: #FFF555;
        --link: #680808;
    }
    
    body {
        font-family: Verdana, sans-serif;
        text-align: center;
        padding: 0 3%;
        display: grid;
        grid-template-columns: 79% 20%;
        grid-template-areas:
            "header header"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: header;
        background-color: var(--cream);
        color: var(--green);
        padding: 10px;
        text-shadow: 5px 5px 10px black;
    }
    
    main {
        grid-area: main;
        background-color: var(--pink);
        height: 600px;
        margin: 15px 0;
        box-shadow: 5px 6px 10px black;
    }
    
    aside {
        grid-area: aside;
        background: linear-gradient(var(--cream), var(--green));
        height: 630px;
    }
    
    footer {
        grid-area: footer;
        background-color: var(--green);
        color: var(--yellow);
        font-weight: bold;
        font-size: 120%;
        padding: 15px;
    }
    
    table {
        margin: auto;
        width: 80%;
    }
    
    th {
        background-color: var(--green);
        font-size: 16px;
        height: 40px;
    }
    
    tr:nth-child(even) {
        background-color: var(--cream);
    }
    Podgląd strony po tym kroku (konkurs.php)
  24. Krok 24: Lista, odnośniki i nagłówek czwartego stopnia

    Kryteria: ,

    R.4.3 Ustawiony kolor czcionki dla: selektora a #680808, dla selektora h4 zielony, bloku nagłówkowego #5BBB77, stopki: #FFF555

    R.4.4 Ustawione dla selektora ul: interlinia 1.5, typ punktora kwadrat oraz dla komórki nagłówkowej tabeli: kolor tła #5BBB77, wysokość 40px, rozmiar czcionki 16px

    ok. 2 min
    Polecenie z arkusza

    Dla selektora listy punktowanej: typ punktora kwadrat, interlinia 1.5. Dla selektora odnośnika: kolor czcionki #680808. Dla selektora nagłówka czwartego stopnia: zielony kolor czcionki.

    Tu nie ma wyboru techniki: arkusz każe użyć wyłącznie selektorów znaczników ul, a, h4, a klucz nie uzna klasy ani selektora potomka (aside ul). Zielony to nazwa koloru green.

    styles.css CSS zmienione: 13 linii
    :root {
        --cream: #FDFAAA;
        --green: #5BBB77;
        --pink: #FFA6A6;
        --yellow: #FFF555;
        --link: #680808;
    }
    
    body {
        font-family: Verdana, sans-serif;
        text-align: center;
        padding: 0 3%;
        display: grid;
        grid-template-columns: 79% 20%;
        grid-template-areas:
            "header header"
            "main   aside"
            "footer footer";
    }
    
    header {
        grid-area: header;
        background-color: var(--cream);
        color: var(--green);
        padding: 10px;
        text-shadow: 5px 5px 10px black;
    }
    
    main {
        grid-area: main;
        background-color: var(--pink);
        height: 600px;
        margin: 15px 0;
        box-shadow: 5px 6px 10px black;
    }
    
    aside {
        grid-area: aside;
        background: linear-gradient(var(--cream), var(--green));
        height: 630px;
    }
    
    footer {
        grid-area: footer;
        background-color: var(--green);
        color: var(--yellow);
        font-weight: bold;
        font-size: 120%;
        padding: 15px;
    }
    
    table {
        margin: auto;
        width: 80%;
    }
    
    th {
        background-color: var(--green);
        font-size: 16px;
        height: 40px;
    }
    
    tr:nth-child(even) {
        background-color: var(--cream);
    }
    
    ul {
        list-style-type: square;
        line-height: 1.5;
    }
    
    a {
        color: var(--link);
    }
    
    h4 {
        color: green;
    }
    Podgląd strony po tym kroku (konkurs.php)
  25. Etap 4: Skrypt PHP

    Kroki 25-30 · po tym etapie masz 37 z 37 kryteriów (pkt)

    Krok 25: Połączenie PDO na górze pliku

    Kryteria: , ,

    R.5.1 Skrypt jest napisany w języku PHP i zawiera przynajmniej jedną zgodną z treścią zadania instrukcję

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

    R.5.3 Ostatnią operacją na bazie jest jej zamknięcie

    ok. 4 min
    Polecenie z arkusza

    Napisany w języku PHP. Należy stosować znaczące nazewnictwo zmiennych i funkcji w języku polskim lub angielskim. Łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie konkurs. Na końcu zamykane jest połączenie z serwerem.

    Praca z bazą stoi przed <!DOCTYPE html>: najpierw dane, potem HTML, który je tylko wypisuje. Połączenie przez PDO (klucz R.5 przyjmuje MySQLi i PDO):

    • napis DSN 'mysql:host=localhost;dbname=konkurs;charset=utf8mb4' - serwer, baza i kodowanie, potem root i puste hasło (R.5.1, R.5.2),
    • ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze z nazwami kolumn,
    • $pdo = null zamyka połączenie; zapytanie trafi przed tę linię, więc zamknięcie zostanie ostatnią operacją na bazie (R.5.3),
    • try / catch (PDOException $exception) - przy awarii bazy szczegóły idą do dziennika serwera, a strona dostaje ogólny komunikat w $dbError,
    • funkcja escape() to htmlspecialchars(..., ENT_QUOTES) pod nazwą, która mówi, co robi.

    Od tego kroku podgląd to wynik prawdziwego uruchomienia.

    konkurs.php PHP zmienione: 20 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $prizes = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=konkurs;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się wylosować nagród. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
            <h1>KONKURS - WOLONTARIAT SZKOLNY</h1>
        </header>
        <main>
            <h3>Konkursowe nagrody</h3>
            <form>
                <button>Losuj nowe nagrody</button>
            </form>
            <table>
                <tr>
                    <th>Nr</th>
                    <th>Nazwa</th>
                    <th>Opis</th>
                    <th>Wartość</th>
                </tr>
            </table>
        </main>
        <aside>
            <img src="puchar.png" alt="Puchar dla wolontariusza">
            <h4>Polecane linki</h4>
            <ul>
                <li><a href="kw1.png">Kwerenda1</a></li>
                <li><a href="kw2.png">Kwerenda2</a></li>
                <li><a href="kw3.png">Kwerenda3</a></li>
                <li><a href="kw4.png">Kwerenda4</a></li>
            </ul>
        </aside>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  26. Krok 26: Zapytanie 1 do bazy

    Kryteria: ,

    R.5.4 Skrypt zawiera instrukcję wysyłającą do bazy danych zapytanie 1

    R.5.7 W skryptach zastosowana przynajmniej jedna zmienna lub funkcja oraz nazwy wszystkich funkcji i zmiennych są w języku polskim lub angielskim i odzwierciedlają cel zastosowania

    ok. 2 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 1.

    $pdo->query(...) wysyła zapytanie 1, a ->fetchAll() odbiera pięć wylosowanych nagród do tablicy $prizes (R.5.4). W zapytaniu nie ma żadnej wartości od użytkownika, więc query wystarcza - przy wartości z formularza byłoby prepare i execute. Nazwy zmiennych po angielsku mówią, co przechowują (R.5.7).

    konkurs.php PHP zmienione: 1 linia
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $prizes = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=konkurs;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $prizes = $pdo->query('SELECT nazwa, opis, cena FROM nagrody ORDER BY RAND() LIMIT 5')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się wylosować nagród. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
            <h1>KONKURS - WOLONTARIAT SZKOLNY</h1>
        </header>
        <main>
            <h3>Konkursowe nagrody</h3>
            <form>
                <button>Losuj nowe nagrody</button>
            </form>
            <table>
                <tr>
                    <th>Nr</th>
                    <th>Nazwa</th>
                    <th>Opis</th>
                    <th>Wartość</th>
                </tr>
            </table>
        </main>
        <aside>
            <img src="puchar.png" alt="Puchar dla wolontariusza">
            <h4>Polecane linki</h4>
            <ul>
                <li><a href="kw1.png">Kwerenda1</a></li>
                <li><a href="kw2.png">Kwerenda2</a></li>
                <li><a href="kw3.png">Kwerenda3</a></li>
                <li><a href="kw4.png">Kwerenda4</a></li>
            </ul>
        </aside>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  27. Krok 27: Wiersz tabeli z numerem

    Kryteria:

    R.5.5 Skrypt wyświetla w tabeli wartości od 1 do 5 w kolumnie Nr

    ok. 3 min
    Polecenie z arkusza

    W każdym wierszu tabeli wpisywany jest obliczony przez skrypt kolejny numer wiersza począwszy od 1.

    Pod wierszem nagłówkowym pętla foreach ($prizes as $index => $prize): ... endforeach; w składni alternatywnej - HTML wiersza zostaje zwykłym HTML-em. <?= ... ?> to skrót od <?php echo ... ?>.

    fetchAll() numeruje listę od 0, więc numer wiersza to $index + 1 - od 1 do 5 (R.5.5). Podgląd: pięć wierszy z numerami, na razie tylko pierwsza kolumna.

    Typowe błędy

    Samo $index daje numery od 0 do 4 - arkusz każe liczyć od 1.

    konkurs.php PHP zmienione: 5 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $prizes = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=konkurs;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $prizes = $pdo->query('SELECT nazwa, opis, cena FROM nagrody ORDER BY RAND() LIMIT 5')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się wylosować nagród. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
            <h1>KONKURS - WOLONTARIAT SZKOLNY</h1>
        </header>
        <main>
            <h3>Konkursowe nagrody</h3>
            <form>
                <button>Losuj nowe nagrody</button>
            </form>
            <table>
                <tr>
                    <th>Nr</th>
                    <th>Nazwa</th>
                    <th>Opis</th>
                    <th>Wartość</th>
                </tr>
                <?php foreach ($prizes as $index => $prize): ?>
                    <tr>
                        <td><?= $index + 1 ?></td>
                    </tr>
                <?php endforeach; ?>
            </table>
        </main>
        <aside>
            <img src="puchar.png" alt="Puchar dla wolontariusza">
            <h4>Polecane linki</h4>
            <ul>
                <li><a href="kw1.png">Kwerenda1</a></li>
                <li><a href="kw2.png">Kwerenda2</a></li>
                <li><a href="kw3.png">Kwerenda3</a></li>
                <li><a href="kw4.png">Kwerenda4</a></li>
            </ul>
        </aside>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  28. Krok 28: Pola rekordu w kolejnych komórkach

    Kryteria: ,

    R.2.5 W bloku lewym zapisana tabela o 4 kolumnach (w skrypcie lub HTML) zastosowane znaczniki <table>, <tr>, <td>

    R.5.6 Skrypt wyświetla zwrócone zapytaniem rekordy w osobnych wierszach tabeli zgodnie z przeznaczeniem kolumn

    ok. 3 min
    Polecenie z arkusza

    W każdym wierszu tabeli wpisywany jest obliczony przez skrypt kolejny numer wiersza począwszy od 1 oraz zwrócone zapytaniem pola kolejnego rekordu.

    Trzy komórki <td> po nazwach kolumn ($prize['opis']), każda przez escape(): opis nagrody wpisuje człowiek, a znak < albo & w opisie przeglądarka wzięłaby za początek znacznika (R.2.5, R.5.6). Podgląd: tabela jak na ilustracji 2, ceny jeszcze w zapisie z bazy (1230.00).

    konkurs.php PHP zmienione: 3 linie
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    $prizes = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=konkurs;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $prizes = $pdo->query('SELECT nazwa, opis, cena FROM nagrody ORDER BY RAND() LIMIT 5')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się wylosować nagród. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
            <h1>KONKURS - WOLONTARIAT SZKOLNY</h1>
        </header>
        <main>
            <h3>Konkursowe nagrody</h3>
            <form>
                <button>Losuj nowe nagrody</button>
            </form>
            <table>
                <tr>
                    <th>Nr</th>
                    <th>Nazwa</th>
                    <th>Opis</th>
                    <th>Wartość</th>
                </tr>
                <?php foreach ($prizes as $index => $prize): ?>
                    <tr>
                        <td><?= $index + 1 ?></td>
                        <td><?= escape($prize['nazwa']) ?></td>
                        <td><?= escape($prize['opis']) ?></td>
                        <td><?= escape($prize['cena']) ?></td>
                    </tr>
                <?php endforeach; ?>
            </table>
        </main>
        <aside>
            <img src="puchar.png" alt="Puchar dla wolontariusza">
            <h4>Polecane linki</h4>
            <ul>
                <li><a href="kw1.png">Kwerenda1</a></li>
                <li><a href="kw2.png">Kwerenda2</a></li>
                <li><a href="kw3.png">Kwerenda3</a></li>
                <li><a href="kw4.png">Kwerenda4</a></li>
            </ul>
        </aside>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  29. Krok 29: Cena po polsku i nagrody bez ceny

    ok. 3 min

    Dodatek spoza arkusza - klucz go nie ocenia. Funkcja formatPrice():

    • number_format((float) $price, 2, ',', "\u{a0}") - zapis dla ludzi: przecinek dziesiętny, odstęp między tysiącami i waluta: 1230.00 z bazy staje się „1 230,00 zł”. Odstępem jest spacja nierozdzielająca "\u{a0}" (w HTML-u to samo co &nbsp;) - w wąskiej kolumnie cena nie rozpadnie się na dwie linie. PDO oddaje liczby z bazy jako napisy, a jawne (float) mówi, że tu zaczyna się liczba,
    • null → „brak ceny”: strona działa na bazie po imporcie, a zapytanie 3 (usunięcie nagród bez ceny) jest osobnym ćwiczeniem. Losowanie może trafić na Skarpety ALPAKA z pustą ceną - bez tego warunku number_format(null, ...) wypisałby „0,00 zł”, jakby nagroda była darmowa, i zgłosił ostrzeżenie Deprecated (PHP 8.1+).

    Sprawdzone uruchomieniem z nagrodami 21 (brak ceny) i 20 (0,00 zł): w tabeli stoi odpowiednio „brak ceny” i „0,00 zł”. Podgląd: ceny po polsku.

    konkurs.php PHP zmienione: 9 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function formatPrice(?string $price): string
    {
        if ($price === null) {
            return 'brak ceny';
        }
        return number_format((float) $price, 2, ',', "\u{a0}") . "\u{a0}zł";
    }
    
    $prizes = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=konkurs;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $prizes = $pdo->query('SELECT nazwa, opis, cena FROM nagrody ORDER BY RAND() LIMIT 5')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się wylosować nagród. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
            <h1>KONKURS - WOLONTARIAT SZKOLNY</h1>
        </header>
        <main>
            <h3>Konkursowe nagrody</h3>
            <form>
                <button>Losuj nowe nagrody</button>
            </form>
            <table>
                <tr>
                    <th>Nr</th>
                    <th>Nazwa</th>
                    <th>Opis</th>
                    <th>Wartość</th>
                </tr>
                <?php foreach ($prizes as $index => $prize): ?>
                    <tr>
                        <td><?= $index + 1 ?></td>
                        <td><?= escape($prize['nazwa']) ?></td>
                        <td><?= escape($prize['opis']) ?></td>
                        <td><?= escape(formatPrice($prize['cena'])) ?></td>
                    </tr>
                <?php endforeach; ?>
            </table>
        </main>
        <aside>
            <img src="puchar.png" alt="Puchar dla wolontariusza">
            <h4>Polecane linki</h4>
            <ul>
                <li><a href="kw1.png">Kwerenda1</a></li>
                <li><a href="kw2.png">Kwerenda2</a></li>
                <li><a href="kw3.png">Kwerenda3</a></li>
                <li><a href="kw4.png">Kwerenda4</a></li>
            </ul>
        </aside>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  30. Krok 30: Komunikat, gdy baza nie odpowiada

    ok. 2 min
    Polecenie z arkusza

    Na końcu zamykane jest połączenie z serwerem.

    Nad tabelą akapit z $dbError - tylko gdy catch coś do niego wpisał. Sprawdzone uruchomieniem z błędną nazwą bazy: strona się wyświetla, pod przyciskiem stoi „Nie udało się wylosować nagród…”, tabela ma sam wiersz nagłówkowy, a w treści nie ma nazwy bazy ani kodu błędu SQL. W normalnym działaniu połączenie jest zamykane ($pdo = null) zaraz po zapytaniu, przed pierwszą linią HTML.

    konkurs.php PHP zmienione: 3 linie
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function formatPrice(?string $price): string
    {
        if ($price === null) {
            return 'brak ceny';
        }
        return number_format((float) $price, 2, ',', "\u{a0}") . "\u{a0}zł";
    }
    
    $prizes = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=konkurs;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $prizes = $pdo->query('SELECT nazwa, opis, cena FROM nagrody ORDER BY RAND() LIMIT 5')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się wylosować nagród. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>WOLONTARIAT SZKOLNY</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <header>
            <h1>KONKURS - WOLONTARIAT SZKOLNY</h1>
        </header>
        <main>
            <h3>Konkursowe nagrody</h3>
            <form>
                <button>Losuj nowe nagrody</button>
            </form>
            <?php if ($dbError !== ''): ?>
                <p><?= escape($dbError) ?></p>
            <?php endif; ?>
            <table>
                <tr>
                    <th>Nr</th>
                    <th>Nazwa</th>
                    <th>Opis</th>
                    <th>Wartość</th>
                </tr>
                <?php foreach ($prizes as $index => $prize): ?>
                    <tr>
                        <td><?= $index + 1 ?></td>
                        <td><?= escape($prize['nazwa']) ?></td>
                        <td><?= escape($prize['opis']) ?></td>
                        <td><?= escape(formatPrice($prize['cena'])) ?></td>
                    </tr>
                <?php endforeach; ?>
            </table>
        </main>
        <aside>
            <img src="puchar.png" alt="Puchar dla wolontariusza">
            <h4>Polecane linki</h4>
            <ul>
                <li><a href="kw1.png">Kwerenda1</a></li>
                <li><a href="kw2.png">Kwerenda2</a></li>
                <li><a href="kw3.png">Kwerenda3</a></li>
                <li><a href="kw4.png">Kwerenda4</a></li>
            </ul>
        </aside>
        <footer>
            <p>Numer zdającego: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (konkurs.php)
  31. Etap 5: Porównanie z wersją podstawową

    Krok 31 · po tym etapie masz 37 z 37 kryteriów (pkt)

    Krok 31: Co jest inaczej i którą wersję wybrać na egzaminie

    ok. 2 min

    Obie wersje dają komplet 37 punktów. Różnice:

    wersja podstawowawersja nowoczesna
    układfloat: left sekcji + clear: both w stopcedisplay: grid i grid-template-areas na <body>
    szerokości blokówwidth: 79% i width: 20%grid-template-columns: 79% 20%
    kolorywpisane w każdej regulezmienne CSS w :root
    bloki bocznedwie <section> z id<main> i <aside>, selektory znaczników
    zapytanie 4LIKE '2021-08-%'przedział dat >= ... AND < ...
    bazamysqli_connect, mysqli_fetch_row, $row[0]PDO z wyjątkami, fetchAll, $prize['nazwa']
    numer wierszalicznik $number++$index + 1
    cenaz bazy (1230.00; w dobrych praktykach number_format)formatPrice(): „1 230,00 zł” albo „brak ceny”
    awaria bazybiała strona z błędemkomunikat nad tabelą, szczegóły w error_log

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i jest szybsza do napisania. Nowoczesna oddziela pobieranie danych od wyglądu i lepiej znosi dane, które odbiegają od normy (pusta cena, awaria bazy). Czego tu świadomie nie ma: <thead>/<tbody> (zmieniłyby liczenie parzystych wierszy) i pliku config.php z danymi logowania (klucz szuka połączenia w kodzie strony).

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

  32. Etap 6: Oddanie pracy

    Krok 32 · po tym etapie masz 37 z 37 kryteriów (pkt)

    Krok 32: 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. Umieść go w folderze z numerem zdającego.

    W przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana przez http://localhost/.... W folderze: konkurs.php, styles.css, kwerendy.txt, import.png, kw1.png - kw4.png, puchar.png i przeglądarka.txt. Nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.