Przejdź do głównej treści
INF.03-01-25.06-SGCzerwiec 2025 · 58 kroków · ok. 118 min

Rozwiązanie krok po kroku: Ranking gier komputerowych

Budujemy portal z rankingiem gier komputerowych: baza gry z czterema kwerendami (w tym INSERT z danymi z pliku), grafika przeskalowana do 550 px szerokości, strona gry.php w trzech kolumnach ze stylami w styl.css (z zapytaniem @media dla wąskich ekranów) i cztery skrypty PHP: ranking top 5, galeria gier, opis wybranej gry i dodawanie nowej gry z formularza.

Kroki idą zdanie po zdaniu za treścią arkusza - każdy zaczyna się cytatem wymagania, a kod pokazuje dokładnie te linie, które je spełniają. PHP piszemy najpierw funkcjami z tabeli 1 arkusza (proceduralne mysqli), a na końcu przerabiamy na wersję, jaką pisze się w prawdziwych projektach: zapytania przygotowane z bindowaniem parametrów i escapowanie danych przed wypisaniem. W tym arkuszu nie ma JavaScriptu.

Zdjęcia, baza i plik rekord.txt pochodzą z prawdziwego archiwum pliki1 (załącznik do pobrania na stronie arkusza, hasło w treści zadania). Zrzuty wyników zapytań są odtworzone z tej bazy - na egzaminie robisz je sam, z całego ekranu.

