Przejdź do głównej treści
EE.09-03-21.06-SGCzerwiec 2021 · 25 kroków · ok. 69 min · 36 kryteriów z klucza

Rozwiązanie krok po kroku: Internetowa wypożyczalnia filmów (Nowoczesny kod (flex, PDO))

To drugi sposób rozwiązania tego samego arkusza. Baza, zapytania, zrzuty i skadrowana grafika są takie same jak w wersji podstawowej, strona video.php wygląda tak samo i spełnia każde kryterium klucza. Inna jest droga:

  • PHP na PDO na górze pliku, przed HTML-em: usunięcie filmu, oba zapytania i zamknięcie połączenia w jednym miejscu, a w HTML-u zostaje szablon wstawiający gotowe dane (<?= ... ?>),
  • usuwanie przed wypisaniem list i przekierowanie po nim (wzorzec POST - przekierowanie - GET): usunięty film od razu znika z listy, a odświeżenie strony nie wysyła formularza drugi raz,
  • numer z formularza sprawdzony na serwerze (filter_input) i zapytanie przygotowane - wpisanie w pole kodu SQL nie usunie całej tabeli,
  • htmlspecialchars przy każdym wypisaniu danych z bazy (funkcja e()),
  • banery obok siebie i filmy w rzędach na flexboksie, kolory w zmiennych CSS, nagłówki tabeli w <th>, pole formularza z etykietą <label>.

To arkusz EE.09 z czerwca 2021 - kwalifikacji formuły 2017, którą zastąpiła INF.03. Baza i grafiki pochodzą z oryginalnego archiwum CKE zad3 (załącznik na stronie arkusza), zapytania i zrzuty są policzone na MariaDB 10.4, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2. Klucz każe sprawdzać w XAMPP 7.4.8 - kod nie używa niczego, czego tam nie ma.

