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

Rozwiązanie krok po kroku: Firma szkoleniowa (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza firma z czterema zapytaniami, baner, statyczna index.html i szkolenia.php, która wypisuje szkolenia z bazy i zapisuje je do pliku harmonogram.txt - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • siatka CSS w kontenerze: dwie kolumny 20% 80% w grid-template-columns (klucz R.4.4 przyjmuje je wprost) zamiast float: left i clear: both, a kolory z arkusza zapisane raz, w zmiennych CSS,
  • PHP na PDO: zapytanie wykonane nad HTML-em, połączenie zamknięte, zanim zacznie się strona, a akapity wypisywane pętlą foreach w szablonie,
  • jedna lista linii, dwa użycia: tekst „data temat” powstaje raz i trafia i do akapitów, i do pliku - zapisanego jednym poleceniem file_put_contents ze sprawdzeniem, czy zapis się udał,
  • bezpieczeństwo: htmlspecialchars na wszystkim, co trafia z bazy na stronę, try/catch zamiast białej strony przy awarii bazy,
  • zapytania z aliasami i złączeniem JOIN ... ON.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i obraz z kodem pochodzą z prawdziwego archiwum pliki8, zapytania zostały wykonane na tej bazie w MariaDB z XAMPP-a, a podgląd strony PHP to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2 - plik harmonogram.txt pokazany w krokach to dokładnie to, co zapisał skrypt.

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

  1. Etap 1: Baza danych

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

    Krok 1: Baza danych firma

    ok. 2 min
    Polecenie z arkusza

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

    Archiwum pliki8.7z rozpakuj hasłem SzkoL3Nia% do folderu z numerem zdającego: firma.sql i obraz kod.jpg. W phpMyAdmin Nowa → firma, porównywanie utf8_unicode_ci, albo zapytaniem:

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

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

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

    ok. 2 min
    Polecenie z arkusza

    Do utworzonej bazy zaimportuj tabele z pliku firma.sql z rozpakowanego archiwum.

    Import → firma.sql. Cztery tabele z ilustracji 1: trenerzy (6) → szkolenia (18, klucz obcy Id_trenera) → zapisy (Id_szkolenia) ← sluchacze (Id_klienta). Nazwy kolumn zaczynają się wielką literą (Data, Temat, Imie) - w zapytaniach i w PHP pisz je tak samo.

  3. Krok 3: Zrzut ekranu import.jpg

    Kryteria:

    R.1.1 Wykonany import tabel do bazy danych firma , co jest udokumentowane w pliku import w formacie JPEG

    ok. 2 min
    Polecenie z arkusza

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

    Cały ekran z paskiem zadań. Uwaga na formaty: import w JPEG (import.jpg), zrzuty zapytań w PNG.

    Typowe błędy

    Wykadrowany zrzut albo zrzut importu zapisany jako PNG to strata R.1.1.

  4. Krok 4: Zapytanie 1: szkolenia według daty

    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 daty i tematy szkoleń posortowane rosnąco według daty szkolenia. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT Data, Temat FROM szkolenia ORDER BY Data; (opcjonalnie ASC ) oraz wynik działania udokumentowany zrzutem informującym o wybraniu dokładnie 18 rekordów, pierwszym jest 2021-09-20 Podstawy języka C++; nie wszystkie rekordy muszą być widoczne

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie daty i tematy szkoleń posortowane rosnąco według daty szkolenia.

    Dwie kolumny i ORDER BY Data - rosnąco jest domyślnie (ASC można dopisać dla czytelności). Klauzule w osobnych liniach czyta się łatwiej niż jeden długi wiersz - MySQL nie zwraca uwagi na nowe linie. Wynik: 18 wierszy, pierwszy 2021-09-20 „Podstawy języka C++”. Zapytania zapisuj w kwerendy.txt, zrzut jako kw1.png. To zapytanie wyśle skrypt PHP.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT Data, Temat
    FROM szkolenia
    ORDER BY Data;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: szkolenia z trenerami

    Kryteria:

    R.1.4 Wykonane zapytanie 2 wybierające jedynie daty i tematy szkoleń oraz odpowiadające im nazwiska i imiona trenerów. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT Data, Temat, Nazwisko, Imie FROM szkolenia JOIN trenerzy ON trenerzy.Id = szkolenia.Id_trenera; (możliwe również INNER JOIN lub porównanie kluczy po WHERE ) oraz wynik działania udokumentowany zrzutem informującym o wybraniu dokładnie 18 rekordów; nie wszystkie rekordy muszą być widoczne

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie daty i tematy szkoleń oraz odpowiadające im nazwiska i imiona trenerów. Należy posłużyć się relacją.

    Aliasy s (szkolenia) i t (trenerzy): przy każdej kolumnie widać, z której tabeli pochodzi, a ON t.Id = s.Id_trenera pokazuje parę kluczy relacji. Kolumna Id jest w obu tabelach, więc bez przedrostka byłaby niejednoznaczna. Wynik: 18 wierszy - każde szkolenie z jednym trenerem. Zrzut: kw2.png.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT Data, Temat
    FROM szkolenia
    ORDER BY Data;
    
    -- Zapytanie 2
    SELECT s.Data, s.Temat, t.Nazwisko, t.Imie
    FROM szkolenia AS s
    JOIN trenerzy AS t ON t.Id = s.Id_trenera;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: liczba szkoleń każdego trenera

    Kryteria:

    R.1.5 Wykonane zapytanie 3 wybierające dla każdego trenera jedynie jego imię i nazwisko oraz zliczające liczbę jego szkoleń. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT Imie, Nazwisko, COUNT(*) FROM trenerzy JOIN szkolenia ON trenerzy.Id = Id_trenera GROUP BY trenerzy.Id; (po GROUP BY też poprawne pole Nazwisko ) oraz wynik działania udokumentowany zrzutem, na którym znajdują się jedynie rekordy: Tomasz Duda 2, Mateusz Kossakowski 3, Sebastian Królik 3, Maria Przepiórkowska 5, Nikodem Ptak 3, Kinga Stopyra 2

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające dla każdego trenera jedynie jego imię i nazwisko oraz zliczające liczbę jego szkoleń. Należy posłużyć się relacją.

    • JOIN ... ON s.Id_trenera = t.Id - każde szkolenie przy swoim trenerze,
    • GROUP BY t.Id, t.Imie, t.Nazwisko - jedna grupa na trenera; klucz t.Id rozdziela dwóch trenerów o tym samym nazwisku, a imię i nazwisko na liście grupowania sprawiają, że zapytanie przejdzie też na serwerze z trybem ONLY_FULL_GROUP_BY (w nowszych MySQL włączonym domyślnie),
    • COUNT(*) AS z nazwą w odwrotnych apostrofach - ze spacją i polską literą.

    Wynik: Duda 2, Kossakowski 3, Królik 3, Przepiórkowska 5, Ptak 3, Stopyra 2 - jak w kluczu. Zrzut: kw3.png.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT Data, Temat
    FROM szkolenia
    ORDER BY Data;
    
    -- Zapytanie 2
    SELECT s.Data, s.Temat, t.Nazwisko, t.Imie
    FROM szkolenia AS s
    JOIN trenerzy AS t ON t.Id = s.Id_trenera;
    
    -- Zapytanie 3
    SELECT t.Imie, t.Nazwisko, COUNT(*) AS `Liczba szkoleń`
    FROM trenerzy AS t
    JOIN szkolenia AS s ON s.Id_trenera = t.Id
    GROUP BY t.Id, t.Imie, t.Nazwisko;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: nowa nazwa kolumny

    Kryteria:

    R.1.6 Wykonane zapytanie 4 zmieniające w tabeli zapisy nazwę kolumny id_klienta na id_sluchacza. W pliku z kwerendami lub na zrzucie istnieje zapis np.: ALTER TABLE zapisy CHANGE COLUMN Id_klienta Id_sluchacza INT; oraz wynik działania udokumentowany zrzutem, na którym jest widoczny komunikat o pomyślnie wykonanej kwerendzie lub widoczna zmieniona struktura tabeli

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 4: zmieniające w tabeli zapisy nazwę kolumny Id_klienta na Id_sluchacza.

    Tu nowoczesna składnia nie zadziała: RENAME COLUMN Id_klienta TO Id_sluchacza pojawiło się w MariaDB 10.5, a XAMPP ma 10.4 - kończy się błędem składni 1064 (sprawdzone). Zostaje CHANGE COLUMN stara nowa typ, w którym typ trzeba podać na nowo - przepisz go z budowy tabeli (INT(11) DEFAULT NULL), żeby kolumna nie zmieniła niczego poza nazwą. Zrzut kw4.png: komunikat albo struktura tabeli z kolumną Id_sluchacza.

    Typowe błędy

    CHANGE COLUMN Id_klienta Id_sluchacza bez typu to błąd składni - typ jest w CHANGE obowiązkowy.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT Data, Temat
    FROM szkolenia
    ORDER BY Data;
    
    -- Zapytanie 2
    SELECT s.Data, s.Temat, t.Nazwisko, t.Imie
    FROM szkolenia AS s
    JOIN trenerzy AS t ON t.Id = s.Id_trenera;
    
    -- Zapytanie 3
    SELECT t.Imie, t.Nazwisko, COUNT(*) AS `Liczba szkoleń`
    FROM trenerzy AS t
    JOIN szkolenia AS s ON s.Id_trenera = t.Id
    GROUP BY t.Id, t.Imie, t.Nazwisko;
    
    -- Zapytanie 4
    ALTER TABLE zapisy
    CHANGE COLUMN Id_klienta Id_sluchacza INT(11) DEFAULT NULL;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strony HTML

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

    Krok 8: Baner: gradient, kod w perspektywie, napis

    Kryteria: , ,

    R.2.1 Utworzony plik o nazwie baner.jpg o szerokości 1000 px, wysokości 150 px i tle - gradiencie od koloru czarnego po lewej do białego po prawej stronie

    R.2.2 Na grafice banera po lewej stronie jest widoczny fragment kodu, widać jedynie dwie sekcje case. Fragment jest transponowany tak, że widać perspektywę

    R.2.3 Na grafice banera po prawej stronie jest umieszczony tekst o treści "Firma szkoleniowa" w czarnym kolorze

    ok. 8 min
    Polecenie z arkusza

    Na potrzeby strony należy przygotować grafikę z banerem o nazwie baner.jpg i cechach: szerokość grafiki 1000 px, wysokość 150 px, tło baneru w formie gradientu od czarnego koloru z lewej do białego przy prawej krawędzi banera. Na pierwszym planie po lewej stronie umieszczona grafika z pliku kod.jpg. Grafika jest skalowana – widoczne dokładnie dwie sekcje case oraz jest transformowana jak na ilustracji 2, tworząc efekt perspektywy. Na pierwszym planie po prawej stronie umieszczony napis o treści „Firma szkoleniowa” w kolorze czarnym.

    Grafika jest taka sama w obu wersjach rozwiązania. W GIMP-ie, warstwa po warstwie:

    1. Plik → Nowy 1000 × 150; Gradient od czarnego (pierwszy plan) do białego (tło), przeciągnięty od lewej do prawej krawędzi z wciśniętym Ctrl,
    2. Plik → Otwórz jako warstwy kod.jpg; zaznaczeniem prostokątnym zostaw dokładnie dwie sekcje case ('+' i '-') i przeskaluj warstwę, żeby zmieściła się po lewej,
    3. Perspektywa (Shift+P): prawe narożniki do środka - kod „ucieka” w głąb jak na ilustracji 2,
    4. Tekst: „Firma szkoleniowa”, czarny, po prawej,
    5. Eksportuj jako baner.jpg.
    Typowe błędy

    Kadr z kawałkiem trzeciej sekcji (case '*') nie spełnia „dokładnie dwie sekcje case” (R.2.2).

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

    Kryteria:

    R.2.4 Witryna internetowa zapisana w plikach index.html i szkolenia.php oraz zapisany jawnie standard kodowania polskich znaków, deklaracja HTML5 <!DOCTYPE html>, zadeklarowany język witryny: polski np. <html lang="pl">. Strona zawiera sekcje head i body

    ok. 2 min
    Polecenie z arkusza

    Składa się z dwóch stron o nazwach index.html i szkolenia.php. Cechy wspólne dla obu stron: Zapisane w języku HTML5. Zadeklarowany polski język zawartości witryny. Jawnie zastosowany właściwy standard kodowania polskich znaków.

    Szkielet od razu z lang="pl" w <html> i <meta charset="UTF-8"> jako pierwszym elementem <head> - przeglądarka powinna poznać kodowanie, zanim przeczyta jakikolwiek polski znak (R.2.4). Najpierw powstaje index.html, a szkolenia.php będzie jej kopią z innym blokiem głównym.

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

    Kryteria: ,

    R.2.5 Nadany stronie tytuł: "Firma szkoleniowa"

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

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Firma szkoleniowa”. Arkusz stylów w pliku o nazwie style.css prawidłowo połączony z kodem strony.

    <title> (R.2.5), <link> do style.css - całe formatowanie z tego pliku (R.3.4) - i viewport, żeby telefon nie pomniejszał strony do miniatury.

    index.html HTML zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  11. Krok 11: Kontener i bloki semantyczne

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

    Strona składa się z kontenera, a w nim podział na bloki: nagłówkowy, poniżej obok siebie menu i główny, na dole stopka. Podział zrealizowany za pomocą semantycznych znaczników sekcji języka HTML5 tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z ilustracją 2.

    Kontener to zwykły <div class="container"> - nie niesie żadnego znaczenia, tylko obejmuje stronę, więc semantyczny znacznik nie byłby tu prawdziwszy. W środku bloki o znaczeniu: <header>, <nav> (menu), <main> i <footer> (R.2.6).

    Klasa zamiast identyfikatora: kontener może się pojawić na stronie więcej niż raz, a klasa ma w CSS niższą „wagę” niż #id, więc łatwiej ją potem nadpisać. Rozmieszczenie bloków zrobi siatka CSS.

    index.html HTML zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
            </header>
            <nav>
            </nav>
            <main>
            </main>
            <footer>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  12. Krok 12: Nagłówek: baner

    Kryteria:

    R.2.8 W bloku nagłówkowym umieszczony obraz banera z tekstem alternatywnym "Szkolenia"

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego: obraz z pliku baner.jpg z tekstem alternatywnym „Szkolenia”.

    Obraz z tekstem alternatywnym z arkusza (R.2.8). HTML5 nie potrzebuje ukośnika zamykającego /> przy <img>.

    index.html HTML zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Szkolenia">
            </header>
            <nav>
            </nav>
            <main>
            </main>
            <footer>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  13. Krok 13: Menu: lista z dwoma odnośnikami

    Kryteria: , ,

    R.2.9 W bloku menu umieszczona jedna lista punktowana z dwoma elementami, zastosowane znaczniki <ul> i <li>

    R.3.2 Po kliknięciu na odnośnik "Szkolenia" otwiera się strona szkolenia.php

    R.3.3 Po kliknięciu na odnośnik "Strona główna" otwiera się strona index.html

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku menu: Lista punktowana (nieuporządkowana) z dwoma elementami: odnośnik do pliku index.html o treści „Strona główna” i odnośnik do pliku szkolenia.php o treści „Szkolenia”.

    <ul> z dwoma <li>, w każdym odnośnik (R.2.9, R.3.2, R.3.3). aria-label nazywa menu dla czytnika ekranu - przy kilku blokach <nav> na stronie użytkownik niewidomy wie, który jest który. Strona szkolenia.php działa tylko przez serwer: otwieraj witrynę przez http://localhost/..., nie dwuklikiem z dysku.

    index.html HTML zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Szkolenia">
            </header>
            <nav aria-label="Menu główne">
                <ul>
                    <li><a href="index.html">Strona główna</a></li>
                    <li><a href="szkolenia.php">Szkolenia</a></li>
                </ul>
            </nav>
            <main>
            </main>
            <footer>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  14. Krok 14: Stopka: adres i autor

    Kryteria:

    R.2.7 W stopce umieszczony <h2>: „Firma szkoleniowa, ul. Główna 1, 23-456 Warszawa” oraz paragraf z numerem zdającego. Na stronie głównej w bloku głównym umieszczony <h1>: „Zapraszamy do udziału w szkoleniach z branży IT” z dwoma łamaniami wiersza

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku stopki: Nagłówek drugiego stopnia zawierający napis „Firma szkoleniowa, ul. Główna 1, 23-456 Warszawa”. Akapit (paragraf) zawierający napis „Autor: ”, dalej wstawiony numer zdającego.

    <h2> i <p>. Na egzaminie po dwukropku wpisujesz swój numer zdającego (PESEL) - tu stoi imię i nazwisko autora rozwiązania.

    index.html HTML zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Szkolenia">
            </header>
            <nav aria-label="Menu główne">
                <ul>
                    <li><a href="index.html">Strona główna</a></li>
                    <li><a href="szkolenia.php">Szkolenia</a></li>
                </ul>
            </nav>
            <main>
            </main>
            <footer>
                <h2>Firma szkoleniowa, ul. Główna 1, 23-456 Warszawa</h2>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  15. Krok 15: Blok główny: nagłówek w trzech wierszach

    Kryteria:

    R.2.7 W stopce umieszczony <h2>: „Firma szkoleniowa, ul. Główna 1, 23-456 Warszawa” oraz paragraf z numerem zdającego. Na stronie głównej w bloku głównym umieszczony <h1>: „Zapraszamy do udziału w szkoleniach z branży IT” z dwoma łamaniami wiersza

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku głównego dla strony index.html: Nagłówek pierwszego stopnia o treści „Zapraszamy do udziału w szkoleniach z branży IT”. Łamanie wiersza po słowie „udziału” oraz po słowie „szkoleniach”.

    Łamanie wiersza wewnątrz nagłówka to <br> - dwa razy, dokładnie po wskazanych słowach (R.2.7). Tu <br> jest na miejscu: to część treści (podział tytułu), a nie sposób na odstęp między blokami.

    index.html HTML zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Szkolenia">
            </header>
            <nav aria-label="Menu główne">
                <ul>
                    <li><a href="index.html">Strona główna</a></li>
                    <li><a href="szkolenia.php">Szkolenia</a></li>
                </ul>
            </nav>
            <main>
                <h1>Zapraszamy do udziału<br>w szkoleniach<br>z branży IT</h1>
            </main>
            <footer>
                <h2>Firma szkoleniowa, ul. Główna 1, 23-456 Warszawa</h2>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.html)
  16. Krok 16: Strona szkolenia.php

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku głównego dla strony szkolenia.php: Efekt działania skryptu.

    Zapisz index.html jako szkolenia.php i opróżnij blok główny - wypełni go skrypt. Na razie w pliku nie ma PHP, więc podgląd pokazuje go wprost.

    szkolenia.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Szkolenia">
            </header>
            <nav aria-label="Menu główne">
                <ul>
                    <li><a href="index.html">Strona główna</a></li>
                    <li><a href="szkolenia.php">Szkolenia</a></li>
                </ul>
            </nav>
            <main>
            </main>
            <footer>
                <h2>Firma szkoleniowa, ul. Główna 1, 23-456 Warszawa</h2>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (szkolenia.php)
  17. Etap 3: Styl CSS

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

    Krok 17: Plik style.css: kolory w zmiennych i krój czcionki

    Kryteria:

    R.4.1 Ustawiony styl domyślny dla wszystkich selektorów (*): krój czcionki Tahoma

    ok. 2 min
    Polecenie z arkusza

    Domyślnie dla wszystkich selektorów: krój czcionki Tahoma.

    Najpierw cztery kolory z arkusza jako zmienne CSS w :root: pomarańczowy pojawia się w kontenerze i w elementach listy, grafitowy w menu i w odnośnikach. Zmiana odcienia to potem jedna linia, a nazwa --graphite mówi więcej niż #232323. W regułach kolor wstawia się przez var(--orange) - przeglądarka i tak liczy z niego ten sam kolor, który egzaminator zobaczy w narzędziach deweloperskich.

    Potem selektor * z krojem Tahoma (R.4.1).

    style.css CSS nowy plik
    :root {
        --orange: #FB8E02;
        --orange-dark: #B36501;
        --graphite: #232323;
        --blue: #AEC6E6;
    }
    
    * {
        font-family: Tahoma;
    }
    Podgląd strony po tym kroku (index.html)
  18. Krok 18: Kontener: tło i siatka z dwiema kolumnami

    Kryteria: , ,

    R.3.1 Układ bloków na stronie po uruchomieniu w przeglądarce jest zgodny z ilustracją 2 w arkuszu egzaminacyjnym (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.2 Ustawiony kolor tła #FB8E02 dla kontenera całej strony oraz selektora li, #232323 dla bloku menu, #AEC6E6 dla bloku głównego

    R.4.4 Ustawiona szerokość 20% dla bloku menu, 80% dla bloku głównego (width lub grid-template- columns lub flex ) oraz 100% dla obrazu, 95% dla elementu listy

    ok. 3 min
    Polecenie z arkusza

    Dla kontenera głównego strony: kolor tła #FB8E02.

    Tło kontenera z arkusza (R.4.2) i od razu cały układ strony:

    • display: grid i grid-template-columns: 20% 80% - dwie kolumny; to szerokości menu i bloku głównego, które klucz R.4.4 przyjmuje zapisane właśnie tu („width lub grid-template-columns lub flex”),
    • header, footer { grid-column: 1 / -1; } - nagłówek i stopka od pierwszej do ostatniej linii siatki, czyli na całą szerokość; -1 znaczy „ostatnia linia”, więc reguła zadziała też przy innej liczbie kolumn,
    • menu i blok główny wpadają do kolumn same, w kolejności z HTML-u - obok siebie, pod nagłówkiem (R.3.1).

    Bez float nie trzeba clear w stopce: siatka nie wyjmuje elementów z przepływu.

    style.css CSS zmienione: 11 linii
    :root {
        --orange: #FB8E02;
        --orange-dark: #B36501;
        --graphite: #232323;
        --blue: #AEC6E6;
    }
    
    * {
        font-family: Tahoma;
    }
    
    .container {
        background-color: var(--orange);
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header,
    footer {
        grid-column: 1 / -1;
    }
    Podgląd strony po tym kroku (index.html)
  19. Krok 19: Menu

    Kryteria: ,

    R.4.2 Ustawiony kolor tła #FB8E02 dla kontenera całej strony oraz selektora li, #232323 dla bloku menu, #AEC6E6 dla bloku głównego

    R.4.5 Ustawiona wysokość 450px dla bloków menu i głównego

    ok. 1 min
    Polecenie z arkusza

    Dla bloku menu: kolor tła #232323, szerokość 20%, wysokość 450 px.

    Grafitowe tło (R.4.2) i wysokość 450 px (R.4.5). Szerokości 20% nie powtarzaj w width: stoi już w kolumnie siatki, a width: 20% na elemencie siatki liczyłby się od jego kolumny - menu zajęłoby 20% z 20%.

    Typowe błędy

    width: 20% i width: 80% dopisane do bloków w siatce 20% 80% zwężają je do 4% i 64% strony - procent liczy się od kolumny siatki.

    style.css CSS zmienione: 5 linii
    :root {
        --orange: #FB8E02;
        --orange-dark: #B36501;
        --graphite: #232323;
        --blue: #AEC6E6;
    }
    
    * {
        font-family: Tahoma;
    }
    
    .container {
        background-color: var(--orange);
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header,
    footer {
        grid-column: 1 / -1;
    }
    
    nav {
        background-color: var(--graphite);
        height: 450px;
    }
    Podgląd strony po tym kroku (index.html)
  20. Krok 20: Blok główny: paski przewijania zawsze

    Kryteria: , ,

    R.4.2 Ustawiony kolor tła #FB8E02 dla kontenera całej strony oraz selektora li, #232323 dla bloku menu, #AEC6E6 dla bloku głównego

    R.4.5 Ustawiona wysokość 450px dla bloków menu i głównego

    R.4.8 Ustawione dla bloku głównego paski przewijania na scroll

    ok. 1 min
    Polecenie z arkusza

    Dla bloku głównego: kolor tła #AEC6E6, szerokość 80%, wysokość 450 px, paski przewijania zawsze widoczne.

    „Zawsze widoczne” to overflow: scroll - paski są nawet przy krótkiej treści (auto pokazałoby je tylko w razie potrzeby) (R.4.8). Tło R.4.2, wysokość R.4.5, a szerokość 80% - jak w menu - w kolumnie siatki.

    style.css CSS zmienione: 6 linii
    :root {
        --orange: #FB8E02;
        --orange-dark: #B36501;
        --graphite: #232323;
        --blue: #AEC6E6;
    }
    
    * {
        font-family: Tahoma;
    }
    
    .container {
        background-color: var(--orange);
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header,
    footer {
        grid-column: 1 / -1;
    }
    
    nav {
        background-color: var(--graphite);
        height: 450px;
    }
    
    main {
        background-color: var(--blue);
        height: 450px;
        overflow: scroll;
    }
    Podgląd strony po tym kroku (index.html)
  21. Krok 21: Stopka

    Kryteria: ,

    R.4.6 Ustawione marginesy wewnętrzne (padding) 2px dla stopki, 50px dla nagłówka stopnia pierwszego, 5px dla elementu listy, oraz margines zewnętrzny górny (margin-top) 10px dla elementu listy

    R.4.7 Ustawione wyrównanie tekstu do prawej strony dla stopki

    ok. 1 min
    Polecenie z arkusza

    Dla bloku stopki: marginesy wewnętrzne 2 px, wyrównanie tekstu do prawej strony.

    Stopka nie ma własnego tła - widać pomarańczowy kontener (R.4.6, R.4.7). To druga reguła dla footer (pierwsza ustawia miejsce w siatce): jedna mówi o układzie, druga o wyglądzie.

    style.css CSS zmienione: 5 linii
    :root {
        --orange: #FB8E02;
        --orange-dark: #B36501;
        --graphite: #232323;
        --blue: #AEC6E6;
    }
    
    * {
        font-family: Tahoma;
    }
    
    .container {
        background-color: var(--orange);
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header,
    footer {
        grid-column: 1 / -1;
    }
    
    nav {
        background-color: var(--graphite);
        height: 450px;
    }
    
    main {
        background-color: var(--blue);
        height: 450px;
        overflow: scroll;
    }
    
    footer {
        padding: 2px;
        text-align: right;
    }
    Podgląd strony po tym kroku (index.html)
  22. Krok 22: Obraz: pełna szerokość

    Kryteria:

    R.4.4 Ustawiona szerokość 20% dla bloku menu, 80% dla bloku głównego (width lub grid-template- columns lub flex ) oraz 100% dla obrazu, 95% dla elementu listy

    ok. 1 min
    Polecenie z arkusza

    Dla obrazu: szerokość 100%.

    Baner rozciąga się na szerokość nagłówka (R.4.4). display: block usuwa kilkupikselową szparę pod obrazem: obraz jest domyślnie elementem liniowym i stoi na linii pisma, pod którą przeglądarka zostawia miejsce na „ogonki” liter.

    style.css CSS zmienione: 5 linii
    :root {
        --orange: #FB8E02;
        --orange-dark: #B36501;
        --graphite: #232323;
        --blue: #AEC6E6;
    }
    
    * {
        font-family: Tahoma;
    }
    
    .container {
        background-color: var(--orange);
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header,
    footer {
        grid-column: 1 / -1;
    }
    
    nav {
        background-color: var(--graphite);
        height: 450px;
    }
    
    main {
        background-color: var(--blue);
        height: 450px;
        overflow: scroll;
    }
    
    footer {
        padding: 2px;
        text-align: right;
    }
    
    img {
        display: block;
        width: 100%;
    }
    Podgląd strony po tym kroku (index.html)
  23. Krok 23: Nagłówek pierwszego stopnia

    Kryteria: ,

    R.4.3 Ustawiony kolor czcionki Maroon dla selektora h1 oraz #232323 dla selektora a

    R.4.6 Ustawione marginesy wewnętrzne (padding) 2px dla stopki, 50px dla nagłówka stopnia pierwszego, 5px dla elementu listy, oraz margines zewnętrzny górny (margin-top) 10px dla elementu listy

    ok. 1 min
    Polecenie z arkusza

    Dla selektora nagłówka pierwszego stopnia: marginesy wewnętrzne 50 px, kolor czcionki Maroon.

    Selektor znacznika h1 - uwaga pod listą stylów wymaga go dla nagłówka, elementu listy i odnośnika, więc tu nie ma miejsca na klasy (R.4.3, R.4.6).

    style.css CSS zmienione: 5 linii
    :root {
        --orange: #FB8E02;
        --orange-dark: #B36501;
        --graphite: #232323;
        --blue: #AEC6E6;
    }
    
    * {
        font-family: Tahoma;
    }
    
    .container {
        background-color: var(--orange);
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header,
    footer {
        grid-column: 1 / -1;
    }
    
    nav {
        background-color: var(--graphite);
        height: 450px;
    }
    
    main {
        background-color: var(--blue);
        height: 450px;
        overflow: scroll;
    }
    
    footer {
        padding: 2px;
        text-align: right;
    }
    
    img {
        display: block;
        width: 100%;
    }
    
    h1 {
        padding: 50px;
        color: Maroon;
    }
    Podgląd strony po tym kroku (index.html)
  24. Krok 24: Lista bez punktorów i jej elementy

    Kryteria: , , ,

    R.4.2 Ustawiony kolor tła #FB8E02 dla kontenera całej strony oraz selektora li, #232323 dla bloku menu, #AEC6E6 dla bloku głównego

    R.4.4 Ustawiona szerokość 20% dla bloku menu, 80% dla bloku głównego (width lub grid-template- columns lub flex ) oraz 100% dla obrazu, 95% dla elementu listy

    R.4.6 Ustawione marginesy wewnętrzne (padding) 2px dla stopki, 50px dla nagłówka stopnia pierwszego, 5px dla elementu listy, oraz margines zewnętrzny górny (margin-top) 10px dla elementu listy

    R.4.9 Ustawiony dla listy punktowanej brak symboli punktorów oraz dla odnośnika brak podkreślenia i rozmiar czcionki 110%

    ok. 2 min
    Polecenie z arkusza

    Dla listy punktowanej: brak symboli punktorów. Dla selektora elementu listy: kolor tła #FB8E02, szerokość 95%, marginesy wewnętrzne 5 px, margines zewnętrzny – górny 10 px.

    list-style-type: none usuwa punktory (R.4.9); li stają się pomarańczowymi paskami - tym samym kolorem z var(--orange) co kontener (R.4.2, R.4.4, R.4.6). Tu width: 95% jest potrzebne: lista nie jest elementem siatki, więc procent liczy się od szerokości listy.

    style.css CSS zmienione: 11 linii
    :root {
        --orange: #FB8E02;
        --orange-dark: #B36501;
        --graphite: #232323;
        --blue: #AEC6E6;
    }
    
    * {
        font-family: Tahoma;
    }
    
    .container {
        background-color: var(--orange);
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header,
    footer {
        grid-column: 1 / -1;
    }
    
    nav {
        background-color: var(--graphite);
        height: 450px;
    }
    
    main {
        background-color: var(--blue);
        height: 450px;
        overflow: scroll;
    }
    
    footer {
        padding: 2px;
        text-align: right;
    }
    
    img {
        display: block;
        width: 100%;
    }
    
    h1 {
        padding: 50px;
        color: Maroon;
    }
    
    ul {
        list-style-type: none;
    }
    
    li {
        background-color: var(--orange);
        width: 95%;
        padding: 5px;
        margin-top: 10px;
    }
    Podgląd strony po tym kroku (index.html)
  25. Krok 25: Odnośniki i element listy pod kursorem

    Kryteria: , ,

    R.4.3 Ustawiony kolor czcionki Maroon dla selektora h1 oraz #232323 dla selektora a

    R.4.9 Ustawiony dla listy punktowanej brak symboli punktorów oraz dla odnośnika brak podkreślenia i rozmiar czcionki 110%

    R.4.10 Gdy kursor myszy znajdzie się na elemencie listy, jego tło zmienia się na #B36501

    ok. 2 min
    Polecenie z arkusza

    Dla selektora odnośnika: brak podkreślenia, kolor czcionki #232323, rozmiar czcionki 110%. Gdy kursor myszy znajdzie się na elemencie listy, jego tło zmienia się na #B36501.

    li:hover - pod kursorem ciemnieje cały pasek elementu listy, nie tylko napis (R.4.10). Kolor odnośnika to ta sama zmienna co tło menu - grafitowy napis na pomarańczowym pasku (R.4.3, R.4.9).

    style.css CSS zmienione: 10 linii
    :root {
        --orange: #FB8E02;
        --orange-dark: #B36501;
        --graphite: #232323;
        --blue: #AEC6E6;
    }
    
    * {
        font-family: Tahoma;
    }
    
    .container {
        background-color: var(--orange);
        display: grid;
        grid-template-columns: 20% 80%;
    }
    
    header,
    footer {
        grid-column: 1 / -1;
    }
    
    nav {
        background-color: var(--graphite);
        height: 450px;
    }
    
    main {
        background-color: var(--blue);
        height: 450px;
        overflow: scroll;
    }
    
    footer {
        padding: 2px;
        text-align: right;
    }
    
    img {
        display: block;
        width: 100%;
    }
    
    h1 {
        padding: 50px;
        color: Maroon;
    }
    
    ul {
        list-style-type: none;
    }
    
    li {
        background-color: var(--orange);
        width: 95%;
        padding: 5px;
        margin-top: 10px;
    }
    
    a {
        text-decoration: none;
        color: var(--graphite);
        font-size: 110%;
    }
    
    li:hover {
        background-color: var(--orange-dark);
    }
    Podgląd strony po tym kroku (index.html)
  26. Etap 4: Skrypt PHP

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

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

    Kryteria: , , ,

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

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

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

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

    ok. 4 min
    Polecenie z arkusza

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

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

    • napis DSN 'mysql:host=localhost;dbname=firma;charset=utf8mb4' - serwer, baza i kodowanie; 'root', '' - użytkownik i puste hasło (R.5.1, R.5.2),
    • ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze jako tablice z nazwami kolumn,
    • $pdo = null zamyka połączenie (PDO nie ma osobnej funkcji „zamknij”); zapytanie trafi między te dwie linie, więc zamknięcie zostanie ostatnią operacją na bazie (R.5.3),
    • try / catch (PDOException $exception) - gdy serwer bazy nie odpowiada, szczegóły idą do dziennika serwera (error_log), a strona dostaje zwykły komunikat w $message,
    • funkcja e() to krótka nazwa htmlspecialchars(..., ENT_QUOTES) - będzie przy każdej wartości z bazy.

    Nazwy po angielsku mówią, co przechowują (R.5.8). Od tego kroku podgląd to wynik prawdziwego uruchomienia.

    szkolenia.php PHP zmienione: 20 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $trainings = [];
    $message = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=firma;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $message = 'Nie udało się pobrać harmonogramu szkoleń. 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>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Szkolenia">
            </header>
            <nav aria-label="Menu główne">
                <ul>
                    <li><a href="index.html">Strona główna</a></li>
                    <li><a href="szkolenia.php">Szkolenia</a></li>
                </ul>
            </nav>
            <main>
            </main>
            <footer>
                <h2>Firma szkoleniowa, ul. Główna 1, 23-456 Warszawa</h2>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (szkolenia.php)
  27. Krok 27: Zapytanie 1 do bazy

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 1.

    $pdo->query(...) wysyła zapytanie 1, a ->fetchAll() od razu odbiera wszystkie 18 wierszy do tablicy $trainings (R.5.4). Zwykłe query wystarcza, bo w zapytaniu nie ma niczego od użytkownika; dane z formularza szłyby przez prepare i execute. Na stronie jeszcze nic nie widać - tablica czeka na wypisanie.

    szkolenia.php PHP zmienione: 1 linia
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $trainings = [];
    $message = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=firma;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $trainings = $pdo->query('SELECT Data, Temat FROM szkolenia ORDER BY Data')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $message = 'Nie udało się pobrać harmonogramu szkoleń. 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>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Szkolenia">
            </header>
            <nav aria-label="Menu główne">
                <ul>
                    <li><a href="index.html">Strona główna</a></li>
                    <li><a href="szkolenia.php">Szkolenia</a></li>
                </ul>
            </nav>
            <main>
            </main>
            <footer>
                <h2>Firma szkoleniowa, ul. Główna 1, 23-456 Warszawa</h2>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>

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

  28. Krok 28: Szkolenia w kolejnych akapitach

    Kryteria:

    R.5.5 Zwrócone zapytaniem data i temat szkolenia są wyświetlane w kolejnych paragrafach w bloku głównym

    ok. 3 min
    Polecenie z arkusza

    Zwrócone zapytaniem data i temat szkolenia są oddzielone spacją i wyświetlane jeden pod drugim w osobnych paragrafach w bloku głównym – ilustracja 3.

    Dwie części:

    • nad HTML-em array_map zamienia każdy wiersz w napis „data temat” - funkcja strzałkowa fn(array $training): string => ... mówi, co przyjmuje i co zwraca. Lista $lines przyda się drugi raz w kolejnym kroku, przy zapisie pliku,
    • w bloku głównym pętla foreach ($lines as $line): ... endforeach; w składni alternatywnej, a w niej <p> z linią przepuszczoną przez e() - <?= ... ?> to skrót od <?php echo ... ?>.

    Akapity są blokowe, więc stoją jeden pod drugim. Podgląd: 18 szkoleń od 2021-09-20 „Podstawy języka C++” i paski przewijania bloku głównego - jak na ilustracji 3 (R.5.5).

    szkolenia.php PHP zmienione: 5 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $trainings = [];
    $message = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=firma;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $trainings = $pdo->query('SELECT Data, Temat FROM szkolenia ORDER BY Data')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $message = 'Nie udało się pobrać harmonogramu szkoleń. Spróbuj ponownie za chwilę.';
    }
    
    $lines = array_map(fn(array $training): string => $training['Data'] . ' ' . $training['Temat'], $trainings);
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Szkolenia">
            </header>
            <nav aria-label="Menu główne">
                <ul>
                    <li><a href="index.html">Strona główna</a></li>
                    <li><a href="szkolenia.php">Szkolenia</a></li>
                </ul>
            </nav>
            <main>
                <?php foreach ($lines as $line): ?>
                    <p><?= e($line) ?></p>
                <?php endforeach; ?>
            </main>
            <footer>
                <h2>Firma szkoleniowa, ul. Główna 1, 23-456 Warszawa</h2>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (szkolenia.php)
  29. Krok 29: Plik harmonogram.txt jednym poleceniem

    Kryteria: ,

    R.5.6 Skrypt tworzy / otwiera do zapisu plik harmonogam.txt

    R.5.7 Zwrócone zapytaniem rekordy danych są zapisywane jako osobne linie w utworzonym pliku. Należy sprawdzić działanie na pliku

    ok. 3 min
    Polecenie z arkusza

    Dodatkowo zwrócone zapytaniem wiersze są zapisywane jako osobne linie w pliku tekstowym o nazwie harmonogram.txt.

    file_put_contents(plik, treść) sam tworzy plik (albo czyści istniejący), zapisuje treść i go zamyka - to samo, co fopen z trybem "w", pętla fwrite i fclose, ale bez ryzyka, że plik zostanie otwarty (R.5.6, R.5.7):

    • treść to te same linie co w akapitach, sklejone implode(PHP_EOL, $lines) - PHP_EOL to znak końca linii systemu serwera (na Windows \r\n), dopisany też po ostatniej linii,
    • __DIR__ . '/harmonogram.txt' - plik zawsze obok skryptu, niezależnie od tego, z jakiego katalogu serwer go uruchomił,
    • LOCK_EX blokuje plik na czas zapisu, gdy dwie osoby otworzą stronę w tej samej chwili,
    • wynik false oznacza błąd zapisu (np. brak uprawnień do folderu) - wtedy komunikat w $message,
    • warunek $lines !== [] chroni plik przed nadpisaniem pustą treścią, gdy baza nie odpowie.

    Obok kodu widać plik dokładnie taki, jaki zapisał skrypt: 18 linii - te same co w wersji podstawowej.

    harmonogram.txt Tekst nowy plik
    2021-09-20 Podstawy języka C++
    2021-09-30 Photoshop dla zaawansowanych
    2021-10-01 Podstawy języka Python
    2021-10-02 Systemy operacyjne
    2021-10-07 Aplikacje Webowe
    2021-10-15 Python dla zaawansowanych
    2021-10-21 Techologia druku 3D
    2021-10-22 PHP - poziom podstawowy
    2021-11-10 Animacje komputerowe
    2021-11-15 Administracja systemem Linux
    2021-11-21 PHP - poziom średniozaawansowany
    2021-11-28 HTML i CSS dla zaawansowanych
    2021-11-30 Programowanie obiektowe w C++
    2021-12-06 Windows Serwer - usługi
    2021-12-07 PHP - poziom zaawansowany
    2021-12-10 C++ dla zaawansowanych
    2021-12-15 Obsługa drukarek 3D
    2021-12-16 Obsługa programu GIMP
    szkolenia.php PHP zmienione: 7 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $trainings = [];
    $message = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=firma;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $trainings = $pdo->query('SELECT Data, Temat FROM szkolenia ORDER BY Data')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $message = 'Nie udało się pobrać harmonogramu szkoleń. Spróbuj ponownie za chwilę.';
    }
    
    $lines = array_map(fn(array $training): string => $training['Data'] . ' ' . $training['Temat'], $trainings);
    
    if ($lines !== []) {
        $saved = file_put_contents(__DIR__ . '/harmonogram.txt', implode(PHP_EOL, $lines) . PHP_EOL, LOCK_EX);
        if ($saved === false) {
            $message = 'Nie udało się zapisać pliku harmonogram.txt.';
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Szkolenia">
            </header>
            <nav aria-label="Menu główne">
                <ul>
                    <li><a href="index.html">Strona główna</a></li>
                    <li><a href="szkolenia.php">Szkolenia</a></li>
                </ul>
            </nav>
            <main>
                <?php foreach ($lines as $line): ?>
                    <p><?= e($line) ?></p>
                <?php endforeach; ?>
            </main>
            <footer>
                <h2>Firma szkoleniowa, ul. Główna 1, 23-456 Warszawa</h2>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>

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

  30. Krok 30: Komunikat, gdy baza albo zapis zawiedzie

    ok. 3 min
    Polecenie z arkusza

    Napisany w języku PHP, działa na stronie szkolenia.php.

    Pod akapitami wiersz z komunikatem z $message - tylko wtedy, gdy catch albo nieudany zapis pliku coś do niego wpisały. Sprawdzone uruchomieniem z błędną nazwą bazy: strona się wyświetla, w bloku głównym stoi „Nie udało się pobrać harmonogramu szkoleń…”, a w treści nie ma ani nazwy bazy, ani kodu błędu SQL. Bez try/catch PHP przerwałby stronę w połowie komunikatem „Fatal error: Uncaught PDOException” razem ze ścieżką pliku na serwerze.

    W normalnym działaniu połączenie jest zamykane zaraz po zapytaniu - jak chce arkusz, na końcu pracy z bazą. Podgląd: strona po prawdziwym uruchomieniu, bez komunikatu.

    harmonogram.txt Tekst zmienione: 0 linii
    2021-09-20 Podstawy języka C++
    2021-09-30 Photoshop dla zaawansowanych
    2021-10-01 Podstawy języka Python
    2021-10-02 Systemy operacyjne
    2021-10-07 Aplikacje Webowe
    2021-10-15 Python dla zaawansowanych
    2021-10-21 Techologia druku 3D
    2021-10-22 PHP - poziom podstawowy
    2021-11-10 Animacje komputerowe
    2021-11-15 Administracja systemem Linux
    2021-11-21 PHP - poziom średniozaawansowany
    2021-11-28 HTML i CSS dla zaawansowanych
    2021-11-30 Programowanie obiektowe w C++
    2021-12-06 Windows Serwer - usługi
    2021-12-07 PHP - poziom zaawansowany
    2021-12-10 C++ dla zaawansowanych
    2021-12-15 Obsługa drukarek 3D
    2021-12-16 Obsługa programu GIMP
    szkolenia.php PHP zmienione: 3 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $trainings = [];
    $message = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=firma;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $trainings = $pdo->query('SELECT Data, Temat FROM szkolenia ORDER BY Data')->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $message = 'Nie udało się pobrać harmonogramu szkoleń. Spróbuj ponownie za chwilę.';
    }
    
    $lines = array_map(fn(array $training): string => $training['Data'] . ' ' . $training['Temat'], $trainings);
    
    if ($lines !== []) {
        $saved = file_put_contents(__DIR__ . '/harmonogram.txt', implode(PHP_EOL, $lines) . PHP_EOL, LOCK_EX);
        if ($saved === false) {
            $message = 'Nie udało się zapisać pliku harmonogram.txt.';
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Firma szkoleniowa</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Szkolenia">
            </header>
            <nav aria-label="Menu główne">
                <ul>
                    <li><a href="index.html">Strona główna</a></li>
                    <li><a href="szkolenia.php">Szkolenia</a></li>
                </ul>
            </nav>
            <main>
                <?php foreach ($lines as $line): ?>
                    <p><?= e($line) ?></p>
                <?php endforeach; ?>
                <?php if ($message !== ''): ?>
                    <p><strong><?= e($message) ?></strong></p>
                <?php endif; ?>
            </main>
            <footer>
                <h2>Firma szkoleniowa, ul. Główna 1, 23-456 Warszawa</h2>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (szkolenia.php)
  31. Etap 5: Porównanie z wersją podstawową

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

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

    ok. 2 min

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

    wersja podstawowawersja nowoczesna
    układfloat: left + clear: bothgrid-template-columns: 20% 80% w kontenerze
    kolorywpisane w każdej regulezmienne CSS w :root
    bazamysqli_connect, mysqli_fetch_row, $row[0]PDO z wyjątkami, fetchAll, $training['Data']
    gdzie PHPw bloku głównym, połączenie zamykane na końcu blokudane nad HTML-em, połączenie zamknięte przed stroną
    plikfopen, fwrite w pętli, fclosefile_put_contents z kontrolą wyniku i blokadą
    dane na stroniewprost (w dobrych praktykach htmlspecialchars)e() przy każdej wartości
    awaria bazybiała strona z błędemkomunikat w bloku głównym, szczegóły w error_log

    Na egzaminie wersja podstawowa trzyma się dosłownie funkcji z tabeli 1 arkusza i łatwo ją sprawdzić krok po kroku z kluczem (fopen - R.5.6, fwrite - R.5.7). Nowoczesna pisze się nie dłużej, ale wymaga pewności, że file_put_contents i $pdo = null to te same operacje innymi słowami. Czego tu świadomie nie ma: pliku config.php z danymi logowania (egzaminator szuka połączenia w szkolenia.php) i zapytań przygotowanych - strona nie przyjmuje żadnych danych od użytkownika.

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

  32. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Umieść go w folderze z numerem zdającego. Nagraj płytę z rezultatami pracy. W folderze z numerem zdającego, powinny znajdować się pliki: baner.jpg, import.jpg, index.html, kw1.png, kw2.png, kw3.png, kw4.png, kwerendy.txt, przeglądarka.txt, style.css, szkolenia.php, ewentualnie inne przygotowane pliki.

    Przed nagraniem otwórz szkolenia.php przez http://localhost/..., żeby skrypt utworzył harmonogram.txt - egzaminator sprawdza ten plik (R.5.7). W przeglądarka.txt wpisz nazwę przeglądarki, nagraj płytę i sprawdź jej odczyt.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

Podgląd

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

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