Etapy pracy (58 kroków to sześć krótkich etapów):

  1. Baza danych - kroki 1-7 (kryteria R.1, 6 punktów)
  2. Grafika i strona HTML - kroki 8-28 (R.2 i R.3, 15 punktów)
  3. Styl CSS - kroki 29-39 (R.4, 10 punktów)
  4. Skrypty PHP - kroki 40-54 (R.5, 10 punktów)
  5. Bezpieczniejszy kod - kroki 55-57 (poza kluczem, dla siebie)
  6. Oddanie pracy - krok 58

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

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.
  1. Krok 1: Baza danych gry

    Kryteria: R.1.1 ok. 2 min

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

    W przeglądarce otwieramy http://localhost/phpmyadmin, klikamy Nowa w lewym panelu, wpisujemy nazwę gry i z listy metod porównywania wybieramy utf8_unicode_ci (arkusz podaje ją jako przykład; utf8_polish_ci też jest poprawne). Bez zestawu z polskimi znakami opisy gier z pliku SQL mogłyby stracić ogonki.

    Typowe błędy

    Nazwa bazy musi być dokładnie gry - skrypty PHP łączą się z bazą po nazwie, więc Gry albo gra odetnie punkty także za skrypty (R.5.1).

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

    Kryteria: R.1.1 ok. 2 min

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

    Wchodzimy do bazy gry (musi być zaznaczona w lewym panelu), karta Import, Wybierz plik → baza.sql z rozpakowanego archiwum, Wykonaj.

    Po imporcie w bazie jest jedna tabela z ilustracji 1: gry z kolumnami id (klucz główny z autonumeracją), nazwa, opis, punkty, cena i zdjecie - 8 wierszy. Kolumna zdjecie trzyma nazwę pliku graficznego (np. Mag.jpg), a same pliki są w archiwum - dlatego arkusz zaleca trzymać grafiki w tym samym folderze co gry.php.

    Typowe błędy

    Import bez wcześniejszego wejścia do bazy gry tworzy tabelę w innej bazie (np. test) - skrypty nie znajdą wtedy tabeli.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria: R.1.1 ok. 2 min

    Arkusz: Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie PNG pod nazwą import. Nie kadruj zrzutu. Powinien on obejmować cały ekran monitora, z widocznym paskiem zadań.

    Klawisz Print Screen (cały ekran, nie Alt+PrtScr, który łapie tylko aktywne okno), wklejamy do edytora grafiki i zapisujemy jako import.png w folderze z numerem zdającego. Na zrzucie ma być widać komunikat phpMyAdmin o pomyślnym imporcie albo tabelę gry w bazie.

    Uwaga na formaty w tym arkuszu: zrzut importu w PNG, zrzuty kwerend w JPEG - odwrotnie niż w wielu innych arkuszach.

    Typowe błędy

    Wykadrowany zrzut (samo okno przeglądarki, bez paska zadań) nie jest oceniany - klucz mówi to wprost.

  4. Krok 4: Zapytanie 1: id, nazwa i zdjęcie

    Kryteria: R.1.2, R.1.3 ok. 3 min

    Arkusz: Zapytanie 1: wybierające jedynie id, nazwę i zdjęcie z tabeli gry.

    Zakładka SQL w phpMyAdmin (przy zaznaczonej bazie gry). Klauzula po klauzuli:

    • SELECT id, nazwa, zdjecie - które kolumny ma zwrócić wynik. „Jedynie” oznacza dokładnie te trzy; SELECT * zwróciłby też opis, punkty i cenę.
    • FROM gry - z której tabeli.

    Bez WHERE, bo zapytanie dotyczy wszystkich gier. Wynik: 8 wierszy. Zapytanie kopiujemy do pliku kwerendy.txt (zwykły edytor tekstu) i robimy zrzut całego ekranu: kw1.jpg. To samo zapytanie wyśle później skrypt 2, żeby zbudować galerię.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT id, nazwa, zdjecie FROM gry;
    Podgląd strony po tym kroku Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: gra o id 1 ze skróconym opisem

    Kryteria: R.1.4 ok. 3 min

    Arkusz: Zapytanie 2: wybierające jedynie nazwę, pierwsze 100 znaków opisu, punkty oraz cenę z tabeli gry dla wiersza o id równym 1.

    • SELECT nazwa, LEFT(opis, 100), punkty, cena - cztery wartości. „Pierwsze 100 znaków opisu” to funkcja LEFT(opis, 100) z tabeli 2 arkusza: zwraca 100 znaków od lewej strony napisu.
    • FROM gry - tabela.
    • WHERE id = 1 - tylko wiersz o kluczu 1. Liczbę porównujemy bez cudzysłowu.

    Wynik: dokładnie jeden wiersz (Czarodziejka, 45 punktów, cena 100). Zrzut: kw2.jpg. Zmodyfikowaną wersję tego zapytania (z id z formularza) wyśle skrypt 3.

    Typowe błędy

    SUBSTRING(opis, 1, 100) daje ten sam wynik i klucz go przyjmie, ale arkusz podpowiada LEFT w tabeli 2 - nie ma powodu szukać dalej.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT id, nazwa, zdjecie FROM gry;
    
    -- Zapytanie 2
    SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = 1;
    Podgląd strony po tym kroku Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: pięć gier z najwyższą punktacją

    Kryteria: R.1.5 ok. 3 min

    Arkusz: Zapytanie 3: wybierające jedynie pola nazwa i punkty z pięciu pierwszych wierszy o najwyższej punktacji z tabeli gry.

    • SELECT nazwa, punkty FROM gry - dwie kolumny z tabeli gier.
    • ORDER BY punkty DESC - sortowanie malejąco po punktach, żeby najwyższa punktacja była na górze. Domyślne ASC dałoby najsłabsze gry.
    • LIMIT 5 - tylko pięć pierwszych wierszy po posortowaniu.

    Wynik: Mag 357, Księżniczka Persji 293, Kalipso 125, Wyprawa Na Księżyc 103, Nieznany Ląd 96. Zrzut: kw3.jpg. To zapytanie wyśle skrypt 1 do listy „Top 5”.

    Typowe błędy

    LIMIT 5 bez ORDER BY zwraca pięć dowolnych wierszy (w praktyce pierwsze wstawione) - to nie jest „pięć o najwyższej punktacji”.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT id, nazwa, zdjecie FROM gry;
    
    -- Zapytanie 2
    SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = 1;
    
    -- Zapytanie 3
    SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;
    Podgląd strony po tym kroku Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: nowa gra z pliku rekord.txt

    Kryteria: R.1.6 ok. 4 min

    Arkusz: Zapytanie 4: wstawiające do tabeli gry wiersz o danych zawartych w pliku rekord.txt (dane należy skopiować z pliku do zapytania). Klucz główny nadawany automatycznie.

    Wstawianie wiersza to INSERT INTO gry (...) VALUES (...). W nawiasie po nazwie tabeli wymieniamy kolumny bez id - klucz główny ma autonumerację, więc baza sama nada mu wartość 9. Wartości w VALUES w tej samej kolejności: nazwa i opis w apostrofach (napisy), punkty i cena jako liczby, nazwa pliku zdjęcia w apostrofach.

    Dane kopiujemy z rekord.txt: nazwa „Zamczysko”, opis, „Punkty: 200”, „Cena: 50 zł” (do bazy trafia sama liczba 50) i zamczysko.jpg. Zrzut kw4.jpg ma pokazać komunikat o wstawieniu wiersza albo tabelę z nowym rekordem.

    Typowe błędy

    Plik rekord.txt w archiwum jest zapisany w kodowaniu Windows-1250 - otwarty w edytorze ustawionym na UTF-8 pokaże krzaki zamiast „ó”, „ę”, „ż”. Otwórz go w Notatniku (rozpozna kodowanie) albo przełącz kodowanie w edytorze, zanim skopiujesz opis. Klucz pozwala pominąć zgodność opisu, ale nazwa i liczby muszą się zgadzać.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT id, nazwa, zdjecie FROM gry;
    
    -- Zapytanie 2
    SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = 1;
    
    -- Zapytanie 3
    SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;
    
    -- Zapytanie 4
    INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie)
    VALUES ('Zamczysko', 'Na odludziu stoi opuszczone zamczysko, w którym znajduje się skarb strzeżony przez złowrogie demony i duchy', 200, 50, 'zamczysko.jpg');
    Podgląd strony po tym kroku Podgląd strony po kroku 7
  8. Krok 8: Przeskalowanie grafiki zamczysko.jpg

    Kryteria: R.2.1 ok. 3 min

    Arkusz: Plik zamczysko.jpg, wypakowany z archiwum, należy przeskalować zachowując proporcje do szerokości 550 px.

    Plik z archiwum ma 640 × 360 px. W edytorze grafiki rastrowej (np. GIMP: Obraz → Skaluj obraz) wpisujemy szerokość 550 px przy zamkniętym łańcuchu proporcji. Wysokość przeliczy się sama na 309 px (360 · 550 / 640 = 309,4) - i dokładnie to sprawdza egzaminator (309 ±5 px). Zapisujemy jako zamczysko.jpg w folderze zdającego, obok pozostałych zdjęć gier.

    Typowe błędy

    Rozłączony łańcuch proporcji = obraz ściśnięty do 550 px szerokości przy starej wysokości 360 px. Szerokość się zgadza, punktu nie ma.

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

    Kryteria: R.2.2 ok. 2 min

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

    W edytorze zaznaczającym składnię tworzymy plik gry.php w folderze zdającego, obok zdjęć. Rozszerzenie .php, bo dojdą skrypty PHP - na razie to czysty HTML.

    Pierwsza linia <!DOCTYPE html> mówi przeglądarce, że dokument jest w HTML5. Dalej szkielet każdej strony: <html>, a w nim <head> (informacje o stronie) i <body> (to, co widać w oknie).

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

    Kryteria: R.2.2 ok. 1 min

    Arkusz: Zadeklarowany polski język zawartości witryny.

    Atrybut lang="pl" w znaczniku <html>. Czytnik ekranu czyta wtedy tekst polską wymową, a przeglądarka dobiera słownik. Na stronie nic się nie zmienia - to informacja dla programów.

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

    Kryteria: R.2.2 ok. 1 min

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

    <meta charset="UTF-8"> jako pierwsza rzecz w <head>. „Jawnie” znaczy: wpisane w kodzie, nie zdane na domysł przeglądarki. Bez tej linii „Księżniczka Persji” z bazy mogłaby wyświetlić się jako krzaki. Plik trzeba też zapisać w UTF-8.

    gry.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 (gry.php)
  12. Krok 12: Tytuł strony

    Kryteria: R.2.3 ok. 1 min

    Arkusz: Tytuł strony widoczny na karcie przeglądarki: „Gry komputerowe”.

    <title> w <head> - arkusz sam podpowiada, gdzie go szukać: na karcie przeglądarki. To nie jest nagłówek na stronie (ten dojdzie w <header>). Klucz sprawdza dokładnie tekst „Gry komputerowe”.

    gry.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  13. Krok 13: Podpięcie arkusza stylów

    Kryteria: R.3.2 ok. 1 min

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

    <link rel="stylesheet" href="styl.css"> w <head>. Plik styl.css napiszemy po HTML - połączenie może już być, zadziała, gdy plik pojawi się w tym samym folderze.

    Typowe błędy

    <link> to znacznik pusty - w HTML5 bez </link> i bez ukośnika />. Bez rel="stylesheet" przeglądarka nie potraktuje pliku jako stylów.

    gry.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  14. Krok 14: Bloki semantyczne

    Kryteria: R.2.4 ok. 2 min

    Arkusz: 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.

    Z ilustracji 3: na górze blok nagłówkowy, pod nim trzy sekcje obok siebie (lewa, środkowa, prawa), na dole stopka. Klucz R.2.4 wymienia znaczniki wprost: <header>, trzy razy <section>, <footer> - z tabeli 3 arkusza.

    Sekcje dostają identyfikatory left, middle, right, bo każda będzie stylowana inaczej. Nazwy identyfikatorów, klas i zmiennych piszemy w tym rozwiązaniu po angielsku (arkusz dopuszcza polski lub angielski, wymaga konsekwencji); wyjątkiem są nazwy narzucone przez arkusz (gry.php, styl.css, kolumny bazy). To, że sekcje staną obok siebie, zrobi CSS.

    Typowe błędy

    Pięć zwykłych <div> wygląda na stronie identycznie, ale kryterium R.2.4 wymaga znaczników semantycznych - sprawdzane w kodzie źródłowym.

    gry.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
        </header>
        <section id="left">
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  15. Krok 15: Nagłówek strony

    Kryteria: R.2.5 ok. 1 min

    Arkusz: Zawartość bloku nagłówkowego: nagłówek pierwszego stopnia o treści „Ranking gier komputerowych”.

    Pierwszego stopnia to <h1>. Egzaminator sprawdza dokładnie ten znacznik, który wskazał arkusz - w innych sekcjach będą <h3>, więc nie mieszaj stopni.

    gry.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  16. Krok 16: Sekcja lewa: nagłówek „Top 5 gier w tym miesiącu”

    Kryteria: R.2.5 ok. 1 min

    Arkusz: Zawartość sekcji lewej: nagłówek trzeciego stopnia o treści: „Top 5 gier w tym miesiącu”.

    Trzeciego stopnia to <h3>, w sekcji left. Klucz R.2.5 wymaga w sekcji lewej przynajmniej jednego <h3> z podanych treści - dajemy wszystkie trzy, zgodnie z arkuszem.

    gry.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  17. Krok 17: Sekcja lewa: lista punktowana

    Kryteria: R.2.6 ok. 1 min

    Arkusz: Lista punktowana (nieuporządkowana) wypełniona przez skrypt 1.

    Lista nieuporządkowana to <ul>; jej elementy <li> dopisze skrypt 1 na podstawie zapytania 3, więc na razie jest pusta. Klucz R.2.6 przyjmuje listę wygenerowaną skryptem - ważne, żeby w wynikowym HTML były <ul> i <li>.

    Typowe błędy

    <ol> to lista uporządkowana (numerowana). Arkusz mówi „punktowana (nieuporządkowana)”, czyli <ul> z kropkami - tak jak na ilustracji 2.

    gry.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  18. Krok 18: Sekcja lewa: nagłówek „Nasz sklep”

    Kryteria: R.2.5 ok. 1 min

    Arkusz: Nagłówek trzeciego stopnia o treści: „Nasz sklep”.

    Drugi <h3> w sekcji lewej, pod listą.

    gry.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  19. Krok 19: Sekcja lewa: odnośnik do sklepu

    Kryteria: R.3.3 ok. 1 min

    Arkusz: Odnośnik o treści „Tu kupisz gry” prowadzący do adresu http://sklep.gry.pl.

    Odnośnik to <a>: adres w atrybucie href, tekst między znacznikami. Adres przepisujemy z arkusza w całości, z http:// - bez protokołu przeglądarka potraktowałaby sklep.gry.pl jako plik w naszym folderze. W podglądzie kliknięcie jest zablokowane (podgląd pokazuje jedną stronę), na egzaminie link otwiera sklep.

    Typowe błędy

    href="sklep.gry.pl" (bez http://) to ścieżka względna - przeglądarka szuka pliku sklep.gry.pl obok gry.php i dostaje 404. R.3.3 przepada.

    gry.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  20. Krok 20: Sekcja lewa: nagłówek „Stronę wykonał”

    Kryteria: R.2.5 ok. 1 min

    Arkusz: Nagłówek trzeciego stopnia o treści: „Stronę wykonał”.

    Trzeci <h3> w sekcji lewej.

    gry.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  21. Krok 21: Sekcja lewa: paragraf z numerem zdającego

    Kryteria: R.2.5 ok. 1 min

    Arkusz: Paragraf z numerem zdającego.

    Paragraf <p> pod nagłówkiem. W tym rozwiązaniu stoi tu nazwisko autora - na egzaminie wpisujesz w to miejsce swój numer zdającego (PESEL), bo klucz R.2.5 sprawdza paragraf na stronie. Sekcja lewa jest kompletna.

    gry.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  22. Krok 22: Sekcja prawa: nagłówek „Dodaj nową grę”

    Kryteria: R.2.5 ok. 1 min

    Arkusz: Zawartość sekcji środkowej: efekt działania skryptu 2. Zawartość sekcji prawej: nagłówek trzeciego stopnia o treści: „Dodaj nową grę”.

    Sekcja środkowa zostaje w HTML pusta - wypełni ją w całości skrypt 2 (galeria gier z bazy). Przechodzimy do sekcji prawej: <h3> z treścią z arkusza.

    gry.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  23. Krok 23: Sekcja prawa: formularz

    Kryteria: R.3.4 ok. 1 min

    Arkusz: Formularz wysyłający dane do tego samego pliku metodą bezpieczną.

    „Metoda bezpieczna” to POST - dane nie pojawiają się w adresie strony, jak przy GET. „Do tego samego pliku” to brak atrybutu action (przeglądarka wysyła formularz na adres bieżącej strony) albo action="gry.php" - klucz przyjmuje oba warianty.

    Typowe błędy

    method="get" (albo brak method, bo GET jest domyślny) wyśle dane w adresie - to nie jest metoda bezpieczna i skrypt czytający $_POST nic nie dostanie.

    gry.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
            </form>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  24. Krok 24: Formularz: pierwsze pole z etykietą

    Kryteria: R.2.7 ok. 2 min

    Arkusz: Formularz zawiera cztery pola edycyjne podpisane etykietami: nazwa, opis, cena, zdjęcie oraz przycisk DODAJ, rozmieszczone zgodnie z ilustracją 2.

    Zaczynamy od pola „nazwa”. Etykieta to <label>; atrybut for wskazuje id pola, dzięki czemu kliknięcie w napis ustawia kursor w polu (klucz R.2.7 wymaga przynajmniej jednej etykiety <label>). Pole edycyjne to <input type="text"> z atrybutem name - pod tą nazwą wartość trafi do PHP.

    Na ilustracji 2 etykieta stoi nad polem, a pola jedno pod drugim - stąd <br> po etykiecie i po polu. Atrybuty id i name po angielsku (name), etykieta po polsku, bo jej treść narzuca arkusz.

    Typowe błędy

    <label>nazwa</label> bez for jest poprawnym HTML, ale nie łączy się z polem - klucz przyjmie, użytkownik straci klik w etykietę. Zawsze for = id pola.

    gry.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
            </form>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  25. Krok 25: Formularz: pola opis, cena, zdjęcie

    Kryteria: R.2.7 ok. 2 min

    Arkusz: Formularz zawiera cztery pola edycyjne podpisane etykietami: nazwa, opis, cena, zdjęcie oraz przycisk DODAJ, rozmieszczone zgodnie z ilustracją 2.

    Trzy kolejne pary etykieta + pole według tego samego wzoru: description, price, image. Nazwy pól (name) odpowiadają temu, co skrypt 4 wstawi do kolumn opis, cena, zdjecie w bazie.

    gry.php PHP zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
            </form>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  26. Krok 26: Formularz: przycisk DODAJ

    Kryteria: R.2.7, R.3.5 ok. 1 min

    Arkusz: Formularz zawiera cztery pola edycyjne podpisane etykietami: nazwa, opis, cena, zdjęcie oraz przycisk DODAJ, rozmieszczone zgodnie z ilustracją 2.

    <button type="submit">DODAJ</button> wewnątrz formularza wysyła go po kliknięciu. Klucz R.3.5 przyjmuje też <input type="submit" value="DODAJ">. Sekcja prawa jest kompletna.

    gry.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  27. Krok 27: Stopka: formularz

    Kryteria: R.3.4 ok. 1 min

    Arkusz: Zawartość stopki: formularz wysyłający dane do tego samego pliku metodą bezpieczną.

    Drugi formularz na stronie, tak samo method="post" bez action. Dwa formularze w jednym pliku to nic złego - PHP rozpozna po nazwach pól, który został wysłany.

    gry.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  28. Krok 28: Stopka: pole i przycisk „Pokaż opis”

    Kryteria: R.2.8, R.3.5 ok. 1 min

    Arkusz: Formularz zawiera pole edycyjne i przycisk o treści „Pokaż opis”. Efekt działania skryptu 3.

    Jedno pole tekstowe name="game_id" (tu użytkownik wpisze id gry) i przycisk <button type="submit"> z treścią z arkusza. Efekt działania skryptu 3 (nagłówek i opis gry) pojawi się pod formularzem - dopisze go PHP. Struktura HTML strony jest kompletna - dalej style.

    gry.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  29. Krok 29: Plik styl.css: krój i kolor czcionki

    Kryteria: R.4.1 ok. 2 min

    Arkusz: Domyślne formatowanie wszystkich selektorów: krój czcionki Garamond, biały kolor czcionki.

    Tworzymy plik styl.css obok gry.php. „Wszystkie selektory” to selektor uniwersalny *. font-family: Garamond ustawia krój, color: white kolor tekstu (czcionki), nie tła. Biały tekst na razie znika na białym tle - to normalne, tła dojdą w kolejnych krokach.

    Uwaga z arkusza: przycisk może nie odziedziczyć białego koloru i zostać czarny - to nie jest błąd.

    styl.css CSS nowy plik
    * {
        font-family: Garamond;
        color: white;
    }
    Podgląd strony po tym kroku (gry.php)
  30. Krok 30: Blok nagłówkowy: tło, wyrównanie, margines wewnętrzny

    Kryteria: R.4.2, R.4.6, R.4.7 ok. 2 min

    Arkusz: Dla bloku nagłówkowego: kolor tła Indigo, wyrównanie tekstu do środka, marginesy wewnętrzne 2 px.

    Selektor header. Kolor nazwany Indigo przepisujemy z arkusza. Margines wewnętrzny to padding (zewnętrzny byłby margin). Nagłówek pojawia się w podglądzie jako fioletowy pas z białym tytułem.

    Typowe błędy

    margin: 2px zamiast padding: 2px - odstęp będzie na zewnątrz fioletowego pola. Kryterium R.4.7 mówi o marginesach wewnętrznych.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: Garamond;
        color: white;
    }
    header {
        background-color: Indigo;
        text-align: center;
        padding: 2px;
    }
    Podgląd strony po tym kroku (gry.php)
  31. Krok 31: Sekcje lewa i prawa: tło i wysokość

    Kryteria: R.4.2, R.4.4 ok. 1 min

    Arkusz: Dla sekcji lewej i prawej: kolor tła Indigo, wysokość 600 px.

    Wspólne cechy dwóch sekcji piszemy raz - selektory identyfikatorów rozdzielone przecinkiem #left, #right. Krzyżyk = id z HTML. Sekcje są jeszcze jedna pod drugą - obok siebie ustawimy je za chwilę.

    styl.css CSS zmienione: 4 linie
    * {
        font-family: Garamond;
        color: white;
    }
    header {
        background-color: Indigo;
        text-align: center;
        padding: 2px;
    }
    #left, #right {
        background-color: Indigo;
        height: 600px;
    }
    Podgląd strony po tym kroku (gry.php)
  32. Krok 32: Sekcja środkowa: tło, wysokość, paski przewijania

    Kryteria: R.4.2, R.4.4, R.4.8 ok. 2 min

    Arkusz: Dla sekcji środkowej: czarny kolor tła, wysokość 600 px, zawsze widoczne paski przewijania.

    Selektor #middle. „Zawsze widoczne paski przewijania” to overflow: scroll - klucz R.4.8 podaje tę wartość wprost. auto pokazywałby paski dopiero przy przepełnieniu, a tego arkusz tu nie chce (odwrotnie niż w wielu innych arkuszach - czytaj uważnie).

    Typowe błędy

    overflow: auto przy pustej sekcji nie pokaże pasków - R.4.8 wymaga scroll.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: Garamond;
        color: white;
    }
    header {
        background-color: Indigo;
        text-align: center;
        padding: 2px;
    }
    #left, #right {
        background-color: Indigo;
        height: 600px;
    }
    #middle {
        background-color: black;
        height: 600px;
        overflow: scroll;
    }
    Podgląd strony po tym kroku (gry.php)
  33. Krok 33: Sekcje obok siebie: 20%, 60%, 20%

    Kryteria: R.3.1, R.4.5 ok. 3 min

    Arkusz: Dla wszystkich rodzajów ekranu o szerokości większej niż 800 px szerokość sekcji lewej i prawej wynosi 20%, środkowej 60% (są wyświetlane obok siebie).

    Dopisujemy szerokości do istniejących reguł i float: left - sekcje pływają w lewo i ustawiają się obok siebie w kolejności z HTML (20% + 60% + 20% = 100%, więc mieszczą się w jednej linii). To jedno z dopuszczalnych rozwiązań; klucz przyjmuje też display: flex albo grid.

    Na razie ten układ działa na każdej szerokości - warunek „większej niż 800 px” dodamy w następnym kroku zapytaniem @media.

    styl.css CSS zmienione: 4 linie
    * {
        font-family: Garamond;
        color: white;
    }
    header {
        background-color: Indigo;
        text-align: center;
        padding: 2px;
    }
    #left, #right {
        background-color: Indigo;
        height: 600px;
        width: 20%;
        float: left;
    }
    #middle {
        background-color: black;
        height: 600px;
        overflow: scroll;
        width: 60%;
        float: left;
    }
    Podgląd strony po tym kroku (gry.php)
  34. Krok 34: Wąskie ekrany: sekcje jedna pod drugą

    Kryteria: R.4.5 ok. 3 min

    Arkusz: Dla wszystkich rodzajów ekranu o pozostałej szerokości sekcje lewa, środkowa i prawa są wyświetlane jedna pod drugą.

    Zapytanie medialne @media all and (max-width: 800px) { ... } - reguły w środku działają tylko wtedy, gdy okno ma 800 px lub mniej („pozostała szerokość” po „większej niż 800”). Klucz R.4.5 podaje dokładnie ten zapis. W środku odwołujemy układ: pełna szerokość i float: none, więc sekcje wracają jedna pod drugą.

    Sprawdź to w podglądzie, zwężając okno przeglądarki - egzaminator robi to samo.

    Typowe błędy

    @media (min-width: 800px) dla układu obok siebie łapie też dokładnie 800 px, a arkusz mówi „większej niż 800”. Z max-width: 800px dla układu pod sobą granica jest tam, gdzie chce klucz.

    styl.css CSS zmienione: 6 linii
    * {
        font-family: Garamond;
        color: white;
    }
    header {
        background-color: Indigo;
        text-align: center;
        padding: 2px;
    }
    #left, #right {
        background-color: Indigo;
        height: 600px;
        width: 20%;
        float: left;
    }
    #middle {
        background-color: black;
        height: 600px;
        overflow: scroll;
        width: 60%;
        float: left;
    }
    @media all and (max-width: 800px) {
        #left, #middle, #right {
            width: 100%;
            float: none;
        }
    }
    Podgląd strony po tym kroku (gry.php)
  35. Krok 35: Stopka: tło i wysokość

    Kryteria: R.4.2, R.4.4, R.3.1 ok. 1 min

    Arkusz: Dla stopki: kolor tła Indigo, wysokość 150 px.

    Selektor footer z tłem i wysokością z arkusza. Dokładamy clear: both - bez tego stopka wcisnęłaby się pod pływające sekcje zamiast stanąć pod nimi (to element układu z ilustracji 3, R.3.1).

    Typowe błędy

    Bez clear: both stopka może wjechać obok kolumn albo pod nie - układ przestaje zgadzać się z ilustracją 3.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: Garamond;
        color: white;
    }
    header {
        background-color: Indigo;
        text-align: center;
        padding: 2px;
    }
    #left, #right {
        background-color: Indigo;
        height: 600px;
        width: 20%;
        float: left;
    }
    #middle {
        background-color: black;
        height: 600px;
        overflow: scroll;
        width: 60%;
        float: left;
    }
    @media all and (max-width: 800px) {
        #left, #middle, #right {
            width: 100%;
            float: none;
        }
    }
    footer {
        background-color: Indigo;
        height: 150px;
        clear: both;
    }
    Podgląd strony po tym kroku (gry.php)
  36. Krok 36: Oznaczenie liczby punktów

    Kryteria: R.4.9 ok. 2 min

    Arkusz: Dla oznaczenia liczby punktów widocznych na ilustracji 4: kolor tła Tomato, zaokrąglenie rogów 50%, marginesy wewnętrzne 5 px.

    Liczba punktów będzie w osobnym <span> generowanym przez skrypt 1, więc stylujemy klasę .points (kropka = klasa). Klucz R.4.9 mówi wprost: klasa, nie id - identyfikator musi być unikalny na stronie, a tych elementów będzie pięć. border-radius: 50% zaokrągla rogi w kółko jak na ilustracji 4, padding: 5px daje tłu miejsce wokół liczby. Efekt zobaczysz, gdy skrypt 1 wypełni listę.

    Typowe błędy

    #points (id) zamiast klasy to błąd wskazany w kluczu - pięć elementów z tym samym id to niepoprawny HTML.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: Garamond;
        color: white;
    }
    header {
        background-color: Indigo;
        text-align: center;
        padding: 2px;
    }
    #left, #right {
        background-color: Indigo;
        height: 600px;
        width: 20%;
        float: left;
    }
    #middle {
        background-color: black;
        height: 600px;
        overflow: scroll;
        width: 60%;
        float: left;
    }
    @media all and (max-width: 800px) {
        #left, #middle, #right {
            width: 100%;
            float: none;
        }
    }
    footer {
        background-color: Indigo;
        height: 150px;
        clear: both;
    }
    .points {
        background-color: Tomato;
        border-radius: 50%;
        padding: 5px;
    }
    Podgląd strony po tym kroku (gry.php)
  37. Krok 37: Element listy: margines wewnętrzny

    Kryteria: R.4.7 ok. 1 min

    Arkusz: Dla elementu listy: marginesy wewnętrzne 5 px.

    Selektor znacznika li, padding: 5px - odstęp wewnątrz każdego elementu listy, dzięki czemu kółka z punktami nie nachodzą na siebie.

    styl.css CSS zmienione: 3 linie
    * {
        font-family: Garamond;
        color: white;
    }
    header {
        background-color: Indigo;
        text-align: center;
        padding: 2px;
    }
    #left, #right {
        background-color: Indigo;
        height: 600px;
        width: 20%;
        float: left;
    }
    #middle {
        background-color: black;
        height: 600px;
        overflow: scroll;
        width: 60%;
        float: left;
    }
    @media all and (max-width: 800px) {
        #left, #middle, #right {
            width: 100%;
            float: none;
        }
    }
    footer {
        background-color: Indigo;
        height: 150px;
        clear: both;
    }
    .points {
        background-color: Tomato;
        border-radius: 50%;
        padding: 5px;
    }
    li {
        padding: 5px;
    }
    Podgląd strony po tym kroku (gry.php)
  38. Krok 38: Bloki gier w sekcji środkowej

    Kryteria: R.4.10 ok. 2 min

    Arkusz: Dla bloków gier znajdujących się w sekcji środkowej: bloki są umieszczone jeden obok drugiego, wyrównanie tekstu do środka, marginesy wewnętrzne 3 px.

    Bloki gier wygeneruje skrypt 2 jako <div class="game">, więc stylujemy klasę .game. „Jeden obok drugiego” to display: inline-block - blok zachowuje wymiary, ale ustawia się w linii jak tekst (i zawija do następnej linii, gdy zabraknie miejsca, jak na ilustracji 2). Klucz przyjmuje też float albo flex; id byłoby błędem.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: Garamond;
        color: white;
    }
    header {
        background-color: Indigo;
        text-align: center;
        padding: 2px;
    }
    #left, #right {
        background-color: Indigo;
        height: 600px;
        width: 20%;
        float: left;
    }
    #middle {
        background-color: black;
        height: 600px;
        overflow: scroll;
        width: 60%;
        float: left;
    }
    @media all and (max-width: 800px) {
        #left, #middle, #right {
            width: 100%;
            float: none;
        }
    }
    footer {
        background-color: Indigo;
        height: 150px;
        clear: both;
    }
    .points {
        background-color: Tomato;
        border-radius: 50%;
        padding: 5px;
    }
    li {
        padding: 5px;
    }
    .game {
        display: inline-block;
        text-align: center;
        padding: 3px;
    }
    Podgląd strony po tym kroku (gry.php)
  39. Krok 39: Pole edycyjne: margines zewnętrzny i kolor czcionki

    Kryteria: R.4.3, R.4.7 ok. 1 min

    Arkusz: Dla pola edycyjnego: marginesy zewnętrzne 10 px, kolor czcionki Indigo.

    Selektor znacznika input. Tu arkusz mówi o marginesach zewnętrznych, więc margin. color: Indigo nadpisuje biały kolor z * - inaczej wpisywany tekst byłby biały na białym polu. Arkusz stylów jest kompletny.

    styl.css CSS zmienione: 4 linie
    * {
        font-family: Garamond;
        color: white;
    }
    header {
        background-color: Indigo;
        text-align: center;
        padding: 2px;
    }
    #left, #right {
        background-color: Indigo;
        height: 600px;
        width: 20%;
        float: left;
    }
    #middle {
        background-color: black;
        height: 600px;
        overflow: scroll;
        width: 60%;
        float: left;
    }
    @media all and (max-width: 800px) {
        #left, #middle, #right {
            width: 100%;
            float: none;
        }
    }
    footer {
        background-color: Indigo;
        height: 150px;
        clear: both;
    }
    .points {
        background-color: Tomato;
        border-radius: 50%;
        padding: 5px;
    }
    li {
        padding: 5px;
    }
    .game {
        display: inline-block;
        text-align: center;
        padding: 3px;
    }
    input {
        margin: 10px;
        color: Indigo;
    }
    Podgląd strony po tym kroku (gry.php)
  40. Krok 40: Połączenie z bazą danych

    Kryteria: R.5.1, R.5.10 ok. 3 min

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

    Kod PHP piszemy między <?php a ?> - serwer wykonuje go, zanim strona trafi do przeglądarki. Połączenie otwieramy raz, na samym początku pliku, i trzymamy w zmiennej $connection (znacząca, angielska nazwa), z której skorzystają wszystkie cztery skrypty. mysqli_connect z tabeli 1 arkusza przyjmuje kolejno: serwer, użytkownika, hasło (puste) i nazwę bazy.

    Od tego kroku podgląd to wynik działania strony po stronie serwera (PHP nie wykonuje się w przeglądarce) - na razie wygląda jak wcześniej, bo skrypt niczego nie wypisuje.

    Typowe błędy

    Kolejność argumentów ma znaczenie: mysqli_connect('localhost', 'root', 'gry') podaje „gry” jako HASŁO. Nazwa bazy jest czwarta. Plik sprawdzaj przez http://localhost/…, nie otwierając go z dysku - inaczej PHP się nie wykona.

    gry.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  41. Krok 41: Skrypt 1: zapytanie 3 do bazy

    Kryteria: R.5.3 ok. 2 min

    Arkusz: Skrypt 1: wysyła do bazy danych zapytanie 3.

    Skrypt 1 piszemy wewnątrz <ul>, bo tam mają trafić elementy listy. mysqli_query($connection, ...) wysyła zapytanie i zwraca wynik, który zapisujemy w $top_games. Treść to dokładnie zapytanie 3 z kwerendy.txt - pięć gier z najwyższą punktacją.

    gry.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>

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

  42. Krok 42: Skrypt 1: pętla po wierszach wyniku

    Kryteria: R.5.6 ok. 2 min

    Arkusz: Zwrócone wiersze są wyświetlane w elementach listy z sekcji lewej według wzoru: <nazwa> <punkty>, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych.

    „Zwrócone wiersze” to pętla. mysqli_fetch_row($top_games) (tabela 1) oddaje kolejny wiersz wyniku jako tablicę numerowaną od zera, a gdy wierszy zabraknie - false. Zapis while ($row = mysqli_fetch_row(...)) przypisuje wiersz do $row i od razu sprawdza, czy coś przyszło - pętla wykona się pięć razy i sama się zakończy. Ciało jest jeszcze puste.

    Typowe błędy

    Pojedyncze = w warunku while jest tu celowe (przypisanie); == porównałoby zmienną z wynikiem i pętla nie zadziałałaby poprawnie.

    gry.php PHP zmienione: 2 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>

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

  43. Krok 43: Skrypt 1: element listy z nazwą i punktami

    Kryteria: R.5.6, R.2.9 ok. 3 min

    Arkusz: Zwrócone wiersze są wyświetlane w elementach listy z sekcji lewej według wzoru: <nazwa> <punkty>, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych. Liczba punktów jest dodatkowo formatowana stylem, którego efekt jest widoczny na ilustracji 4.

    echo w pętli wypisuje jeden <li>: nazwa z $row[0], spacja, a liczba punktów z $row[1] owinięta w <span class='points'> - to ten „dodatkowy element” z kryterium R.2.9, na który czeka reguła .points w CSS (pomarańczowe kółko z ilustracji 4). Wewnątrz napisu w cudzysłowie atrybut HTML piszemy w apostrofach, żeby nie kończyć napisu PHP.

    W podglądzie lista ma pięć pozycji z kółkami - w tym Zamczysko, bo zapytanie 4 już je wstawiło.

    Typowe błędy

    echo '<li>$row[0]</li>' w apostrofach wypisze dosłownie „$row[0]” - PHP nie podstawia zmiennych w apostrofach. Zewnętrzny napis w cudzysłowie, atrybuty w apostrofach.

    gry.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  44. Krok 44: Skrypt 2: zapytanie 1 do bazy

    Kryteria: R.5.3 ok. 2 min

    Arkusz: Skrypt 2: wysyła do bazy danych zapytanie 1.

    Skrypt 2 piszemy w sekcji środkowej - jego wynik jest jej całą zawartością. Zapytanie 1 z kwerendy.txt (id, nazwa, zdjęcie wszystkich gier) trafia do $games. Kolejność kolumn w SELECT zaraz będzie ważna: $row[0] to id, $row[1] nazwa, $row[2] zdjęcie.

    gry.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>

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

  45. Krok 45: Skrypt 2: blok z obrazem gry

    Kryteria: R.5.7, R.2.10 ok. 4 min

    Arkusz: Zwrócone zapytaniem wiersze są wyświetlone w bloku, który składa się z: obrazu, którego źródłem jest pole zdjecie, tekstem alternatywnym jest pole nazwa, a podpowiedzią (dymek widoczny na ilustracji 2 dla pierwszej grafiki) jest pole id.

    Ta sama pętla while co w skrypcie 1. Każdy obieg wypisuje blok <div class='game'> (klasa, na którą czeka reguła .game w CSS) z obrazem <img>: src = nazwa pliku z kolumny zdjecie ($row[2]), alt = nazwa ($row[1]), title = id ($row[0]). Atrybut title to właśnie „dymek” - podpowiedź po najechaniu kursorem, którą pokazuje ilustracja 2 (cyfra 1 przy kursorze na obrazie Czarodziejki). Kryterium R.2.10: obraz z tekstem alternatywnym i atrybutem title.

    Pliki graficzne muszą leżeć obok gry.php - w bazie są same nazwy, dokładnie takie jak w archiwum (wielkość liter ma znaczenie na serwerach linuksowych).

    Typowe błędy

    Zamiana alt z title (alt = id, title = nazwa) to strata R.2.10 i R.5.7 - arkusz mówi wprost, które pole gdzie.

    gry.php PHP zmienione: 5 linii
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  46. Krok 46: Skrypt 2: paragraf z nazwą

    Kryteria: R.5.7 ok. 1 min

    Arkusz: Paragrafu z nazwą zdjęcia.

    Do bloku dokładamy <p> z nazwą gry ($row[1]) pod obrazem, przed zamknięciem </div>. Sekcja środkowa w podglądzie wygląda jak na ilustracji 2: dziewięć bloków obok siebie, zawijanych, z paskami przewijania.

    gry.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo "<p>$row[1]</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  47. Krok 47: Skrypt 3: tylko gdy wpisano id

    Kryteria: R.5.4, R.5.5 ok. 2 min

    Arkusz: Skrypt 3 związany z formularzem w stopce: jeżeli wpisano id do pola edycyjnego, skrypt wysyła do bazy danych zapytanie 2 zmodyfikowane tak, że wybierany jest wiersz o id podanym w polu edycyjnym.

    Skrypt 3 piszemy w stopce, pod formularzem - tam ma się pojawić wynik. Warunek ma dwie części: isset($_POST['game_id']) (czy formularz w ogóle został wysłany) i $_POST['game_id'] != '' (czy pole nie jest puste). „Jeżeli wpisano id” to właśnie ta druga część - klucz R.5.4 sprawdza, że skrypt działa tylko przy wypełnionym polu. Przy pierwszym wejściu na stronę blok if jest pomijany.

    Typowe błędy

    Samo isset nie wystarczy: po kliknięciu „Pokaż opis” z pustym polem $_POST['game_id'] istnieje (jako pusty napis) i skrypt wysłałby zapytanie WHERE id = - błąd SQL na stronie.

    gry.php PHP zmienione: 4 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo "<p>$row[1]</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php
    if (isset($_POST['game_id']) && $_POST['game_id'] != '') {
    }
    ?>
        </footer>
    </body>
    </html>

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

  48. Krok 48: Skrypt 3: zapytanie 2 z id z formularza

    Kryteria: R.5.3, R.5.5 ok. 2 min

    Arkusz: Skrypt wysyła do bazy danych zapytanie 2 zmodyfikowane tak, że wybierany jest wiersz o id podanym w polu edycyjnym.

    Wartość z pola trafia do $game_id ($_POST['game_id'] - klucz to atrybut name pola). Bierzemy zapytanie 2 z kwerendy.txt i w warunku zamiast id = 1 wstawiamy id = $game_id. Cały napis zapytania w cudzysłowie, żeby PHP podstawił zmienną. Wynik trafia do $result.

    gry.php PHP zmienione: 2 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo "<p>$row[1]</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php
    if (isset($_POST['game_id']) && $_POST['game_id'] != '') {
        $game_id = $_POST['game_id'];
        $result = mysqli_query($connection, "SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = $game_id;");
    }
    ?>
        </footer>
    </body>
    </html>

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

  49. Krok 49: Skrypt 3: nagłówek z nazwą, punktami i ceną

    Kryteria: R.5.8 ok. 3 min

    Arkusz: Zwrócony zapytaniem wiersz jest wyświetlany pod formularzem według wzoru: w nagłówku drugiego stopnia: <nazwa>, <punkty> punktów, <cena> zł.

    Zapytanie z warunkiem na klucz główny zwraca jeden wiersz, więc pętla nie jest potrzebna - jedno mysqli_fetch_row($result) do $game. Kolejność pól jak w SELECT: $game[0] nazwa, $game[1] skrócony opis, $game[2] punkty, $game[3] cena. Wzór z arkusza przepisujemy dosłownie do <h2>, z przecinkami i słowami „punktów” oraz „zł”.

    Podgląd pokazuje stan z ilustracji 2 - w formularzu wprowadzono 6: „Mag, 357 punktów, 150 zł”.

    gry.php PHP zmienione: 2 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo "<p>$row[1]</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php
    if (isset($_POST['game_id']) && $_POST['game_id'] != '') {
        $game_id = $_POST['game_id'];
        $result = mysqli_query($connection, "SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = $game_id;");
        $game = mysqli_fetch_row($result);
        echo "<h2>$game[0], $game[2] punktów, $game[3] zł</h2>";
    }
    ?>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  50. Krok 50: Skrypt 3: opis w paragrafie

    Kryteria: R.5.8 ok. 1 min

    Arkusz: Opis w paragrafie.

    <p> z $game[1] - to opis skrócony do 100 znaków przez LEFT(opis, 100) w zapytaniu, dokładnie tak, jak urwany tekst w stopce na ilustracji 2.

    gry.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo "<p>$row[1]</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php
    if (isset($_POST['game_id']) && $_POST['game_id'] != '') {
        $game_id = $_POST['game_id'];
        $result = mysqli_query($connection, "SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = $game_id;");
        $game = mysqli_fetch_row($result);
        echo "<h2>$game[0], $game[2] punktów, $game[3] zł</h2>";
        echo "<p>$game[1]</p>";
    }
    ?>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (gry.php)
  51. Krok 51: Skrypt 4: tylko gdy wypełniono pole nazwa

    Kryteria: R.5.4 ok. 2 min

    Arkusz: Skrypt 4 związany z formularzem z sekcji prawej: jeżeli wypełniono pole nazwa, skrypt wysyła do bazy zmodyfikowane zapytanie 4 w ten sposób, że wstawione są wartości pobrane z formularza, a liczba punktów wynosi 0.

    Skrypt 4 nic nie wypisuje, tylko zapisuje do bazy - piszemy go na początku pliku, zaraz po połączeniu. Dzięki temu nowa gra jest w bazie, zanim skrypty 1 i 2 zbudują listę i galerię, i pojawia się na stronie od razu po kliknięciu DODAJ.

    Warunek jak w skrypcie 3: isset($_POST['name']) i pole niepuste. Formularz w stopce nie ma pola name, więc wysłanie „Pokaż opis” nie uruchomi skryptu 4 - tak PHP rozróżnia dwa formularze w jednym pliku.

    gry.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    
    if (isset($_POST['name']) && $_POST['name'] != '') {
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo "<p>$row[1]</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php
    if (isset($_POST['game_id']) && $_POST['game_id'] != '') {
        $game_id = $_POST['game_id'];
        $result = mysqli_query($connection, "SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = $game_id;");
        $game = mysqli_fetch_row($result);
        echo "<h2>$game[0], $game[2] punktów, $game[3] zł</h2>";
        echo "<p>$game[1]</p>";
    }
    ?>
        </footer>
    </body>
    </html>

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

  52. Krok 52: Skrypt 4: odczyt pól formularza

    Kryteria: R.5.5 ok. 2 min

    Arkusz: Skrypt wysyła do bazy zmodyfikowane zapytanie 4 w ten sposób, że wstawione są wartości pobrane z formularza, a liczba punktów wynosi 0.

    Cztery wartości z $_POST do czterech zmiennych o znaczących nazwach: $name, $description, $price, $image. Klucze $_POST to atrybuty name pól z sekcji prawej. Punktów z formularza nie czytamy - arkusz każe wstawić 0.

    gry.php PHP zmienione: 4 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    
    if (isset($_POST['name']) && $_POST['name'] != '') {
        $name = $_POST['name'];
        $description = $_POST['description'];
        $price = $_POST['price'];
        $image = $_POST['image'];
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo "<p>$row[1]</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php
    if (isset($_POST['game_id']) && $_POST['game_id'] != '') {
        $game_id = $_POST['game_id'];
        $result = mysqli_query($connection, "SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = $game_id;");
        $game = mysqli_fetch_row($result);
        echo "<h2>$game[0], $game[2] punktów, $game[3] zł</h2>";
        echo "<p>$game[1]</p>";
    }
    ?>
        </footer>
    </body>
    </html>

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

  53. Krok 53: Skrypt 4: wstawienie wiersza z zerem punktów

    Kryteria: R.5.9 ok. 3 min

    Arkusz: Skrypt wysyła do bazy zmodyfikowane zapytanie 4 w ten sposób, że wstawione są wartości pobrane z formularza, a liczba punktów wynosi 0.

    Bierzemy zapytanie 4 z kwerendy.txt i zamiast danych Zamczyska wstawiamy zmienne: napisy w apostrofach ('$name'), a w miejscu punktów liczba 0. Cały napis w cudzysłowie, żeby PHP podstawił wartości. mysqli_query wykonuje INSERT - nic nie zwraca do wyświetlenia, efekt sprawdza się w phpMyAdmin (R.5.9) albo na stronie po odświeżeniu: nowa gra na końcu galerii.

    Ta wersja spełnia klucz. Ma jednak wadę, o której powiemy przy refaktorze: dane z formularza wchodzą do zapytania bez żadnej kontroli.

    Typowe błędy

    Wpisanie w formularzu nazwy z apostrofem (np. „Assassin's Creed”) rozrywa napis SQL i zapytanie kończy się błędem - to objaw podatności, którą usuniemy na końcu.

    gry.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    
    if (isset($_POST['name']) && $_POST['name'] != '') {
        $name = $_POST['name'];
        $description = $_POST['description'];
        $price = $_POST['price'];
        $image = $_POST['image'];
        mysqli_query($connection, "INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie) VALUES ('$name', '$description', 0, '$price', '$image');");
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo "<p>$row[1]</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php
    if (isset($_POST['game_id']) && $_POST['game_id'] != '') {
        $game_id = $_POST['game_id'];
        $result = mysqli_query($connection, "SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = $game_id;");
        $game = mysqli_fetch_row($result);
        echo "<h2>$game[0], $game[2] punktów, $game[3] zł</h2>";
        echo "<p>$game[1]</p>";
    }
    ?>
        </footer>
    </body>
    </html>

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

  54. Krok 54: Zamknięcie połączenia z bazą

    Kryteria: R.5.2 ok. 1 min

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

    mysqli_close($connection) na końcu pliku, gdy wszystkie skrypty już skorzystały z połączenia. Klucz R.5.2 sprawdza, że zamknięcie jest ostatnią operacją na bazie. Strona spełnia wszystkie kryteria arkusza - dalej trzy kroki „jak w prawdziwym projekcie”.

    Typowe błędy

    mysqli_close po skrypcie 1 sprawia, że skrypty 2 i 3 idą po zamkniętym połączeniu - galeria i opis znikają.

    gry.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    
    if (isset($_POST['name']) && $_POST['name'] != '') {
        $name = $_POST['name'];
        $description = $_POST['description'];
        $price = $_POST['price'];
        $image = $_POST['image'];
        mysqli_query($connection, "INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie) VALUES ('$name', '$description', 0, '$price', '$image');");
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo "<p>$row[1]</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php
    if (isset($_POST['game_id']) && $_POST['game_id'] != '') {
        $game_id = $_POST['game_id'];
        $result = mysqli_query($connection, "SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = $game_id;");
        $game = mysqli_fetch_row($result);
        echo "<h2>$game[0], $game[2] punktów, $game[3] zł</h2>";
        echo "<p>$game[1]</p>";
    }
    ?>
        </footer>
    <?php
    mysqli_close($connection);
    ?>
    </body>
    </html>

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

  55. Krok 55: Refaktor: zapytanie przygotowane w skrypcie 3

    Kryteria: R.5.3, R.5.8 ok. 5 min

    Arkusz: Skrypt wysyła do bazy danych zapytanie 2 zmodyfikowane tak, że wybierany jest wiersz o id podanym w polu edycyjnym.

    Wersja z "... WHERE id = $game_id" sklejała zapytanie z tekstem wpisanym przez użytkownika - to SQL injection: wpisanie 1 OR 1=1 zmienia sens zapytania. W prawdziwych projektach dane nigdy nie trafiają do zapytania jako tekst, tylko przez zapytanie przygotowane:

    • mysqli_prepare dostaje zapytanie z ? w miejscu wartości - baza zna jego strukturę, zanim zobaczy dane,
    • mysqli_stmt_bind_param($statement, 'i', $game_id) podpina zmienną pod ?; litera i mówi, że to liczba całkowita (s napis, d liczba zmiennoprzecinkowa),
    • mysqli_stmt_execute wykonuje, a mysqli_stmt_get_result oddaje wynik, z którego czytamy jak dotąd.

    Stare dwie linie zostają w komentarzu // do porównania. Podgląd bez zmian - to ta sama logika, tylko bezpieczna.

    Typowe błędy

    Na egzaminie zapytania przygotowane nie są wymagane (funkcji mysqli_prepare nie ma w tabeli 1), ale klucz R.5 przyjmuje MySQLi w każdej postaci. Jeśli je znasz - używaj; jeśli nie - wersja prosta daje pełne punkty.

    gry.php PHP zmienione: 6 linii
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    
    if (isset($_POST['name']) && $_POST['name'] != '') {
        $name = $_POST['name'];
        $description = $_POST['description'];
        $price = $_POST['price'];
        $image = $_POST['image'];
        mysqli_query($connection, "INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie) VALUES ('$name', '$description', 0, '$price', '$image');");
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo "<p>$row[1]</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php
    if (isset($_POST['game_id']) && $_POST['game_id'] != '') {
        $game_id = $_POST['game_id'];
        // $result = mysqli_query($connection, "SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = $game_id;");
        // $game = mysqli_fetch_row($result);
        $statement = mysqli_prepare($connection, 'SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = ?');
        mysqli_stmt_bind_param($statement, 'i', $game_id);
        mysqli_stmt_execute($statement);
        $game = mysqli_fetch_row(mysqli_stmt_get_result($statement));
        echo "<h2>$game[0], $game[2] punktów, $game[3] zł</h2>";
        echo "<p>$game[1]</p>";
    }
    ?>
        </footer>
    <?php
    mysqli_close($connection);
    ?>
    </body>
    </html>

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

  56. Krok 56: Refaktor: zapytanie przygotowane w skrypcie 4

    Kryteria: R.5.9 ok. 4 min

    Arkusz: Skrypt wysyła do bazy zmodyfikowane zapytanie 4 w ten sposób, że wstawione są wartości pobrane z formularza, a liczba punktów wynosi 0.

    Tu ryzyko jest największe - cztery pola z formularza szły prosto do INSERT. Ten sam schemat: ? za każdą wartość z formularza (zero punktów zostaje w zapytaniu jako stała), a mysqli_stmt_bind_param z typami 'ssds': nazwa (s), opis (s), cena (d - liczba z częścią ułamkową, jak kolumna float), zdjęcie (s). Litery odpowiadają kolejności ? w zapytaniu.

    Bonus: nazwa gry z apostrofem, która wcześniej wywalała zapytanie, teraz zapisze się poprawnie - baza dostaje ją jako dane, nie jako fragment SQL.

    Typowe błędy

    Liczba liter w 'ssds' musi równać się liczbie ? i liczbie zmiennych - inaczej PHP zgłosi błąd i nic nie zostanie wstawione.

    gry.php PHP zmienione: 4 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    
    if (isset($_POST['name']) && $_POST['name'] != '') {
        $name = $_POST['name'];
        $description = $_POST['description'];
        $price = $_POST['price'];
        $image = $_POST['image'];
        // mysqli_query($connection, "INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie) VALUES ('$name', '$description', 0, '$price', '$image');");
        $statement = mysqli_prepare($connection, 'INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie) VALUES (?, ?, 0, ?, ?)');
        mysqli_stmt_bind_param($statement, 'ssds', $name, $description, $price, $image);
        mysqli_stmt_execute($statement);
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        echo "<p>$row[1]</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php
    if (isset($_POST['game_id']) && $_POST['game_id'] != '') {
        $game_id = $_POST['game_id'];
        // $result = mysqli_query($connection, "SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = $game_id;");
        // $game = mysqli_fetch_row($result);
        $statement = mysqli_prepare($connection, 'SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = ?');
        mysqli_stmt_bind_param($statement, 'i', $game_id);
        mysqli_stmt_execute($statement);
        $game = mysqli_fetch_row(mysqli_stmt_get_result($statement));
        echo "<h2>$game[0], $game[2] punktów, $game[3] zł</h2>";
        echo "<p>$game[1]</p>";
    }
    ?>
        </footer>
    <?php
    mysqli_close($connection);
    ?>
    </body>
    </html>

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

  57. Krok 57: Refaktor: escapowanie danych przed wypisaniem

    Kryteria: R.5.6, R.5.7, R.5.8 ok. 5 min

    Arkusz: Zwrócony zapytaniem wiersz jest wyświetlany pod formularzem według wzoru: w nagłówku drugiego stopnia: <nazwa>, <punkty> punktów, <cena> zł, gdzie nawiasy <> oznaczają wartość pobraną z bazy danych.

    Druga strona tego samego problemu: skrypty 1-3 wypisują do HTML to, co jest w bazie - a do bazy każdy może dodać grę przez formularz. Nazwa <script>alert(1)</script> wykonałaby się w przeglądarce każdego odwiedzającego (XSS). htmlspecialchars() zamienia <, >, ", & na encje, więc tekst wyświetla się dosłownie i nie staje się kodem.

    W każdym skrypcie stare echo zostaje w komentarzu, a przed nowym wartości z bazy przechodzą przez htmlspecialchars() (liczby - id, punkty, cena - nie potrzebują). Podgląd wygląda identycznie, bo dane z archiwum nie mają znaków specjalnych - różnicę zobaczysz dopiero przy złośliwej nazwie gry.

    Typowe błędy

    htmlspecialchars przy wpisywaniu do bazy (w skrypcie 4) zamiast przy wypisywaniu to częsty błąd: baza dostaje zniekształcone dane (&amp; zamiast &), a każdy inny odbiorca danych i tak musi je escapować po swojemu. Escapujemy na wyjściu.

    gry.php PHP zmienione: 15 linii
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'gry');
    
    if (isset($_POST['name']) && $_POST['name'] != '') {
        $name = $_POST['name'];
        $description = $_POST['description'];
        $price = $_POST['price'];
        $image = $_POST['image'];
        // mysqli_query($connection, "INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie) VALUES ('$name', '$description', 0, '$price', '$image');");
        $statement = mysqli_prepare($connection, 'INSERT INTO gry (nazwa, opis, punkty, cena, zdjecie) VALUES (?, ?, 0, ?, ?)');
        mysqli_stmt_bind_param($statement, 'ssds', $name, $description, $price, $image);
        mysqli_stmt_execute($statement);
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Gry komputerowe</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Ranking gier komputerowych</h1>
        </header>
        <section id="left">
            <h3>Top 5 gier w tym miesiącu</h3>
            <ul>
    <?php
    $top_games = mysqli_query($connection, 'SELECT nazwa, punkty FROM gry ORDER BY punkty DESC LIMIT 5;');
    while ($row = mysqli_fetch_row($top_games)) {
        // echo "<li>$row[0] <span class='points'>$row[1]</span></li>";
        $name = htmlspecialchars($row[0]);
        echo "<li>$name <span class='points'>$row[1]</span></li>";
    }
    ?>
            </ul>
            <h3>Nasz sklep</h3>
            <a href="http://sklep.gry.pl">Tu kupisz gry</a>
            <h3>Stronę wykonał</h3>
            <p>Bartosz Bryniarski</p>
        </section>
        <section id="middle">
    <?php
    $games = mysqli_query($connection, 'SELECT id, nazwa, zdjecie FROM gry;');
    while ($row = mysqli_fetch_row($games)) {
        echo "<div class='game'>";
        // echo "<img src='$row[2]' alt='$row[1]' title='$row[0]'>";
        // echo "<p>$row[1]</p>";
        $name = htmlspecialchars($row[1]);
        $image = htmlspecialchars($row[2]);
        echo "<img src='$image' alt='$name' title='$row[0]'>";
        echo "<p>$name</p>";
        echo '</div>';
    }
    ?>
        </section>
        <section id="right">
            <h3>Dodaj nową grę</h3>
            <form method="post">
                <label for="name">nazwa</label><br>
                <input type="text" id="name" name="name"><br>
                <label for="description">opis</label><br>
                <input type="text" id="description" name="description"><br>
                <label for="price">cena</label><br>
                <input type="text" id="price" name="price"><br>
                <label for="image">zdjęcie</label><br>
                <input type="text" id="image" name="image"><br>
                <button type="submit">DODAJ</button>
            </form>
        </section>
        <footer>
            <form method="post">
                <input type="text" name="game_id">
                <button type="submit">Pokaż opis</button>
            </form>
    <?php
    if (isset($_POST['game_id']) && $_POST['game_id'] != '') {
        $game_id = $_POST['game_id'];
        // $result = mysqli_query($connection, "SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = $game_id;");
        // $game = mysqli_fetch_row($result);
        $statement = mysqli_prepare($connection, 'SELECT nazwa, LEFT(opis, 100), punkty, cena FROM gry WHERE id = ?');
        mysqli_stmt_bind_param($statement, 'i', $game_id);
        mysqli_stmt_execute($statement);
        $game = mysqli_fetch_row(mysqli_stmt_get_result($statement));
        // echo "<h2>$game[0], $game[2] punktów, $game[3] zł</h2>";
        // echo "<p>$game[1]</p>";
        $name = htmlspecialchars($game[0]);
        $description = htmlspecialchars($game[1]);
        echo "<h2>$name, $game[2] punktów, $game[3] zł</h2>";
        echo "<p>$description</p>";
    }
    ?>
        </footer>
    <?php
    mysqli_close($connection);
    ?>
    </body>
    </html>

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

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

    ok. 5 min

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

    W przeglądarka.txt wpisujemy nazwę przeglądarki, w której sprawdzaliśmy stronę przez http://localhost/…. Na egzaminie nazwa pliku dokładnie jak w arkuszu, z „ą” - w tym rozwiązaniu plik nazywa się przegladarka.txt tylko dlatego, że podgląd nie przyjmuje polskich znaków w nazwach plików.

    W folderze z numerem zdającego muszą być: Czarodziejka.jpg, gry.php, import.png, kalipso.jpg, katedra.jpg, krokodyl.jpg, kw1.jpg, kw2.jpg, kw3.jpg, kw4.jpg, kwerendy.txt, Mag.jpg, NieznanyLad.jpg, Persja.jpg, przeglądarka.txt, styl.css, WyprawaNaKsiezyc.jpg, zamczysko.jpg. Nagrywamy płytę, sprawdzamy jej odczyt i opisujemy numerem zdającego. Pusta lub nieczytelna płyta to zero punktów za wszystkie pięć rezultatów.

    przegladarka.txt Tekst nowy plik
    Google Chrome

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