Przejdź do głównej treści
INF.03-12-25.06-SGCzerwiec 2025 · 45 kroków · ok. 79 min · 37 kryteriów z klucza

Rozwiązanie krok po kroku: Przychodnia Medica (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza medica z relacją n:m, cztery zapytania, krzyż 32 × 32 px jako favicon, strona medica.php z blokiem artykułu i trzema sekcjami, style w styl.css - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • trzy sekcje na siatce CSS (grid-template-columns: repeat(3, 28%)) zamiast display: flex, kolory z arkusza w zmiennych CSS,
  • PHP na PDO: zapytanie 3 jest przygotowane raz i wykonywane trzy razy z innym numerem abonamentu przez małą funkcję planFeatures() - zamiast trzech kopii skryptu 2 różniących się jedną cyfrą,
  • logika przed HTML-em: oba skrypty pobierają dane na początku pliku, a szablon niżej tylko je wypisuje przez funkcję e() (escapowanie); dane połączenia są w osobnym pliku config.php,
  • podstrona opis.html jest pełnym dokumentem HTML5, a nie samym napisem.

Wersja podstawowa (zakładka obok) pisze kod najprostszymi funkcjami z tabeli 1 arkusza - z trzema kopiami skryptu, jak każe arkusz - i dopiero na końcu go poprawia; tu kod jest od razu taki, jak w prawdziwym projekcie. Na końcu jest krótkie porównanie obu wersji i podpowiedź, którą wybrać na egzaminie.

Baza i grafiki pochodzą z prawdziwego archiwum pliki3 (załącznik na stronie arkusza, hasło w treści zadania), a podgląd każdego kroku z PHP to wynik prawdziwego uruchomienia strony na serwerze z tą bazą.

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

  1. Etap 1: Baza danych

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

    Krok 1: Baza danych medica

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych medica, czynność udokumentowana w pliku import.png

    ok. 2 min
    Polecenie z arkusza

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

    W phpMyAdmin (http://localhost/phpmyadmin) kliknij Nowa, wpisz nazwę medica i wybierz metodę porównywania utf8_unicode_ci. To samo w SQL:

    CREATE DATABASE medica CHARACTER SET utf8 COLLATE utf8_unicode_ci;
    

    Metoda porównywania decyduje m.in. o tym, czy LIKE rozróżnia wielkość liter. W zapytaniu 4 działa jednak porównywanie kolumny opis zapisane w definicji tabeli z baza.sql (utf8mb4_general_ci), a nie ustawienie bazy - końcówka _ci (case insensitive) sprawia, że „Zdrowie” na początku opisu pasuje do wzorca %zdrowie%.

    Typowe błędy

    Literówka w nazwie bazy odcina też punkty za skrypty - połączenie w PHP podaje nazwę bazy (R.5.1).

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

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych medica, czynność udokumentowana w pliku import.png

    ok. 2 min
    Polecenie z arkusza

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

    Zaznacz bazę medica, karta Import, plik baza.sql, Wykonaj. Powstają trzy tabele z ilustracji 1: abonamenty (3 pakiety), cechy (12 cech) i tabela pośrednia szczegolyabonamentu (22 przypisania, kolumny Abonamenty_id i Cechy_id). To relacja n:m: abonament ma wiele cech, a cecha (np. Kardiolog) należy do wielu abonamentów.

    Typowe błędy

    Import bez zaznaczonej bazy medica tworzy tabele w innej bazie - strona ich nie znajdzie.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych medica, czynność udokumentowana w pliku import.png

    ok. 2 min
    Polecenie z arkusza

    Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie PNG pod nazwą import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań. Na zrzucie powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel.

    Cały ekran (Print Screen) z komunikatem o udanym imporcie albo listą trzech tabel, zapis jako import.png. Uwaga na formaty: import w PNG, zrzuty zapytań w JPEG.

    Typowe błędy

    Wykadrowany zrzut (bez paska zadań) nie jest oceniany.

  4. Krok 4: Zapytanie 1: nazwa, cena i opis abonamentów

    Kryteria: ,

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

    R.1.3 Wykonane zapytanie 1 wybierające jedynie nazwę, cenę i opis wszystkich abonamentów. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, cena, opis FROM abonamenty; oraz wynik działania udokumentowany zrzutem, na którym są widoczne dokładnie 3 rekordy, jedynie pola nazwa, cena i opis, pola nazwy mają wartość: Standard, Premium, Dziecko

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie nazwę, cenę i opis wszystkich abonamentów.

    Trzy kolumny w kolejności z arkusza, bez warunku („wszystkich”). Wynik: Standard, Premium, Dziecko (R.1.3). Zapytanie do kwerendy.txt, zrzut kw1.jpg.

    Typowe błędy

    SELECT * zwróci też id - klucz sprawdza „jedynie” trzy pola.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT nazwa, cena, opis
    FROM abonamenty;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: średnia cena z zaokrągleniem i aliasem

    Kryteria:

    R.1.4 Wykonane zapytanie 2 liczące średnią cenę abonamentów. Cena jest zaokrąglona do dwóch miejsc po przecinku oraz nazwano ją „Srednio_abonament”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT ROUND(AVG(cena), 2) AS Srednio_abonament FROM abonamenty; (AS opcjonalne) oraz wynik działania udokumentowany zrzutem, na którym jest widoczna kolumna „Srednio_abonament” z wartością 266,67

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: liczące średnią cenę abonamentów. Cena jest zaokrąglona do dwóch miejsc po przecinku oraz nadano nazwę kolumny (alias) na „Srednio_abonament”.

    Funkcje czyta się od środka: AVG(cena) liczy średnią z 200, 500 i 100 (266,6667), a ROUND(…, 2) zaokrągla wynik do dwóch miejsc. AS Srednio_abonament - alias z arkusza. Wynik: 266.67 (R.1.4), zrzut kw2.jpg.

    Typowe błędy

    AVG(ROUND(cena, 2)) zaokrągla każdą cenę z osobna - średnia zostaje z długim ułamkiem.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT nazwa, cena, opis
    FROM abonamenty;
    
    -- Zapytanie 2
    SELECT ROUND(AVG(cena), 2) AS Srednio_abonament
    FROM abonamenty;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: cechy abonamentu o id 1 przez relację n:m

    Kryteria:

    R.1.5 Wykonane zapytanie 3 wybierające jedynie nazwę abonamentu i odpowiadającą mu cechę dla abonamentu o id równym 1. Należy zastosować pełną relację n..n. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, cecha FROM abonamenty JOIN szczegolyabonamentu ON abonamenty.id = Abonamenty_id JOIN cechy ON cechy.id = Cechy_id WHERE abonamenty.id = 1; (możliwe też INNER JOIN lub porównanie kluczy po WHERE, kolejność tabel dowolna) oraz wynik działania udokumentowany zrzutem, na którym widocznych jest dokładnie 6 rekordów, jedynie pola nazwa i cecha, nazwa to Standard, cechy to: Lekarz POZ, Endokrynolog, Kardiolog, Ginekolog, Urolog, Podstawowe badania laboratoryjne

    ok. 4 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie nazwę abonamentu i odpowiadającą mu cechę dla abonamentu o id równym 1. Należy zastosować pełną relację n:m.

    „Pełna relacja n:m” to złączenie trzech tabel - z abonamentu przez tabelę pośrednią do cechy. Każda tabela dostaje krótki alias (a, s, c), a każda kolumna jest poprzedzona aliasem:

    • JOIN szczegolyabonamentu AS s ON s.Abonamenty_id = a.id - do abonamentu jego wiersze z tabeli pośredniej,
    • JOIN cechy AS c ON c.id = s.Cechy_id - do każdego z nich nazwa cechy,
    • WHERE a.id = 1 - kolumnę id mają wszystkie trzy tabele; alias rozstrzyga, o którą chodzi.

    Wynik: 6 wierszy, Standard i cechy od Lekarza POZ do Podstawowych badań laboratoryjnych (R.1.5). Zrzut kw3.jpg.

    Typowe błędy

    WHERE id = 1 bez aliasu to błąd „Column id in where clause is ambiguous”.

    kwerendy.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT nazwa, cena, opis
    FROM abonamenty;
    
    -- Zapytanie 2
    SELECT ROUND(AVG(cena), 2) AS Srednio_abonament
    FROM abonamenty;
    
    -- Zapytanie 3
    SELECT a.nazwa, c.cecha
    FROM abonamenty AS a
    JOIN szczegolyabonamentu AS s ON s.Abonamenty_id = a.id
    JOIN cechy AS c ON c.id = s.Cechy_id
    WHERE a.id = 1;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: opisy ze słowem „zdrowie” albo „opieką”

    Kryteria:

    R.1.6 Wykonane zapytanie 4 wybierające jedynie nazwę i opis abonamentów, w których w opisie w dowolnym miejscu znajdują się słowa „zdrowie” albo „opieką”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, opis FROM abonamenty WHERE opis LIKE "%zdrowie%" OR opis LIKE "%opieką%"; oraz wynik działania udokumentowany zrzutem, na którym są widoczne dokładnie 2 rekordy, jedynie pola nazwa i opis, pola nazwy mają wartości Standard, Dziecko

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie nazwę i opis abonamentów, w których w opisie w dowolnym miejscu znajdują się słowa „zdrowie” albo „opieką”.

    LIKE ze wzorcem %słowo% - % to dowolny ciąg znaków z obu stron, czyli „w dowolnym miejscu”. „Albo” to OR. Wzorce w apostrofach: to standard SQL (cudzysłów w innych bazach oznacza nazwę kolumny). Wynik: Standard i Dziecko (R.1.6), zrzut kw4.jpg.

    Typowe błędy

    AND zamiast OR wymaga obu słów w jednym opisie - wynik byłby pusty.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT nazwa, cena, opis
    FROM abonamenty;
    
    -- Zapytanie 2
    SELECT ROUND(AVG(cena), 2) AS Srednio_abonament
    FROM abonamenty;
    
    -- Zapytanie 3
    SELECT a.nazwa, c.cecha
    FROM abonamenty AS a
    JOIN szczegolyabonamentu AS s ON s.Abonamenty_id = a.id
    JOIN cechy AS c ON c.id = s.Cechy_id
    WHERE a.id = 1;
    
    -- Zapytanie 4
    SELECT nazwa, opis
    FROM abonamenty
    WHERE opis LIKE '%zdrowie%'
       OR opis LIKE '%opieką%';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

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

    Krok 8: Grafika obraz2.png: dokładnie 32 × 32 px

    Kryteria:

    R.2.1 Plik obraz2.png przeskalowany dokładnie do szerokości 32 px i wysokości 32 px

    ok. 2 min
    Polecenie z arkusza

    Plik obraz2.png z czerwonym krzyżem, wypakowany z archiwum, należy przeskalować do dokładnych rozmiarów 32 px na 32 px.

    W GIMP-ie Obraz → Skaluj obraz: 32 × 32 (obraz jest kwadratowy, więc proporcje się zgadzają), Eksportuj jako obraz2.png. PNG zachowuje przezroczyste tło krzyża (R.2.1). 32 × 32 to typowy rozmiar favicony.

    Typowe błędy

    Skalowanie płótna zamiast obrazu ucina krzyż zamiast go zmniejszyć.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Podstrona opis.html

    Kryteria:

    R.3.3 Odnośnik o treści „Dowiedz się więcej” otwiera podstronę opis.html

    ok. 2 min
    Polecenie z arkusza

    Należy utworzyć plik opis.html i zapisać w nim „Strona w trakcie budowy”.

    Arkusz wymaga tylko napisu, ale plik z rozszerzeniem .html warto zapisać jako pełny dokument HTML5: z lang, kodowaniem i tytułem. Sam napis bez <meta charset> przeglądarka może odczytać w złym kodowaniu („Strona w trakcie budowy” ma dwie polskie litery), a karta bez <title> pokazuje nazwę pliku. Odnośnik z bloku artykułu będzie miał dokąd prowadzić (R.3.3).

    opis.html HTML nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Przychodnia Medica - opis</title>
    </head>
    <body>
        <p>Strona w trakcie budowy</p>
    </body>
    </html>
    Podgląd strony po tym kroku (opis.html)
  10. Krok 10: Plik medica.php w HTML5

    Kryteria:

    R.2.2 W pliku medica.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków

    ok. 1 min
    Polecenie z arkusza

    Składa się ze strony o nazwie medica.php. Zapisana w języku HTML5.

    Plik medica.php w folderze zdającego. <!DOCTYPE html> włącza tryb standardów HTML5, dalej szkielet: <html>, <head> i <body>. Rozszerzenie .php, bo na początku pliku stanie kod PHP.

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

    Kryteria:

    R.2.2 W pliku medica.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków

    ok. 1 min
    Polecenie z arkusza

    Zadeklarowany polski język zawartości witryny.

    lang="pl" w <html> - czytnik ekranu czyta polską wymową, przeglądarka dobiera słownik i dzielenie wyrazów.

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

    Kryteria:

    R.2.2 W pliku medica.php zapisana deklaracja HTML5 <!DOCTYPE HTML>, deklaracja języka zawartości: polski np. <html lang="pl">, w sekcji head znajduje się jawnie podany standard kodowania polskich znaków

    ok. 1 min
    Polecenie z arkusza

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

    <meta charset="UTF-8"> jako pierwszy element <head>. Ten sam standard ustawi połączenie z bazą (charset=utf8mb4 w PDO), więc polskie litery przejdą bez zmian całą drogę: baza → PHP → przeglądarka.

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

    Kryteria:

    R.2.3 Nadany tytuł strony: "Przychodnia Medica"

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony „Przychodnia Medica”.

    <title>Przychodnia Medica</title> - napis na karcie przeglądarki. Obok <meta name="viewport">: bez niego telefon pokazałby stronę pomniejszoną jak na monitorze. Arkusz tego nie wymaga, a w każdym współczesnym szablonie ta linia jest.

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

    Kryteria:

    R.3.2 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.css , formatowanie CSS pochodzi jedynie z tego arkusza

    ok. 1 min
    Polecenie z arkusza

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

    <link rel="stylesheet" href="styl.css"> - nazwa dokładnie z arkusza. Cały wygląd będzie w tym pliku, w HTML nie ma ani jednego atrybutu style.

    Typowe błędy

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

    medica.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>Przychodnia Medica</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  15. Krok 15: Favicon na karcie przeglądarki

    Kryteria:

    R.2.8 Na karcie przeglądarki jest widoczny favicon w postaci obrazu o nazwie obraz2.png

    ok. 1 min
    Polecenie z arkusza

    Obraz2.png jako favicon widoczny na karcie przeglądarki.

    <link rel="icon"> w <head> - przeglądarka rysuje wskazany obraz obok tytułu na karcie (R.2.8). Atrybut type="image/png" mówi z góry, jaki to format, więc przeglądarka nie musi pobierać pliku, żeby się przekonać. Ikony w podglądzie nie widać - podgląd nie ma karty przeglądarki.

    Typowe błędy

    <img> w <head> albo rel="stylesheet" z obrazem nie tworzą favicony - potrzebny jest rel="icon".

    medica.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>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  16. Krok 16: Bloki semantyczne

    Kryteria:

    R.2.4 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML 5: header, article, main -> section + section + section, footer

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 3. Zawartość bloku głównego: trzy sekcje.

    Cztery bloki na jednym poziomie, jak na ilustracji 3 i w kluczu R.2.4: header, article, main z trzema section, footer. Blok artykułu stoi poza main - to osobny pas na całą szerokość między nagłówkiem a sekcjami. Sekcje mają identyczny wygląd, więc nie potrzebują identyfikatorów ani klas.

    Typowe błędy

    <article> wewnątrz <main> nie zgadza się z R.2.4 - klucz wymienia go jako osobny blok.

    medica.php PHP zmienione: 14 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
        </header>
        <article>
        </article>
        <main>
            <section>
            </section>
            <section>
            </section>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  17. Krok 17: Blok nagłówkowy: nagłówek pierwszego stopnia

    Kryteria:

    R.2.5 W bloku nagłówkowym zapisany nagłówek <h1>: "Abonamenty w przychodni Medica", w przynajmniej jednej sekcji zapisany nagłówek <h2> "Standardowy" lub "Premium" lub "Dziecko" oraz zapisany w stopce paragraf

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści „Abonamenty w przychodni Medica”.

    Jedyny <h1> na stronie (R.2.5). Hierarchia nagłówków: h1 tytuł strony, h2 sekcje, h3 pakiety w artykule.

    medica.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>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
        </article>
        <main>
            <section>
            </section>
            <section>
            </section>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  18. Krok 18: Blok artykułu: odnośnik „Dowiedz się więcej”

    Kryteria:

    R.3.3 Odnośnik o treści „Dowiedz się więcej” otwiera podstronę opis.html

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku artykułu: efekt działania skryptu 1. Odnośnik o treści „Dowiedz się więcej” prowadzący do podstrony opis.html.

    Ścieżka względna do podstrony w tym samym folderze (R.3.3). Pakiety ze skryptu 1 staną nad odnośnikiem. W podglądzie kliknięcia są zablokowane.

    medica.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>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
            </section>
            <section>
            </section>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  19. Krok 19: Sekcja „Standardowy”: nagłówek i lista

    Kryteria: ,

    R.2.5 W bloku nagłówkowym zapisany nagłówek <h1>: "Abonamenty w przychodni Medica", w przynajmniej jednej sekcji zapisany nagłówek <h2> "Standardowy" lub "Premium" lub "Dziecko" oraz zapisany w stopce paragraf

    R.2.6 W przynajmniej jednej sekcji umieszczona jedna lista punktowana, zastosowane znaczniki <ul>, <li> (lista wygenerowana skryptem lub w HTML)

    ok. 1 min
    Polecenie z arkusza

    Zawartość pierwszej sekcji: nagłówek drugiego stopnia o treści „Standardowy”. Lista punktowana (nieuporządkowana), a w niej efekt działania skryptu 2.

    <h2> i pusta lista <ul>; elementy <li> wypisze szablon PHP (R.2.5, R.2.6). Nagłówki sekcji to teksty z arkusza, nie z bazy - pierwszy pakiet nazywa się w bazie „Standard”.

    medica.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
                </ul>
            </section>
            <section>
            </section>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  20. Krok 20: Sekcja „Premium”: nagłówek i lista

    Kryteria: ,

    R.2.5 W bloku nagłówkowym zapisany nagłówek <h1>: "Abonamenty w przychodni Medica", w przynajmniej jednej sekcji zapisany nagłówek <h2> "Standardowy" lub "Premium" lub "Dziecko" oraz zapisany w stopce paragraf

    R.2.6 W przynajmniej jednej sekcji umieszczona jedna lista punktowana, zastosowane znaczniki <ul>, <li> (lista wygenerowana skryptem lub w HTML)

    ok. 1 min
    Polecenie z arkusza

    Zawartość drugiej sekcji: nagłówek drugiego stopnia o treści „Premium”. Lista punktowana (nieuporządkowana), a w niej efekt działania zmodyfikowanego skryptu 2 dla id abonamentu równego 2.

    Ta sama budowa: <h2> i pusta <ul> - wypełnią ją cechy abonamentu o id 2.

    medica.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
                </ul>
            </section>
            <section>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  21. Krok 21: Sekcja „Dziecko”: nagłówek i lista

    Kryteria: ,

    R.2.5 W bloku nagłówkowym zapisany nagłówek <h1>: "Abonamenty w przychodni Medica", w przynajmniej jednej sekcji zapisany nagłówek <h2> "Standardowy" lub "Premium" lub "Dziecko" oraz zapisany w stopce paragraf

    R.2.6 W przynajmniej jednej sekcji umieszczona jedna lista punktowana, zastosowane znaczniki <ul>, <li> (lista wygenerowana skryptem lub w HTML)

    ok. 1 min
    Polecenie z arkusza

    Zawartość trzeciej sekcji: nagłówek drugiego stopnia o treści „Dziecko”. Lista punktowana (nieuporządkowana), a w niej efekt działania zmodyfikowanego skryptu 2 dla id abonamentu równego 3.

    Trzecia sekcja jak dwie poprzednie - na cechy abonamentu o id 3.

    medica.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
                </ul>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  22. Krok 22: Stopka: paragraf z obrazem i numerem zdającego

    Kryteria: ,

    R.2.5 W bloku nagłówkowym zapisany nagłówek <h1>: "Abonamenty w przychodni Medica", w przynajmniej jednej sekcji zapisany nagłówek <h2> "Standardowy" lub "Premium" lub "Dziecko" oraz zapisany w stopce paragraf

    R.2.7 W stopce umieszczony obraz o nazwie obraz2.png z tekstem alternatywnym "przychodnia"

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: paragraf, a w nim obraz o nazwie obraz2.png z tekstem alternatywnym „przychodnia” oraz treść „Stronę przygotował:”, dalej numer zdającego.

    Paragraf z obrazem i tekstem (R.2.5, R.2.7). Tu stoi nazwisko autora rozwiązania - na egzaminie wpisujesz swój numer zdającego (PESEL).

    medica.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>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
                </ul>
            </section>
        </main>
        <footer>
            <p><img src="obraz2.png" alt="przychodnia"> Stronę przygotował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  23. Etap 3: Styl CSS

    Kroki 23-32 · po tym etapie masz 27 z 37 kryteriów (pkt)

    Krok 23: Zmienne CSS z kolorami arkusza

    ok. 2 min
    Polecenie z arkusza

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

    Plik styl.css zaczyna się od zmiennych CSS w :root. Ciemna zieleń #1B5E20 występuje w arkuszu cztery razy (tło nagłówka i stopki, tekst sekcji, nagłówki h2/h3) - wpisana raz, w regułach jest var(--green-dark). Egzaminator w inspektorze widzi obliczoną wartość koloru, więc kryteria sprawdza tak samo.

    styl.css CSS nowy plik
    :root {
        --green-dark: #1B5E20;
        --green: #00E676;
        --green-light: #10EF84;
        --shadow: DimGray;
    }
    Podgląd strony po tym kroku (medica.php)
  24. Krok 24: Krój czcionki dla wszystkich selektorów

    Kryteria:

    R.4.1 Ustawione domyślne formatowanie wszystkich selektorów (dla *): krój czcionki Georgia

    ok. 1 min
    Polecenie z arkusza

    Domyślne formatowanie wszystkich selektorów: krój czcionki Georgia.

    Selektor * i jeden krój, bez rodziny zastępczej - dokładnie tak, jak w arkuszu i w kluczu R.4.1. W prawdziwym projekcie dopisuje się serif na wypadek braku kroju, ale tu treść arkusza jest jednoznaczna.

    styl.css CSS zmienione: 4 linie
    :root {
        --green-dark: #1B5E20;
        --green: #00E676;
        --green-light: #10EF84;
        --shadow: DimGray;
    }
    
    * {
        font-family: Georgia;
    }
    Podgląd strony po tym kroku (medica.php)
  25. Krok 25: Nagłówek i stopka

    Kryteria: , ,

    R.4.2 Ustawiony kolor tła #1B5E20 dla bloków: nagłówkowego i stopki, #00E676 dla trzech sekcji oraz tło będące obrazem obraz1.jpg dla bloku artykułu

    R.4.3 Ustawiony biały kolor czcionki dla bloku nagłówkowego i stopki, DimGray dla bloku artykułu, #1B5E20 dla trzech sekcji, rozmiar czcionki 150% dla bloku artykułu

    R.4.7 Ustawione marginesy wewnętrzne (padding) : 5px dla bloku nagłówkowego i stopki, marginesy zewnętrzne (margin) : 2% dla trzech sekcji. Ustawione wyrównanie tekstu do środka dla bloku nagłówkowego i stopki

    ok. 1 min
    Polecenie z arkusza

    Dla bloku nagłówkowego i stopki: kolor tła #1B5E20, biały kolor czcionki, wyrównanie tekstu do środka, marginesy wewnętrzne 5 px.

    Jedna reguła dla dwóch selektorów (R.4.2, R.4.3, R.4.7). Każdy selektor w osobnej linii - przy dłuższych listach łatwiej je czytać i porównywać w historii zmian.

    styl.css CSS zmienione: 8 linii
    :root {
        --green-dark: #1B5E20;
        --green: #00E676;
        --green-light: #10EF84;
        --shadow: DimGray;
    }
    
    * {
        font-family: Georgia;
    }
    
    header,
    footer {
        padding: 5px;
        text-align: center;
        background-color: var(--green-dark);
        color: white;
    }
    Podgląd strony po tym kroku (medica.php)
  26. Krok 26: Blok artykułu: tło z obrazu, kolor, rozmiar, wysokość, paski przewijania

    Kryteria: , , ,

    R.4.2 Ustawiony kolor tła #1B5E20 dla bloków: nagłówkowego i stopki, #00E676 dla trzech sekcji oraz tło będące obrazem obraz1.jpg dla bloku artykułu

    R.4.3 Ustawiony biały kolor czcionki dla bloku nagłówkowego i stopki, DimGray dla bloku artykułu, #1B5E20 dla trzech sekcji, rozmiar czcionki 150% dla bloku artykułu

    R.4.4 Ustawiona wysokość bloku artykułu 350px, trzech sekcji 560px oraz szerokość 28% dla trzech sekcji (width lub grid-template-columns lub flex)

    R.4.8 Ustawione dla bloku artykułu paski przewijania (scroll lub auto)

    ok. 2 min
    Polecenie z arkusza

    Dla bloku artykułu: tłem jest obraz o nazwie obraz1.jpg, kolor czcionki DimGray, rozmiar czcionki 150%, wysokość 350 px, widoczne paski przewijania.

    • background-image: url("obraz1.jpg") - tło z obrazu; background-size: cover dopasowuje zdjęcie do bloku (wypełnia go całego, przycinając nadmiar), więc przy szerokim oknie nie powtarza się kafelkami,
    • height: 350px i overflow: scroll - „widoczne paski przewijania”, czyli zawsze (R.4.8),
    • kolor DimGray i rozmiar 150% (R.4.2, R.4.3, R.4.4).
    Typowe błędy

    background-color: url(...) nie działa - kolor i obraz tła to różne właściwości.

    styl.css CSS zmienione: 9 linii
    :root {
        --green-dark: #1B5E20;
        --green: #00E676;
        --green-light: #10EF84;
        --shadow: DimGray;
    }
    
    * {
        font-family: Georgia;
    }
    
    header,
    footer {
        padding: 5px;
        text-align: center;
        background-color: var(--green-dark);
        color: white;
    }
    
    article {
        height: 350px;
        overflow: scroll;
        background-image: url("obraz1.jpg");
        background-size: cover;
        color: DimGray;
        font-size: 150%;
    }
    Podgląd strony po tym kroku (medica.php)
  27. Krok 27: Trzy sekcje: tło, kolor, wysokość, margines, zaokrąglenie, cień

    Kryteria: , , ,

    R.4.2 Ustawiony kolor tła #1B5E20 dla bloków: nagłówkowego i stopki, #00E676 dla trzech sekcji oraz tło będące obrazem obraz1.jpg dla bloku artykułu

    R.4.3 Ustawiony biały kolor czcionki dla bloku nagłówkowego i stopki, DimGray dla bloku artykułu, #1B5E20 dla trzech sekcji, rozmiar czcionki 150% dla bloku artykułu

    R.4.6 Ustawione dla trzech sekcji zaokrąglenie rogów 10px oraz cień bloku box-shadow: 8px 8px 6px DimGray;

    R.4.7 Ustawione marginesy wewnętrzne (padding) : 5px dla bloku nagłówkowego i stopki, marginesy zewnętrzne (margin) : 2% dla trzech sekcji. Ustawione wyrównanie tekstu do środka dla bloku nagłówkowego i stopki

    ok. 3 min
    Polecenie z arkusza

    Dla trzech sekcji: kolor tła #00E676, kolor czcionki #1B5E20, szerokość 28%, wysokość 560 px, marginesy zewnętrzne 2%, zaokrąglenie rogów 10 px, cień bloku o przesunięciu 8 px w obu osiach, rozmyciu 6 px i kolorze DimGray.

    Wszystko z arkusza poza szerokością: tło i kolor tekstu ze zmiennych, wysokość 560 px, margines 2%, zaokrąglenie i box-shadow: 8px 8px 6px w kolorze DimGray - przesunięcie w poziomie, w pionie i rozmycie, w kolejności z tabeli 2 arkusza (R.4.6).

    Szerokość 28% ustawi następny krok - w siatce, nie w width. Właściwości w regule idą w stałej kolejności: wymiary i odstępy, potem obramowanie i cień, na końcu kolory.

    Typowe błędy

    text-shadow to cień tekstu, nie bloku.

    styl.css CSS zmienione: 9 linii
    :root {
        --green-dark: #1B5E20;
        --green: #00E676;
        --green-light: #10EF84;
        --shadow: DimGray;
    }
    
    * {
        font-family: Georgia;
    }
    
    header,
    footer {
        padding: 5px;
        text-align: center;
        background-color: var(--green-dark);
        color: white;
    }
    
    article {
        height: 350px;
        overflow: scroll;
        background-image: url("obraz1.jpg");
        background-size: cover;
        color: DimGray;
        font-size: 150%;
    }
    
    section {
        height: 560px;
        margin: 2%;
        border-radius: 10px;
        box-shadow: 8px 8px 6px var(--shadow);
        background-color: var(--green);
        color: var(--green-dark);
    }
    Podgląd strony po tym kroku (medica.php)
  28. Krok 28: Układ bloków zgodny z ilustracją 3: siatka

    Kryteria: ,

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

    R.4.4 Ustawiona wysokość bloku artykułu 350px, trzech sekcji 560px oraz szerokość 28% dla trzech sekcji (width lub grid-template-columns lub flex)

    ok. 3 min
    Polecenie z arkusza

    Podział strony na bloki zrealizowany za pomocą semantycznych znaczników bloków języka HTML5 tak, aby po uruchomieniu w przeglądarce układ bloków na stronie był zgodny z ilustracją 3.

    Blok główny staje się siatką o trzech kolumnach po 28% - repeat(3, 28%) to skrót od 28% 28% 28%, a klucz R.4.4 przyjmuje szerokość sekcji zapisaną właśnie w grid-template-columns (R.3.1, R.4.4).

    Uwaga na pułapkę procentów: w siatce procenty w marginesie sekcji liczą się od szerokości jej kolumny, nie od całego bloku głównego. Margines 2% z poprzedniego kroku zostaje (wymaga go arkusz i klucz R.4.7), ale w kolumnie 28% to niewiele - dlatego odstępy między sekcjami daje siatka: column-gap: 4% i padding: 0 2% po bokach. Razem 3 × 28% + 2 × 4% + 2 × 2% = 96%, czyli ten sam rozkład co na ilustracji 2.

    W wersji podstawowej display: flex z width: 28% - tam procenty liczą się od bloku głównego, więc margines 2% sam robi odstępy. Obie drogi są zgodne z kluczem.

    Typowe błędy

    width: 28% na sekcji wewnątrz siatki dałoby 28% z kolumny, czyli sekcje wąskie jak paski.

    styl.css CSS zmienione: 7 linii
    :root {
        --green-dark: #1B5E20;
        --green: #00E676;
        --green-light: #10EF84;
        --shadow: DimGray;
    }
    
    * {
        font-family: Georgia;
    }
    
    header,
    footer {
        padding: 5px;
        text-align: center;
        background-color: var(--green-dark);
        color: white;
    }
    
    article {
        height: 350px;
        overflow: scroll;
        background-image: url("obraz1.jpg");
        background-size: cover;
        color: DimGray;
        font-size: 150%;
    }
    
    section {
        height: 560px;
        margin: 2%;
        border-radius: 10px;
        box-shadow: 8px 8px 6px var(--shadow);
        background-color: var(--green);
        color: var(--green-dark);
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(3, 28%);
        column-gap: 4%;
        padding: 0 2%;
    }
    Podgląd strony po tym kroku (medica.php)
  29. Krok 29: Sekcja pod kursorem

    Kryteria:

    R.4.10 Gdy kursor znajdzie się na sekcji jej kolor tła zmienia się na #10EF84 oraz gdy kursor znajdzie się na paragrafie jego kolor czcionki zmienia się na czarny

    ok. 1 min
    Polecenie z arkusza

    Gdy kursor znajdzie się na dowolnej sekcji jej kolor tła zmienia się na #10EF84.

    Pseudoklasa :hover na section (R.4.10).

    styl.css CSS zmienione: 4 linie
    :root {
        --green-dark: #1B5E20;
        --green: #00E676;
        --green-light: #10EF84;
        --shadow: DimGray;
    }
    
    * {
        font-family: Georgia;
    }
    
    header,
    footer {
        padding: 5px;
        text-align: center;
        background-color: var(--green-dark);
        color: white;
    }
    
    article {
        height: 350px;
        overflow: scroll;
        background-image: url("obraz1.jpg");
        background-size: cover;
        color: DimGray;
        font-size: 150%;
    }
    
    section {
        height: 560px;
        margin: 2%;
        border-radius: 10px;
        box-shadow: 8px 8px 6px var(--shadow);
        background-color: var(--green);
        color: var(--green-dark);
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(3, 28%);
        column-gap: 4%;
        padding: 0 2%;
    }
    
    section:hover {
        background-color: var(--green-light);
    }
    Podgląd strony po tym kroku (medica.php)
  30. Krok 30: Paragraf pod kursorem

    Kryteria:

    R.4.10 Gdy kursor znajdzie się na sekcji jej kolor tła zmienia się na #10EF84 oraz gdy kursor znajdzie się na paragrafie jego kolor czcionki zmienia się na czarny

    ok. 1 min
    Polecenie z arkusza

    Gdy kursor znajdzie się na paragrafie jego kolor czcionki zmienia się na czarny.

    Pseudoklasa :hover na p - działa na opisach pakietów (skrypt 1) i na paragrafie w stopce (R.4.10).

    styl.css CSS zmienione: 4 linie
    :root {
        --green-dark: #1B5E20;
        --green: #00E676;
        --green-light: #10EF84;
        --shadow: DimGray;
    }
    
    * {
        font-family: Georgia;
    }
    
    header,
    footer {
        padding: 5px;
        text-align: center;
        background-color: var(--green-dark);
        color: white;
    }
    
    article {
        height: 350px;
        overflow: scroll;
        background-image: url("obraz1.jpg");
        background-size: cover;
        color: DimGray;
        font-size: 150%;
    }
    
    section {
        height: 560px;
        margin: 2%;
        border-radius: 10px;
        box-shadow: 8px 8px 6px var(--shadow);
        background-color: var(--green);
        color: var(--green-dark);
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(3, 28%);
        column-gap: 4%;
        padding: 0 2%;
    }
    
    section:hover {
        background-color: var(--green-light);
    }
    
    p:hover {
        color: black;
    }
    Podgląd strony po tym kroku (medica.php)
  31. Krok 31: Nagłówki drugiego i trzeciego stopnia

    Kryteria:

    R.4.5 Ustawiony styl nagłówków drugiego i trzeciego stopnia: kolor czcionki #1B5E20, wyrównanie tekstu do środka, odstępy między literami 3px, padding-top: 20px;

    ok. 1 min
    Polecenie z arkusza

    Dla nagłówków drugiego i trzeciego stopnia: kolor czcionki #1B5E20, wyrównanie tekstu do środka, odstępy między literami 3 px, margines wewnętrzny górny 20 px.

    letter-spacing: 3px rozstrzela litery, padding-top to margines wewnętrzny górny (R.4.5). Nagłówki h3 wypisze szablon PHP.

    Typowe błędy

    margin-top zamiast padding-top to margines zewnętrzny.

    styl.css CSS zmienione: 8 linii
    :root {
        --green-dark: #1B5E20;
        --green: #00E676;
        --green-light: #10EF84;
        --shadow: DimGray;
    }
    
    * {
        font-family: Georgia;
    }
    
    header,
    footer {
        padding: 5px;
        text-align: center;
        background-color: var(--green-dark);
        color: white;
    }
    
    article {
        height: 350px;
        overflow: scroll;
        background-image: url("obraz1.jpg");
        background-size: cover;
        color: DimGray;
        font-size: 150%;
    }
    
    section {
        height: 560px;
        margin: 2%;
        border-radius: 10px;
        box-shadow: 8px 8px 6px var(--shadow);
        background-color: var(--green);
        color: var(--green-dark);
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(3, 28%);
        column-gap: 4%;
        padding: 0 2%;
    }
    
    section:hover {
        background-color: var(--green-light);
    }
    
    p:hover {
        color: black;
    }
    
    h2,
    h3 {
        padding-top: 20px;
        text-align: center;
        letter-spacing: 3px;
        color: var(--green-dark);
    }
    Podgląd strony po tym kroku (medica.php)
  32. Krok 32: Lista punktowana: punktor z obrazu

    Kryteria:

    R.4.9 Ustawiony styl listy punktowanej: punktorem jest obraz3.png (np. list-style-image)

    ok. 1 min
    Polecenie z arkusza

    Dla listy punktowanej: punktor jest obrazem o nazwie obraz3.png.

    list-style-image z adresem obrazu - zielony „ptaszek” zamiast kropki (R.4.9).

    Dodatkowo footer img { vertical-align: middle; }: obraz w linii tekstu domyślnie stoi na linii bazowej liter, więc krzyż 32 px wystaje ponad napis. Wyrównanie do środka ustawia napis na wysokości środka krzyża, jak na ilustracji 2. Arkusz tego nie wymaga.

    styl.css CSS zmienione: 8 linii
    :root {
        --green-dark: #1B5E20;
        --green: #00E676;
        --green-light: #10EF84;
        --shadow: DimGray;
    }
    
    * {
        font-family: Georgia;
    }
    
    header,
    footer {
        padding: 5px;
        text-align: center;
        background-color: var(--green-dark);
        color: white;
    }
    
    article {
        height: 350px;
        overflow: scroll;
        background-image: url("obraz1.jpg");
        background-size: cover;
        color: DimGray;
        font-size: 150%;
    }
    
    section {
        height: 560px;
        margin: 2%;
        border-radius: 10px;
        box-shadow: 8px 8px 6px var(--shadow);
        background-color: var(--green);
        color: var(--green-dark);
    }
    
    main {
        display: grid;
        grid-template-columns: repeat(3, 28%);
        column-gap: 4%;
        padding: 0 2%;
    }
    
    section:hover {
        background-color: var(--green-light);
    }
    
    p:hover {
        color: black;
    }
    
    h2,
    h3 {
        padding-top: 20px;
        text-align: center;
        letter-spacing: 3px;
        color: var(--green-dark);
    }
    
    ul {
        list-style-image: url("obraz3.png");
    }
    
    footer img {
        vertical-align: middle;
    }
    Podgląd strony po tym kroku (medica.php)
  33. Etap 4: Skrypty PHP

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

    Krok 33: Dane połączenia w pliku config.php

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Napisane w języku PHP. Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie medica.

    Dane logowania do bazy w osobnym pliku config.php, który tylko zwraca tablicę (return [...]). Na serwerze produkcyjnym hasło jest inne niż na komputerze programisty i nie trafia do repozytorium z kodem - podmienia się jeden plik. Arkusz dopuszcza dodatkowe pliki.

    DSN (mysql:host=localhost;dbname=medica;charset=utf8mb4) to adres bazy dla PDO w jednym napisie: serwer, nazwa bazy, kodowanie połączenia. Plik bez HTML-u nie ma końcowego ?>.

    Typowe błędy

    config.php musi trafić do folderu z rezultatami - bez niego strona się nie połączy.

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

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

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

    Kryteria: ,

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

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

    ok. 3 min
    Polecenie z arkusza

    Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie medica.

    Kod PHP stoi na samym początku pliku, przed <!DOCTYPE html>: najpierw operacje na bazie, potem szablon, który tylko wypisuje gotowe dane. Klucz R.5 przyjmuje PDO tak samo jak MySQLi (R.5.1, R.5.10).

    • new PDO(...) - połączenie i wybór bazy; ERRMODE_EXCEPTION zamienia błędy SQL w wyjątki, FETCH_ASSOC daje wiersze z nazwami kolumn ($plan['opis'] zamiast $row[2]),
    • try … catch (PDOException $error) - przy awarii bazy użytkownik widzi ogólny komunikat, a nie treść błędu z nazwą serwera.

    Od tego kroku podgląd to prawdziwy wynik strony z serwera.

    Typowe błędy

    Wypisanie $error->getMessage() na ekran zdradza szczegóły serwera.

    medica.php PHP zmienione: 13 linii
    <?php
    $config = require 'config.php';
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
                </ul>
            </section>
        </main>
        <footer>
            <p><img src="obraz2.png" alt="przychodnia"> Stronę przygotował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  35. Krok 35: Funkcja e(): bezpieczne wypisywanie tekstu

    ok. 2 min
    Polecenie z arkusza

    Należy stosować znaczące nazewnictwo zmiennych i funkcji w języku polskim lub angielskim.

    Każdy napis z bazy przejdzie w szablonie przez e(): htmlspecialchars z ENT_QUOTES zamienia <, >, & i cudzysłowy na encje, więc napis <script> zapisany kiedyś w opisie pakietu pokaże się jako tekst, a nie wykona (XSS). Escapuje się na wyjściu, w bazie zostają dane surowe.

    Funkcje stoją na górze pliku, przed blokiem try - definicja przed użyciem czyta się naturalnie od góry do dołu.

    medica.php PHP zmienione: 5 linii
    <?php
    $config = require 'config.php';
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
                </ul>
            </section>
        </main>
        <footer>
            <p><img src="obraz2.png" alt="przychodnia"> Stronę przygotował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  36. Krok 36: Skrypt 1: zapytanie 1 do bazy

    Kryteria:

    R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 1 lub skrypt 2 wysyła zapytanie 3 (wystarczy jedno zapytanie z dowolnym warunkiem dla id równego 1 lub 2 lub 3)

    ok. 2 min
    Polecenie z arkusza

    Skrypt 1 wysyła do bazy danych zapytanie 1.

    Zapytanie 1 w bloku try, zaraz po połączeniu (R.5.3). fetchAll() odbiera wszystkie trzy pakiety do tablicy $plans. Danych od użytkownika w zapytaniu nie ma, więc wystarcza query.

    medica.php PHP zmienione: 2 linie
    <?php
    $config = require 'config.php';
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $plans = $db->query('SELECT nazwa, cena, opis FROM abonamenty')->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
                </ul>
            </section>
        </main>
        <footer>
            <p><img src="obraz2.png" alt="przychodnia"> Stronę przygotował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  37. Krok 37: Skrypt 1: nagłówek z nazwą i ceną pakietu

    Kryteria:

    R.5.4 Dla wszystkich zwróconych wierszy skrypt 1 wyświetla tekst: „Pakiet <nazwa> - cena <cena> zł”, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych

    ok. 2 min
    Polecenie z arkusza

    Zwrócone wiersze są wyświetlane według wzoru: w nagłówku trzeciego stopnia: „Pakiet <nazwa> - cena <cena> zł”, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych.

    W bloku artykułu, nad odnośnikiem, pętla w składni szablonu (foreach (...): … endforeach;): HTML nagłówka zostaje zwykłym HTML-em, a wartości wstawia <?= ... ?> (skrót od <?php echo ... ?>), każdą przez e() (R.5.4). Kolumny po nazwach, więc kolejność w SELECT nie ma znaczenia.

    medica.php PHP zmienione: 3 linie
    <?php
    $config = require 'config.php';
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $plans = $db->query('SELECT nazwa, cena, opis FROM abonamenty')->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
    <?php foreach ($plans as $plan): ?>
            <h3>Pakiet <?= e($plan['nazwa']) ?> - cena <?= e($plan['cena']) ?> zł</h3>
    <?php endforeach; ?>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
                </ul>
            </section>
        </main>
        <footer>
            <p><img src="obraz2.png" alt="przychodnia"> Stronę przygotował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  38. Krok 38: Skrypt 1: paragraf z opisem

    Kryteria:

    R.5.5 Dla wszystkich zwróconych wierszy skrypt 1 wyświetla opis pobrany z bazy danych

    ok. 1 min
    Polecenie z arkusza

    W paragrafie opis pobrany z bazy danych.

    Pod nagłówkiem <p> z opisem (R.5.5). Trzy długie opisy przekraczają 350 px - działa pasek przewijania bloku artykułu.

    medica.php PHP zmienione: 1 linia
    <?php
    $config = require 'config.php';
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $plans = $db->query('SELECT nazwa, cena, opis FROM abonamenty')->fetchAll();
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
    <?php foreach ($plans as $plan): ?>
            <h3>Pakiet <?= e($plan['nazwa']) ?> - cena <?= e($plan['cena']) ?> zł</h3>
            <p><?= e($plan['opis']) ?></p>
    <?php endforeach; ?>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
                </ul>
            </section>
        </main>
        <footer>
            <p><img src="obraz2.png" alt="przychodnia"> Stronę przygotował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  39. Krok 39: Skrypt 2: zapytanie 3 przygotowane raz

    Kryteria:

    R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 1 lub skrypt 2 wysyła zapytanie 3 (wystarczy jedno zapytanie z dowolnym warunkiem dla id równego 1 lub 2 lub 3)

    ok. 4 min
    Polecenie z arkusza

    Skrypt 2 wysyła do bazy danych zapytanie 3.

    Arkusz każe użyć zapytania 3 trzy razy, za każdym razem z innym numerem abonamentu. Zamiast trzech kopii zapytania jest jedno zapytanie przygotowane: $db->prepare(...) wysyła treść z parametrem nazwanym :planId, a baza zapamiętuje ją i czeka na wartości.

    Mała funkcja planFeatures() wykonuje to zapytanie z podanym numerem (execute) i zwraca listę cech. fetchAll(PDO::FETCH_COLUMN, 1) zamiast całych wierszy oddaje same wartości drugiej kolumny (numeracja od zera), czyli cechy - zapytanie zwraca też nazwę abonamentu, bo tak brzmi zapytanie 3, ale arkusz każe wyświetlać „tylko cechę”.

    Wyniki trafiają do tablicy $featuresByPlan pod numerem abonamentu - na razie tylko numer 1 (R.5.3). Typy przy parametrach (PDOStatement $query, int $planId) i wyniku (array) mówią, czego funkcja oczekuje.

    Typowe błędy

    FETCH_COLUMN bez numeru kolumny zwraca pierwszą kolumnę - czyli sześć razy „Standard” zamiast cech.

    medica.php PHP zmienione: 17 linii
    <?php
    $config = require 'config.php';
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function planFeatures(PDOStatement $query, int $planId): array
    {
        $query->execute(['planId' => $planId]);
        return $query->fetchAll(PDO::FETCH_COLUMN, 1);
    }
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $plans = $db->query('SELECT nazwa, cena, opis FROM abonamenty')->fetchAll();
    
        $featuresQuery = $db->prepare(
            'SELECT a.nazwa, c.cecha
             FROM abonamenty AS a
             JOIN szczegolyabonamentu AS s ON s.Abonamenty_id = a.id
             JOIN cechy AS c ON c.id = s.Cechy_id
             WHERE a.id = :planId'
        );
        $featuresByPlan = [
            1 => planFeatures($featuresQuery, 1),
        ];
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
    <?php foreach ($plans as $plan): ?>
            <h3>Pakiet <?= e($plan['nazwa']) ?> - cena <?= e($plan['cena']) ?> zł</h3>
            <p><?= e($plan['opis']) ?></p>
    <?php endforeach; ?>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
                </ul>
            </section>
        </main>
        <footer>
            <p><img src="obraz2.png" alt="przychodnia"> Stronę przygotował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  40. Krok 40: Skrypt 2: cechy jako elementy listy

    Kryteria: ,

    R.5.6 Skrypt 2 wyświetla w pierwszej sekcji wiersze zwrócone zapytaniem dla abonamentu o id równym 1

    R.5.9 Skrypt 2 wyświetla wiersze w elemencie listy (tylko cecha)

    ok. 2 min
    Polecenie z arkusza

    Zwrócone zapytaniem wiersze są wyświetlane jako elementy listy, tylko <cecha>, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych.

    W <ul> pierwszej sekcji pętla po cechach abonamentu 1: jeden <li> na cechę, sama cecha przez e() (R.5.6, R.5.9). Podgląd: sześć cech Standardu z punktorem z obrazu.

    medica.php PHP zmienione: 3 linie
    <?php
    $config = require 'config.php';
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function planFeatures(PDOStatement $query, int $planId): array
    {
        $query->execute(['planId' => $planId]);
        return $query->fetchAll(PDO::FETCH_COLUMN, 1);
    }
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $plans = $db->query('SELECT nazwa, cena, opis FROM abonamenty')->fetchAll();
    
        $featuresQuery = $db->prepare(
            'SELECT a.nazwa, c.cecha
             FROM abonamenty AS a
             JOIN szczegolyabonamentu AS s ON s.Abonamenty_id = a.id
             JOIN cechy AS c ON c.id = s.Cechy_id
             WHERE a.id = :planId'
        );
        $featuresByPlan = [
            1 => planFeatures($featuresQuery, 1),
        ];
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
    <?php foreach ($plans as $plan): ?>
            <h3>Pakiet <?= e($plan['nazwa']) ?> - cena <?= e($plan['cena']) ?> zł</h3>
            <p><?= e($plan['opis']) ?></p>
    <?php endforeach; ?>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
    <?php foreach ($featuresByPlan[1] as $feature): ?>
                    <li><?= e($feature) ?></li>
    <?php endforeach; ?>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
                </ul>
            </section>
        </main>
        <footer>
            <p><img src="obraz2.png" alt="przychodnia"> Stronę przygotował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  41. Krok 41: Skrypt 2 w drugiej sekcji: id abonamentu 2

    Kryteria:

    R.5.7 Skrypt 2 wyświetla w drugiej sekcji wiersze zwrócone zapytaniem dla abonamentu o id równym 2

    ok. 1 min
    Polecenie z arkusza

    Modyfikacje skryptu 2: w drugiej sekcji należy zastosować zmodyfikowane zapytanie, które sprawdza warunek dla abonamentu o id równym 2. Pozostałe cechy skryptu są identyczne.

    „Zmodyfikowane zapytanie” to tu ten sam szablon zapytania z inną wartością parametru: jedna nowa linia 2 => planFeatures($featuresQuery, 2) i pętla w drugiej sekcji (R.5.7). Baza wykonuje to samo zapytanie przygotowane z :planId = 2 - nie trzeba go wysyłać ani analizować drugi raz. Podgląd: jedenaście cech pakietu Premium.

    medica.php PHP zmienione: 4 linie
    <?php
    $config = require 'config.php';
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function planFeatures(PDOStatement $query, int $planId): array
    {
        $query->execute(['planId' => $planId]);
        return $query->fetchAll(PDO::FETCH_COLUMN, 1);
    }
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $plans = $db->query('SELECT nazwa, cena, opis FROM abonamenty')->fetchAll();
    
        $featuresQuery = $db->prepare(
            'SELECT a.nazwa, c.cecha
             FROM abonamenty AS a
             JOIN szczegolyabonamentu AS s ON s.Abonamenty_id = a.id
             JOIN cechy AS c ON c.id = s.Cechy_id
             WHERE a.id = :planId'
        );
        $featuresByPlan = [
            1 => planFeatures($featuresQuery, 1),
            2 => planFeatures($featuresQuery, 2),
        ];
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
    <?php foreach ($plans as $plan): ?>
            <h3>Pakiet <?= e($plan['nazwa']) ?> - cena <?= e($plan['cena']) ?> zł</h3>
            <p><?= e($plan['opis']) ?></p>
    <?php endforeach; ?>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
    <?php foreach ($featuresByPlan[1] as $feature): ?>
                    <li><?= e($feature) ?></li>
    <?php endforeach; ?>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
    <?php foreach ($featuresByPlan[2] as $feature): ?>
                    <li><?= e($feature) ?></li>
    <?php endforeach; ?>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
                </ul>
            </section>
        </main>
        <footer>
            <p><img src="obraz2.png" alt="przychodnia"> Stronę przygotował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  42. Krok 42: Skrypt 2 w trzeciej sekcji: id abonamentu 3

    Kryteria:

    R.5.8 Skrypt 2 wyświetla w trzeciej sekcji wiersze zwrócone zapytaniem dla abonamentu o id równym 3

    ok. 1 min
    Polecenie z arkusza

    W trzeciej sekcji należy zastosować zmodyfikowane zapytanie, które sprawdza warunek dla abonamentu o id równym 3. Pozostałe cechy skryptu są identyczne.

    Trzecie wykonanie z :planId = 3 i pętla w trzeciej sekcji: pięć cech pakietu Dziecko (R.5.8). Poprawka w zapytaniu albo w sposobie pobierania cech to teraz zmiana w jednym miejscu, a nie w trzech kopiach.

    medica.php PHP zmienione: 4 linie
    <?php
    $config = require 'config.php';
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function planFeatures(PDOStatement $query, int $planId): array
    {
        $query->execute(['planId' => $planId]);
        return $query->fetchAll(PDO::FETCH_COLUMN, 1);
    }
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $plans = $db->query('SELECT nazwa, cena, opis FROM abonamenty')->fetchAll();
    
        $featuresQuery = $db->prepare(
            'SELECT a.nazwa, c.cecha
             FROM abonamenty AS a
             JOIN szczegolyabonamentu AS s ON s.Abonamenty_id = a.id
             JOIN cechy AS c ON c.id = s.Cechy_id
             WHERE a.id = :planId'
        );
        $featuresByPlan = [
            1 => planFeatures($featuresQuery, 1),
            2 => planFeatures($featuresQuery, 2),
            3 => planFeatures($featuresQuery, 3),
        ];
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
    <?php foreach ($plans as $plan): ?>
            <h3>Pakiet <?= e($plan['nazwa']) ?> - cena <?= e($plan['cena']) ?> zł</h3>
            <p><?= e($plan['opis']) ?></p>
    <?php endforeach; ?>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
    <?php foreach ($featuresByPlan[1] as $feature): ?>
                    <li><?= e($feature) ?></li>
    <?php endforeach; ?>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
    <?php foreach ($featuresByPlan[2] as $feature): ?>
                    <li><?= e($feature) ?></li>
    <?php endforeach; ?>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
    <?php foreach ($featuresByPlan[3] as $feature): ?>
                    <li><?= e($feature) ?></li>
    <?php endforeach; ?>
                </ul>
            </section>
        </main>
        <footer>
            <p><img src="obraz2.png" alt="przychodnia"> Stronę przygotował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (medica.php)
  43. Krok 43: Zamknięcie połączenia z bazą

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

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

    W PDO połączenie zamyka się, gdy znika ostatnie odwołanie do obiektu połączenia (R.5.2). Przygotowane zapytanie $featuresQuery też trzyma takie odwołanie - dlatego najpierw $featuresQuery = null, potem $db = null. Samo $db = null zostawiłoby połączenie otwarte do końca działania skryptu.

    To ostatnia operacja na bazie: szablon niżej korzysta tylko z gotowych tablic $plans i $featuresByPlan.

    Typowe błędy

    $db->close() nie istnieje w PDO - to błąd „Call to undefined method”.

    medica.php PHP zmienione: 3 linie
    <?php
    $config = require 'config.php';
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    function planFeatures(PDOStatement $query, int $planId): array
    {
        $query->execute(['planId' => $planId]);
        return $query->fetchAll(PDO::FETCH_COLUMN, 1);
    }
    
    try {
        $db = new PDO($config['dsn'], $config['user'], $config['password'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $plans = $db->query('SELECT nazwa, cena, opis FROM abonamenty')->fetchAll();
    
        $featuresQuery = $db->prepare(
            'SELECT a.nazwa, c.cecha
             FROM abonamenty AS a
             JOIN szczegolyabonamentu AS s ON s.Abonamenty_id = a.id
             JOIN cechy AS c ON c.id = s.Cechy_id
             WHERE a.id = :planId'
        );
        $featuresByPlan = [
            1 => planFeatures($featuresQuery, 1),
            2 => planFeatures($featuresQuery, 2),
            3 => planFeatures($featuresQuery, 3),
        ];
    
        $featuresQuery = null;
        $db = null;
    } catch (PDOException $error) {
        http_response_code(500);
        exit('Baza danych jest chwilowo niedostępna. Spróbuj ponownie później.');
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Przychodnia Medica</title>
        <link rel="icon" type="image/png" href="obraz2.png">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Abonamenty w przychodni Medica</h1>
        </header>
        <article>
    <?php foreach ($plans as $plan): ?>
            <h3>Pakiet <?= e($plan['nazwa']) ?> - cena <?= e($plan['cena']) ?> zł</h3>
            <p><?= e($plan['opis']) ?></p>
    <?php endforeach; ?>
            <a href="opis.html">Dowiedz się więcej</a>
        </article>
        <main>
            <section>
                <h2>Standardowy</h2>
                <ul>
    <?php foreach ($featuresByPlan[1] as $feature): ?>
                    <li><?= e($feature) ?></li>
    <?php endforeach; ?>
                </ul>
            </section>
            <section>
                <h2>Premium</h2>
                <ul>
    <?php foreach ($featuresByPlan[2] as $feature): ?>
                    <li><?= e($feature) ?></li>
    <?php endforeach; ?>
                </ul>
            </section>
            <section>
                <h2>Dziecko</h2>
                <ul>
    <?php foreach ($featuresByPlan[3] as $feature): ?>
                    <li><?= e($feature) ?></li>
    <?php endforeach; ?>
                </ul>
            </section>
        </main>
        <footer>
            <p><img src="obraz2.png" alt="przychodnia"> Stronę przygotował: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

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

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

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

    ok. 2 min
    Polecenie z arkusza

    Czas przeznaczony na wykonanie zadania wynosi 150 minut.

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

    wersja podstawowawersja nowoczesna
    układ sekcjidisplay: flex, width: 28%siatka repeat(3, 28%), odstępy z column-gap
    kolorywpisane w każdej regulezmienne w :root
    bazafunkcje mysqli_* z tabeli 1PDO, wyjątki, config.php
    skrypt 2trzy kopie kodu z inną cyfrąjedno zapytanie przygotowane, funkcja planFeatures()
    wypisywanieecho w pętli whileszablon foreach … endforeach i e()
    opis.htmlsam napispełny dokument HTML5

    Na egzaminie: wersja podstawowa jest krótsza w pisaniu i używa tylko funkcji z tabeli 1 arkusza - przy trzech kopiach skryptu łatwo się pomylić jedną cyfrą, ale łatwo też to sprawdzić. Wersję nowoczesną pisz, jeśli znasz PDO na pamięć. Na tej stronie nie ma formularza, więc nie ma walidacji wejścia ani tokenu CSRF - numery abonamentów są stałymi w kodzie. Gdyby numer przychodził z adresu strony (medica.php?plan=2), zapytanie przygotowane już jest gotowe na bezpieczne przyjęcie takiej wartości.

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

  45. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

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

    W przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana przez http://localhost/…. W folderze z numerem zdającego: import.png, kw1.jpg … kw4.jpg, kwerendy.txt, medica.php, config.php, obraz1.jpg, obraz2.png, obraz3.png, opis.html, przeglądarka.txt, styl.css. Płytę sprawdź po nagraniu i opisz numerem zdającego.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.