Przejdź do głównej treści
INF.03-04-24.01-SGStyczeń 2024 · 35 kroków · ok. 73 min · 33 kryteria z klucza

Rozwiązanie krok po kroku: Kalendarz z bazą danych (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza: baza terminarz z czterema zapytaniami, logo przeskalowane do 140 px, strona kalendarz.php z dwoma blokami banera i kalendarzem lipca, style w styl6.css i strona sierpien.html. Obie wersje spełniają wszystkie 33 kryteria klucza - różni je sposób pisania kodu, nie wynik.

Czym ta wersja różni się od podstawowej:

  • SQL czytelnie: każda klauzula w osobnej linii, zapytanie 2 filtruje po dacie (MONTH(dataZadania) = 7) zamiast po polu tekstowym z nazwą miesiąca i sortuje dni (ORDER BY),
  • układ na siatce CSS (display: grid na <body>) zamiast float i clear, bloki dni na flexboksie z zawijaniem, kolory w zmiennych CSS,
  • PHP na PDO (klucz przyjmuje MySQLi albo PDO): połączenie i oba zapytania na początku pliku kalendarz.php, w try/catch, połączenie zamknięte, zanim zacznie się HTML; w treści strony już tylko implode i pętla foreach,
  • każdy tekst z bazy przechodzi przez funkcję e() (htmlspecialchars), data dnia stoi w znaczniku <time>, a pełny wpis jest w dymku bloku, gdy overflow: hidden go ucina.

Baza i logo pochodzą z prawdziwego archiwum materialy4 (załącznik do arkusza, hasło w treści zadania). Zapytania są policzone na MariaDB 10.4 - tej samej, która jest w XAMPP. Podgląd strony to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2 - na bazie po zapytaniu 4, tak jak na obrazie 2 (18 lipca „Wycieczka nad morze”). Sprawdzony jest też komunikat przy niedziałającej bazie.

Jak liczą się punkty. Klucz ma 33 kryteria (R.1.1 - R.5.7), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 25 punktów. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.

  1. Etap 1: Baza danych

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

    Krok 1: Baza danych terminarz

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Utwórz bazę danych o nazwie terminarz.

    Rozpakuj materialy4.7z z pulpitu (hasło kaL(end)arz%) do folderu z numerem zdającego: baza.sql, logo1.png i logo2.png. W phpMyAdmin: Nowa, nazwa terminarz, porównywanie np. utf8mb4_polish_ci.

    Tabela z baza.sql ma zestaw znaków zapisany we własnej definicji (latin1), więc wybór dla bazy jej nie zmienia - dane i tak nie zawierają polskich liter (miesiąc jest zapisany jako „sierpien”).

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

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Do bazy terminarz zaimportuj tabele z pliku baza.sql z rozpakowanego archiwum.

    Zaznaczona baza terminarz, Import → baza.sql → Wykonaj. Powstaje tabela zadania z obrazu 1: 63 dni lipca i sierpnia 2020 z datą (dataZadania, typ DATE), wpisem (często pustym), nazwą miesiąca i rokiem.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonano import tabel do bazy danych terminarz 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.

    Print Screen i zapis jako import.png - import w PNG, a zrzuty zapytań w JPEG (kw1.jpg - kw4.jpg).

    Typowe błędy

    Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany.

  4. Krok 4: Zapytanie 1: wpisy z pierwszego tygodnia lipca

    Kryteria: ,

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

    R.1.3 Wykonano zapytanie 1 wybierające jedynie niepowtarzające się wartości z pola wpis dla zadań z pierwszych 7 dni lipca oraz takich, gdzie pole wpis nie jest puste. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT DISTINCT wpis FROM zadania WHERE dataZadania <= '2020-07-07' AND wpis <> ''; (możliwe również wpis != '', dodatkowo AND dataZadania >= '2020-07-01') oraz wynik działania udokumentowano zrzutem ekranu, na którym są widoczne jedynie dwie dane: Projekt z programowania oraz Weterynarz - Brutus

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie niepowtarzające się wartości z pola wpis dla zadań z pierwszych 7 dni lipca roku 2020 (od 1 do 7 lipca) oraz takich, gdzie pole wpis nie jest puste.

    Każda klauzula w osobnej linii - tak czyta się dłuższe zapytania:

    • SELECT DISTINCT wpis - bez powtórzeń: „Projekt z programowania” jest 1 i 2 lipca, w wyniku raz,
    • WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' - przedział dat z oboma końcami; pole ma typ DATE, więc porównanie idzie po datach, nie po napisach,
    • AND wpis <> '' - bez pustych wpisów. Wartość NULL też odpada, bo porównanie z NULL nigdy nie jest prawdziwe.

    Wynik: dwa wpisy, „Projekt z programowania” i „Weterynarz - Brutus” (R.1.3). Zapytanie do kwerendy.txt, zrzut kw1.jpg razem z treścią zapytania. To zapytanie wyśle Skrypt 1.

    Typowe błędy

    Bez wpis <> '' w wyniku pojawi się pusty wiersz - pięć pustych dni zlewa się przez DISTINCT w jeden.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT DISTINCT wpis
    FROM zadania
    WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07'
      AND wpis <> '';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: zadania z lipca w kolejności dni

    Kryteria:

    R.1.4 Wykonano zapytanie 2 wybierające jedynie pola dataZadania i wpis dla zadań z miesiąca lipca. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT dataZadania, wpis FROM zadania WHERE miesiac='lipiec'; (poprawne też dataZadania LIKE "____-07-__" lub użyto %) 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 lipca, jedynie pola dataZadania i wpis. Zrzut nie musi zawierać wszystkich zwróconych zapytaniem wierszy

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pola dataZadania i wpis dla zadań z miesiąca lipca.

    • WHERE MONTH(dataZadania) = 7 - funkcja MONTH wyciąga z daty numer miesiąca. Tabela ma też tekstowe pole miesiac, ale to kopia informacji, którą niesie już data - i to kopia, w której można się pomylić (sierpień jest zapisany bez „ń”). Data jest źródłem prawdy. Warunek nie wpisuje roku, więc działa dla lipca każdego roku - klucz wymaga zapytań „uniwersalnych dla każdego zestawu danych”,
    • ORDER BY dataZadania - kolejność dni. Bez tej klauzuli baza oddaje wiersze w dowolnej kolejności (zwykle w kolejności zapisu, ale niczego to nie gwarantuje), a kalendarz musi iść od 1 do 31 lipca.

    Wynik: 31 wierszy, komunikat „(31 ogółem)” (R.1.4). Zrzut kw2.jpg nie musi mieścić wszystkich wierszy. To zapytanie wyśle Skrypt 2.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT DISTINCT wpis
    FROM zadania
    WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07'
      AND wpis <> '';
    
    -- Zapytanie 2
    SELECT dataZadania, wpis
    FROM zadania
    WHERE MONTH(dataZadania) = 7
    ORDER BY dataZadania;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: wpisy na literę S

    Kryteria:

    R.1.5 Wykonano zapytanie 3 wybierające jedynie pole miesiac i wpis dla wpisów zaczynających się na literę „S”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT miesiac, wpis FROM zadania WHERE wpis LIKE "S%"; oraz wynik działania udokumentowano zrzutem, na którym widoczne jest jedynie: lipiec Szczepienie, sierpien Sopot, sierpien Sopot

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pole miesiac i wpis dla wpisów zaczynających się na literę „S”.

    LIKE 'S%' - napis zaczynający się od „S”, po nim cokolwiek (%). Wielkość liter nie ma znaczenia przy porównywaniu latin1_swedish_ci tej tabeli, więc „s” na początku też by pasowało - w danych takich wpisów nie ma.

    Wynik: trzy wiersze - lipiec „Szczepienie” i dwa razy sierpień „Sopot” (R.1.5). Zrzut kw3.jpg.

    Typowe błędy

    LIKE '%S%' szuka litery S w dowolnym miejscu - nie tylko na początku wpisu.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT DISTINCT wpis
    FROM zadania
    WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07'
      AND wpis <> '';
    
    -- Zapytanie 2
    SELECT dataZadania, wpis
    FROM zadania
    WHERE MONTH(dataZadania) = 7
    ORDER BY dataZadania;
    
    -- Zapytanie 3
    SELECT miesiac, wpis
    FROM zadania
    WHERE wpis LIKE 'S%';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: nowy wpis na 18 lipca

    Kryteria:

    R.1.6 Wykonano zapytanie 4 aktualizujące pole wpis dla zadania w dniu 2020-07-18, nowy wpis to „Wycieczka nad morze”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: UPDATE zadania SET wpis='Wycieczka nad morze' WHERE dataZadania = '2020-07-18'; 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 w dniu 2020-07-18, nowy wpis to „Wycieczka nad morze”.

    • UPDATE zadania SET wpis = 'Wycieczka nad morze' - nowa wartość pola,
    • WHERE dataZadania = '2020-07-18' - tylko wiersz tego dnia; wcześniej był tam wpis „Wyjazd na wakacje!”.

    phpMyAdmin pokazuje „1 wiersz zmieniony” (R.1.6). Zrzut kw4.jpg. Od tej chwili kalendarz pokazuje 18 lipca nowy wpis.

    Typowe błędy

    UPDATE bez WHERE wpisuje „Wycieczka nad morze” we wszystkich 63 dniach.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT DISTINCT wpis
    FROM zadania
    WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07'
      AND wpis <> '';
    
    -- Zapytanie 2
    SELECT dataZadania, wpis
    FROM zadania
    WHERE MONTH(dataZadania) = 7
    ORDER BY dataZadania;
    
    -- Zapytanie 3
    SELECT miesiac, wpis
    FROM zadania
    WHERE wpis LIKE 'S%';
    
    -- Zapytanie 4
    UPDATE zadania
    SET wpis = 'Wycieczka nad morze'
    WHERE dataZadania = '2020-07-18';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

    Kroki 8-17 · po tym etapie masz 15 z 33 kryteriów (pkt)

    Krok 8: Logo: wysokość 140 px

    Kryteria:

    R.2.1 Plik logo1.png przeskalowano z zachowaniem proporcji do wysokości 140 px odpowiadająca temu szerokość wynosi 175 px (± 5 px)

    ok. 2 min
    Polecenie z arkusza

    Plik logo1.png, wypakowany z archiwum, należy przeskalować z zachowaniem proporcji tak, aby jego wysokość wynosiła dokładnie 140 px.

    W GIMP-ie: Obraz → Skaluj obraz, wysokość 140 z zamkniętym łańcuchem - szerokość wychodzi 175 px (350 × 140 / 280), jak w kluczu (R.2.1). Plik → Nadpisz logo1.png - plik zostaje PNG z przezroczystością.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Plik kalendarz.php w HTML5

    Kryteria:

    R.2.2 Witrynę internetową zapisano w pliku kalendarz.php oraz zastosowano deklarację języka HTML5 <!DOCTYPE html>, zadeklarowano język witryny polski np. <html lang = "pl"> oraz zapisano jawnie właściwy standard kodowania polskich znaków

    ok. 1 min
    Polecenie z arkusza

    Cechy witryny: Składa się ze strony o nazwie kalendarz.php zapisanej w języku HTML 5.

    Jedna strona, kalendarz.php. Dopóki nie ma w niej kodu PHP, podgląd pokazuje ją wprost.

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

    Kryteria:

    R.2.2 Witrynę internetową zapisano w pliku kalendarz.php oraz zastosowano deklarację języka HTML5 <!DOCTYPE html>, zadeklarowano język witryny polski np. <html lang = "pl"> oraz zapisano jawnie właściwy standard kodowania polskich znaków

    ok. 1 min
    Polecenie z arkusza

    Ustawiony język zawartości strony na polski.

    Atrybut lang="pl" w <html>.

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

    Kryteria:

    R.2.2 Witrynę internetową zapisano w pliku kalendarz.php oraz zastosowano deklarację języka HTML5 <!DOCTYPE html>, zadeklarowano język witryny polski np. <html lang = "pl"> oraz zapisano jawnie właściwy standard kodowania polskich znaków

    ok. 1 min
    Polecenie z arkusza

    Jawnie zastosowany właściwy standard kodowania polskich znaków.

    <meta charset="UTF-8"> jako pierwszy element <head> (R.2.2), a pod nim <meta name="viewport"> - telefon pokaże stronę w swojej szerokości. Arkusz tej drugiej linii nie wymaga.

    kalendarz.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">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (kalendarz.php)
  12. Krok 12: Tytuł strony

    Kryteria:

    R.2.3 Nadano tytuł strony: "Zadania na lipiec"

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Zadania na lipiec”.

    <title> w <head>.

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

    Kryteria:

    R.3.3 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl6.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

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

    Nietypowa nazwa: styl6.css - z cyfrą.

    Typowe błędy

    styl.css bez szóstki odcina wszystkie style, a razem z nimi R.3.3.

    kalendarz.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>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (kalendarz.php)
  14. Krok 14: Bloki strony

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: dwa 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.

    • dwa bloki banera to <section> z dwiema klasami: wspólną banner (te same kolory i wysokość) i własną (banner-logo, banner-title) - element może mieć kilka klas oddzielonych spacją,
    • <main class="calendar"> na blok główny i <footer> (R.2.4).

    Bloki dni nie są tu wpisane - wygeneruje je Skrypt 2.

    kalendarz.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>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section class="banner banner-logo">
        </section>
        <section class="banner banner-title">
        </section>
        <main class="calendar">
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (kalendarz.php)
  15. Krok 15: Pierwszy blok banera: logo

    Kryteria:

    R.2.7 W pierwszym bloku banera umieszczono obraz logo1.png wraz z tekstem alternatywnym "lipiec"

    ok. 1 min
    Polecenie z arkusza

    Zawartość pierwszego bloku banera: logo1.png z tekstem alternatywnym o treści: „lipiec”.

    Obraz z alt (R.2.7). width i height z wymiarów przeskalowanego pliku rezerwują miejsce, zanim obraz się wczyta.

    kalendarz.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>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section class="banner banner-logo">
            <img src="logo1.png" alt="lipiec" width="175" height="140">
        </section>
        <section class="banner banner-title">
        </section>
        <main class="calendar">
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (kalendarz.php)
  16. Krok 16: Drugi blok banera: nagłówek i akapit

    Kryteria: ,

    R.2.5 W banerze drugim zapisano nagłówek h1: "TERMINARZ"

    R.2.6 W banerze drugim zapisano paragraf rozpoczynający się od: "najbliższe zadania:" oraz w stopce zapisano paragraf z numerem zdającego

    ok. 1 min
    Polecenie z arkusza

    Zawartość drugiego bloku banera: Nagłówek pierwszego stopnia o treści: „TERMINARZ”. Paragraf (akapit) z wpisem: „najbliższe zadania:”, a dalej efekt działania skryptu 1.

    <h1> i <p> (R.2.5, R.2.6). Akapit kończy się po dwukropku i spacji - zadania dopisze Skrypt 1.

    kalendarz.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>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section class="banner banner-logo">
            <img src="logo1.png" alt="lipiec" width="175" height="140">
        </section>
        <section class="banner banner-title">
            <h1>TERMINARZ</h1>
            <p>najbliższe zadania: </p>
        </section>
        <main class="calendar">
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (kalendarz.php)
  17. Krok 17: Stopka i strona sierpien.html

    Kryteria: ,

    R.2.6 W banerze drugim zapisano paragraf rozpoczynający się od: "najbliższe zadania:" oraz w stopce zapisano paragraf z numerem zdającego

    R.3.2 Wybranie odnośnika "Terminarz na sierpień" powoduje otwarcie strony sierpien.html

    ok. 2 min
    Polecenie z arkusza

    Zawartość stopki: Odnośnik o treści „Terminarz na sierpień” prowadzący do pliku sierpien.html. Plik sierpien.html należy utworzyć i zapisać w nim „strona w trakcie budowy”. Paragraf (akapit) o treści: „Stronę wykonał: ”, dalej wstawiony numer zdającego.

    Odnośnik do pliku z tego samego folderu i nowy plik sierpien.html z napisem w krótkim szkielecie HTML5 (R.3.2). Na egzaminie po „Stronę wykonał:” wpisujesz swój numer zdającego (PESEL); tu stoi autor rozwiązania (R.2.6).

    kalendarz.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>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section class="banner banner-logo">
            <img src="logo1.png" alt="lipiec" width="175" height="140">
        </section>
        <section class="banner banner-title">
            <h1>TERMINARZ</h1>
            <p>najbliższe zadania: </p>
        </section>
        <main class="calendar">
        </main>
        <footer>
            <a href="sierpien.html">Terminarz na sierpień</a>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    sierpien.html HTML nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Terminarz na sierpień</title>
    </head>
    <body>
        <p>strona w trakcie budowy</p>
    </body>
    </html>
    Podgląd strony po tym kroku (kalendarz.php)
  18. Etap 3: Styl CSS

    Kroki 18-25 · po tym etapie masz 26 z 33 kryteriów (pkt)

    Krok 18: Plik styl6.css: kolory z arkusza w zmiennych

    ok. 2 min
    Polecenie z arkusza

    Styl CSS zdefiniowany w całości w zewnętrznym pliku o nazwie styl6.css.

    Kolor #6F79A8 pojawia się w arkuszu pięć razy (banery, stopka, ramka dnia, dzień pod kursorem). Zapisz kolory raz, jako zmienne CSS w :root, i odwołuj się do nich przez var(--nazwa). Sama definicja nic nie zmienia na stronie.

    styl6.css CSS nowy plik
    :root {
        --color-dark: #6F79A8;
        --color-day: #9FA8DA;
        --color-light: white;
    }
    Podgląd strony po tym kroku (kalendarz.php)
  19. Krok 19: Czcionka dla wszystkich elementów

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

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

    Selektor uniwersalny * (R.4.1), po kroju Verdana zapasowe Geneva i rodzina ogólna sans-serif.

    styl6.css CSS zmienione: 4 linie
    :root {
        --color-dark: #6F79A8;
        --color-day: #9FA8DA;
        --color-light: white;
    }
    
    * {
        font-family: Verdana, Geneva, sans-serif;
    }
    Podgląd strony po tym kroku (kalendarz.php)
  20. Krok 20: Bloki banera

    Kryteria: , ,

    R.4.2 Ustawiono kolor tła #6F79A8 dla obu bloków banera i stopki

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

    R.4.5 Ustawiono wysokość 140px dla obu bloków banera

    ok. 1 min
    Polecenie z arkusza

    Dla obu bloków banera: kolor tła #6F79A8, biały kolor czcionki, wysokość 140 px.

    Jedna reguła dla wspólnej klasy banner (R.4.2, R.4.3, R.4.5). Wysokość 140 px to dokładnie wysokość logo.

    styl6.css CSS zmienione: 6 linii
    :root {
        --color-dark: #6F79A8;
        --color-day: #9FA8DA;
        --color-light: white;
    }
    
    * {
        font-family: Verdana, Geneva, sans-serif;
    }
    
    .banner {
        background-color: var(--color-dark);
        color: var(--color-light);
        height: 140px;
    }
    Podgląd strony po tym kroku (kalendarz.php)
  21. Krok 21: Szerokości banerów i układ strony na siatce

    Kryteria: ,

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu (Nie należy sprawdzać bloku głównego i bloków kalendarza; prawidłowo zastosowano właściwości CSS decydujące o układzie strony, np. float i clear albo display flex albo grid albo position)

    R.4.4 Ustawiono szerokość bloków banera: 30% pierwszy oraz 70% drugi (width lub grid- template-columns lub flex)

    ok. 3 min
    Polecenie z arkusza

    Dodatkowo szerokość pierwszego bloku banera wynosi 30%, drugiego 70%.

    <body> staje się siatką o kolumnach 30% i 70% - szerokości banerów z arkusza, zapisane w grid-template-columns (klucz to przyjmuje, R.4.4).

    grid-template-areas rysuje układ obrazu 2: w pierwszym wierszu logo i tytuł, pod nimi kalendarz i stopka przez obie kolumny. W wersji podstawowej ten sam układ dają float: left na banerach i clear: both w bloku głównym - tu nie ma opływania do czyszczenia (R.3.1).

    Typowe błędy

    Szerokości banerów wpisuj tylko w grid-template-columns. Dodatkowe width: 30% na bloku liczyłoby się od kolumny siatki - blok miałby 9% strony.

    styl6.css CSS zmienione: 14 linii
    :root {
        --color-dark: #6F79A8;
        --color-day: #9FA8DA;
        --color-light: white;
    }
    
    * {
        font-family: Verdana, Geneva, sans-serif;
    }
    
    .banner {
        background-color: var(--color-dark);
        color: var(--color-light);
        height: 140px;
    }
    
    body {
        display: grid;
        grid-template-columns: 30% 70%;
        grid-template-areas:
            "logo     title"
            "calendar calendar"
            "footer   footer";
    }
    
    .banner-logo { grid-area: logo; }
    .banner-title { grid-area: title; }
    .calendar { grid-area: calendar; }
    footer { grid-area: footer; }
    Podgląd strony po tym kroku (kalendarz.php)
  22. Krok 22: Blok kalendarza

    Kryteria: ,

    R.4.8 Ustawiono dla bloku kalendarza: kolor tła #9FA8DA, szerokość 160px, wysokość 85px, marginesy zewnętrzne (margin) 5px, obramowanie 1px solid #6F79A8 (np. za pomocą klasy. Użycie id jest błędem)

    R.4.9 Ustawiono dla bloku kalendarza ukrycie tekstu, który nie mieści się w bloku bez paska przewijania (overflow: hidden;)

    ok. 3 min
    Polecenie z arkusza

    Dla bloku kalendarza: kolor tła #9FA8DA, szerokość 160 px, wysokość 85 px, marginesy zewnętrzne 5 px, obramowanie linią ciągłą o szerokości 1 px i kolorze #6F79A8; tekst, który nie mieści się w bloku jest ukryty i wyświetlany bez paska przewijania (np. obraz 2, pierwszy blok kalendarza).

    • bloków dnia jest 31, więc styl dostaje klasa .day - identyfikator musi być na stronie jednorazowy (klucz: użycie id jest błędem, R.4.8),
    • overflow: hidden obcina tekst, który nie mieści się w 85 px, bez paska przewijania (R.4.9),
    • blok główny .calendar układa dni flexboksem z zawijaniem (flex-wrap: wrap): dni stoją w wierszu, a gdy brak miejsca, przechodzą do następnego - tak jak na obrazie 2. Marginesy 5 px w flexboksie się nie sumują ani nie łączą, więc między dniami jest 10 px odstępu.
    Typowe błędy

    #day zamiast .day - klucz wprost uznaje identyfikator za błąd (R.4.8).

    styl6.css CSS zmienione: 14 linii
    :root {
        --color-dark: #6F79A8;
        --color-day: #9FA8DA;
        --color-light: white;
    }
    
    * {
        font-family: Verdana, Geneva, sans-serif;
    }
    
    .banner {
        background-color: var(--color-dark);
        color: var(--color-light);
        height: 140px;
    }
    
    body {
        display: grid;
        grid-template-columns: 30% 70%;
        grid-template-areas:
            "logo     title"
            "calendar calendar"
            "footer   footer";
    }
    
    .banner-logo { grid-area: logo; }
    .banner-title { grid-area: title; }
    .calendar { grid-area: calendar; }
    footer { grid-area: footer; }
    
    .calendar {
        display: flex;
        flex-wrap: wrap;
    }
    
    .day {
        background-color: var(--color-day);
        width: 160px;
        height: 85px;
        margin: 5px;
        border: 1px solid var(--color-dark);
        overflow: hidden;
    }
    Podgląd strony po tym kroku (kalendarz.php)
  23. Krok 23: Blok kalendarza po wskazaniu kursorem

    Kryteria:

    R.4.10 Gdy kursor znajdzie się na bloku kalendarza jego kolor tła zmienia się na #6F79A8, a kolor czcionki na biały

    ok. 1 min
    Polecenie z arkusza

    Gdy kursor znajdzie się na bloku kalendarza jego kolor tła zmienia się na #6F79A8, a kolor czcionki na biały.

    Pseudoklasa :hover dla klasy day (R.4.10). Efekt widać w podglądzie, gdy pojawią się bloki dni.

    styl6.css CSS zmienione: 5 linii
    :root {
        --color-dark: #6F79A8;
        --color-day: #9FA8DA;
        --color-light: white;
    }
    
    * {
        font-family: Verdana, Geneva, sans-serif;
    }
    
    .banner {
        background-color: var(--color-dark);
        color: var(--color-light);
        height: 140px;
    }
    
    body {
        display: grid;
        grid-template-columns: 30% 70%;
        grid-template-areas:
            "logo     title"
            "calendar calendar"
            "footer   footer";
    }
    
    .banner-logo { grid-area: logo; }
    .banner-title { grid-area: title; }
    .calendar { grid-area: calendar; }
    footer { grid-area: footer; }
    
    .calendar {
        display: flex;
        flex-wrap: wrap;
    }
    
    .day {
        background-color: var(--color-day);
        width: 160px;
        height: 85px;
        margin: 5px;
        border: 1px solid var(--color-dark);
        overflow: hidden;
    }
    
    .day:hover {
        background-color: var(--color-dark);
        color: var(--color-light);
    }
    Podgląd strony po tym kroku (kalendarz.php)
  24. Krok 24: Stopka

    Kryteria: , , ,

    R.4.2 Ustawiono kolor tła #6F79A8 dla obu bloków banera i stopki

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

    R.4.6 Ustawiono marginesy wewnętrzne (padding) 5px dla stopki

    R.4.7 Ustawiono wyrównanie tekstu do środka dla selektora h6 oraz do prawej strony dla stopki

    ok. 1 min
    Polecenie z arkusza

    Dla stopki: kolor tła #6F79A8, biały kolor czcionki, marginesy wewnętrzne 5 px, wyrównanie tekstu do prawej strony.

    Selektor footer (R.4.2, R.4.3, R.4.6, R.4.7).

    styl6.css CSS zmienione: 7 linii
    :root {
        --color-dark: #6F79A8;
        --color-day: #9FA8DA;
        --color-light: white;
    }
    
    * {
        font-family: Verdana, Geneva, sans-serif;
    }
    
    .banner {
        background-color: var(--color-dark);
        color: var(--color-light);
        height: 140px;
    }
    
    body {
        display: grid;
        grid-template-columns: 30% 70%;
        grid-template-areas:
            "logo     title"
            "calendar calendar"
            "footer   footer";
    }
    
    .banner-logo { grid-area: logo; }
    .banner-title { grid-area: title; }
    .calendar { grid-area: calendar; }
    footer { grid-area: footer; }
    
    .calendar {
        display: flex;
        flex-wrap: wrap;
    }
    
    .day {
        background-color: var(--color-day);
        width: 160px;
        height: 85px;
        margin: 5px;
        border: 1px solid var(--color-dark);
        overflow: hidden;
    }
    
    .day:hover {
        background-color: var(--color-dark);
        color: var(--color-light);
    }
    
    footer {
        background-color: var(--color-dark);
        color: var(--color-light);
        padding: 5px;
        text-align: right;
    }
    Podgląd strony po tym kroku (kalendarz.php)
  25. Krok 25: Nagłówek szóstego stopnia

    Kryteria:

    R.4.7 Ustawiono wyrównanie tekstu do środka dla selektora h6 oraz do prawej strony dla stopki

    ok. 1 min
    Polecenie z arkusza

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

    Selektor znacznika h6 - arkusz zastrzega, że styl ma być zdefiniowany wyłącznie tym selektorem (R.4.7). W <h6> Skrypt 2 wypisze datę dnia.

    Typowe błędy

    Styl dla .day h6 albo klasy nie spełnia R.4.7 - klucz uznaje tylko selektor znacznika.

    styl6.css CSS zmienione: 4 linie
    :root {
        --color-dark: #6F79A8;
        --color-day: #9FA8DA;
        --color-light: white;
    }
    
    * {
        font-family: Verdana, Geneva, sans-serif;
    }
    
    .banner {
        background-color: var(--color-dark);
        color: var(--color-light);
        height: 140px;
    }
    
    body {
        display: grid;
        grid-template-columns: 30% 70%;
        grid-template-areas:
            "logo     title"
            "calendar calendar"
            "footer   footer";
    }
    
    .banner-logo { grid-area: logo; }
    .banner-title { grid-area: title; }
    .calendar { grid-area: calendar; }
    footer { grid-area: footer; }
    
    .calendar {
        display: flex;
        flex-wrap: wrap;
    }
    
    .day {
        background-color: var(--color-day);
        width: 160px;
        height: 85px;
        margin: 5px;
        border: 1px solid var(--color-dark);
        overflow: hidden;
    }
    
    .day:hover {
        background-color: var(--color-dark);
        color: var(--color-light);
    }
    
    footer {
        background-color: var(--color-dark);
        color: var(--color-light);
        padding: 5px;
        text-align: right;
    }
    
    h6 {
        text-align: center;
    }
    Podgląd strony po tym kroku (kalendarz.php)
  26. Etap 4: Skrypty PHP

    Kroki 26-32 · po tym etapie masz 33 z 33 kryteriów (pkt)

    Krok 26: Połączenie PDO na początku strony i zamknięcie

    Kryteria: ,

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

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

    ok. 4 min
    Polecenie z arkusza

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

    Cały dostęp do bazy stoi na początku pliku, przed <!DOCTYPE html>: najpierw dane, potem strona. Klucz przyjmuje bibliotekę MySQLi albo PDO - tu PDO:

    • new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [...]) - serwer, baza terminarz i kodowanie w jednym napisie (DSN), potem użytkownik i hasło (R.5.1),
    • ERRMODE_EXCEPTION - każdy błąd bazy rzuca wyjątek, zamiast cicho zwrócić false; FETCH_ASSOC - wiersze jako tablice z nazwami kolumn ($day['wpis'] zamiast $row[1]),
    • $pdo = null zamyka połączenie. Między połączeniem a tą linią staną oba zapytania, więc zamknięcie będzie ostatnią operacją na bazie (R.5.2),
    • try/catch: gdy serwer nie działa albo baza nie istnieje, użytkownik zobaczy zrozumiały komunikat z $error, a nie treść wyjątku z nazwą serwera i użytkownika. Od PHP 8 catch (PDOException) nie musi nazywać zmiennej, gdy treść wyjątku nie jest potrzebna.

    Podgląd pokazuje wynik uruchomienia strony - skrypty jeszcze niczego nie wypisują.

    kalendarz.php PHP zmienione: 12 linii
    <?php
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $pdo = null;
    } catch (PDOException) {
        $error = 'Nie udało się wczytać terminarza. 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>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section class="banner banner-logo">
            <img src="logo1.png" alt="lipiec" width="175" height="140">
        </section>
        <section class="banner banner-title">
            <h1>TERMINARZ</h1>
            <p>najbliższe zadania: </p>
        </section>
        <main class="calendar">
        </main>
        <footer>
            <a href="sierpien.html">Terminarz na sierpień</a>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (kalendarz.php)
  27. Krok 27: Skrypt 1: zapytanie 1

    Kryteria:

    R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 1 lub skrypt 2 wysyła zapytanie 2

    ok. 3 min
    Polecenie z arkusza

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

    • $pdo->query(...) wysyła zapytanie 1 (R.5.3). W zapytaniu nie ma żadnej wartości od użytkownika, więc nie trzeba go przygotowywać (prepare) - przygotowanie chroni przed wstrzyknięciem danych, których tu nie ma,
    • fetchAll(PDO::FETCH_COLUMN) oddaje od razu zwykłą listę wartości jednej kolumny: ['Projekt z programowania', 'Weterynarz - Brutus'],
    • $upcoming = [] przed try - gdy baza zawiedzie, lista jest pusta, a strona i tak się wyświetli.
    kalendarz.php PHP zmienione: 6 linii
    <?php
    $upcoming = [];
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania
            WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''")
            ->fetchAll(PDO::FETCH_COLUMN);
    
        $pdo = null;
    } catch (PDOException) {
        $error = 'Nie udało się wczytać terminarza. 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>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section class="banner banner-logo">
            <img src="logo1.png" alt="lipiec" width="175" height="140">
        </section>
        <section class="banner banner-title">
            <h1>TERMINARZ</h1>
            <p>najbliższe zadania: </p>
        </section>
        <main class="calendar">
        </main>
        <footer>
            <a href="sierpien.html">Terminarz na sierpień</a>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  28. Krok 28: Skrypt 1: wpisy rozdzielone średnikiem

    Kryteria:

    R.5.4 Skrypt 1 wyświetla rozdzielone średnikiem i spacją wszystkie wartości zwrócone zapytaniem

    ok. 3 min
    Polecenie z arkusza

    Wyświetla rozdzielone średnikiem i spacją wszystkie wartości zwrócone zapytaniem.

    • implode('; ', $upcoming) skleja wszystkie wpisy z listy, wstawiając średnik i spację między nimi (R.5.4),
    • <?= ... ?> to skrót od <?php echo ... ?> - wygodny w treści HTML,
    • funkcja e() (od escape) przepuszcza tekst przez htmlspecialchars: znaki <, >, & i cudzysłowy zamienia na encje, więc wpis z kalendarza zawsze wyświetli się jako tekst, a nie kawałek HTML. ?string i ?? '' pozwalają przekazać też NULL z bazy.

    Podgląd: „najbliższe zadania: Projekt z programowania; Weterynarz - Brutus”. Na obrazie 2 po ostatnim wpisie stoi jeszcze średnik - tak wypisuje pętla z echo "...; ". Klucz wymaga wartości rozdzielonych średnikiem, więc obie formy są poprawne.

    kalendarz.php PHP zmienione: 6 linii
    <?php
    $upcoming = [];
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania
            WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''")
            ->fetchAll(PDO::FETCH_COLUMN);
    
        $pdo = null;
    } catch (PDOException) {
        $error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';
    }
    
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section class="banner banner-logo">
            <img src="logo1.png" alt="lipiec" width="175" height="140">
        </section>
        <section class="banner banner-title">
            <h1>TERMINARZ</h1>
            <p>najbliższe zadania: <?= e(implode('; ', $upcoming)) ?></p>
        </section>
        <main class="calendar">
        </main>
        <footer>
            <a href="sierpien.html">Terminarz na sierpień</a>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (kalendarz.php)
  29. Krok 29: Skrypt 2: zapytanie 2

    Kryteria:

    R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 1 lub skrypt 2 wysyła zapytanie 2

    ok. 2 min
    Polecenie z arkusza

    Działanie skryptu 2: Wysyła do bazy danych zapytanie 2.

    Drugie zapytanie na tym samym połączeniu, przed jego zamknięciem (R.5.3). fetchAll() oddaje listę 31 wierszy, każdy z kluczami dataZadania i wpis, już posortowaną po dacie. Lista $days też startuje pusta przed try.

    Na stronie jeszcze nic się nie zmienia - dni wypisze pętla w bloku głównym.

    kalendarz.php PHP zmienione: 5 linii
    <?php
    $upcoming = [];
    $days = [];
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania
            WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''")
            ->fetchAll(PDO::FETCH_COLUMN);
    
        $days = $pdo->query("SELECT dataZadania, wpis FROM zadania
            WHERE MONTH(dataZadania) = 7 ORDER BY dataZadania")
            ->fetchAll();
    
        $pdo = null;
    } catch (PDOException) {
        $error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';
    }
    
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section class="banner banner-logo">
            <img src="logo1.png" alt="lipiec" width="175" height="140">
        </section>
        <section class="banner banner-title">
            <h1>TERMINARZ</h1>
            <p>najbliższe zadania: <?= e(implode('; ', $upcoming)) ?></p>
        </section>
        <main class="calendar">
        </main>
        <footer>
            <a href="sierpien.html">Terminarz na sierpień</a>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  30. Krok 30: Skrypt 2: blok dla każdego dnia

    Kryteria:

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

    ok. 4 min
    Polecenie z arkusza

    Za pomocą znacznika sekcji definiuje blok, a w nim wyświetla dane dotyczące jednego zwróconego wiersza. Data zadania w nagłówku szóstego stopnia. Liczba wygenerowanych bloków odpowiada liczbie wierszy zwróconych zapytaniem.

    Pętla foreach ($days as $day): ... endforeach; - zapis z dwukropkiem jest przeznaczony do szablonów: kod HTML bloku stoi zwykłym tekstem między dwoma krótkimi wstawkami PHP, bez sklejania znaczników w napisach i bez ukośników przed cudzysłowami.

    Każdy wiersz daje jeden <section class="day"> (R.5.5) z datą w <h6>. Data jest dodatkowo w znaczniku <time datetime="..."> - przeglądarki, wyszukiwarki i czytniki ekranu wiedzą wtedy, że to data, a nie zwykły tekst. Podgląd: 31 bloków, tyle, ile wierszy zwraca zapytanie.

    Typowe błędy

    Jeden <section> wypisany przed pętlą, a w pętli tylko daty - wtedy jest jeden blok zamiast 31 (R.5.5).

    kalendarz.php PHP zmienione: 5 linii
    <?php
    $upcoming = [];
    $days = [];
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania
            WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''")
            ->fetchAll(PDO::FETCH_COLUMN);
    
        $days = $pdo->query("SELECT dataZadania, wpis FROM zadania
            WHERE MONTH(dataZadania) = 7 ORDER BY dataZadania")
            ->fetchAll();
    
        $pdo = null;
    } catch (PDOException) {
        $error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';
    }
    
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section class="banner banner-logo">
            <img src="logo1.png" alt="lipiec" width="175" height="140">
        </section>
        <section class="banner banner-title">
            <h1>TERMINARZ</h1>
            <p>najbliższe zadania: <?= e(implode('; ', $upcoming)) ?></p>
        </section>
        <main class="calendar">
    <?php foreach ($days as $day): ?>
            <section class="day">
                <h6><time datetime="<?= e($day['dataZadania']) ?>"><?= e($day['dataZadania']) ?></time></h6>
            </section>
    <?php endforeach; ?>
        </main>
        <footer>
            <a href="sierpien.html">Terminarz na sierpień</a>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (kalendarz.php)
  31. Krok 31: Skrypt 2: wpis w akapicie

    Kryteria: ,

    R.5.6 Skrypt 2 wyświetla datę zadania w nagłówku h6 oraz pole wpis w paragrafie

    R.5.7 Zastosowano znaczące nazewnictwo dla wszystkich zmiennych lub funkcji w języku polskim lub angielskim. Nazwy odzwierciedlają cel zastosowania

    ok. 3 min
    Polecenie z arkusza

    Pole wpis w paragrafie (akapicie).

    Pod datą akapit z wpisem (R.5.6), jak zawsze przez e(). Ten sam wpis trafia do atrybutu title bloku: overflow: hidden ucina długie wpisy („Wycieczka nad”), a po najechaniu przeglądarka pokaże całość w dymku. e() z ENT_QUOTES zamienia też cudzysłowy - wpis z cudzysłowem nie zamknie atrybutu przedwcześnie.

    Podgląd jak na obrazie 2: wpisy w blokach, 18 lipca ucięte „Wycieczka nad”. Najedź na dzień - zmieni kolor. Nazwy mówią, co przechowują: $upcoming, $days, $day, e() (R.5.7).

    kalendarz.php PHP zmienione: 2 linie
    <?php
    $upcoming = [];
    $days = [];
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania
            WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''")
            ->fetchAll(PDO::FETCH_COLUMN);
    
        $days = $pdo->query("SELECT dataZadania, wpis FROM zadania
            WHERE MONTH(dataZadania) = 7 ORDER BY dataZadania")
            ->fetchAll();
    
        $pdo = null;
    } catch (PDOException) {
        $error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';
    }
    
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section class="banner banner-logo">
            <img src="logo1.png" alt="lipiec" width="175" height="140">
        </section>
        <section class="banner banner-title">
            <h1>TERMINARZ</h1>
            <p>najbliższe zadania: <?= e(implode('; ', $upcoming)) ?></p>
        </section>
        <main class="calendar">
    <?php foreach ($days as $day): ?>
            <section class="day" title="<?= e($day['wpis']) ?>">
                <h6><time datetime="<?= e($day['dataZadania']) ?>"><?= e($day['dataZadania']) ?></time></h6>
                <p><?= e($day['wpis']) ?></p>
            </section>
    <?php endforeach; ?>
        </main>
        <footer>
            <a href="sierpien.html">Terminarz na sierpień</a>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (kalendarz.php)
  32. Krok 32: Komunikat, gdy baza nie działa

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku głównego: efekt działania skryptu 2.

    Gdy połączenie albo zapytanie się nie uda, catch z pierwszego kroku ustawia $error. Blok główny wyświetla wtedy ten komunikat zamiast pustego kalendarza: isset($error) sprawdza, czy zmienna w ogóle powstała. Klasa .error zajmuje cały wiersz flexboksu (flex-basis: 100%) i ma ciemnoczerwony kolor.

    Sprawdzone z błędną nazwą bazy: strona wyświetla banery, stopkę i komunikat, bez ostrzeżeń PHP. Klucz tego nie ocenia.

    kalendarz.php PHP zmienione: 3 linie
    <?php
    $upcoming = [];
    $days = [];
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=terminarz;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $upcoming = $pdo->query("SELECT DISTINCT wpis FROM zadania
            WHERE dataZadania BETWEEN '2020-07-01' AND '2020-07-07' AND wpis <> ''")
            ->fetchAll(PDO::FETCH_COLUMN);
    
        $days = $pdo->query("SELECT dataZadania, wpis FROM zadania
            WHERE MONTH(dataZadania) = 7 ORDER BY dataZadania")
            ->fetchAll();
    
        $pdo = null;
    } catch (PDOException) {
        $error = 'Nie udało się wczytać terminarza. Spróbuj ponownie za chwilę.';
    }
    
    function e(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Zadania na lipiec</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section class="banner banner-logo">
            <img src="logo1.png" alt="lipiec" width="175" height="140">
        </section>
        <section class="banner banner-title">
            <h1>TERMINARZ</h1>
            <p>najbliższe zadania: <?= e(implode('; ', $upcoming)) ?></p>
        </section>
        <main class="calendar">
    <?php if (isset($error)): ?>
            <p class="error"><?= e($error) ?></p>
    <?php endif; ?>
    <?php foreach ($days as $day): ?>
            <section class="day" title="<?= e($day['wpis']) ?>">
                <h6><time datetime="<?= e($day['dataZadania']) ?>"><?= e($day['dataZadania']) ?></time></h6>
                <p><?= e($day['wpis']) ?></p>
            </section>
    <?php endforeach; ?>
        </main>
        <footer>
            <a href="sierpien.html">Terminarz na sierpień</a>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    styl6.css CSS zmienione: 5 linii
    :root {
        --color-dark: #6F79A8;
        --color-day: #9FA8DA;
        --color-light: white;
    }
    
    * {
        font-family: Verdana, Geneva, sans-serif;
    }
    
    .banner {
        background-color: var(--color-dark);
        color: var(--color-light);
        height: 140px;
    }
    
    body {
        display: grid;
        grid-template-columns: 30% 70%;
        grid-template-areas:
            "logo     title"
            "calendar calendar"
            "footer   footer";
    }
    
    .banner-logo { grid-area: logo; }
    .banner-title { grid-area: title; }
    .calendar { grid-area: calendar; }
    footer { grid-area: footer; }
    
    .calendar {
        display: flex;
        flex-wrap: wrap;
    }
    
    .day {
        background-color: var(--color-day);
        width: 160px;
        height: 85px;
        margin: 5px;
        border: 1px solid var(--color-dark);
        overflow: hidden;
    }
    
    .day:hover {
        background-color: var(--color-dark);
        color: var(--color-light);
    }
    
    footer {
        background-color: var(--color-dark);
        color: var(--color-light);
        padding: 5px;
        text-align: right;
    }
    
    h6 {
        text-align: center;
    }
    
    .error {
        flex-basis: 100%;
        color: darkred;
    }
    Podgląd strony po tym kroku (kalendarz.php)
  33. Etap 5: Porównanie z wersją podstawową

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

    Krok 33: Co jest inaczej i dlaczego

    ok. 3 min
    Polecenie z arkusza

    Ocenie będzie podlegać 5 rezultatów: operacje na bazie danych, zawartość witryny internetowej, działanie witryny internetowej, styl CSS witryny internetowej, skrypt połączenia z bazą.

    Obie wersje dają te same punkty. Różnice:

    wersja podstawowata wersja
    zapytanie 2WHERE miesiac = 'lipiec'WHERE MONTH(dataZadania) = 7 ORDER BY dataZadania
    układfloat: left + clear: bothsiatka na <body>, dni na flexboksie
    banerydwa identyfikatorywspólna klasa banner + własne klasy
    kolorywpisane w każdej regulezmienne w :root
    bibliotekamysqli_* w dwóch blokach PHP w treściPDO na początku pliku, w HTML-u tylko wyświetlanie
    błąd bazyostrzeżenia PHP na stronietry/catch i komunikat dla użytkownika
    Skrypt 1pętla z echo "...; " (średnik też na końcu)implode('; ', ...) (średniki tylko między wpisami)
    Skrypt 2blok sklejany w napisie z \"szablon foreach: ... endforeach;
    tekst z bazywypisywany wprostprzez e() (htmlspecialchars), pełny wpis w title

    Sprawdzone: podgląd każdego kroku to prawdziwe uruchomienie w PHP 8.2 na MariaDB, układ zmierzony w Chrome (banery 30% i 70% w jednym wierszu, 31 dni po 162 × 87 px z ramką, stopka pod ostatnim), awaria bazy daje komunikat bez ostrzeżeń.

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

  34. Krok 34: Którą wersję wybrać na egzaminie

    ok. 2 min
    Polecenie z arkusza

    Czas przeznaczony na wykonanie zadania wynosi 150 minut.

    • Wersja podstawowa, gdy liczy się czas: funkcje mysqli_* są w tabeli 1 arkusza, więc nie trzeba ich pamiętać, a float wystarczy do tego układu.
    • Ta wersja, gdy PDO i siatkę CSS pisze się z pamięci. Rozdzielenie „najpierw dane, potem strona” pomaga przy szukaniu błędów: jeśli coś jest nie tak z bazą, wszystko stoi w kilkunastu liniach na górze pliku.

    Mieszanie też daje pełne punkty: siatka i zmienne z tej wersji, mysqli z podstawowej.

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

  35. Etap 6: Oddanie pracy

    Krok 35 · po tym etapie masz 33 z 33 kryteriów (pkt)

    Krok 35: Plik przeglądarka.txt i nagranie płyty

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz w folderze z numerem zdającego plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Nagraj płytę z rezultatami pracy.

    W przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana.

    W folderze: import.png, kalendarz.php, kw1.jpg - kw4.jpg, kwerendy.txt, logo1.png, przeglądarka.txt, sierpien.html, styl6.css. 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 32.

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.