Przejdź do głównej treści
INF.03-05-23.01-SGStyczeń 2023 · 32 kroki · ok. 66 min · 34 kryteria z klucza

Rozwiązanie krok po kroku: Internetowy kalendarz sierpniowy (Nowoczesny kod (grid, flex, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza kalendarz z czterema zapytaniami, logo 120 × 120 px i strona organizer.php z formularzem zapisującym wydarzenie i kalendarzem sierpnia - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • jeden <header> z siatką zamiast trzech pływających banerów: szerokości 40%, 40% i 20% opisuje reguła grid-template-columns,
  • bloki kalendarza we flexboksie z zawijaniem - układają się w rzędy jak przy float, ale stopka nie potrzebuje clear; kolory w zmiennych CSS,
  • PHP na PDO: cały kod rozmawiający z bazą stoi na górze pliku organizer.php - najpierw zapis z formularza, potem odczyt kalendarza,
  • zapis zapytaniem przygotowanym (wpis z apostrofem, np. „Urodziny Ani's”, nie psuje zapytania) i przekierowanie po zapisie (wzorzec PRG): odświeżenie strony po wysłaniu nie wysyła formularza drugi raz,
  • bezpieczne wypisywanie wpisów (htmlspecialchars w funkcji e()) i komunikat, gdy bazy nie da się odczytać.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i logo pochodzą z prawdziwego archiwum zad5, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 - od kroków formularza po wysłaniu wpisu „zakupy”, jak na obrazie 2.

Jak liczą się punkty. Klucz ma 34 kryteria (R.1.1 - R.5.7), każde za 1 punkt. Próg zdania to 75%, czyli 26 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 34 kryteriów (pkt)

    Krok 1: Baza danych kalendarz

    ok. 2 min
    Polecenie z arkusza

    Utwórz bazę danych o nazwie kalendarz

    Rozpakuj zad5.zip z pulpitu (hasło kaL(end)arz% - z nawiasami w środku i procentem na końcu) do folderu z numerem zdającego: baza5.sql, logo1.png i logo2.png. W phpMyAdmin Nowa → kalendarz albo zapytaniem:

    CREATE DATABASE kalendarz CHARACTER SET utf8mb4 COLLATE utf8mb4_polish_ci;
    

    Arkusz nie podaje porównywania. Tabela z baza5.sql ma kodowanie zapisane we własnej definicji (latin1), więc ustawienie bazy jej nie zmienia - wraca to przy zapisie z formularza.

    Typowe błędy

    Nazwa bazy musi brzmieć dokładnie kalendarz (nie baza5) - skrypt łączy się z bazą po nazwie (R.5.1).

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

    ok. 2 min
    Polecenie z arkusza

    Do bazy kalendarz zaimportuj tabele z pliku baza5.sql z rozpakowanego archiwum

    Zaznaczona baza kalendarz, Import → baza5.sql → Wykonaj. Tabela zadania z obrazu 1: 63 dni - lipiec i sierpień 2020 oraz jeden dzień listopada. Pole miesiac ma nazwę bez polskich liter (sierpien), a dni bez zadań mają w polu wpis pusty tekst.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonano import tabel do bazy danych kalendarz czynność udokumentowano plikiem o nazwie import w formacie PNG

    ok. 2 min
    Polecenie z arkusza

    Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie PNG pod nazwą import. Nie kadruj zrzutu. Zrzut powinien 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ń, w PNG, pod nazwą import. Zrzuty zapytań kw1 - kw4 będą już w JPEG.

    Typowe błędy

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

  4. Krok 4: Zapytanie 1: zadania z sierpnia

    Kryteria: ,

    R.1.2 Zapisano plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania

    R.1.3 Utworzono zapytanie 1 wybierające jedynie pola dataZadania i wpis dla zadań z sierpnia. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT dataZadania, wpis FROM zadania WHERE miesiac = 'sierpien'; (poprawne też: dataZadania LIKE '____-08-__' lub użycie %) oraz wynik działania udokumentowano zrzutem, na którym widoczny jest w komunikacie zapis o liczbie rekordów "(31 ogółem...)", rekordy są jedynie z sierpnia, jedynie pola dataZadania i wpis. Na zrzucie nie muszą być widoczne wszystkie zwrócone zapytaniem wiersze

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pola dataZadania i wpis dla zadań, z miesiąca sierpnia

    WHERE miesiac = 'sierpien' - w bazie bez polskich liter. ORDER BY dataZadania ustala kolejność dni: bez sortowania serwer może oddać wiersze w dowolnej kolejności, a kalendarz musi iść od 1 do 31 sierpnia. Wynik: 31 wierszy („31 ogółem”), od 2020-08-01 Remont. To samo zapytanie wyśle Skrypt 1. Zapytanie do kwerendy.txt, zrzut kw1.jpg.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT dataZadania, wpis
    FROM zadania
    WHERE miesiac = 'sierpien'
    ORDER BY dataZadania;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: miesiąc i rok dla 7 sierpnia 2020

    Kryteria:

    R.1.4 Utworzono zapytanie 2 wybierające jedynie pola miesiac i rok dla zadania z dnia 7 sierpnia 2020 r. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08- 07'; oraz wynik działania udokumentowano zrzutem, na którym widoczne jest: sierpien, 2020

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pola miesiac i rok dla zadania z dnia 7 sierpnia 2020 r.

    Data w SQL w postaci rok-miesiąc-dzień, w apostrofach. Wynik: sierpien, 2020 - jak w kluczu. Zrzut: kw2.jpg.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT dataZadania, wpis
    FROM zadania
    WHERE miesiac = 'sierpien'
    ORDER BY dataZadania;
    
    -- Zapytanie 2
    SELECT miesiac, rok
    FROM zadania
    WHERE dataZadania = '2020-08-07';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: wizyty u weterynarza w każdym miesiącu

    Kryteria:

    R.1.5 Utworzono zapytanie 3 wybierające jedynie nazwę miesiąca oraz w kolumnie wpis zliczające dla każdego miesiąca wpisy rozpoczynające się od słowa „Weterynarz”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT miesiac, COUNT(*) FROM zadania WHERE wpis LIKE 'Weterynarz%' GROUP BY miesiac; (zamiast * może być dowolne pole) oraz wynik działania udokumentowano zrzutem, na którym widoczne jest jedynie: lipiec, 2; sierpien, 2

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie nazwę miesiąca oraz liczące dla każdego miesiąca ile jest wpisów rozpoczynających się od słowa „Weterynarz” w kolumnie wpis

    • LIKE 'Weterynarz%' - % tylko na końcu wzorca znaczy „zaczyna się od”,
    • GROUP BY miesiac - liczenie osobno dla każdego miesiąca; COUNT(id) z aliasem wizyty - kolumna wyniku ma nazwę zamiast COUNT(*) (klucz dopuszcza dowolne pole),
    • ORDER BY miesiac - stała kolejność grup.

    Wynik: lipiec 2 i sierpien 2 - jak w kluczu. Zrzut: kw3.jpg.

    kwerendy.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT dataZadania, wpis
    FROM zadania
    WHERE miesiac = 'sierpien'
    ORDER BY dataZadania;
    
    -- Zapytanie 2
    SELECT miesiac, rok
    FROM zadania
    WHERE dataZadania = '2020-08-07';
    
    -- Zapytanie 3
    SELECT miesiac, COUNT(id) AS wizyty
    FROM zadania
    WHERE wpis LIKE 'Weterynarz%'
    GROUP BY miesiac
    ORDER BY miesiac;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: nowy wpis na 9 sierpnia 2020

    Kryteria:

    R.1.6 Utworzono zapytanie 4 aktualizujące pole wpis dla zadania z datą 2020-08-09, nowy wpis to „Wycieczka: Zalew Mietkowski”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: UPDATE zadania SET wpis='Wycieczka: Zalew Mietkowski' WHERE dataZadania="2020-08-09"; oraz wynik działania udokumentowano zrzutem, na którym widoczny jest komunikat o poprawnej aktualizacji lub tabela ze zaktualizowanym wpisem

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: aktualizujące pole wpis dla zadania z datą 9 sierpnia 2020, nowy wpis to „Wycieczka: Zalew Mietkowski”

    UPDATE z warunkiem na dacie - zmienia się tylko jeden rekord (bez WHERE zmieniłyby się wszystkie 63). Zrzut kw4.jpg: komunikat o jednym zmienionym wierszu i rekord po zmianie. To zapytanie wróci w skrypcie formularza - z parametrem w miejscu tekstu.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT dataZadania, wpis
    FROM zadania
    WHERE miesiac = 'sierpien'
    ORDER BY dataZadania;
    
    -- Zapytanie 2
    SELECT miesiac, rok
    FROM zadania
    WHERE dataZadania = '2020-08-07';
    
    -- Zapytanie 3
    SELECT miesiac, COUNT(id) AS wizyty
    FROM zadania
    WHERE wpis LIKE 'Weterynarz%'
    GROUP BY miesiac
    ORDER BY miesiac;
    
    -- Zapytanie 4
    UPDATE zadania
    SET wpis = 'Wycieczka: Zalew Mietkowski'
    WHERE dataZadania = '2020-08-09';
    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 17 z 34 kryteriów (pkt)

    Krok 8: Logo 120 × 120 px

    Kryteria:

    R.2.1 Plik logo2.png przeskalowano do wymiarów 120 px na 120 px (± 2 px)

    ok. 2 min
    Polecenie z arkusza

    Plik logo2.png, wypakowany z archiwum, należy przeskalować dokładnie do wymiarów 120 px na 120 px

    Grafika jest taka sama jak w wersji podstawowej: w GIMP-ie Obraz → Skaluj obraz z 300 × 300 do 120 × 120 i Nadpisz logo2.png - PNG zachowuje przezroczyste tło.

    Typowe błędy

    Sam atrybut width w HTML-u nie zmienia pliku - klucz sprawdza wymiary pliku logo2.png.

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

    Kryteria:

    R.2.2 Witrynę internetową zapisano w pliku organizer.php oraz zapisano jawnie standard kodowania polskich znaków i deklarację HTML5 <!DOCTYPE HTML> i zadeklarowano język witryny: polski np. <html lang="pl">

    ok. 2 min
    Polecenie z arkusza

    Składa się ze strony o nazwie organizer.php. Zapisana w języku HTML 5. Jawnie zastosowany właściwy standard kodowania polskich znaków. Zadeklarowany język witryny: polski.

    <!DOCTYPE html>, <html lang="pl"> i <meta charset="UTF-8"> jako pierwszy element <head> (R.2.2). Rozszerzenie .php - strona działa przez http://localhost/....

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

    Kryteria:

    R.2.3 Nadano tytuł stronie: "Sierpniowy kalendarz"

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Sierpniowy kalendarz”

    <title> (R.2.3) i viewport - telefon nie pomniejsza strony do szerokości ekranu komputera.

    organizer.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>Sierpniowy kalendarz</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  11. Krok 11: Podpięcie arkusza stylów

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Arkusz stylów w pliku o nazwie styl5.css prawidłowo połączony z kodem strony

    Nazwa z cyfrą 5: styl5.css. Całe formatowanie pochodzi z tego pliku (R.3.4).

    Typowe błędy

    Klucz odbiera R.3.4 także wtedy, gdy choć jeden selektor nie trafia w blok (np. kropka zamiast #).

    organizer.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>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  12. Krok 12: Bloki: baner z trzech części, blok główny, stopka

    Kryteria:

    R.2.4 Układ strony zdefiniowano dzieląc ją na: 3 bloki banera, blok główny i stopkę. Zastosowano znaczniki sekcji

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: trzy bloki banera, poniżej blok główny, w którym znajdują się generowane skryptem bloki kalendarza, na dole blok stopki. Podział zrealizowany za pomocą znaczników sekcji tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z Obrazem 2

    Baner strony jest jeden, więc to jeden <header>, a w nim trzy bloki banera - trzy <section> z identyfikatorami mówiącymi, co zawierają: title, add-event, logo. <header> będzie dla nich kontenerem nadrzędnym - klucz przy R.4.4 wprost przewiduje takie rozwiązanie. Blok główny to <main> (bloki kalendarza wygeneruje w nim skrypt), stopka - <footer> (R.2.4).

    organizer.php PHP zmienione: 12 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
            </section>
            <section id="add-event">
            </section>
            <section id="logo">
            </section>
        </header>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  13. Krok 13: Pierwszy blok banera: nagłówek

    ok. 1 min
    Polecenie z arkusza

    Zawartość pierwszego bloku banera: nagłówek pierwszego stopnia o treści „Organizer: SIERPIEŃ”

    <h1> z napisem dokładnie jak w arkuszu - „SIERPIEŃ” wielkimi literami, z literą Ń.

    organizer.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>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
            </section>
            <section id="logo">
            </section>
        </header>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  14. Krok 14: Drugi blok banera: formularz POST do tej samej strony

    Kryteria: , , ,

    R.2.6 W drugim bloku banera umieszczono formularz, zastosowano znacznik <form>

    R.2.7 Na stronie umieszczono pole do wprowadzania danych oraz przycisk "OK"

    R.3.2 Przycisk ma funkcję wysyłania danych (input lub button z typem submit lub button bez typu)

    R.3.3 Zdefiniowano dla formularza metodę POST

    ok. 3 min
    Polecenie z arkusza

    Zawartość drugiego bloku banera: formularz wysyłający dane metodą bezpieczną do tej samej strony, zawierający: Pole edycyjne podpisane: „Zapisz wydarzenie: ”, Przycisk o treści „OK” wysyłający zawartość formularza

    • method="post" - „metodę bezpieczną” arkusz rozumie jako POST, a klucz (R.3.3) wymaga POST; tu to także właściwy wybór, bo formularz zmienia dane w bazie. Bez atrybutu action formularz wysyła dane do tej samej strony,
    • <label for="event"> - podpis połączony z polem; name="event" - nazwa, pod którą tekst trafi do $_POST; required - przeglądarka nie wyśle pustego pola, więc kliknięcie OK bez tekstu nie skasuje wpisu,
    • <button type="submit">OK</button> wysyła formularz (R.2.6, R.2.7, R.3.2, R.3.3).
    Typowe błędy

    Formularz bez method wysyła dane metodą GET - klucz nie uzna R.3.3, a skrypt czytający $_POST nic nie dostanie.

    organizer.php PHP zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
                <form method="post">
                    <label for="event">Zapisz wydarzenie: </label>
                    <input type="text" id="event" name="event" required>
                    <button type="submit">OK</button>
                </form>
            </section>
            <section id="logo">
            </section>
        </header>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  15. Krok 15: Trzeci blok banera: logo

    Kryteria:

    R.2.8 W trzecim bloku banera umieszczono obraz logo2.png wraz z tekstem alternatywnym "sierpień"

    ok. 1 min
    Polecenie z arkusza

    Zawartość trzeciego bloku banera: logo2.png z tekstem alternatywnym o treści: „sierpień”

    <img> z przeskalowanym plikiem i tekstem alternatywnym z arkusza (R.2.8).

    organizer.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>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
                <form method="post">
                    <label for="event">Zapisz wydarzenie: </label>
                    <input type="text" id="event" name="event" required>
                    <button type="submit">OK</button>
                </form>
            </section>
            <section id="logo">
                <img src="logo2.png" alt="sierpień">
            </section>
        </header>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  16. Krok 16: Stopka

    Kryteria:

    R.2.5 W banerze pierwszym zapisano nagłówek h1: "Organizer: SIERPIEŃ" oraz w stopce zapisano paragraf

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: Paragraf (akapit) o treści: „Stronę wykonał: ”, dalej wstawiony numer zdającego

    Akapit w <footer>. Na egzaminie wpisujesz swój numer zdającego (PESEL), tu stoi autor rozwiązania. Z nagłówkiem <h1> to R.2.5. Blok główny wypełni Skrypt 1.

    organizer.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>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
                <form method="post">
                    <label for="event">Zapisz wydarzenie: </label>
                    <input type="text" id="event" name="event" required>
                    <button type="submit">OK</button>
                </form>
            </section>
            <section id="logo">
                <img src="logo2.png" alt="sierpień">
            </section>
        </header>
        <main>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  17. Etap 3: Styl CSS

    Kroki 17-22 · po tym etapie masz 27 z 34 kryteriów (pkt)

    Krok 17: Plik styl5.css: zmienne i krój czcionki

    Kryteria:

    R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (*): krój czcionki Tahoma

    ok. 2 min
    Polecenie z arkusza

    Ustawione domyślne wartości dla wszystkich selektorów stylu CSS: krój czcionki Tahoma

    #7DA453 pojawia się w arkuszu cztery razy (banery, stopka, ramka dnia, dzień pod kursorem), więc trafia do zmiennej --green. Selektor * z Tahomą i rodziną zapasową sans-serif (R.4.1).

    styl5.css CSS nowy plik
    :root {
        --green: #7DA453;
        --day: #AED581;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    Podgląd strony po tym kroku (organizer.php)
  18. Krok 18: Bloki banera: siatka 40% / 40% / 20%

    Kryteria: ,

    R.4.4 Ustawiono szerokość bloków banera: 40% pierwszy i drugi oraz 20% trzeci (width lub grid- template-columns lub flex) poprawne również rozwiązanie bez szerokości dla trzeciego bloku jeśli bloki wstawiono w kontener nadrzędny

    R.4.5 Ustawiono wysokość 120px dla trzech bloków banera

    ok. 2 min
    Polecenie z arkusza

    Dla wszystkich bloków banera: kolor tła #7DA453, biały kolor czcionki, wysokość 120 px. Dodatkowo pierwszy i drugi blok banera ma szerokość 40%, trzeci 20%

    • display: grid na <header> i grid-template-columns: 40% 40% 20% - trzy kolumny o szerokościach z arkusza; bloki banera nie mają własnej szerokości (klucz przy R.4.4 przyjmuje szerokości w grid-template-columns),
    • header > section - selektor dziecka: trafia w trzy bloki banera i tylko w nie, bez klasy w HTML-u. Kolor, biała czcionka i wysokość 120 px (tyle ma logo).
    Typowe błędy

    width: 40% dopisane jeszcze na blokach liczy się w siatce od kolumny - blok miałby 40% z 40% strony.

    styl5.css CSS zmienione: 11 linii
    :root {
        --green: #7DA453;
        --day: #AED581;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    header {
        display: grid;
        grid-template-columns: 40% 40% 20%;
    }
    
    header > section {
        background-color: var(--green);
        color: white;
        height: 120px;
    }
    Podgląd strony po tym kroku (organizer.php)
  19. Krok 19: Bloki kalendarza: flexbox z zawijaniem

    Kryteria: ,

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu (Uwaga! Blok główny nie musi zawierać bloków kalendarza; prawidłowo zastosowano właściwości CSS układające bloki na stronie, np. float i clear albo display flex albo grid)

    R.4.8 Ustawiono dla bloku kalendarza: kolor tła #AED581, szerokość 200px, wysokość 100px, marginesy zewnętrzne (margin) 7px, obramowanie 1px solid #7DA453 (np. za pomocą klasy. Użycie id jest błędem)

    ok. 3 min
    Polecenie z arkusza

    Dla bloku kalendarza: kolor tła #AED581, szerokość 200 px, wysokość 100 px, marginesy zewnętrzne 7 px, obramowanie o szerokości 1 px, linią ciągłą o kolorze #7DA453. Podział zrealizowany za pomocą znaczników sekcji tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z Obrazem 2

    • main { display: flex; flex-wrap: wrap; } - bloki kalendarza stają w rzędzie, a gdy się nie mieszczą, przechodzą do następnego. To typowy układ „kafelków” o stałej szerokości; siatka byłaby tu potrzebna dopiero przy kolumnach dopasowanych do okna,
    • klasa .day - bloków będzie 31, a klucz przy R.4.8 pisze wprost, że identyfikator jest tu błędem. Wymiary, margines i ramka z arkusza; ramka w kolorze ze zmiennej.

    Stopka stoi pod <main> sama z siebie - bez clear (R.3.1).

    styl5.css CSS zmienione: 13 linii
    :root {
        --green: #7DA453;
        --day: #AED581;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    header {
        display: grid;
        grid-template-columns: 40% 40% 20%;
    }
    
    header > section {
        background-color: var(--green);
        color: white;
        height: 120px;
    }
    
    main {
        display: flex;
        flex-wrap: wrap;
    }
    
    .day {
        background-color: var(--day);
        width: 200px;
        height: 100px;
        margin: 7px;
        border: 1px solid var(--green);
    }
    Podgląd strony po tym kroku (organizer.php)
  20. Krok 20: Blok kalendarza pod kursorem

    Kryteria:

    R.4.9 Gdy kursor znajdzie się na bloku kalendarza, kolor tła bloku zmienia się na #7DA453, czcionki na biały

    ok. 1 min
    Polecenie z arkusza

    Gdy kursor znajdzie się na bloku kalendarza, kolor tła bloku zmienia się na #7DA453, czcionki na biały

    Pseudoklasa :hover - na obrazie 2 kursor stoi na bloku z 9 sierpnia (R.4.9).

    styl5.css CSS zmienione: 5 linii
    :root {
        --green: #7DA453;
        --day: #AED581;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    header {
        display: grid;
        grid-template-columns: 40% 40% 20%;
    }
    
    header > section {
        background-color: var(--green);
        color: white;
        height: 120px;
    }
    
    main {
        display: flex;
        flex-wrap: wrap;
    }
    
    .day {
        background-color: var(--day);
        width: 200px;
        height: 100px;
        margin: 7px;
        border: 1px solid var(--green);
    }
    
    .day:hover {
        background-color: var(--green);
        color: white;
    }
    Podgląd strony po tym kroku (organizer.php)
  21. Krok 21: Stopka

    Kryteria: ,

    R.4.2 Ustawiono kolor tła #7DA453 dla trzech bloków banera i stopki

    R.4.3 Ustawiono biały kolor czcionki dla trzech bloków banera i stopki

    ok. 1 min
    Polecenie z arkusza

    Dla stopki: kolor tła #7DA453, biały kolor czcionki, marginesy wewnętrzne 5 px

    Kolor tła i czcionki mają już trzy bloki banera i stopka (R.4.2, R.4.3).

    styl5.css CSS zmienione: 6 linii
    :root {
        --green: #7DA453;
        --day: #AED581;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    header {
        display: grid;
        grid-template-columns: 40% 40% 20%;
    }
    
    header > section {
        background-color: var(--green);
        color: white;
        height: 120px;
    }
    
    main {
        display: flex;
        flex-wrap: wrap;
    }
    
    .day {
        background-color: var(--day);
        width: 200px;
        height: 100px;
        margin: 7px;
        border: 1px solid var(--green);
    }
    
    .day:hover {
        background-color: var(--green);
        color: white;
    }
    
    footer {
        background-color: var(--green);
        color: white;
        padding: 5px;
    }
    Podgląd strony po tym kroku (organizer.php)
  22. Krok 22: Nagłówek piątego stopnia i formularz

    Kryteria: ,

    R.4.6 Ustawiono marginesy wewnętrzne (padding) 5px dla stopki oraz wewnętrzny górny (padding-top) 50px dla selektora form

    R.4.7 Ustawiono wyrównanie tekstu do środka dla selektora h5

    ok. 1 min
    Polecenie z arkusza

    Dla selektora nagłówka piątego stopnia: wyrównanie tekstu do środka. Dla selektora formularza: margines wewnętrzny górny: 50 px

    Selektory znaczników h5 i form - uwaga pod listą stylów wyklucza tu klasy, a klucz (R.4.6, R.4.7) uznaje tylko taki zapis. padding-top zsuwa formularz mniej więcej na środek wysokości banera, jak na obrazie 2.

    Typowe błędy

    margin-top zamiast padding-top przy form - klucz uznaje tylko margines wewnętrzny.

    styl5.css CSS zmienione: 8 linii
    :root {
        --green: #7DA453;
        --day: #AED581;
    }
    
    * {
        font-family: Tahoma, sans-serif;
    }
    
    header {
        display: grid;
        grid-template-columns: 40% 40% 20%;
    }
    
    header > section {
        background-color: var(--green);
        color: white;
        height: 120px;
    }
    
    main {
        display: flex;
        flex-wrap: wrap;
    }
    
    .day {
        background-color: var(--day);
        width: 200px;
        height: 100px;
        margin: 7px;
        border: 1px solid var(--green);
    }
    
    .day:hover {
        background-color: var(--green);
        color: white;
    }
    
    footer {
        background-color: var(--green);
        color: white;
        padding: 5px;
    }
    
    h5 {
        text-align: center;
    }
    
    form {
        padding-top: 50px;
    }
    Podgląd strony po tym kroku (organizer.php)
  23. Etap 4: Skrypty PHP

    Kroki 23-30 · po tym etapie masz 34 z 34 kryteriów (pkt)

    Krok 23: Połączenie z bazą na górze pliku i zamknięcie

    Kryteria: ,

    R.5.1 Skrypt zawiera działające połączenie z serwerem bazy danych i wybór bazy kalendarz

    R.5.2 Ostatnią operacją na bazie jest jej zamknięcie (zgodne ze składnią)

    ok. 4 min
    Polecenie z arkusza

    Napisany w języku PHP, w pliku organizer.php. Skrypt łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie kalendarz. Na końcu działania skrypt zamyka połączenie z serwerem.

    Oba skrypty z arkusza - zapis z formularza i Skrypt 1 rysujący kalendarz - stoją w jednym bloku PHP na górze organizer.php, przed HTML-em. Kolejność jest ta sama co w wersji podstawowej: najpierw zapis, potem odczyt, więc nowy wpis jest od razu w kalendarzu.

    • new PDO(...) - serwer localhost, baza kalendarz, użytkownik root bez hasła (R.5.1); ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze z nazwami kolumn,
    • $pdo = null - PDO zamyka połączenie, gdy znika odwołanie do obiektu; zostanie ostatnią operacją na bazie (R.5.2),
    • try / catch (PDOException ...) - przy awarii bazy strona pokaże zwykłe zdanie, a nie szczegóły serwera.

    PDO zamiast mysqli z tabeli 1 - klucz przyjmuje obie biblioteki (uwaga przy R.5). Od tego kroku podgląd to wynik prawdziwego uruchomienia strony.

    organizer.php PHP zmienione: 12 linii
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kalendarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $databaseError = 'Nie udało się połączyć z kalendarzem. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
                <form method="post">
                    <label for="event">Zapisz wydarzenie: </label>
                    <input type="text" id="event" name="event" required>
                    <button type="submit">OK</button>
                </form>
            </section>
            <section id="logo">
                <img src="logo2.png" alt="sierpień">
            </section>
        </header>
        <main>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  24. Krok 24: Formularz: tekst z pola edycyjnego

    Kryteria:

    R.5.6 W skrypcie obsługującym formularz zastosowano tablicę $_POST, zapis jest poprawny składniowo i logicznie

    ok. 2 min
    Polecenie z arkusza

    Działanie skryptu obsługującego formularz: Skrypt pobiera dane z pola edycyjnego

    Tekst jest w tablicy $_POST pod nazwą z atrybutu name (R.5.6). Przy pierwszym wejściu na stronę formularz nie był wysłany i klucza event nie ma: operator ?? podstawia wtedy pusty tekst - bez ostrzeżenia „Undefined array key”. trim obcina spacje z brzegów, więc wpis z samych spacji też będzie pusty. Od tego kroku podgląd to strona po wysłaniu formularza z wpisem „zakupy”.

    organizer.php PHP zmienione: 3 linie
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kalendarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // skrypt obsługujący formularz
        $event = trim($_POST['event'] ?? '');
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $databaseError = 'Nie udało się połączyć z kalendarzem. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
                <form method="post">
                    <label for="event">Zapisz wydarzenie: </label>
                    <input type="text" id="event" name="event" required>
                    <button type="submit">OK</button>
                </form>
            </section>
            <section id="logo">
                <img src="logo2.png" alt="sierpień">
            </section>
        </header>
        <main>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  25. Krok 25: Formularz: zmodyfikowane zapytanie 4

    Kryteria:

    R.5.7 Skrypt obsługujący formularz uaktualnia dane w bazie danych. Dane pobrane z formularza są wpisane do rekordu o dacie 2020-08-09. Należy sprawdzić w phpMyAdmin czy po wciśnięciu przycisku baza została uaktualniona, nie jest wymagane uaktualnienie na stronie

    ok. 3 min
    Polecenie z arkusza

    Następnie za pomocą zmodyfikowanego zapytania 4 uaktualnia dane w bazie danych tak, że dane w polu wpis zostały pobrane z formularza. Aby uprościć zadanie skrypt wprowadza wpis zawsze do rekordu o dacie 2020-08-09.

    Zapytanie przygotowane: w zapytaniu 4 w miejscu tekstu stoi parametr :entry, a data - parametr :day; wartości podaje execute([...]). Baza dostaje treść zapytania i dane osobno, więc wpis nigdy nie stanie się kodem SQL.

    Zapis wykonuje się tylko dla niepustego tekstu (if ($event !== '')). Sprawdzone na bazie: „zakupy” trafia do rekordu z 9 sierpnia - to sprawdza egzaminator w phpMyAdmin (R.5.7); „Urodziny Ani's” z apostrofem też zapisuje się poprawnie, a same spacje niczego nie zmieniają.

    Typowe błędy

    Tabela zadania z pliku baza5.sql ma kodowanie latin1, w którym nie ma liter „ś”, „ł”, „ą”: wpis „Śniadanie w Łodzi” zapisuje się jako „?niadanie w ?odzi” (sprawdzone). Zadanie tego nie ocenia; zmianę kodowania tabeli pokazuje wersja podstawowa.

    organizer.php PHP zmienione: 6 linii
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kalendarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // skrypt obsługujący formularz
        $event = trim($_POST['event'] ?? '');
        if ($event !== '') {
            // zmodyfikowane zapytanie 4 - wpis z formularza zawsze do dnia 2020-08-09
            $statement = $pdo->prepare('UPDATE zadania SET wpis = :entry WHERE dataZadania = :day');
            $statement->execute(['entry' => $event, 'day' => '2020-08-09']);
            $statement = null;
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $databaseError = 'Nie udało się połączyć z kalendarzem. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
                <form method="post">
                    <label for="event">Zapisz wydarzenie: </label>
                    <input type="text" id="event" name="event" required>
                    <button type="submit">OK</button>
                </form>
            </section>
            <section id="logo">
                <img src="logo2.png" alt="sierpień">
            </section>
        </header>
        <main>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  26. Krok 26: Przekierowanie po zapisie (PRG)

    ok. 3 min

    Dodatek spoza arkusza - klucz go nie ocenia (R.5.7 sprawdza tylko bazę). Strona, która odpowiada na formularz POST bezpośrednio, ma wadę: odświeżenie (F5) każe przeglądarce wysłać formularz jeszcze raz - pojawia się pytanie o ponowne przesłanie danych, a zapis wykonuje się drugi raz.

    Wzorzec PRG (Post/Redirect/Get): po zapisie skrypt zamyka połączenie i odsyła kod 303 z nagłówkiem Location: organizer.php - przeglądarka sama otwiera stronę zwykłym żądaniem GET i dopiero ono rysuje kalendarz. exit jest konieczne: header() tylko ustawia nagłówek, a reszta pliku wykonywałaby się dalej. Odświeżenie powtarza już tylko GET.

    header() działa, bo blok PHP stoi przed <!DOCTYPE html> - przed nim nie poszedł do przeglądarki żaden znak. Sprawdzone na prawdziwym PHP: odpowiedź na formularz to 303 na organizer.php, a baza ma wpis „zakupy”.

    Typowe błędy

    header() po wypisaniu choćby spacji przed <?php kończy się ostrzeżeniem „headers already sent” i przekierowanie nie działa.

    organizer.php PHP zmienione: 3 linie
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kalendarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // skrypt obsługujący formularz
        $event = trim($_POST['event'] ?? '');
        if ($event !== '') {
            // zmodyfikowane zapytanie 4 - wpis z formularza zawsze do dnia 2020-08-09
            $statement = $pdo->prepare('UPDATE zadania SET wpis = :entry WHERE dataZadania = :day');
            $statement->execute(['entry' => $event, 'day' => '2020-08-09']);
            $statement = null;
            $pdo = null;
            header('Location: organizer.php', true, 303);    // PRG: po zapisie zwykłe wejście na stronę
            exit;
        }
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $databaseError = 'Nie udało się połączyć z kalendarzem. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
                <form method="post">
                    <label for="event">Zapisz wydarzenie: </label>
                    <input type="text" id="event" name="event" required>
                    <button type="submit">OK</button>
                </form>
            </section>
            <section id="logo">
                <img src="logo2.png" alt="sierpień">
            </section>
        </header>
        <main>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  27. Krok 27: Skrypt 1: zapytanie 1

    Kryteria:

    R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 1 lub skrypt obsługujący formularz wysyła zmodyfikowane zapytanie 4

    ok. 2 min
    Polecenie z arkusza

    Działanie skryptu 1: Wysyła do bazy danych zapytanie 1

    Zapytanie bez danych od użytkownika, więc wystarczy query; fetchAll() zwraca listę 31 dni w kolejności dat. Stoi po zapisie z formularza i przed $pdo = null, więc odczytuje już nowy wpis, a zamknięcie zostaje ostatnią operacją. W catch lista dostaje wartość pustą (R.5.3). Kalendarza jeszcze nie widać.

    organizer.php PHP zmienione: 4 linie
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kalendarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // skrypt obsługujący formularz
        $event = trim($_POST['event'] ?? '');
        if ($event !== '') {
            // zmodyfikowane zapytanie 4 - wpis z formularza zawsze do dnia 2020-08-09
            $statement = $pdo->prepare('UPDATE zadania SET wpis = :entry WHERE dataZadania = :day');
            $statement->execute(['entry' => $event, 'day' => '2020-08-09']);
            $statement = null;
            $pdo = null;
            header('Location: organizer.php', true, 303);    // PRG: po zapisie zwykłe wejście na stronę
            exit;
        }
    
        // Skrypt 1: zapytanie 1 - dni sierpnia
        $days = $pdo->query("SELECT dataZadania, wpis FROM zadania WHERE miesiac = 'sierpien' ORDER BY dataZadania")->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $days = [];
        $databaseError = 'Nie udało się połączyć z kalendarzem. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
                <form method="post">
                    <label for="event">Zapisz wydarzenie: </label>
                    <input type="text" id="event" name="event" required>
                    <button type="submit">OK</button>
                </form>
            </section>
            <section id="logo">
                <img src="logo2.png" alt="sierpień">
            </section>
        </header>
        <main>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  28. Krok 28: Skrypt 1: blok kalendarza z datą dla każdego wiersza

    Kryteria:

    R.5.4 Skrypt 1 wyświetla w bloku głównym bloki zdefiniowane za pomocą znaczników sekcji. Liczba wygenerowanych bloków odpowiada liczbie wierszy zwróconych zapytaniem

    ok. 3 min
    Polecenie z arkusza

    Za pomocą znacznika sekcji definiuje blok, a w nim wyświetla dane przesłane skryptem dotyczące jednego wiersza. Blok jest zgodny z Obrazem 3 oraz jest w nim wyświetlone: Data zadania w nagłówku piątego stopnia. Liczba wygenerowanych bloków odpowiada liczbie wierszy zwróconych zapytaniem

    Pętla foreach po liście dni: jeden obieg to jeden <section class="day"> (R.5.4) - bloków jest tyle, ile wierszy zwróciło zapytanie, czyli 31. Data stoi w <h5>, a w nim w znaczniku <time datetime="..."> - na stronie wygląda tak samo, a przeglądarka i wyszukiwarka wiedzą, że to data. Funkcja e() przepuszcza tekst z bazy przez htmlspecialchars.

    organizer.php PHP zmienione: 10 linii
    <?php
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kalendarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // skrypt obsługujący formularz
        $event = trim($_POST['event'] ?? '');
        if ($event !== '') {
            // zmodyfikowane zapytanie 4 - wpis z formularza zawsze do dnia 2020-08-09
            $statement = $pdo->prepare('UPDATE zadania SET wpis = :entry WHERE dataZadania = :day');
            $statement->execute(['entry' => $event, 'day' => '2020-08-09']);
            $statement = null;
            $pdo = null;
            header('Location: organizer.php', true, 303);    // PRG: po zapisie zwykłe wejście na stronę
            exit;
        }
    
        // Skrypt 1: zapytanie 1 - dni sierpnia
        $days = $pdo->query("SELECT dataZadania, wpis FROM zadania WHERE miesiac = 'sierpien' ORDER BY dataZadania")->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $days = [];
        $databaseError = 'Nie udało się połączyć z kalendarzem. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
                <form method="post">
                    <label for="event">Zapisz wydarzenie: </label>
                    <input type="text" id="event" name="event" required>
                    <button type="submit">OK</button>
                </form>
            </section>
            <section id="logo">
                <img src="logo2.png" alt="sierpień">
            </section>
        </header>
        <main>
    <?php foreach ($days as $day): ?>
            <section class="day">
                <h5><time datetime="<?= e($day['dataZadania']) ?>"><?= e($day['dataZadania']) ?></time></h5>
            </section>
    <?php endforeach; ?>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  29. Krok 29: Skrypt 1: wpis w akapicie

    Kryteria:

    R.5.5 Skrypt 1 wyświetla datę zadania w nagłówku h5 oraz pole wpis w paragrafie

    ok. 2 min
    Polecenie z arkusza

    Pole wpis w paragrafie (akapicie)

    Wpis w <p> pod datą - blok wygląda jak na obrazie 3 (R.5.5). Dni bez zadań mają w bazie pusty tekst, więc ich akapit jest pusty.

    Wpis pochodzi z formularza, więc e() jest tu konieczne: sprawdzone na prawdziwym PHP - wpis <b>zakupy</b> pokazuje się dosłownie, razem ze znakami, zamiast wykonać się jako znacznik (tak samo zostałby zatrzymany <script>). Podgląd: po wysłaniu „zakupy” i przekierowaniu blok 9 sierpnia ma nowy wpis - jak na obrazie 2.

    organizer.php PHP zmienione: 1 linia
    <?php
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kalendarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // skrypt obsługujący formularz
        $event = trim($_POST['event'] ?? '');
        if ($event !== '') {
            // zmodyfikowane zapytanie 4 - wpis z formularza zawsze do dnia 2020-08-09
            $statement = $pdo->prepare('UPDATE zadania SET wpis = :entry WHERE dataZadania = :day');
            $statement->execute(['entry' => $event, 'day' => '2020-08-09']);
            $statement = null;
            $pdo = null;
            header('Location: organizer.php', true, 303);    // PRG: po zapisie zwykłe wejście na stronę
            exit;
        }
    
        // Skrypt 1: zapytanie 1 - dni sierpnia
        $days = $pdo->query("SELECT dataZadania, wpis FROM zadania WHERE miesiac = 'sierpien' ORDER BY dataZadania")->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $days = [];
        $databaseError = 'Nie udało się połączyć z kalendarzem. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
                <form method="post">
                    <label for="event">Zapisz wydarzenie: </label>
                    <input type="text" id="event" name="event" required>
                    <button type="submit">OK</button>
                </form>
            </section>
            <section id="logo">
                <img src="logo2.png" alt="sierpień">
            </section>
        </header>
        <main>
    <?php foreach ($days as $day): ?>
            <section class="day">
                <h5><time datetime="<?= e($day['dataZadania']) ?>"><?= e($day['dataZadania']) ?></time></h5>
                <p><?= e($day['wpis']) ?></p>
            </section>
    <?php endforeach; ?>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  30. Krok 30: Awaria bazy: komunikat

    ok. 2 min

    Dodatek spoza arkusza - klucz go nie ocenia. Gdy baza nie odpowiada, w bloku głównym pojawia się komunikat z role="alert" (czytnik ekranu przeczyta go od razu), a baner z formularzem i stopka działają dalej, bo lista dni ma w catch wartość pustą. Sprawdzone na prawdziwym PHP ze złym hasłem do bazy - bez ostrzeżeń PHP. Podgląd: zwykły kalendarz.

    organizer.php PHP zmienione: 3 linie
    <?php
    function e(string|int|float|null $value): string
    {
        return htmlspecialchars((string) $value, ENT_QUOTES);
    }
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kalendarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // skrypt obsługujący formularz
        $event = trim($_POST['event'] ?? '');
        if ($event !== '') {
            // zmodyfikowane zapytanie 4 - wpis z formularza zawsze do dnia 2020-08-09
            $statement = $pdo->prepare('UPDATE zadania SET wpis = :entry WHERE dataZadania = :day');
            $statement->execute(['entry' => $event, 'day' => '2020-08-09']);
            $statement = null;
            $pdo = null;
            header('Location: organizer.php', true, 303);    // PRG: po zapisie zwykłe wejście na stronę
            exit;
        }
    
        // Skrypt 1: zapytanie 1 - dni sierpnia
        $days = $pdo->query("SELECT dataZadania, wpis FROM zadania WHERE miesiac = 'sierpien' ORDER BY dataZadania")->fetchAll();
    
        $pdo = null;    // zamknięcie połączenia - ostatnia operacja na bazie
    } catch (PDOException $exception) {
        $days = [];
        $databaseError = 'Nie udało się połączyć z kalendarzem. Spróbuj odświeżyć stronę za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Sierpniowy kalendarz</title>
        <link rel="stylesheet" href="styl5.css">
    </head>
    <body>
        <header>
            <section id="title">
                <h1>Organizer: SIERPIEŃ</h1>
            </section>
            <section id="add-event">
                <form method="post">
                    <label for="event">Zapisz wydarzenie: </label>
                    <input type="text" id="event" name="event" required>
                    <button type="submit">OK</button>
                </form>
            </section>
            <section id="logo">
                <img src="logo2.png" alt="sierpień">
            </section>
        </header>
        <main>
    <?php if (isset($databaseError)): ?>
            <p role="alert"><?= e($databaseError) ?></p>
    <?php endif; ?>
    <?php foreach ($days as $day): ?>
            <section class="day">
                <h5><time datetime="<?= e($day['dataZadania']) ?>"><?= e($day['dataZadania']) ?></time></h5>
                <p><?= e($day['wpis']) ?></p>
            </section>
    <?php endforeach; ?>
        </main>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

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

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

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

    ok. 3 min

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

    wersja podstawowawersja nowoczesna
    banertrzy <header> z float i klasą bannerjeden <header> z siatką 40% / 40% / 20%
    bloki kalendarzafloat: left, stopka z clear<main> z flex-wrap: wrap
    kolorywpisane w regułyzmienne CSS
    bibliotekamysqli (tabela 1 arkusza)PDO z wyjątkami
    gdzie są skryptyformularz w banerze, Skrypt 1 w bloku głównymjeden blok na górze organizer.php
    wpis w zapytaniu 4wklejony do napisu SQLparametr :entry
    wpis z apostrofembłąd i urwana stronazapisuje się
    po zapisieodpowiedź na POST, F5 wysyła ponownieprzekierowanie 303 (PRG)
    wypisywanie wpisuwprost z bazye() i <time>

    Oba warianty mają tę samą granicę: tabela zadania jest w latin1, więc polskie litery z formularza giną - poprawkę (zmianę kodowania tabeli) pokazuje wersja podstawowa, poza zadaniem egzaminacyjnym.

    Dane logowania do bazy w prawdziwym projekcie trzyma się zwykle w osobnym pliku (np. config.php), ale arkusz każe napisać skrypt w pliku organizer.php - tu cały kod PHP zostaje w nim.

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i łatwiej ją napisać bez dokumentacji. Nowoczesna zapisuje każdy tekst bezpiecznie i nie powtarza zapisu po odświeżeniu. Obie biblioteki klucz przyjmuje.

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

  32. Etap 6: Oddanie pracy

    Krok 32 · po tym etapie masz 34 z 34 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. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Nazwij plik przeglądarka.txt i zapisz go w folderze z numerem zdającego. Nagraj płytę z rezultatami pracy. W folderze powinny się znajdować następujące pliki: import.png, kw1.jpg, kw2.jpg, kw3.jpg, kw4.jpg, kwerendy.txt, logo2.png, organizer.php, przeglądarka.txt, styl5.css, ewentualnie inne przygotowane pliki.

    W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była strona przez http://localhost/.... 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 29.

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.