Przejdź do głównej treści
INF.03-11-26.01-SGStyczeń 2026 · 46 kroków · ok. 91 min · 39 kryteriów z klucza

Rozwiązanie krok po kroku: Portal kina KinoTEKA (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza: portal kina KinoTEKA z bazą kino (relacja n:m aktorzy - filmy), stroną index.php z okrągłymi awatarami wszystkich aktorów i stroną aktor.php z jednym aktorem i liczbą jego filmów. Obie wersje spełniają wszystkie 39 kryteriów klucza - różni je sposób pisania kodu, nie wynik.

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

  • siatka CSS zamiast float + clear i inline-block: kolumny 40% 60% na <body>, a bloki aktorów w siatce repeat(auto-fill, 15em), kolory i szerokość karty w zmiennych CSS,
  • blok aktora sam jest odnośnikiem (<a class="actor">) zamiast <div> owiniętego w <a>, awatary zawsze okrągłe dzięki aspect-ratio i object-fit,
  • zapytania z jawną listą pól i aliasami tabel,
  • PHP na PDO: w obu plikach dane pobierane na górze, połączenie zamknięte przed HTML-em, w HTML tylko pętla foreach i warunki, imię i nazwisko z funkcji fullName(),
  • identyfikator z adresu sprawdzony filter_var i wstawiony do zapytań przygotowanych, komunikat dla nieistniejącego aktora, wartości z bazy przez escape().

Kroki nadal idą zdanie po zdaniu za treścią arkusza. Podgląd w każdym kroku z PHP to wynik prawdziwego uruchomienia kodu z tego kroku w PHP 8.2 na bazie z arkusza; aktor.php pokazuje Pinto Teixeirę (id 10) z ilustracji 4, a w kroku z warunkiem - Lorenzo Di Romano (id 8), który nie gra w żadnym filmie. Baza i awatary pochodzą z prawdziwego archiwum pliki17 (załącznik do arkusza, hasło w treści zadania).

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

  1. Etap 1: Baza danych

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

    Krok 1: Baza danych kino

    ok. 2 min
    Polecenie z arkusza

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

    Archiwum pliki17.7z ma hasło @ktorzy+f1Lmy (małpa zamiast „a”, cyfra 1 zamiast „i”). W phpMyAdmin: Nowa, nazwa kino, porównywanie utf8_unicode_ci (podpowiedź z arkusza), albo w zakładce SQL:

    CREATE DATABASE kino CHARACTER SET utf8 COLLATE utf8_unicode_ci;
    

    Tabele z kino.sql mają w definicji własne porównywanie utf8mb4_unicode_ci i to ono działa w zapytaniu 3: „Élodie” i „Jímenez” sortują się jak zwykłe „E” i „i”. Ustawienie bazy dziedziczą tylko tabele zakładane później bez własnego ustawienia.

    Typowe błędy

    Skrypty łączą się z bazą po nazwie - literówka w kino zabiera także punkt za połączenie (R.5.1).

  2. Krok 2: Import danych z pliku kino.sql

    ok. 2 min
    Polecenie z arkusza

    Z rozpakowanego archiwum zaimportuj do utworzonej bazy dane z pliku kino.sql

    Zaznacz bazę kino, potem Import → kino.sql → Wykonaj. Trzy tabele z ilustracji 1: aktorzy (10 aktorów, pole plik_awatara), filmy (15 filmów, czas_trwania w minutach) i filmy_aktorzy - same pary identyfikatorów, czyli relacja n:m.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonany import do bazy danych kino - plik import.png

    ok. 2 min
    Polecenie z arkusza

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

    Print Screen (cały ekran z paskiem zadań), wklej do edytora grafiki i zapisz jako import.png. Zrzuty zapytań są w formacie JPEG - tylko import idzie do PNG.

    Typowe błędy

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

  4. Krok 4: Zapytanie 1: filmy trwające co najmniej 100 minut

    Kryteria: ,

    R.1.2 W pliku kwerendy.txt zapisane co najmniej jedno zapytanie SQL, wynikające z treści zadania

    R.1.3 Wykonane zapytanie 1 wybierające jedynie pola id_filmu, tytul, rok_produkcji, czas_trwania z tabeli filmy dla filmów, które trwają nie krócej niż 100 minut. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id_filmu, tytul, rok_produkcji, czas_trwania FROM filmy WHERE czas_trwania >= 100; oraz na zrzucie jest dokładnie 11 rekordów

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie identyfikator, tytuł, rok produkcji oraz czas trwania, dla filmów które trwają nie krócej niż 100 minut

    • SELECT id_filmu, tytul, rok_produkcji, czas_trwania - cztery pola, bez reżysera,
    • WHERE czas_trwania >= 100 - „nie krócej niż 100 minut” to większe lub równe, więc film trwający równo 100 minut też jest w wyniku.

    Wynik: 11 wierszy. Każda klauzula w osobnej linii - SQL nie zwraca uwagi na znaki nowej linii. Zapisz w kwerendy.txt, zrzut jako kw1.jpg.

    Typowe błędy

    czas_trwania > 100 gubi film trwający równo 100 minut - 10 wierszy zamiast 11.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT id_filmu, tytul, rok_produkcji, czas_trwania
    FROM filmy
    WHERE czas_trwania >= 100;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: aktor o identyfikatorze 3

    Kryteria:

    R.1.4 Wykonane zapytanie 2 wybierające jedynie pola imie, nazwisko, plik_awatara z tabeli aktorzy dla aktora, którego identyfikator to 3. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT imie, nazwisko, plik_awatara FROM aktorzy WHERE id_aktora = 3; oraz na zrzucie jest dokładnie 1 wiersz przedstawiający dane: Jerzy, Pasek, awatar_3.jpg

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie imię, nazwisko i nazwę pliku awatara aktora, którego identyfikator to 3

    Trzy pola, warunek na kluczu głównym - wynik ma najwyżej jeden wiersz: Jerzy Pasek, awatar_3.jpg. To zapytanie wróci w skrypcie 2, z parametrem zamiast trójki. Zrzut: kw2.jpg.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT id_filmu, tytul, rok_produkcji, czas_trwania
    FROM filmy
    WHERE czas_trwania >= 100;
    
    -- Zapytanie 2
    SELECT imie, nazwisko, plik_awatara
    FROM aktorzy
    WHERE id_aktora = 3;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: aktorzy alfabetycznie

    Kryteria:

    R.1.5 Wykonane zapytanie 3 wybierające wszystkie dane o aktorach, sortując je według nazwiska i imienia alfabetycznie (rosnąco). W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT * FROM aktorzy ORDER BY nazwisko, imie; (opcjonalnie ASC, zamiast * można wymienić wszystkie pola z tabeli) oraz na zrzucie jest co najmniej 10 rekordów

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające wszystkie dane o aktorach, sortując je według nazwiska i imienia alfabetycznie (rosnąco)

    • SELECT id_aktora, imie, nazwisko, ocena, plik_awatara - wszystkie pięć pól wymienione z nazwy zamiast *. Wynik jest ten sam, ale kod mówi, czego używa, i nie zmieni się po cichu, gdy ktoś doda do tabeli kolumnę (klucz uznaje oba zapisy),
    • ORDER BY nazwisko, imie - najpierw nazwisko, przy równych nazwiskach imię; rosnąco to kierunek domyślny.

    Wynik: 10 aktorów od Anaïs do Teixeiry - kolejność awatarów z ilustracji 2. Zrzut: kw3.jpg.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT id_filmu, tytul, rok_produkcji, czas_trwania
    FROM filmy
    WHERE czas_trwania >= 100;
    
    -- Zapytanie 2
    SELECT imie, nazwisko, plik_awatara
    FROM aktorzy
    WHERE id_aktora = 3;
    
    -- Zapytanie 3
    SELECT id_aktora, imie, nazwisko, ocena, plik_awatara
    FROM aktorzy
    ORDER BY nazwisko, imie;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: filmy aktora 3 przez relację

    Kryteria:

    R.1.6 Wykonane zapytanie 4 wybierające jedynie identyfikator, tytuł oraz rok produkcji filmów, w których gra aktor z identyfikatorem 3. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT filmy.id_filmu, tytul, rok_produkcji FROM filmy JOIN filmy_aktorzy USING(id_filmu) WHERE filmy_aktorzy.id_aktora = 3; (możliwe również porównanie kluczy po ON lub WHERE) oraz na zrzucie jest dokładnie 11 rekordów

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie identyfikatory, tytuły oraz rok produkcji filmów, w których gra aktor z identyfikatorem 3. Należy posłużyć się relacją

    • FROM filmy AS f JOIN filmy_aktorzy AS fa - aliasy tabel,
    • ON fa.id_filmu = f.id_filmu - relacja z ilustracji 1,
    • WHERE fa.id_aktora = 3 - tylko pary z aktorem 3,
    • SELECT f.id_filmu, f.tytul, f.rok_produkcji - pole id_filmu jest w obu tabelach, prefiks f. mówi, z której (bez niego błąd „ambiguous”).

    Wynik: 11 filmów. Z tego zapytania powstanie skrypt 3. Zrzut: kw4.jpg.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT id_filmu, tytul, rok_produkcji, czas_trwania
    FROM filmy
    WHERE czas_trwania >= 100;
    
    -- Zapytanie 2
    SELECT imie, nazwisko, plik_awatara
    FROM aktorzy
    WHERE id_aktora = 3;
    
    -- Zapytanie 3
    SELECT id_aktora, imie, nazwisko, ocena, plik_awatara
    FROM aktorzy
    ORDER BY nazwisko, imie;
    
    -- Zapytanie 4
    SELECT f.id_filmu, f.tytul, f.rok_produkcji
    FROM filmy AS f
    JOIN filmy_aktorzy AS fa ON fa.id_filmu = f.id_filmu
    WHERE fa.id_aktora = 3;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Krok 8: Zapytanie 5: liczba filmów każdego aktora

    Kryteria:

    R.1.7 Wykonane zapytanie 5 wybierające id aktora oraz zliczające liczbę filmów alias liczba_filmow, w których grał każdy aktor, pogrupowane ze względu na identyfikator aktora. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id_aktora, COUNT(*) AS liczba_filmow FROM filmy_aktorzy GROUP BY id_aktora; (AS opcjonalnie, zamiast * możliwe dowolne pole) oraz na zrzucie jest dokładnie 9 rekordów: 1, 8; 2, 9; 3, 11; 4, 10; 5, 9; 6, 8; 7, 11; 9, 11; 10, 7

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 5: wybierające id aktora oraz zliczające liczbę filmów, alias „liczba_filmow”, w których grał każdy aktor, pogrupowane ze względu na identyfikator aktora

    • COUNT(*) AS liczba_filmow - liczba wierszy w grupie pod nazwą z arkusza,
    • FROM filmy_aktorzy - każdy wiersz tabeli pośredniej to jeden film aktora,
    • GROUP BY id_aktora - jedna grupa na aktora.

    Wynik: 9 wierszy (1, 8; 2, 9; 3, 11; 4, 10; 5, 9; 6, 8; 7, 11; 9, 11; 10, 7). Aktora 8 nie ma - nie gra w żadnym filmie; to Lorenzo Di Romano, przy którym skrypt 3 wypisze drugi wariant napisu. Zrzut: kw5.jpg.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT id_filmu, tytul, rok_produkcji, czas_trwania
    FROM filmy
    WHERE czas_trwania >= 100;
    
    -- Zapytanie 2
    SELECT imie, nazwisko, plik_awatara
    FROM aktorzy
    WHERE id_aktora = 3;
    
    -- Zapytanie 3
    SELECT id_aktora, imie, nazwisko, ocena, plik_awatara
    FROM aktorzy
    ORDER BY nazwisko, imie;
    
    -- Zapytanie 4
    SELECT f.id_filmu, f.tytul, f.rok_produkcji
    FROM filmy AS f
    JOIN filmy_aktorzy AS fa ON fa.id_filmu = f.id_filmu
    WHERE fa.id_aktora = 3;
    
    -- Zapytanie 5
    SELECT id_aktora, COUNT(*) AS liczba_filmow
    FROM filmy_aktorzy
    GROUP BY id_aktora;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Etap 2: Strona HTML

    Kroki 9-20 · po tym etapie masz 17 z 39 kryteriów (pkt)

    Krok 9: Plik index.php w HTML5 i awatary w folderze

    ok. 2 min
    Polecenie z arkusza

    Zapisane w języku HTML5

    <!DOCTYPE html> i szkielet strony; rozszerzenie .php, bo dojdą skrypty. Do tego samego folderu trafiają awatary z katalogu img archiwum - baza podaje samą nazwę pliku.

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

    Kryteria:

    R.2.1 W plikach index.php lub aktor.php znajduje się deklaracja HTML5 <!DOCTYPE HTML> i deklaracja języka zawartości witryny: polski np. <html lang="pl">

    ok. 1 min
    Polecenie z arkusza

    Zadeklarowany polski język zawartości witryny

    Atrybut lang="pl" - z deklaracją HTML5 to jedno kryterium.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  11. Krok 11: Kodowanie 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> - „Sobiesław” i „Élodie” wyświetlą się poprawnie.

    index.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 (index.php)
  12. Krok 12: 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 właściwie połączony z kodem strony

    Plik styl.css. Całe formatowanie pochodzi z tego pliku - także w tekstach wypisywanych przez PHP.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  13. Krok 13: Bloki: dwa nagłówkowe, główny, stopka

    Kryteria:

    R.2.3 W sekcji body zdefiniowany jest podział na bloki za pomocą semantycznych znaczników HTML5: header+header, main, footer (dopuszcza się zastosowanie bloków pomocniczych)

    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

    Klucz wymaga wprost header + header, main i footer (R.2.3). Bez identyfikatorów: szerokości nagłówków ustawi siatka CSS według kolejności w kodzie, a drugi nagłówek CSS znajdzie selektorem sąsiada.

    index.php PHP zmienione: 8 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
        </header>
        <header>
        </header>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  14. Krok 14: Blok nagłówkowy 1: nagłówek z odnośnikiem

    Kryteria:

    R.3.3 Blok nagłówkowy 1 zawiera odnośnik prowadzący do pliku index.php

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego 1: nagłówek drugiego stopnia zawierający odnośnik do pliku index.php o treści „KinoTEKA”

    „Nagłówek zawierający odnośnik” - <a> stoi wewnątrz <h2>. Na stronie aktor.php to będzie powrót do listy aktorów.

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
        </header>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Blok nagłówkowy 2: paragraf z tekstem uwypuklonym

    Kryteria:

    R.2.5 W bloku nagłówkowym 1 umieszczony <h2> „KinoTEKA” oraz w bloku nagłówkowym 2 - <p> o treści „W naszej bazie znajdują się najlepsi aktorzy”

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego 2: paragraf o treści „W naszej bazie znajdują się najlepsi aktorzy”. Tekst ten jest zapisany w znaczniku semantycznym oznaczającym tekst uwypuklony, formatowany domyślnie jako pochylony

    Tekst uwypuklony to <em> - domyślnie pochylony; <i> też pochyla, ale nie niesie znaczenia (R.2.5).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Stopka: autor w znaczniku tekstu ważnego

    Kryteria: ,

    R.2.6 W stopce umieszczony <p> o treści: "Autor:" z numerem zdającego

    R.2.7 W bloku nagłówkowym 2 użyty znacznik semantyczny <em> do tekstu oraz w stopce użyty znacznik semantyczny <strong> do numeru zdającego

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: paragraf o treści „Autor: ”, dalej wstawiony numer zdającego. Numer zdającego jest zapisany w znaczniku semantycznym oznaczającym tekst ważny, formatowany domyślnie jako pogrubiony

    Tekst ważny to <strong> - domyślnie pogrubiony. Na egzaminie wpisuje się numer zdającego (PESEL); tu stoi „Bartosz Bryniarski” (R.2.6, R.2.7).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  17. Krok 17: index.php: tytuł strony

    Kryteria:

    R.2.2 W sekcji head znajduje się jawnie podany standard kodowania polskich znaków. Stronie index.php nadano tytuł "Lista aktorów | KinoTEKA " lub stronie aktor.php nadano tytuł "Informacje o aktorze | KinoTEKA"

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Lista aktorów | KinoTEKA”

    <title> z pionową kreską, jak w arkuszu - najpierw treść strony, potem nazwa serwisu, więc na wąskiej karcie widać najważniejsze. Z kodowaniem to jedno kryterium (R.2.2).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Lista aktorów | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: index.php: nagłówek pierwszego stopnia

    Kryteria:

    R.2.4 W bloku głównym na stronie głównej umieszczony <h1> o treści: „Najlepsi aktorzy tylko w naszym kinie”

    ok. 1 min
    Polecenie z arkusza

    Nagłówek pierwszego stopnia o treści: „Najlepsi aktorzy tylko w naszym kinie”

    <h1> na początku bloku głównego (R.2.4).

    index.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Lista aktorów | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
            <h1>Najlepsi aktorzy tylko w naszym kinie</h1>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  19. Krok 19: index.php: blok wszystkich aktorów

    Kryteria:

    R.2.8 Na stronie głównej w bloku głównym utworzony blok wszystkich aktorów (w HTML lub skrypcie)

    ok. 1 min
    Polecenie z arkusza

    Blok wszystkich aktorów wypełniony skryptem 1

    Pusty <div id="actors"> - skrypt 1 wypisze w nim karty aktorów. Zwykły div, bo to pojemnik do ułożenia kart w siatkę, bez własnego znaczenia (R.2.8).

    index.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Lista aktorów | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
            <h1>Najlepsi aktorzy tylko w naszym kinie</h1>
            <div id="actors">
            </div>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  20. Krok 20: aktor.php: kopia strony z innym tytułem

    ok. 2 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Informacje o aktorze | KinoTEKA”

    aktor.php powstaje jako kopia index.php z innym tytułem i bez <h1> - tu nagłówkiem pierwszego stopnia będzie imię i nazwisko aktora ze skryptu 2. Blok wszystkich aktorów zostaje: arkusz każe wypełnić go skryptem 2.

    aktor.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Informacje o aktorze | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
            <div id="actors">
            </div>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (aktor.php)
  21. Etap 3: Styl CSS

    Kroki 21-32 · po tym etapie masz 28 z 39 kryteriów (pkt)

    Krok 21: Zmienne i krój czcionki dla wszystkich elementów

    Kryteria:

    R.4.1 domyślnie dla wszystkich selektorów (*): krój czcionki "Gill Sans MT", "Calibri" (dozwolona tylko taka kolejność)

    ok. 2 min
    Polecenie z arkusza

    Domyślne formatowanie wszystkich selektorów: krój czcionki „Gill Sans MT”, a w przypadku jej braku „Calibri”

    W :root zmienne CSS: kolor odnośnika pod kursorem i szerokość karty aktora - ta wartość przyda się w dwóch regułach. Selektor *: "Gill Sans MT" w cudzysłowie (nazwa ze spacjami), potem Calibri, dokładnie w tej kolejności i bez trzeciego kroju - klucz dopuszcza tylko taki zapis (R.4.1).

    styl.css CSS nowy plik
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Ciało strony: szerokość i wyśrodkowanie

    Kryteria:

    R.4.3 dla ciała strony lub dla bloku wszystkich aktorów - marginesy zewnętrzne górny i dolny 0, lewy i prawy auto

    ok. 1 min
    Polecenie z arkusza

    Dla selektora ciała strony: szerokość 90%, marginesy zewnętrzne górny i dolny 0, lewy i prawy automatycznie wyliczone przez przeglądarkę

    margin: 0 auto - góra i dół 0, boki wyliczone po równo: strona na 90% szerokości stoi na środku okna. Selektor znacznika body, jak każe uwaga arkusza (R.4.3).

    styl.css CSS zmienione: 5 linii
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    
    body {
        width: 90%;
        margin: 0 auto;
    }
    Podgląd strony po tym kroku (index.php)
  23. Krok 23: Blok nagłówkowy 1: szerokość z kolumny siatki

    ok. 2 min
    Polecenie z arkusza

    Dla bloku nagłówkowego 1: szerokość 40%

    <body> staje się siatką o kolumnach 40% 60%: pierwszy nagłówek trafia do kolumny 40%, drugi do 60% - stoją obok siebie bez float. Klucz uznaje szerokości z grid-template-columns. Blok główny i stopka trafiają na razie do pojedynczych komórek - rozciągnie je krok o układzie.

    styl.css CSS zmienione: 2 linie
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    
    body {
        width: 90%;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 40% 60%;
    }
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Blok nagłówkowy 2: tekst do prawej

    Kryteria:

    R.4.6 wyrównanie tekstu do prawej strony dla bloku nagłówkowego 2

    ok. 2 min
    Polecenie z arkusza

    Dla bloku nagłówkowego 2: szerokość 60% oraz wyrównanie tekstu do prawej strony

    Szerokość 60% daje druga kolumna siatki. Wyrównanie do prawej potrzebuje selektora drugiego nagłówka: header + header to kombinator sąsiada - <header> stojący tuż po innym <header>, czyli tylko drugi z nich (R.4.6).

    styl.css CSS zmienione: 4 linie
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    
    body {
        width: 90%;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 40% 60%;
    }
    
    header + header {
        text-align: right;
    }
    Podgląd strony po tym kroku (index.php)
  25. Krok 25: Układ bloków zgodny z ilustracją 3

    Kryteria:

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 3 w arkuszu egzaminacyjnym (prawidłowo zastosowano właściwości CSS decydujące o układzie bloków na stronie, np. float i clear albo display flex albo grid albo position), (nie należy brać pod uwagę bloków aktorów)

    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

    grid-column: 1 / -1 rozciąga blok główny i stopkę od pierwszej do ostatniej linii siatki - każde zajmuje cały wiersz pod nagłówkami (R.3.1). Bez float nie trzeba clear.

    Typowe błędy

    Bez tej reguły blok główny zostaje w kolumnie 40%, a stopka w kolumnie 60%.

    styl.css CSS zmienione: 5 linii
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    
    body {
        width: 90%;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 40% 60%;
    }
    
    header + header {
        text-align: right;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    Podgląd strony po tym kroku (index.php)
  26. Krok 26: Blok wszystkich aktorów: marginesy

    ok. 1 min
    Polecenie z arkusza

    Dla bloku wszystkich aktorów: marginesy zewnętrzne górny i dolny 0, lewy i prawy automatyczne

    To samo margin: 0 auto co dla body. Blok bez własnej szerokości zajmuje cały wiersz, więc boczne marginesy wychodzą zerowe - reguła zadziała, gdy blok dostanie szerokość.

    styl.css CSS zmienione: 4 linie
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    
    body {
        width: 90%;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 40% 60%;
    }
    
    header + header {
        text-align: right;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    #actors {
        margin: 0 auto;
    }
    Podgląd strony po tym kroku (index.php)
  27. Krok 27: index.php: karty aktorów obok siebie w siatce

    Kryteria: ,

    R.4.4 dla bloku pojedynczego aktora - tekst wyśrodkowany

    R.4.5 bloki pojedynczych aktorów na stronie głównej są ustawione obok siebie (np. float, flex, grid)

    ok. 3 min
    Polecenie z arkusza

    Dla bloku pojedynczego aktora na stronie index.php, generowanego skryptem 1: szerokość 15 em, tekst wyśrodkowany, bloki są wyświetlane obok siebie

    Karta aktora ma klasę actor: szerokość var(--actor-width) (15em) i środkowanie (R.4.4). Obok siebie stawia je siatka w bloku wszystkich aktorów: repeat(auto-fill, 15em) - tyle kolumn po 15em, ile zmieści się w szerokości, a reszta przechodzi do następnego wiersza (R.4.5). Na szerokim ekranie sześć w rzędzie, jak na ilustracji 2. Ta sama zmienna w obu regułach - zmiana szerokości karty to jedna linia.

    styl.css CSS zmienione: 7 linii
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    
    body {
        width: 90%;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 40% 60%;
    }
    
    header + header {
        text-align: right;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    #actors {
        margin: 0 auto;
        display: grid;
        grid-template-columns: repeat(auto-fill, var(--actor-width));
    }
    
    .actor {
        width: var(--actor-width);
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  28. Krok 28: aktor.php: blok pojedynczego aktora

    ok. 1 min
    Polecenie z arkusza

    Dla bloku pojedynczego aktora na stronie aktor.php, generowanego skryptem 2: szerokość 25 em, tekst wyśrodkowany

    Na aktor.php karta jest jedna i większa - klasa actor-profile, 25em. Stoi w tym samym bloku #actors, więc zajmuje pierwszą kolumnę siatki i rozszerza ją do swojej szerokości.

    styl.css CSS zmienione: 5 linii
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    
    body {
        width: 90%;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 40% 60%;
    }
    
    header + header {
        text-align: right;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    #actors {
        margin: 0 auto;
        display: grid;
        grid-template-columns: repeat(auto-fill, var(--actor-width));
    }
    
    .actor {
        width: var(--actor-width);
        text-align: center;
    }
    
    .actor-profile {
        width: 25em;
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  29. Krok 29: Obraz: szerokość i koło

    Kryteria: ,

    R.4.2 szerokości elementów: 90% - dla selektora body, 40% - dla bloku nagłówkowego 1 60% - dla bloku nagłówkowego 2 15em - dla bloku pojedynczego aktora na stronie index.php 25em - dla bloku pojedynczego aktora na stronie aktor.php 90% - dla selektora img Wystarczą 4 z 6 ustawień

    R.4.8 dla obrazu - zaokrąglenie obramowania 50%

    ok. 2 min
    Polecenie z arkusza

    Dla selektora obrazu: szerokość 90%, zaokrąglenie obramowania 50%

    width: 90% liczy się od karty, a border-radius: 50% zaokrągla rogi w koło (R.4.8). Koło wychodzi tylko z kwadratu, więc dwie właściwości dodatkowe: aspect-ratio: 1 - wysokość równa szerokości, i object-fit: cover - zdjęcie w innych proporcjach wypełnia ten kwadrat i obcina nadmiar zamiast się rozciągać. Awatary z archiwum są kwadratowe, więc tu nic się nie zmienia; zabezpieczenie dotyczy następnego zdjęcia w bazie. Komplet szerokości z klucza (R.4.2).

    styl.css CSS zmienione: 7 linii
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    
    body {
        width: 90%;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 40% 60%;
    }
    
    header + header {
        text-align: right;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    #actors {
        margin: 0 auto;
        display: grid;
        grid-template-columns: repeat(auto-fill, var(--actor-width));
    }
    
    .actor {
        width: var(--actor-width);
        text-align: center;
    }
    
    .actor-profile {
        width: 25em;
        text-align: center;
    }
    
    img {
        width: 90%;
        border-radius: 50%;
        aspect-ratio: 1;
        object-fit: cover;
    }
    Podgląd strony po tym kroku (index.php)
  30. Krok 30: Paragraf w karcie aktora: większa czcionka

    Kryteria:

    R.4.7 dla selektora p znajdującego się wyłącznie w bloku pojedynczego aktora - rozmiar czcionki 125%

    ok. 1 min
    Polecenie z arkusza

    Dla selektora paragrafu znajdującego się w bloku pojedynczego aktora: rozmiar czcionki 125%

    Selektor potomka .actor p - tylko akapity w karcie; motto, stopka i napis skryptu 3 zostają w zwykłym rozmiarze. To nadal selektor znacznika p (R.4.7).

    styl.css CSS zmienione: 4 linie
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    
    body {
        width: 90%;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 40% 60%;
    }
    
    header + header {
        text-align: right;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    #actors {
        margin: 0 auto;
        display: grid;
        grid-template-columns: repeat(auto-fill, var(--actor-width));
    }
    
    .actor {
        width: var(--actor-width);
        text-align: center;
    }
    
    .actor-profile {
        width: 25em;
        text-align: center;
    }
    
    img {
        width: 90%;
        border-radius: 50%;
        aspect-ratio: 1;
        object-fit: cover;
    }
    
    .actor p {
        font-size: 125%;
    }
    Podgląd strony po tym kroku (index.php)
  31. Krok 31: Odnośniki: czarne, bez podkreślenia

    Kryteria:

    R.4.9 dla selektora a - czarny kolor czcionki i brak podkreślenia

    ok. 1 min
    Polecenie z arkusza

    Dla selektora odnośnika: kolor czcionki czarny, brak podkreślenia

    text-decoration: none zdejmuje podkreślenie - karty aktorów są odnośnikami, więc bez tej reguły imiona byłyby niebieskie i podkreślone (R.4.9).

    styl.css CSS zmienione: 5 linii
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    
    body {
        width: 90%;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 40% 60%;
    }
    
    header + header {
        text-align: right;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    #actors {
        margin: 0 auto;
        display: grid;
        grid-template-columns: repeat(auto-fill, var(--actor-width));
    }
    
    .actor {
        width: var(--actor-width);
        text-align: center;
    }
    
    .actor-profile {
        width: 25em;
        text-align: center;
    }
    
    img {
        width: 90%;
        border-radius: 50%;
        aspect-ratio: 1;
        object-fit: cover;
    }
    
    .actor p {
        font-size: 125%;
    }
    
    a {
        color: black;
        text-decoration: none;
    }
    Podgląd strony po tym kroku (index.php)
  32. Krok 32: Odnośnik pod kursorem i z klawiatury

    Kryteria:

    R.4.10 gdy kursor znajdzie się na odnośniku, jego kolor czcionki zmienia się na #E30613

    ok. 1 min
    Polecenie z arkusza

    Gdy kursor znajdzie się na odnośniku: kolor czcionki #E30613

    :hover (R.4.10) i ten sam kolor dla :focus-visible - karty wybranej klawiszem Tab. Na ilustracji 2 kursor stoi na drugiej karcie - „Davide Costaluca” jest czerwony.

    styl.css CSS zmienione: 5 linii
    :root {
        --link-hover: #E30613;
        --actor-width: 15em;
    }
    
    * {
        font-family: "Gill Sans MT", Calibri;
    }
    
    body {
        width: 90%;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 40% 60%;
    }
    
    header + header {
        text-align: right;
    }
    
    main,
    footer {
        grid-column: 1 / -1;
    }
    
    #actors {
        margin: 0 auto;
        display: grid;
        grid-template-columns: repeat(auto-fill, var(--actor-width));
    }
    
    .actor {
        width: var(--actor-width);
        text-align: center;
    }
    
    .actor-profile {
        width: 25em;
        text-align: center;
    }
    
    img {
        width: 90%;
        border-radius: 50%;
        aspect-ratio: 1;
        object-fit: cover;
    }
    
    .actor p {
        font-size: 125%;
    }
    
    a {
        color: black;
        text-decoration: none;
    }
    
    a:hover,
    a:focus-visible {
        color: var(--link-hover);
    }
    Podgląd strony po tym kroku (index.php)
  33. Etap 4: Skrypty PHP

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

    Krok 33: index.php: połączenie PDO na górze pliku

    Kryteria:

    R.5.1 Dowolny ze skryptów realizuje połączenie z serwerem bazy danych i wybór bazy kino , a ostatnią operacją na bazie jest jej zamknięcie

    ok. 4 min
    Polecenie z arkusza

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

    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):

    • DSN 'mysql:host=localhost;dbname=kino;charset=utf8mb4' - serwer, baza i kodowanie; 'root', '' - użytkownik i puste hasło,
    • ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze jako tablice z nazwami kolumn,
    • $pdo = null zamyka połączenie - zapytanie stanie przed tą linią, więc zamknięcie zostanie ostatnią operacją na bazie (R.5.1),
    • try / catch - gdy baza nie działa, szczegóły idą do dziennika serwera, a w bloku głównym stoi ogólny komunikat $dbError,
    • dwie funkcje pomocnicze: escape() (htmlspecialchars(..., ENT_QUOTES) dla wartości z bazy) i fullName() - imię i nazwisko ze spacją, potrzebne na obu stronach po kilka razy.
    index.php PHP zmienione: 28 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function fullName(array $actor): string
    {
        return $actor['imie'] . ' ' . $actor['nazwisko'];
    }
    
    $actors = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kino;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o aktorach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Lista aktorów | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <h1>Najlepsi aktorzy tylko w naszym kinie</h1>
            <div id="actors">
            </div>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  34. Krok 34: Skrypt 1: zapytanie 3

    Kryteria:

    R.5.2 Skrypt 1 (strona główna) zawiera instrukcję wysyłającą do bazy zapytanie 3

    ok. 2 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 3

    $pdo->query(...) wysyła zapytanie 3 z kwerendy.txt, a ->fetchAll() odbiera wszystkich dziesięciu aktorów do tablicy $actors (R.5.2). Bez danych od użytkownika wystarcza query.

    index.php PHP zmienione: 5 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function fullName(array $actor): string
    {
        return $actor['imie'] . ' ' . $actor['nazwisko'];
    }
    
    $actors = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kino;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $actors = $pdo->query(
            'SELECT id_aktora, imie, nazwisko, ocena, plik_awatara
             FROM aktorzy
             ORDER BY nazwisko, imie'
        )->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o aktorach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Lista aktorów | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <h1>Najlepsi aktorzy tylko w naszym kinie</h1>
            <div id="actors">
            </div>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>

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

  35. Krok 35: Skrypt 1: karta aktora z imieniem i nazwiskiem

    ok. 3 min
    Polecenie z arkusza

    Dla każdego wiersza zwróconego z bazy tworzy blok pojedynczego aktora zawierający paragraf zawierający tekst „<IMIE> <NAZWISKO>”

    W bloku wszystkich aktorów pętla foreach ($actors as $actor): w składni alternatywnej - jedna karta actor na wiersz. W akapicie fullName($actor) przez escape(). Pola po nazwach kolumn, więc kolejność pól w zapytaniu nie ma znaczenia. Dziesięć nazwisk w kolejności zapytania 3, już w siatce obok siebie.

    index.php PHP zmienione: 5 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function fullName(array $actor): string
    {
        return $actor['imie'] . ' ' . $actor['nazwisko'];
    }
    
    $actors = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kino;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $actors = $pdo->query(
            'SELECT id_aktora, imie, nazwisko, ocena, plik_awatara
             FROM aktorzy
             ORDER BY nazwisko, imie'
        )->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o aktorach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Lista aktorów | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <h1>Najlepsi aktorzy tylko w naszym kinie</h1>
            <div id="actors">
    <?php foreach ($actors as $actor): ?>
                <div class="actor">
                    <p><?= escape(fullName($actor)) ?></p>
                </div>
    <?php endforeach; ?>
            </div>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  36. Krok 36: Skrypt 1: awatar z tekstem alternatywnym i atrybutem title

    Kryteria: , ,

    R.5.3 Skrypt 1 wyświetla obrazy oraz imię i nazwisko aktorów dla wszystkich zwróconych zapytaniem wierszy

    R.3.5 Na stronie głównej obrazy znajdujące się w blokach pojedynczych aktorów mają ustawiony tekst alternatywny i atrybut title

    R.2.9 Na stronie głównej w bloku wszystkich aktorów znajduje się co najmniej jeden blok pojedynczego aktora zawierający obraz oraz paragraf (w HTML lub skrypcie)

    ok. 3 min
    Polecenie z arkusza

    Obraz, którego źródłem jest plik podany w kolumnie plik_awatara, z tekstem alternatywnym oraz atrybutem title z wartością „<IMIE> <NAZWISKO>” (Ilustracja 2, awatar 2)

    src z pola plik_awatara, a imię i nazwisko trafia do alt (czytnik ekranu, brak obrazu) i title (dymek po najechaniu kursorem; R.5.3, R.3.5, R.2.9). Wartości stoją w atrybutach w cudzysłowach, więc escape() z ENT_QUOTES jest tu konieczne - nazwisko z cudzysłowem zamknęłoby atrybut. Podgląd wygląda jak ilustracja 2.

    index.php PHP zmienione: 1 linia
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function fullName(array $actor): string
    {
        return $actor['imie'] . ' ' . $actor['nazwisko'];
    }
    
    $actors = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kino;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $actors = $pdo->query(
            'SELECT id_aktora, imie, nazwisko, ocena, plik_awatara
             FROM aktorzy
             ORDER BY nazwisko, imie'
        )->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o aktorach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Lista aktorów | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <h1>Najlepsi aktorzy tylko w naszym kinie</h1>
            <div id="actors">
    <?php foreach ($actors as $actor): ?>
                <div class="actor">
                    <img src="<?= escape($actor['plik_awatara']) ?>" alt="<?= escape(fullName($actor)) ?>" title="<?= escape(fullName($actor)) ?>">
                    <p><?= escape(fullName($actor)) ?></p>
                </div>
    <?php endforeach; ?>
            </div>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  37. Krok 37: Skrypt 1: karta aktora jako odnośnik

    Kryteria:

    R.3.4 Na stronie głównej po kliknięciu bloku pojedynczego aktora jest otwierana strona aktor.php?id=… , gdzie … to id wybranego aktora (wystarczy sprawdzić dla dwóch bloków)

    ok. 2 min
    Polecenie z arkusza

    Wygenerowany blok pojedynczego aktora jest odnośnikiem do pliku aktor.php?id=<ID_AKTORA>

    Karta aktora sama staje się odnośnikiem: <div class="actor"> zamienia się na <a class="actor" href="aktor.php?id=7">. Nie trzeba owijać bloku w dodatkowy <a> - odnośnik w HTML5 może zawierać obraz i akapit, a jako element siatki jest blokiem tak samo jak div, więc reguła .actor i układ działają bez zmian (R.3.4). (int) przy identyfikatorze gwarantuje liczbę w adresie.

    Teraz działa też reguła a: podpisy są czarne, a pod kursorem czerwone.

    index.php PHP zmienione: 2 linie
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function fullName(array $actor): string
    {
        return $actor['imie'] . ' ' . $actor['nazwisko'];
    }
    
    $actors = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kino;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $actors = $pdo->query(
            'SELECT id_aktora, imie, nazwisko, ocena, plik_awatara
             FROM aktorzy
             ORDER BY nazwisko, imie'
        )->fetchAll();
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o aktorach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Lista aktorów | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <h1>Najlepsi aktorzy tylko w naszym kinie</h1>
            <div id="actors">
    <?php foreach ($actors as $actor): ?>
                <a class="actor" href="aktor.php?id=<?= (int) $actor['id_aktora'] ?>">
                    <img src="<?= escape($actor['plik_awatara']) ?>" alt="<?= escape(fullName($actor)) ?>" title="<?= escape(fullName($actor)) ?>">
                    <p><?= escape(fullName($actor)) ?></p>
                </a>
    <?php endforeach; ?>
            </div>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  38. Krok 38: aktor.php: połączenie i identyfikator z adresu

    ok. 3 min
    Polecenie z arkusza

    Skrypt 2, na stronie aktor.php: Korzysta z pola id przesłanego do pliku metodą GET

    Na górze aktor.php te same funkcje i to samo połączenie co w index.php, a przed nimi odczyt identyfikatora: $_GET['id'] przechodzi przez filter_var z FILTER_VALIDATE_INT i minimum 1. Wynik to liczba albo false - dla abc, 3 OR 1=1 i dla braku parametru (operator ?? podstawia wtedy pusty napis, bez ostrzeżenia „Undefined array key”).

    aktor.php PHP zmienione: 29 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function fullName(array $actor): string
    {
        return $actor['imie'] . ' ' . $actor['nazwisko'];
    }
    
    $actorId = filter_var($_GET['id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    $actor = null;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kino;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o aktorach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Informacje o aktorze | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <div id="actors">
            </div>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (aktor.php)
  39. Krok 39: Skrypt 2: zapytanie 2 przygotowane

    Kryteria:

    R.5.4 Skrypt 2 lub skrypt 3 (podstrona) zawiera instrukcje wysyłającą odpowiednio zapytanie 2 lub 4 zmodyfikowane w ten sposób, że klauzula WHERE sprawdza wartość id z metody GET

    ok. 3 min
    Polecenie z arkusza

    Wysyła do bazy danych zapytanie 2 zmodyfikowane w ten sposób, aby uzyskiwać informacje o aktorze z identyfikatorem równym polu id pobranym metodą GET

    Tylko dla poprawnego identyfikatora: zapytanie 2 z parametrem :id zamiast trójki. prepare wysyła strukturę zapytania, execute(['id' => $actorId]) - wartość osobno, więc nie da się nią dopisać warunku do SQL (R.5.4). fetch() oddaje wiersz albo false, gdy aktora nie ma; ?: null zamienia false na czytelniejsze „brak”. Podgląd: aktor.php?id=10, blok jest jeszcze pusty.

    aktor.php PHP zmienione: 7 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function fullName(array $actor): string
    {
        return $actor['imie'] . ' ' . $actor['nazwisko'];
    }
    
    $actorId = filter_var($_GET['id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    $actor = null;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kino;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($actorId !== false) {
            $statement = $pdo->prepare(
                'SELECT imie, nazwisko, plik_awatara FROM aktorzy WHERE id_aktora = :id'
            );
            $statement->execute(['id' => $actorId]);
            $actor = $statement->fetch() ?: null;
        }
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o aktorach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Informacje o aktorze | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <div id="actors">
            </div>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>

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

  40. Krok 40: Skrypt 2: karta aktora z awatarem i nagłówkiem

    Kryteria:

    R.5.5 Skrypt 2 wyświetla jeden blok pojedynczego aktora z obrazem i danymi zwróconymi zapytaniem

    ok. 3 min
    Polecenie z arkusza

    Dla zwróconego z bazy wiersza tworzy blok pojedynczego aktora zawierający obraz, którego źródłem jest plik podany w kolumnie plik_awatara, z tekstem alternatywnym oraz atrybutem title z wartością „<IMIE> <NAZWISKO>” oraz nagłówek pierwszego stopnia zawierający tekst „<IMIE> <NAZWISKO>”

    Gdy aktor się znalazł: karta <article class="actor-profile"> - article to samodzielna treść (strona o jednym aktorze), z awatarem (alt i title z fullName()) i imieniem i nazwiskiem w <h1> (R.5.5). Gdy nie - „Nie znaleziono aktora” zamiast pustej karty i ostrzeżeń PHP. Podgląd: Pinto Teixeira jak na ilustracji 4.

    aktor.php PHP zmienione: 8 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function fullName(array $actor): string
    {
        return $actor['imie'] . ' ' . $actor['nazwisko'];
    }
    
    $actorId = filter_var($_GET['id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    $actor = null;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kino;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($actorId !== false) {
            $statement = $pdo->prepare(
                'SELECT imie, nazwisko, plik_awatara FROM aktorzy WHERE id_aktora = :id'
            );
            $statement->execute(['id' => $actorId]);
            $actor = $statement->fetch() ?: null;
        }
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o aktorach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Informacje o aktorze | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <div id="actors">
    <?php if ($actor !== null): ?>
                <article class="actor-profile">
                    <img src="<?= escape($actor['plik_awatara']) ?>" alt="<?= escape(fullName($actor)) ?>" title="<?= escape(fullName($actor)) ?>">
                    <h1><?= escape(fullName($actor)) ?></h1>
                </article>
    <?php else: ?>
                <h1>Nie znaleziono aktora</h1>
    <?php endif; ?>
            </div>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (aktor.php)
  41. Krok 41: Skrypt 3: zapytanie 4 i liczba rekordów

    ok. 3 min
    Polecenie z arkusza

    Skrypt 3, na stronie aktor.php: Korzysta z pola id przesłanego do pliku metodą GET. Wysyła do bazy danych zapytanie 4 zmodyfikowane w taki sposób, że pobiera wyniki dotyczące aktora z identyfikatorem równym polu id pobranym metodą GET

    Zapytanie 4 z aliasami i tym samym parametrem :id - wysyłane tylko wtedy, gdy aktor istnieje. Skrypt potrzebuje liczby filmów, nie ich listy: count($statement->fetchAll()) liczy zwrócone rekordy. PDO ma też rowCount(), ale dla SELECT nie każda baza go obsługuje - count na pobranej tablicy działa zawsze. Dla Pinto Teixeiry: 7.

    aktor.php PHP zmienione: 11 linii
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function fullName(array $actor): string
    {
        return $actor['imie'] . ' ' . $actor['nazwisko'];
    }
    
    $actorId = filter_var($_GET['id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    $actor = null;
    $filmCount = 0;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kino;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($actorId !== false) {
            $statement = $pdo->prepare(
                'SELECT imie, nazwisko, plik_awatara FROM aktorzy WHERE id_aktora = :id'
            );
            $statement->execute(['id' => $actorId]);
            $actor = $statement->fetch() ?: null;
        }
        if ($actor !== null) {
            $statement = $pdo->prepare(
                'SELECT f.id_filmu, f.tytul, f.rok_produkcji
                 FROM filmy AS f
                 JOIN filmy_aktorzy AS fa ON fa.id_filmu = f.id_filmu
                 WHERE fa.id_aktora = :id'
            );
            $statement->execute(['id' => $actorId]);
            $filmCount = count($statement->fetchAll());
        }
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o aktorach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Informacje o aktorze | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <div id="actors">
    <?php if ($actor !== null): ?>
                <article class="actor-profile">
                    <img src="<?= escape($actor['plik_awatara']) ?>" alt="<?= escape(fullName($actor)) ?>" title="<?= escape(fullName($actor)) ?>">
                    <h1><?= escape(fullName($actor)) ?></h1>
                </article>
    <?php else: ?>
                <h1>Nie znaleziono aktora</h1>
    <?php endif; ?>
            </div>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>

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

  42. Krok 42: Skrypt 3: aktor bez żadnego filmu

    ok. 2 min
    Polecenie z arkusza

    Jeśli liczba rekordów uzyskanych zapytaniem wynosi 0, wyświetlany jest napis „<IMIE> nie znajduje się na listach obsady znanych nam produkcji.”, gdzie <IMIE> jest imieniem aktora pobranym w skrypcie 2

    Pod blokiem aktora warunek: aktor istnieje i ma zero filmów. Imię „pobrane w skrypcie 2” to $actor['imie'] - wiersz z zapytania 2 czeka w zmiennej. Porównanie ścisłe === 0, bo count zawsze zwraca liczbę całkowitą. Podgląd: aktor.php?id=8, Lorenzo Di Romano.

    aktor.php PHP zmienione: 3 linie
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function fullName(array $actor): string
    {
        return $actor['imie'] . ' ' . $actor['nazwisko'];
    }
    
    $actorId = filter_var($_GET['id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    $actor = null;
    $filmCount = 0;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kino;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($actorId !== false) {
            $statement = $pdo->prepare(
                'SELECT imie, nazwisko, plik_awatara FROM aktorzy WHERE id_aktora = :id'
            );
            $statement->execute(['id' => $actorId]);
            $actor = $statement->fetch() ?: null;
        }
        if ($actor !== null) {
            $statement = $pdo->prepare(
                'SELECT f.id_filmu, f.tytul, f.rok_produkcji
                 FROM filmy AS f
                 JOIN filmy_aktorzy AS fa ON fa.id_filmu = f.id_filmu
                 WHERE fa.id_aktora = :id'
            );
            $statement->execute(['id' => $actorId]);
            $filmCount = count($statement->fetchAll());
        }
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o aktorach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Informacje o aktorze | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <div id="actors">
    <?php if ($actor !== null): ?>
                <article class="actor-profile">
                    <img src="<?= escape($actor['plik_awatara']) ?>" alt="<?= escape(fullName($actor)) ?>" title="<?= escape(fullName($actor)) ?>">
                    <h1><?= escape(fullName($actor)) ?></h1>
                </article>
    <?php else: ?>
                <h1>Nie znaleziono aktora</h1>
    <?php endif; ?>
            </div>
    <?php if ($actor !== null && $filmCount === 0): ?>
            <p><?= escape($actor['imie']) ?> nie znajduje się na listach obsady znanych nam produkcji.</p>
    <?php endif; ?>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (aktor.php)
  43. Krok 43: Skrypt 3: liczba filmów aktora

    Kryteria: , ,

    R.5.6 Skrypt 3: jeśli liczba rekordów zwrócona zapytaniem 4 wynosi 0 (Lorenzo Di Romano) , wyświetla tekst "<imie> nie znajduje się na listach obsady znanych nam produkcji.", a w przeciwnym wypadku wyświetla tekst "<imie> znajduje się na listach obsady <X> znanych nam produkcji."

    R.5.7 Skrypt 3 w miejscu <X> wyświetla liczbę rekordów zwróconą przesłanym zapytaniem

    R.5.8 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

    W przeciwnym wypadku wyświetlany jest napis „<IMIE> znajduje się na listach obsady <X> znanych nam produkcji.”, gdzie <IMIE> jest imieniem aktora pobranym w skrypcie 2, a <X> oznacza liczbę rekordów uzyskanych zapytaniem 4

    Gałąź elseif ($actor !== null) - aktor istnieje i ma filmy; w miejscu <X> liczba $filmCount (R.5.6, R.5.7). Podgląd dla id 10: „Pinto znajduje się na listach obsady 7 znanych nam produkcji.”, jak na ilustracji 4.

    Sprawdzone złe adresy: ?id=99, ?id=abc, ?id=3 OR 1=1, puste ?id= i samo aktor.php - zawsze „Nie znaleziono aktora”, bez napisu o filmach i bez ostrzeżeń PHP. Nazwy zmiennych i funkcji mówią, co przechowują i robią (R.5.8): $pdo, $actors, $actor, $actorId, $statement, $filmCount, $dbError, escape(), fullName().

    aktor.php PHP zmienione: 2 linie
    <?php
    function escape(?string $text): string
    {
        return htmlspecialchars($text ?? '', ENT_QUOTES);
    }
    
    function fullName(array $actor): string
    {
        return $actor['imie'] . ' ' . $actor['nazwisko'];
    }
    
    $actorId = filter_var($_GET['id'] ?? '', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
    $actor = null;
    $filmCount = 0;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=kino;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($actorId !== false) {
            $statement = $pdo->prepare(
                'SELECT imie, nazwisko, plik_awatara FROM aktorzy WHERE id_aktora = :id'
            );
            $statement->execute(['id' => $actorId]);
            $actor = $statement->fetch() ?: null;
        }
        if ($actor !== null) {
            $statement = $pdo->prepare(
                'SELECT f.id_filmu, f.tytul, f.rok_produkcji
                 FROM filmy AS f
                 JOIN filmy_aktorzy AS fa ON fa.id_filmu = f.id_filmu
                 WHERE fa.id_aktora = :id'
            );
            $statement->execute(['id' => $actorId]);
            $filmCount = count($statement->fetchAll());
        }
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać danych o aktorach. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Informacje o aktorze | KinoTEKA</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h2><a href="index.php">KinoTEKA</a></h2>
        </header>
        <header>
            <p><em>W naszej bazie znajdują się najlepsi aktorzy</em></p>
        </header>
        <main>
    <?php if ($dbError !== ''): ?>
            <p><?= $dbError ?></p>
    <?php endif; ?>
            <div id="actors">
    <?php if ($actor !== null): ?>
                <article class="actor-profile">
                    <img src="<?= escape($actor['plik_awatara']) ?>" alt="<?= escape(fullName($actor)) ?>" title="<?= escape(fullName($actor)) ?>">
                    <h1><?= escape(fullName($actor)) ?></h1>
                </article>
    <?php else: ?>
                <h1>Nie znaleziono aktora</h1>
    <?php endif; ?>
            </div>
    <?php if ($actor !== null && $filmCount === 0): ?>
            <p><?= escape($actor['imie']) ?> nie znajduje się na listach obsady znanych nam produkcji.</p>
    <?php elseif ($actor !== null): ?>
            <p><?= escape($actor['imie']) ?> znajduje się na listach obsady <?= $filmCount ?> znanych nam produkcji.</p>
    <?php endif; ?>
        </main>
        <footer>
            <p>Autor: <strong>Bartosz Bryniarski</strong></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (aktor.php)
  44. Etap 5: Porównanie z wersją podstawową

    Kroki 44-45 · po tym etapie masz 39 z 39 kryteriów (pkt)

    Krok 44: Co jest inaczej i dlaczego

    ok. 3 min
    Polecenie z arkusza

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

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

    wersja podstawowata wersja
    układfloat: left + clear: both, identyfikatory logo, mottosiatka 40% 60%, header + header
    karty aktorówinline-blocksiatka repeat(auto-fill, 15em)
    karta jako odnośnik<a> owija <div class="actor"><a class="actor">
    zapytanie 3SELECT *wszystkie pola z nazwy
    bazamysqli_* w miejscu wypisywaniaPDO na górze każdego pliku
    pola wiersza$row[4], $row[1] $row[2]nazwy kolumn, fullName()
    id z adresuwstawiony do SQLfilter_var i parametr :id
    liczba filmówmysqli_num_rowscount($statement->fetchAll())
    zły adresostrzeżenia, błąd SQL„Nie znaleziono aktora”

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

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

    ok. 2 min
    Polecenie z arkusza

    Czas przeznaczony na wykonanie zadania wynosi 150 minut.

    • Wersja podstawowa, gdy liczy się czas albo PDO nie jest opanowane: mysqli_num_rows z tabeli 1 arkusza od razu daje liczbę filmów, a skrypty stoją tam, gdzie widać ich efekt.
    • Ta wersja, gdy PDO i siatka są pisane z pamięci. repeat(auto-fill, ...) warto znać - jedna linia zastępuje ręczne liczenie, ile kart zmieści się w rzędzie.

    Mieszanie też daje pełne punkty: siatka z tej wersji i mysqli_* z podstawowej. Funkcje escape() i fullName() są powtórzone w obu plikach - w większym projekcie trafiłyby do osobnego pliku dołączanego przez require.

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

  46. Etap 6: Oddanie pracy

    Krok 46 · po tym etapie masz 39 z 39 kryteriów (pkt)

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

    W folderze z numerem zdającego: aktor.php, import.png, index.php, kwerendy.txt, kw1.jpg - kw5.jpg, przeglądarka.txt, styl.css i awatary awatar_1.jpg - awatar_10.jpg. Po nagraniu sprawdź odczyt płyty.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.