Jak liczą się punkty. Klucz ma 36 kryteriów (R.1.1 - R.5.6), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 27 punktów. Klucz przyjmuje zarówno mysqli, jak i PDO.

  1. Etap 1: Baza danych

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

    Krok 1: Folder zdającego i archiwum zad3

    ok. 2 min
    Polecenie z arkusza

    Należy utworzyć folder o nazwie odpowiadającej numerowi PESEL zdającego lub w przypadku jego braku innemu numerowi, którym został podpisany arkusz. Archiwum należy rozpakować do tego folderu. Ważne! Aby strona działała poprawnie rozpakowane pliki muszą znaleźć się w tym samym folderze, co pliki strony. Wszystkie wykonane w czasie egzaminu pliki w tym zrzuty ekranu, pliki strony, stylów należy umieścić w tym folderze.

    Folder nazwany numerem PESEL - w tym rozwiązaniu w jego miejscu stoi imię i nazwisko autora, Bartosz Bryniarski. Archiwum zad3.zip rozpakowujesz do niego, hasło: #MoJeVoD$ (wielkość liter ma znaczenie). W środku: dane.sql i osiem grafik filmów (alien.jpg, et.jpg, fantasy.jpg, hobbit.jpg, koralowa_wyspa.jpg, krokodyl.jpg, star-wars.jpg, steampunk.jpg). Nazwy plików są zapisane w bazie, w kolumnie zdjecie - skrypt wstawi je do znaczników <img>, dlatego grafiki muszą leżeć obok strony.

    Strona jest w PHP, więc działa przez serwer: folder możesz trzymać w C:\xampp\htdocs i na koniec skopiować wyniki do folderu z numerem.

  2. Krok 2: Pusta baza dane3

    ok. 2 min
    Polecenie z arkusza

    Uruchom usługi MySQL i Apache za pomocą XAMPP Control Panel. Za pomocą narzędzia phpMyAdmin wykonaj podane operacje na bazie danych: Utwórz bazę danych o nazwie dane3

    XAMPP Control Panel - Start przy Apache i MySQL, potem http://localhost/phpmyadmin. Nowa, nazwa dane3 (z cyfrą), Utwórz.

  3. Krok 3: Import pliku dane.sql i zrzut egzamin.jpg

    Kryteria:

    R.1.1 Wykonano import tabel do bazy danych dane3 , czynność udokumentowano plikiem o nazwie egzamin w formacie JPEG

    ok. 3 min
    Polecenie z arkusza

    Do bazy dane3 zaimportuj plik dane.sql z rozpakowanego archiwum. Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w folderze z numerem PESEL lub w przypadku jego braku w folderze z innym numerem, którym został podpisany arkusz, w formacie JPEG i nazwij egzamin. 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

    Zaznacz bazę dane3, karta Import, plik dane.sql, Wykonaj. Powstaje pięć tabel - poza trzema z obrazu 1 (produkty, producenci, rodzaje) także hobby i osoby, których zadanie nie używa. Tabela produkty ma 25 wierszy: warzywa, owoce i filmy.

    Zrzut całego ekranu z komunikatem o sukcesie i listą tabel zapisz jako egzamin.jpg - w tym arkuszu zrzut importu nazywa się egzamin, nie import.

    Typowe błędy

    Zrzut nazwany import.jpg albo zapisany w PNG nie spełnia R.1.1.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 3
  4. Krok 4: Zapytanie 1: filmy o numerach 18, 22, 23, 25

    Kryteria: ,

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

    R.1.3 Utworzono zapytanie 1 wybierające jedynie pola id, nazwa, opis, zdjecie z tabeli produkty , dla produktów, których id jest jedną z wartości: 18, 22, 23, 25. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id, nazwa, opis, zdjecie FROM produkty WHERE id IN (18, 22, 23, 25); (możliwe różne warianty klauzuli WHERE) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są dokładnie 4 rekordy, jedynie pola id, nazwa, opis, zdjecie

    ok. 3 min
    Polecenie z arkusza

    Zapisz i wykonaj zapytania SQL działające na bazie dane3. Zapytania zapisz w pliku kwerendy.txt, w folderze z numerem PESEL lub w przypadku jego braku w folderze z innym numerem, którym został podpisany arkusz. Wykonaj zrzuty ekranu przedstawiające wyniki działania kwerend. Zrzuty zapisz w formacie PNG i nadaj im nazwy kw1, kw2, kw3, kw4. Zrzuty powinny obejmować cały ekran monitora z widocznym paskiem zadań. Zapytanie 1: wybierające jedynie pola id, nazwa, opis, zdjecie z tabeli produkty, dla produktów, których id jest jedną z wartości: 18, 22, 23, 25

    • SELECT id, nazwa, opis, zdjecie - cztery pola, w tej kolejności; skrypt odczyta je po numerach 0 - 3,
    • FROM produkty,
    • WHERE id IN (18, 22, 23, 25) - „jedna z wartości” to właśnie IN z listą; dłuższy zapis id = 18 OR id = 22 OR ... daje ten sam wynik.

    Wynik: 4 wiersze - Alien4, Koralowa wyspa, krokodyl, SteamPunk - to filmy z bloku „Polecamy” na obrazie 2. Zapytanie do kwerendy.txt (R.1.2), zrzut: kw1.png.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT id, nazwa, opis, zdjecie FROM produkty WHERE id IN (18, 22, 23, 25);
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: filmy z rodzaju 12

    Kryteria:

    R.1.4 Utworzono zapytanie 2 wybierające jedynie pola id, nazwa, opis, zdjecie z tabeli produkty , dla produktów, których pole Rodzaje_id przyjmuje wartość 12. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id, nazwa, opis, zdjecie FROM produkty WHERE Rodzaje_id = 12; oraz wynik działania zapytania udokumentowano zrzutem, na którym widocznych jest 11 rekordów, jedynie pola id, nazwa, opis, zdjecie

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pola id, nazwa, opis, zdjecie z tabeli produkty, dla produktów, których pole Rodzaje_id przyjmuje wartość 12

    Te same pola, warunek WHERE Rodzaje_id = 12 - nazwa kolumny wielką literą R, jak w bazie. Rodzaj 12 to „fantastyczne”. Wynik: 11 wierszy (Star Wars 1 - 4, E.T., hobbit, hobbit2 i hobbit3, Fantasy i Fantasy2, SteamPunk). Zrzut: kw2.png.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT id, nazwa, opis, zdjecie FROM produkty WHERE id IN (18, 22, 23, 25);
    
    -- Zapytanie 2
    SELECT id, nazwa, opis, zdjecie FROM produkty WHERE Rodzaje_id = 12;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: produkt i nazwa jego rodzaju

    Kryteria:

    R.1.5 Utworzono zapytanie 3 wybierające nazwę z tabeli produkty i odpowiadającą mu nazwę z tabeli rodzaje dla wierszy, których w polu opis występuje „Leia”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT produkty.nazwa, rodzaje.nazwa FROM produkty JOIN rodzaje ON produkty.Rodzaje_id = rodzaje.id WHERE opis LIKE "%Leia%"; możliwe też INNER JOIN lub bez JOIN (porównanie kluczy w warunku) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są dokładnie 4 rekordy z danymi: Star Wars 1 fantastyczne; Star Wars 2 fantastyczne; Star Wars 3 fantastyczne; Star Wars 4 fantastyczne

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające nazwę z tabeli produkty i odpowiadającą mu nazwę z tabeli rodzaje dla wierszy, których w polu opis występuje ciąg znaków: „Leia”

    Dwie tabele mają kolumnę nazwa, więc przed każdą stoi nazwa tabeli:

    • SELECT produkty.nazwa, rodzaje.nazwa,
    • FROM produkty JOIN rodzaje ON produkty.Rodzaje_id = rodzaje.id - do każdego produktu dołączony wiersz jego rodzaju; warunek ON łączy klucz obcy z kluczem głównym (relacja z obrazu 1),
    • WHERE produkty.opis LIKE '%Leia%' - % zastępuje dowolny ciąg znaków, więc „Leia” może stać w dowolnym miejscu opisu.

    Wynik: 4 wiersze - Star Wars 1, 2, 3 i 4, każdy z rodzajem „fantastyczne”. Zrzut: kw3.png.

    Typowe błędy

    Bez nazw tabel (SELECT nazwa, nazwa) MariaDB zgłosi błąd „kolumna nazwa jest niejednoznaczna”. LIKE 'Leia' bez znaków % szuka opisu złożonego tylko z tego słowa i nie znajdzie nic.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT id, nazwa, opis, zdjecie FROM produkty WHERE id IN (18, 22, 23, 25);
    
    -- Zapytanie 2
    SELECT id, nazwa, opis, zdjecie FROM produkty WHERE Rodzaje_id = 12;
    
    -- Zapytanie 3
    SELECT produkty.nazwa, rodzaje.nazwa FROM produkty JOIN rodzaje ON produkty.Rodzaje_id = rodzaje.id WHERE produkty.opis LIKE '%Leia%';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: usunięcie wiersza o id 15

    Kryteria:

    R.1.6 Utworzono zapytanie 4 usuwające wiersz z tabeli produkty o id 15. W pliku z kwerendami lub na zrzucie istnieje zapis np.: DELETE FROM produkty WHERE id = 15; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest komunikat o poprawnym usunięciu lub tabela bez wiersza o id = 15

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: usuwające wiersz z tabeli produkty o id równym 15

    DELETE FROM produkty WHERE id = 15 - phpMyAdmin pyta o potwierdzenie i odpowiada „1 wiersz usunięty” (zrzut kw4.png). Usunięty film to Alien3 - nie ma go w żadnej z list strony. To samo zapytanie, z numerem z formularza, wykona skrypt 3.

    Typowe błędy

    DELETE FROM produkty bez WHERE usuwa wszystkie wiersze tabeli. Przed takim zapytaniem w prawdziwej bazie robi się kopię - tu wystarczy ponowny import dane.sql.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT id, nazwa, opis, zdjecie FROM produkty WHERE id IN (18, 22, 23, 25);
    
    -- Zapytanie 2
    SELECT id, nazwa, opis, zdjecie FROM produkty WHERE Rodzaje_id = 12;
    
    -- Zapytanie 3
    SELECT produkty.nazwa, rodzaje.nazwa FROM produkty JOIN rodzaje ON produkty.Rodzaje_id = rodzaje.id WHERE produkty.opis LIKE '%Leia%';
    
    -- Zapytanie 4
    DELETE FROM produkty WHERE id = 15;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

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

    Krok 8: Grafika: kadrowanie do 226 × 150 px

    Kryteria:

    R.2.1 Plik koralowa_wyspa.jpg skadrowano tak, aby jego wysokość wynosiła dokładnie 150 px, szerokość pozostaje bez zmian 226 px

    ok. 3 min
    Polecenie z arkusza

    Plik koralowa_wyspa.jpg, wypakowany z archiwum, należy skadrować tak, aby jego wysokość wynosiła dokładnie 150 px, szerokość pozostaje bez zmian 226 px. Pozostałe pliki graficzne są przygotowane i nie należy ich zmieniać

    Plik ma 226 × 181 px, pozostałe grafiki filmów - 226 × 150. Kadrowanie to wycięcie fragmentu, a nie skalowanie: obraz nie zmienia proporcji, odcinasz 31 px. W GIMP-ie narzędzie Kadrowanie (Shift+C), w opcjach narzędzia pozycja 0, 0 i rozmiar 226 × 150, Enter, potem Plik → Nadpisz koralowa_wyspa.jpg.

    Pas odcięty jest z dołu kadru - zostają całe głowy aktorów, jak na obrazie 2. Klucz sprawdza tylko wymiary (R.2.1).

    Typowe błędy

    Obraz → Skaluj obraz do 226 × 150 przy rozłączonym łańcuchu też da te wymiary, ale spłaszczy twarze - to nie jest kadrowanie.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Szkielet video.php: HTML5, kodowanie, tytuł, styl

    Kryteria: , , ,

    R.2.2 Witrynę internetową zapisano w pliku PHP o nazwie video

    R.2.3 Zastosowano właściwy standard kodowania polskich znaków dla witryny

    R.2.4 Nadano tytuł strony: "Video On Demand"

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

    ok. 3 min
    Polecenie z arkusza

    Składa się ze strony o nazwie video.php. Zastosowany właściwy standard kodowania polskich znaków. Tytuł strony widoczny na karcie przeglądarki: „Video On Demand”. Arkusz stylów w pliku o nazwie styl3.css prawidłowo połączony z kodem strony

    Plik video.php (R.2.2) z nagłówkiem, który w każdej stronie wygląda tak samo: <!DOCTYPE html>, <html lang="pl">, <meta charset="UTF-8"> (R.2.3), <title>Video On Demand</title> (R.2.4) i <link> do styl3.css (R.3.5). Doszedł <meta name="viewport"> - telefon pokaże stronę w szerokości ekranu; klucz go nie ocenia.

    video.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>Video On Demand</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (video.php)
  10. Krok 10: Bloki: baner z dwiema częściami, dwie listy, stopka

    Kryteria:

    R.2.5 Wygląd strony zdefiniowano dzieląc ją na bloki: dwa bloki banera, blok listy „Polecamy”, blok listy „Filmy fantastyczne” i stopka. Zastosowano znaczniki sekcji

    ok. 3 min
    Polecenie z arkusza

    Podział strony na bloki: dwa bloki banera obok siebie, blok listy „Polecamy”, blok listy „Filmy fantastyczne” i stopka. W blokach listy znajdują się bloki filmów generowane przez skrypt. Podział zrealizowany za pomocą znaczników sekcji tak, aby wygląd układu bloków strony po uruchomieniu w przeglądarce, był zgodny z Obrazem 2

    • <header> obejmuje oba banery - to jeden nagłówek strony podzielony na dwie części; na nim flexbox ustawi banery obok siebie,
    • banery jako <section> z identyfikatorami banner-left i banner-right,
    • <main> - główna treść strony, czyli dwie listy: <section> z identyfikatorami recommended i fantasy oraz wspólną klasą list (jedna reguła CSS dla obu),
    • <footer> - stopka.

    Wszystkie bloki z arkusza to znaczniki sekcji (R.2.5), a header i main mówią czytnikom ekranu i wyszukiwarkom, która część strony jest czym.

    video.php PHP zmienione: 14 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Video On Demand</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
            </section>
            <section id="banner-right">
            </section>
        </header>
        <main>
            <section id="recommended" class="list">
            </section>
            <section id="fantasy" class="list">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (video.php)
  11. Krok 11: Banery: nagłówek i tabela z nagłówkami kolumn

    Kryteria:

    R.2.7 W bloku banera prawego umieszczono tabelę o dwóch wierszach i trzech kolumnach zgodną z obrazem 3 arkusza, zastosowano znaczniki <table>, <tr>, <td> oraz ich zamknięcia. Zastosowanie <th> nie jest błędem

    ok. 3 min
    Polecenie z arkusza

    Zawartość banera lewego: nagłówek pierwszego stopnia o treści „Internetowa wypożyczalnia filmów”. Zawartość banera prawego: tabela o dwóch wierszach i trzech kolumnach, której widok jest zgodny z obrazem 3

    • w lewym banerze <h1>,
    • w prawym tabela 2 × 3 (R.2.7). Pierwszy wiersz to nazwy gatunków, czyli nagłówki kolumn, więc stoją w <th scope="col">, a liczby w <td>. Czytnik ekranu przeczyta wtedy „Horror: 30”, a nie same liczby. Klucz dopuszcza <th> w pierwszym wierszu.

    Przeglądarka pogrubia i centruje <th> - styl przywróci wygląd z obrazu 3.

    video.php PHP zmienione: 13 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Video On Demand</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowa wypożyczalnia filmów</h1>
            </section>
            <section id="banner-right">
                <table>
                    <tr>
                        <th scope="col">Kryminał</th>
                        <th scope="col">Horror</th>
                        <th scope="col">Przygodowy</th>
                    </tr>
                    <tr>
                        <td>20</td>
                        <td>30</td>
                        <td>20</td>
                    </tr>
                </table>
            </section>
        </header>
        <main>
            <section id="recommended" class="list">
            </section>
            <section id="fantasy" class="list">
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (video.php)
  12. Krok 12: Listy: nagłówki i kontenery filmów

    Kryteria:

    R.2.6 W bloku banera lewego zapisano nagłówek h1: "Internetowa wypożyczalnia filmów" oraz w przynajmniej jednym bloku listy zapisano nagłówek h3: „Polecamy” lub „Filmy fantastyczne”

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku listy „Polecamy”: Nagłówek trzeciego stopnia o treści „Polecamy”. Efekt działania skryptu 1. Zawartość bloku listy „Filmy fantastyczne”: Nagłówek trzeciego stopnia o treści „Filmy fantastyczne”. Efekt działania skryptu 2

    W każdej liście <h3> (R.2.6) i pod nim <div class="films"> - pojemnik na bloki filmów, które wstawi szablon PHP. Pojemnik oddziela filmy od nagłówka: flexbox ustawi w rzędy tylko filmy, a pasek <h3> zostanie nad nimi na całą szerokość.

    video.php PHP zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Video On Demand</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowa wypożyczalnia filmów</h1>
            </section>
            <section id="banner-right">
                <table>
                    <tr>
                        <th scope="col">Kryminał</th>
                        <th scope="col">Horror</th>
                        <th scope="col">Przygodowy</th>
                    </tr>
                    <tr>
                        <td>20</td>
                        <td>30</td>
                        <td>20</td>
                    </tr>
                </table>
            </section>
        </header>
        <main>
            <section id="recommended" class="list">
                <h3>Polecamy</h3>
                <div class="films">
                </div>
            </section>
            <section id="fantasy" class="list">
                <h3>Filmy fantastyczne</h3>
                <div class="films">
                </div>
            </section>
        </main>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (video.php)
  13. Krok 13: Stopka: formularz z etykietą pola

    Kryteria: , ,

    R.2.9 W stopce umieszczono formularz z polem edycyjnym i przyciskiem, zastosowano znacznik <form> oraz jego zamknięcie

    R.3.3 Formularz wysyła dane metodą POST

    R.3.4 Zastosowano typ number dla pola edycyjnego oraz wciśnięcie przycisku wysyła dane do skryptu (typ submit dla input lub button albo button bez atrybutu typu)

    ok. 3 min
    Polecenie z arkusza

    Zawartość stopki: Formularz wysyłający dane do tego samego pliku metodą POST z zawartością: Pole edycyjne typu numerycznego poprzedzone wpisem „Usuń film nr.: ”. Przycisk „Usuń film” wysyłający dane do skryptu 3

    • <form method="post"> bez action - dane wracają do video.php (R.3.3); POST, bo formularz zmienia dane w bazie,
    • napis przed polem to <label for="film-id"> połączony z polem przez id: kliknięcie napisu ustawia kursor w polu, a czytnik ekranu odczyta, czego pole dotyczy. Skrót „nr” po polsku pisze się bez kropki - klucz nie ocenia tej różnicy,
    • <input type="number" name="id" min="1" required> - przeglądarka nie wyśle pustego pola ani liczby mniejszej od 1; serwer i tak sprawdzi numer sam,
    • <button type="submit"> wysyła formularz (R.2.9, R.3.4).
    video.php PHP zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Video On Demand</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowa wypożyczalnia filmów</h1>
            </section>
            <section id="banner-right">
                <table>
                    <tr>
                        <th scope="col">Kryminał</th>
                        <th scope="col">Horror</th>
                        <th scope="col">Przygodowy</th>
                    </tr>
                    <tr>
                        <td>20</td>
                        <td>30</td>
                        <td>20</td>
                    </tr>
                </table>
            </section>
        </header>
        <main>
            <section id="recommended" class="list">
                <h3>Polecamy</h3>
                <div class="films">
                </div>
            </section>
            <section id="fantasy" class="list">
                <h3>Filmy fantastyczne</h3>
                <div class="films">
                </div>
            </section>
        </main>
        <footer>
            <form method="post">
                <label for="film-id">Usuń film nr:</label>
                <input type="number" id="film-id" name="id" min="1" required>
                <button type="submit">Usuń film</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (video.php)
  14. Krok 14: Autor strony jako odnośnik pocztowy

    Kryteria:

    R.3.2 Na stronie umieszczono odnośnik pocztowy połączony z adresem mailto:[email protected]

    ok. 1 min
    Polecenie z arkusza

    Napis o treści „Stronę wykonał: ”, dalej wstawiony numer PESEL zdającego lub w przypadku jego braku inny numer, którym został podpisany arkusz. Numer PESEL lub w przypadku jego braku inny numer, którym został podpisany arkusz jest odnośnikiem pocztowym do adresu e-mail: „[email protected]”

    Na egzaminie numer PESEL, tu imię i nazwisko autora, Bartosz Bryniarski, w odnośniku <a href="mailto:[email protected]"> (R.3.2).

    video.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Video On Demand</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowa wypożyczalnia filmów</h1>
            </section>
            <section id="banner-right">
                <table>
                    <tr>
                        <th scope="col">Kryminał</th>
                        <th scope="col">Horror</th>
                        <th scope="col">Przygodowy</th>
                    </tr>
                    <tr>
                        <td>20</td>
                        <td>30</td>
                        <td>20</td>
                    </tr>
                </table>
            </section>
        </header>
        <main>
            <section id="recommended" class="list">
                <h3>Polecamy</h3>
                <div class="films">
                </div>
            </section>
            <section id="fantasy" class="list">
                <h3>Filmy fantastyczne</h3>
                <div class="films">
                </div>
            </section>
        </main>
        <footer>
            <form method="post">
                <label for="film-id">Usuń film nr:</label>
                <input type="number" id="film-id" name="id" min="1" required>
                <button type="submit">Usuń film</button>
            </form>
            <p>Stronę wykonał: <a href="mailto:[email protected]">Bartosz Bryniarski</a></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (video.php)
  15. Etap 3: Styl CSS

    Kroki 15-19 · po tym etapie masz 29 z 36 kryteriów (pkt)

    Krok 15: Zmienne kolorów i krój strony

    Kryteria:

    R.4.1 Ustawiono domyślny krój czcionki dla całej strony na Verdana (dla selektora * lub body lub html lub kontenera całej strony)

    ok. 2 min
    Polecenie z arkusza

    Domyślne dla całej strony: krój czcionki Verdana

    Na początku styl3.css trzy zmienne CSS w :root: ciemny granat banerów i stopki, jasny szary list i kolor pasków nagłówków. Kolor #29434E stoi w arkuszu przy trzech blokach - zmiana odcienia to teraz jedna linia. W regułach kolor wstawia var(--dark); przeglądarka podstawia wartość, więc obliczony kolor tła to dokładnie #29434E.

    Krój Verdana na body (R.4.1).

    styl3.css CSS nowy plik
    :root {
        --dark: #29434E;
        --light: #B0BEC5;
        --heading: #546E7A;
    }
    body {
        font-family: Verdana;
    }
    Podgląd strony po tym kroku (video.php)
  16. Krok 16: Banery obok siebie na flexboksie

    Kryteria: , ,

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

    R.4.5 Ustawiono szerokość banera lewego: 70%, prawego 30%

    R.4.6 Ustawiono wyrównanie tekstu do środka dla banera lewego

    ok. 3 min
    Polecenie z arkusza

    Dla banera lewego: kolor tła #29434E, biały kolor czcionki, wyrównanie tekstu do środka, szerokość 70%, wysokość 70 px. Dla banera prawego: kolor tła #29434E, biały kolor czcionki, szerokość 30%, wysokość 70 px

    • header { display: flex; } - dzieci nagłówka, czyli oba banery, stają w jednym rzędzie; bez float i bez późniejszego clear na listach,
    • szerokości 70% i 30% zostają na banerach (R.4.5) - klucz ocenia szerokość ustawioną na blokach,
    • tło, biały tekst, wysokość 70 px i wyrównanie do środka w lewym (R.4.3, R.4.6),
    • #banner-right th - zwykła grubość i wyrównanie do lewej: nagłówki tabeli wyglądają jak na obrazie 3, a znaczenie <th> zostaje w kodzie.
    styl3.css CSS zmienione: 20 linii
    :root {
        --dark: #29434E;
        --light: #B0BEC5;
        --heading: #546E7A;
    }
    body {
        font-family: Verdana;
    }
    header {
        display: flex;
    }
    #banner-left {
        background-color: var(--dark);
        color: white;
        text-align: center;
        width: 70%;
        height: 70px;
    }
    #banner-right {
        background-color: var(--dark);
        color: white;
        width: 30%;
        height: 70px;
    }
    #banner-right th {
        font-weight: normal;
        text-align: left;
    }
    Podgląd strony po tym kroku (video.php)
  17. Krok 17: Listy z paskami przewijania, filmy w rzędach

    Kryteria: , ,

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu egzaminacyjnym (nie uwzględnia się bloków zawierających filmy)

    R.4.4 Ustawiono wysokość obu bloków banera: 70px, obu bloków listy: 320px

    R.4.7 Ustawiono przynajmniej jeden pasek przewijania (przesuwania) dla przynajmniej jednego bloku listy, np. overflow: scroll; (lub auto)

    ok. 3 min
    Polecenie z arkusza

    Dla obu bloków list: kolor tła #B0BEC5, wysokość 320 px, paski przesuwania (scroll)

    • .list - tło, wysokość 320 px (banery i listy mają wysokości, R.4.4) i overflow: scroll - treść, która się nie mieści, przewija się wewnątrz bloku (R.4.7),
    • .films { display: flex; flex-wrap: wrap; } - filmy stają obok siebie, a gdy brakuje miejsca, przechodzą do następnego rzędu.

    Listy są zwykłymi blokami pod nagłówkiem strony, więc układ jest zgodny z obrazem 2 bez clear (R.3.1).

    styl3.css CSS zmienione: 9 linii
    :root {
        --dark: #29434E;
        --light: #B0BEC5;
        --heading: #546E7A;
    }
    body {
        font-family: Verdana;
    }
    header {
        display: flex;
    }
    #banner-left {
        background-color: var(--dark);
        color: white;
        text-align: center;
        width: 70%;
        height: 70px;
    }
    #banner-right {
        background-color: var(--dark);
        color: white;
        width: 30%;
        height: 70px;
    }
    #banner-right th {
        font-weight: normal;
        text-align: left;
    }
    .list {
        background-color: var(--light);
        height: 320px;
        overflow: scroll;
    }
    .films {
        display: flex;
        flex-wrap: wrap;
    }
    Podgląd strony po tym kroku (video.php)
  18. Krok 18: Blok filmu i nagłówki h3, h4

    Kryteria: , ,

    R.4.8 Ustawiono styl bloku filmu: marginesy wewnętrzne 5px (padding), szerokość 226px, wysokość 280px

    R.4.9 Ustawiono styl selektora h3: kolor tła #546E7A, biały kolor czcionki, marginesy zewnętrzne (margin) 0px (0 również poprawne bez jednostki)

    R.4.10 Ustawiono styl selektora h4: wyrównanie tekstu do środka, marginesy zewnętrzne (margin) 0px (0 również poprawne bez jednostki)

    ok. 2 min
    Polecenie z arkusza

    Dla bloku filmu generowanego przez skrypt 1 lub 2: marginesy wewnętrzne 5 px, szerokość 226px, wysokość 280px. Dla selektora nagłówka trzeciego stopnia: kolor tła #546E7A, biały kolor czcionki, marginesy zewnętrzne 0 px. Dla selektora nagłówka czwartego stopnia: wyrównanie tekstu do środka, marginesy zewnętrzne 0 px

    • .film - wymiary z arkusza (R.4.8),
    • selektory znaczników h3 i h4 - klucz uznaje R.4.9 i R.4.10 tylko przy selektorze znacznika, nie klasy.

    Element flex może się zwężać, gdy brakuje miejsca, ale przy flex-wrap: wrap film przechodzi do nowego rzędu, zanim musiałby się zwęzić - każdy blok ma pełne 226 px (sprawdzone w Chrome).

    styl3.css CSS zmienione: 14 linii
    :root {
        --dark: #29434E;
        --light: #B0BEC5;
        --heading: #546E7A;
    }
    body {
        font-family: Verdana;
    }
    header {
        display: flex;
    }
    #banner-left {
        background-color: var(--dark);
        color: white;
        text-align: center;
        width: 70%;
        height: 70px;
    }
    #banner-right {
        background-color: var(--dark);
        color: white;
        width: 30%;
        height: 70px;
    }
    #banner-right th {
        font-weight: normal;
        text-align: left;
    }
    .list {
        background-color: var(--light);
        height: 320px;
        overflow: scroll;
    }
    .films {
        display: flex;
        flex-wrap: wrap;
    }
    .film {
        padding: 5px;
        width: 226px;
        height: 280px;
    }
    h3 {
        background-color: var(--heading);
        color: white;
        margin: 0;
    }
    h4 {
        text-align: center;
        margin: 0;
    }
    Podgląd strony po tym kroku (video.php)
  19. Krok 19: Stopka

    Kryteria:

    R.4.2 Ustawiono kolor tła #29434E dla obu bloków banera i stopki, #B0BEC5 dla obu bloków listy

    ok. 1 min
    Polecenie z arkusza

    Dla stopki: kolor tła #29434E, biały kolor czcionki

    Stopka w tych samych zmiennych co banery. Tło banerów, stopki i list jest kompletne (R.4.2).

    styl3.css CSS zmienione: 4 linie
    :root {
        --dark: #29434E;
        --light: #B0BEC5;
        --heading: #546E7A;
    }
    body {
        font-family: Verdana;
    }
    header {
        display: flex;
    }
    #banner-left {
        background-color: var(--dark);
        color: white;
        text-align: center;
        width: 70%;
        height: 70px;
    }
    #banner-right {
        background-color: var(--dark);
        color: white;
        width: 30%;
        height: 70px;
    }
    #banner-right th {
        font-weight: normal;
        text-align: left;
    }
    .list {
        background-color: var(--light);
        height: 320px;
        overflow: scroll;
    }
    .films {
        display: flex;
        flex-wrap: wrap;
    }
    .film {
        padding: 5px;
        width: 226px;
        height: 280px;
    }
    h3 {
        background-color: var(--heading);
        color: white;
        margin: 0;
    }
    h4 {
        text-align: center;
        margin: 0;
    }
    footer {
        background-color: var(--dark);
        color: white;
    }
    Podgląd strony po tym kroku (video.php)
  20. Etap 4: Skrypt PHP

    Kroki 20-23 · po tym etapie masz 36 z 36 kryteriów (pkt)

    Krok 20: Na górze pliku: połączenie PDO z bazą dane3

    Kryteria: ,

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

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

    ok. 4 min
    Polecenie z arkusza

    Napisany w języku PHP. Skrypt łączy się z serwerem bazy danych na localhost, użytkownik root bez hasła, baza danych o nazwie dane3. Po wykonaniu operacji na bazie danych skrypt zamyka połączenie z serwerem.

    Cały PHP stoi na początku pliku, przed <!DOCTYPE html>: najpierw praca z bazą, potem HTML, który tylko wstawia gotowe dane.

    • declare(strict_types=1); - PHP nie zamienia po cichu typów przy wywołaniach funkcji,
    • new PDO('mysql:host=localhost;dbname=dane3;charset=utf8mb4', 'root', '', ...) - serwer, baza dane3 i kodowanie w jednym napisie, użytkownik root, puste hasło (R.5.1),
    • ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, który łapie catch i zamienia na komunikat w stopce (role="alert" - czytnik ekranu odczyta go od razu); FETCH_ASSOC - wiersze jako tablice z nazwami kolumn,
    • finally { $pdo = null; } - wykona się zawsze, po udanych zapytaniach i po błędzie; usunięcie obiektu PDO zamyka połączenie. Wszystkie zapytania dojdą w środku try, więc zamknięcie zawsze będzie ostatnią operacją na bazie (R.5.2).

    Od tego kroku podgląd to wynik prawdziwego uruchomienia w PHP 8.2 - na razie strona wygląda jak wcześniej.

    video.php PHP zmienione: 18 linii
    <?php
    declare(strict_types=1);
    
    $error = '';
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=dane3;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    } catch (PDOException $exception) {
        $error = 'Brak połączenia z bazą danych.';
    } finally {
        $pdo = null;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Video On Demand</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowa wypożyczalnia filmów</h1>
            </section>
            <section id="banner-right">
                <table>
                    <tr>
                        <th scope="col">Kryminał</th>
                        <th scope="col">Horror</th>
                        <th scope="col">Przygodowy</th>
                    </tr>
                    <tr>
                        <td>20</td>
                        <td>30</td>
                        <td>20</td>
                    </tr>
                </table>
            </section>
        </header>
        <main>
            <section id="recommended" class="list">
                <h3>Polecamy</h3>
                <div class="films">
                </div>
            </section>
            <section id="fantasy" class="list">
                <h3>Filmy fantastyczne</h3>
                <div class="films">
                </div>
            </section>
        </main>
        <footer>
            <form method="post">
                <label for="film-id">Usuń film nr:</label>
                <input type="number" id="film-id" name="id" min="1" required>
                <button type="submit">Usuń film</button>
            </form>
            <?php if ($error !== ''): ?>
                <p role="alert"><?= $error ?></p>
            <?php endif; ?>
            <p>Stronę wykonał: <a href="mailto:[email protected]">Bartosz Bryniarski</a></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (video.php)
  21. Krok 21: Lista „Polecamy”: zapytanie 1 i szablon filmów

    Kryteria: , ,

    R.2.8 Przynajmniej jeden obraz ma tekst alternatywny "film" (w HTML lub skrypcie)

    R.5.3 Skrypt 1 wysyła do bazy zapytanie 1 lub skrypt 2 - zapytanie 2, lub skrypt 3 - zapytanie 4

    R.5.4 Przynajmniej jeden: skrypt 1 lub 2 tworzy blok w którym znajdują się elementy: nagłówek h4 o treści „<id>. <nazwa>”, obraz, paragraf (akapit) o treści <opis>. Wartości wszystkich elementów zostały pobrane z bazy

    ok. 4 min
    Polecenie z arkusza

    Działanie skryptu 1: Wysyła do bazy zapytanie 1. Dla każdego zwróconego zapytaniem wiersza tworzy blok filmu, w którym znajdują się: Nagłówek czwartego stopnia o treści „<id>. <nazwa>”, gdzie pola < > są pobrane z bazy. Obraz o nazwie pobranej z bazy danych i tekście alternatywnym „film”. Paragraf (akapit) o treści zgodnej z polem opis pobranym z bazy

    Na górze pliku:

    • funkcja e() - skrót htmlspecialchars z kodowaniem UTF-8; każde wypisanie danych z bazy przechodzi przez nią, więc znacznik zapisany w opisie filmu pokaże się jako tekst, a nie stanie się kodem strony (ochrona przed XSS),
    • zapytanie 1 przez query (nie ma w nim danych od użytkownika) i fetchAll() - wszystkie wiersze naraz w tablicy $recommended (R.5.3).

    W kontenerze filmów pętla foreach w składni z dwukropkiem i blok filmu jako szablon: <?= ... ?> to skrót od <?php echo ... ?>; nagłówek <h4> z numerem i nazwą, <img> z nazwą pliku z kolumny zdjecie i tekstem film, akapit z opisem (R.2.8, R.5.4). Atrybuty width i height rezerwują miejsce na obraz, zanim się wczyta - strona nie przeskakuje. Podgląd: lista „Polecamy” jak na obrazie 2.

    video.php PHP zmienione: 14 linii
    <?php
    declare(strict_types=1);
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $error = '';
    $recommended = [];
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=dane3;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $recommended = $pdo->query('SELECT id, nazwa, opis, zdjecie FROM produkty WHERE id IN (18, 22, 23, 25)')->fetchAll();
    } catch (PDOException $exception) {
        $error = 'Brak połączenia z bazą danych.';
    } finally {
        $pdo = null;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Video On Demand</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowa wypożyczalnia filmów</h1>
            </section>
            <section id="banner-right">
                <table>
                    <tr>
                        <th scope="col">Kryminał</th>
                        <th scope="col">Horror</th>
                        <th scope="col">Przygodowy</th>
                    </tr>
                    <tr>
                        <td>20</td>
                        <td>30</td>
                        <td>20</td>
                    </tr>
                </table>
            </section>
        </header>
        <main>
            <section id="recommended" class="list">
                <h3>Polecamy</h3>
                <div class="films">
                    <?php foreach ($recommended as $film): ?>
                        <div class="film">
                            <h4><?= e($film['id'] . '. ' . $film['nazwa']) ?></h4>
                            <img src="<?= e($film['zdjecie']) ?>" alt="film" width="226" height="150">
                            <p><?= e($film['opis']) ?></p>
                        </div>
                    <?php endforeach; ?>
                </div>
            </section>
            <section id="fantasy" class="list">
                <h3>Filmy fantastyczne</h3>
                <div class="films">
                </div>
            </section>
        </main>
        <footer>
            <form method="post">
                <label for="film-id">Usuń film nr:</label>
                <input type="number" id="film-id" name="id" min="1" required>
                <button type="submit">Usuń film</button>
            </form>
            <?php if ($error !== ''): ?>
                <p role="alert"><?= $error ?></p>
            <?php endif; ?>
            <p>Stronę wykonał: <a href="mailto:[email protected]">Bartosz Bryniarski</a></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (video.php)
  22. Krok 22: Lista „Filmy fantastyczne”: zapytanie 2

    Kryteria:

    R.5.5 Skrypt 1 oraz skrypt 2 wyświetlają wszystkie zwrócone zapytaniami bloki zawierające pobrane z bazy filmy przy zastosowaniu pętli

    ok. 2 min
    Polecenie z arkusza

    Działanie skryptu 2 jest identyczne jak skryptu 1, z tą różnicą, że źródłem danych jest zapytanie 2

    Zapytanie 2 do tablicy $fantasy, w tym samym try, i ten sam szablon w drugiej liście. Obie listy wypisują w pętli wszystkie wiersze swoich zapytań (R.5.5). Podgląd: 11 filmów fantastycznych w rzędach, do obejrzenia po przewinięciu bloku.

    video.php PHP zmienione: 9 linii
    <?php
    declare(strict_types=1);
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $error = '';
    $recommended = [];
    $fantasy = [];
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=dane3;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $recommended = $pdo->query('SELECT id, nazwa, opis, zdjecie FROM produkty WHERE id IN (18, 22, 23, 25)')->fetchAll();
        $fantasy = $pdo->query('SELECT id, nazwa, opis, zdjecie FROM produkty WHERE Rodzaje_id = 12')->fetchAll();
    } catch (PDOException $exception) {
        $error = 'Brak połączenia z bazą danych.';
    } finally {
        $pdo = null;
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Video On Demand</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowa wypożyczalnia filmów</h1>
            </section>
            <section id="banner-right">
                <table>
                    <tr>
                        <th scope="col">Kryminał</th>
                        <th scope="col">Horror</th>
                        <th scope="col">Przygodowy</th>
                    </tr>
                    <tr>
                        <td>20</td>
                        <td>30</td>
                        <td>20</td>
                    </tr>
                </table>
            </section>
        </header>
        <main>
            <section id="recommended" class="list">
                <h3>Polecamy</h3>
                <div class="films">
                    <?php foreach ($recommended as $film): ?>
                        <div class="film">
                            <h4><?= e($film['id'] . '. ' . $film['nazwa']) ?></h4>
                            <img src="<?= e($film['zdjecie']) ?>" alt="film" width="226" height="150">
                            <p><?= e($film['opis']) ?></p>
                        </div>
                    <?php endforeach; ?>
                </div>
            </section>
            <section id="fantasy" class="list">
                <h3>Filmy fantastyczne</h3>
                <div class="films">
                    <?php foreach ($fantasy as $film): ?>
                        <div class="film">
                            <h4><?= e($film['id'] . '. ' . $film['nazwa']) ?></h4>
                            <img src="<?= e($film['zdjecie']) ?>" alt="film" width="226" height="150">
                            <p><?= e($film['opis']) ?></p>
                        </div>
                    <?php endforeach; ?>
                </div>
            </section>
        </main>
        <footer>
            <form method="post">
                <label for="film-id">Usuń film nr:</label>
                <input type="number" id="film-id" name="id" min="1" required>
                <button type="submit">Usuń film</button>
            </form>
            <?php if ($error !== ''): ?>
                <p role="alert"><?= $error ?></p>
            <?php endif; ?>
            <p>Stronę wykonał: <a href="mailto:[email protected]">Bartosz Bryniarski</a></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (video.php)
  23. Krok 23: Usuwanie filmu: przed listami i z przekierowaniem

    Kryteria:

    R.5.6 Skrypt 3 wysyła do bazy zmodyfikowane zapytanie 4, w ten sposób, że usuwany jest wiersz, którego id podano w formularzu. Należy sprawdzić w bazie danych czy wiersz został usunięty (do testów należy wykorzystać id z zakresu 1 ÷ 8)

    ok. 5 min
    Polecenie z arkusza

    Działanie skryptu 3: Jeśli wypełniono formularz, pobiera z niego numer filmu do usunięcia. Wysyła zmodyfikowane zapytanie 4 do bazy danych, w ten sposób, że jest usuwany wiersz, którego id podano w formularzu

    Usuwanie stoi na górze pliku, w try, przed zapytaniami o listy:

    • $_SERVER['REQUEST_METHOD'] === 'POST' - formularz został wysłany,
    • filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT, ...) przepuszcza tylko liczbę całkowitą od 1 w górę (min_range); tekst, ułamek albo puste pole dają false lub null i komunikat zamiast zapytania,
    • zapytanie 4 jako zapytanie przygotowane: prepare z parametrem :id i execute(['id' => $id]) - numer trafia do bazy osobno od treści zapytania (R.5.6),
    • rowCount() mówi, ile wierszy usunięto - 0 znaczy, że filmu o tym numerze nie było,
    • po usunięciu skrypt nie wypisuje list, tylko po finally (zamknięcie bazy) przekierowuje kodem 303 na video.php?deleted=22. Przeglądarka pobiera stronę od nowa zwykłym GET-em: listy są już bez usuniętego filmu, a odświeżenie (F5) nie wysyła formularza ponownie. To wzorzec POST - przekierowanie - GET.

    Na stronie po przekierowaniu filter_input(INPUT_GET, ...) odczytuje numer z adresu, a stopka pokazuje komunikat (role="status"). Przekierowanie stoi za try, bo exit w środku try pominąłby finally.

    Podgląd: po usunięciu filmu nr 22 - komunikat w stopce, w liście „Polecamy” zostały trzy filmy (sprawdzone w bazie: wiersza 22 już nie ma).

    Typowe błędy

    header(...) działa tylko przed wysłaniem pierwszego znaku strony - dlatego cały PHP stoi przed <!DOCTYPE html>, a w pliku nic nie może być przed <?php (nawet spacja albo znacznik BOM z edytora).

    video.php PHP zmienione: 26 linii
    <?php
    declare(strict_types=1);
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
    
    $error = '';
    $recommended = [];
    $fantasy = [];
    $redirect = '';
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=dane3;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
            if ($id === false || $id === null) {
                $error = 'Numer filmu musi być liczbą całkowitą większą od zera.';
            } else {
                $statement = $pdo->prepare('DELETE FROM produkty WHERE id = :id');
                $statement->execute(['id' => $id]);
                $redirect = 'video.php?' . ($statement->rowCount() > 0 ? 'deleted' : 'missing') . '=' . $id;
            }
        }
        if ($redirect === '') {
            $recommended = $pdo->query('SELECT id, nazwa, opis, zdjecie FROM produkty WHERE id IN (18, 22, 23, 25)')->fetchAll();
            $fantasy = $pdo->query('SELECT id, nazwa, opis, zdjecie FROM produkty WHERE Rodzaje_id = 12')->fetchAll();
        }
    } catch (PDOException $exception) {
        $error = 'Brak połączenia z bazą danych.';
    } finally {
        $pdo = null;
    }
    if ($redirect !== '') {
        header('Location: ' . $redirect, true, 303);
        exit;
    }
    $deleted = filter_input(INPUT_GET, 'deleted', FILTER_VALIDATE_INT);
    $missing = filter_input(INPUT_GET, 'missing', FILTER_VALIDATE_INT);
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Video On Demand</title>
        <link rel="stylesheet" href="styl3.css">
    </head>
    <body>
        <header>
            <section id="banner-left">
                <h1>Internetowa wypożyczalnia filmów</h1>
            </section>
            <section id="banner-right">
                <table>
                    <tr>
                        <th scope="col">Kryminał</th>
                        <th scope="col">Horror</th>
                        <th scope="col">Przygodowy</th>
                    </tr>
                    <tr>
                        <td>20</td>
                        <td>30</td>
                        <td>20</td>
                    </tr>
                </table>
            </section>
        </header>
        <main>
            <section id="recommended" class="list">
                <h3>Polecamy</h3>
                <div class="films">
                    <?php foreach ($recommended as $film): ?>
                        <div class="film">
                            <h4><?= e($film['id'] . '. ' . $film['nazwa']) ?></h4>
                            <img src="<?= e($film['zdjecie']) ?>" alt="film" width="226" height="150">
                            <p><?= e($film['opis']) ?></p>
                        </div>
                    <?php endforeach; ?>
                </div>
            </section>
            <section id="fantasy" class="list">
                <h3>Filmy fantastyczne</h3>
                <div class="films">
                    <?php foreach ($fantasy as $film): ?>
                        <div class="film">
                            <h4><?= e($film['id'] . '. ' . $film['nazwa']) ?></h4>
                            <img src="<?= e($film['zdjecie']) ?>" alt="film" width="226" height="150">
                            <p><?= e($film['opis']) ?></p>
                        </div>
                    <?php endforeach; ?>
                </div>
            </section>
        </main>
        <footer>
            <form method="post">
                <label for="film-id">Usuń film nr:</label>
                <input type="number" id="film-id" name="id" min="1" required>
                <button type="submit">Usuń film</button>
            </form>
            <?php if ($error !== ''): ?>
                <p role="alert"><?= $error ?></p>
            <?php endif; ?>
            <?php if ($deleted): ?>
                <p role="status">Usunięto film nr <?= $deleted ?>.</p>
            <?php elseif ($missing): ?>
                <p role="status">Nie ma filmu nr <?= $missing ?>.</p>
            <?php endif; ?>
            <p>Stronę wykonał: <a href="mailto:[email protected]">Bartosz Bryniarski</a></p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (video.php)
  24. Etap 5: Porównanie z wersją podstawową

    Krok 24 · po tym etapie masz 36 z 36 kryteriów (pkt)

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

    ok. 3 min

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

    wersja podstawowawersja nowoczesna
    bibliotekamysqli (tabela 1 arkusza)PDO z wyjątkami
    gdzie jest PHPtrzy bloki w HTML-u (dwie listy, stopka)całość na górze pliku, w HTML-u szablon <?= ?>
    kiedy usuwapo wypisaniu list - film znika dopiero po odświeżeniuprzed listami, potem przekierowanie i komunikat
    odświeżenie po usunięciuprzeglądarka pyta o ponowne wysłanie formularzazwykłe odświeżenie strony
    numer z formularza$_POST['id'] wklejony w zapytaniefilter_input i zapytanie przygotowane
    tekst 0 OR 1=1 w poluusuwa wszystkie produktykomunikat o złym numerze, baza bez zmian
    dane z bazyecho bez zmiane() - htmlspecialchars
    zamknięcie bazymysqli_close w skrypcie 3$pdo = null w finally
    układfloat i clearflex w nagłówku i w listach
    tabela, polesame <td>, napis przed polem<th scope="col">, <label for>
    kolorywpisane w każdej regulezmienne --dark, --light, --heading

    Wersja podstawowa z krokami „Dobre praktyki” dochodzi do tego samego poziomu bezpieczeństwa zapytań, nowoczesna ma go od pierwszej linii. Żadna z nich nie sprawdza, kto usuwa - w prawdziwym serwisie formularz usuwania jest w panelu po zalogowaniu, dane logowania do bazy leżą w osobnym pliku poza katalogiem strony, a konto bazy nie jest kontem root bez hasła. Arkusz tego nie przewiduje.

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie da się zepsuć jednym wpisem w polu formularza - a klucz przyjmuje obie.

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

  25. Etap 6: Oddanie pracy

    Krok 25 · po tym etapie masz 36 z 36 kryteriów (pkt)

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

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz w folderze z numerem PESEL lub w przypadku jego braku w folderze z innym numerem, którym został podpisany arkusz plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowałeś poprawność działania witryny. Nagraj płytę z rezultatami pracy.

    W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była strona. Przed nagraniem płyty zaimportuj dane.sql ponownie, jeśli usuwanie było testowane na filmach z list. W folderze z numerem zdającego: osiem grafik filmów, egzamin.jpg, kw1.png - kw4.png, kwerendy.txt, przeglądarka.txt, styl3.css i video.php. Nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.

Potwierdzenie

Informacja