Przejdź do głównej treści
E.14-01-22.01-SGStyczeń 2022 · 39 kroków · ok. 73 min · 34 kryteria z klucza

Rozwiązanie krok po kroku: Grzybobranie - baza grzybów (Wersja podstawowa)

Grzybobranie: baza grzybobranie z tabelami grzyby, rodzina i potrawy, cztery zapytania (grzyby jadalne, grzyby do zup z nazwą rodziny, pliki zdjęć i nowa kolumna opis), miniatura zdjęcia podgrzybka, strona grzyby.php z pięcioma blokami, style w styl.css oraz trzy skrypty PHP: lista grzybów jadalnych w akapitach, grzyby do zup na liście punktowanej i galeria zdjęć z nazwą grzyba w dymku.

To arkusz E.14 z sesji styczeń 2022 - kwalifikacji formuły 2012, którą zastąpiła INF.03. Kroki idą zdanie po zdaniu za treścią arkusza - przy każdym jest polecenie z arkusza, a kod pokazuje dokładnie te linie, które je spełniają. Skrypty są najpierw napisane najprościej, funkcjami mysqli z tabeli 1 arkusza, a na końcu sprawdzone na prawdziwym ataku i zabezpieczone.

To jest wersja podstawowa. W zakładce „Nowoczesny kod (flex, PDO)” jest drugi sposób rozwiązania tego samego arkusza: bloki ustawione flexboxem, zmienne CSS i PDO z jednym połączeniem na górze pliku - obie wersje są zgodne z kluczem.

Skąd materiały. Arkusz wymienia archiwum pliki1.zip z plikiem dane.sql; to archiwum nie zostało opublikowane. Tym samym hasłem @grzYbY otwiera się oryginalne archiwum CKE z sesji styczeń 2019 (zadanie E.14-05-19.01, załącznik na stronie arkusza): są w nim wszystkie zdjęcia wymienione w arkuszu i plik dane2.sql z tabelami grzyby, rodzina i potrawy zgodnymi z obrazem 1 i z wynikami w kluczu. Na egzaminie importuje się dane.sql z archiwum na pulpicie - w tym rozwiązaniu jego rolę gra dane2.sql. Zapytania i zrzuty są policzone na MariaDB 10.4, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2.

Jak liczą się punkty. Klucz ma 34 kryteria (R.1.1 - R.5.8), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 26 punktów - można stracić 8 kryteriów i nadal zdać. Przy każdym kroku jest napisane, które kryteria spełnia.

  1. Etap 1: Baza danych

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

    Krok 1: Folder zdającego i archiwum z materiałami

    ok. 2 min
    Polecenie z arkusza

    Aby wykonać zadanie, zaloguj się na konto Egzamin bez hasła. Na pulpicie znajdziesz archiwum ZIP o nazwie pliki1.zip zabezpieczone hasłem: @grzYbY Archiwum należy rozpakować. Na pulpicie konta Egzamin utwórz folder. Jako nazwy folderu użyj numeru, którym został podpisany arkusz. Rozpakowane pliki umieść w tym folderze. Po skończonej pracy wyniki zapisz w tym folderze.

    Na pulpicie tworzysz folder nazwany numerem PESEL - w tym rozwiązaniu w jego miejscu stoi imię i nazwisko autora, Bartosz Bryniarski. Archiwum rozpakowujesz 7-Zipem do tego folderu, podając hasło @grzYbY (wielkość liter ma znaczenie). W środku są zdjęcia ośmiu grzybów i plik SQL z tabelami.

    Archiwum z sesji 2022 nie zostało opublikowane; na stronie arkusza jest archiwum pliki5.zip z sesji styczeń 2019, które otwiera się tym samym hasłem i ma te same zdjęcia oraz tabele (plik nazywa się tam dane2.sql).

    Typowe błędy

    Pliki zapisane poza folderem zdającego (np. tylko w htdocs) nie trafią na płytę - a ocenia się wyłącznie to, co jest na płycie.

  2. Krok 2: XAMPP i pusta baza grzybobranie

    ok. 2 min
    Polecenie z arkusza

    Za pomocą narzędzia phpMyAdmin wykonaj podane operacje na bazie danych: Utwórz nową bazę danych o nazwie grzybobranie

    W XAMPP Control Panel klikasz Start przy Apache i MySQL, potem http://localhost/phpmyadmin. Nowa w lewym panelu, nazwa grzybobranie, porównywanie np. utf8mb4_polish_ci, Utwórz.

    Typowe błędy

    Nazwa bazy musi brzmieć dokładnie grzybobranie - import do innej bazy nie daje R.1.1, a skrypt połączy się z bazą o tej nazwie (R.5.2).

  3. Krok 3: Import tabel i zrzut import.png

    Kryteria:

    R.1.1 Wykonano import tabel do bazy danych grzybobranie , czynność udokumentowano plikiem o nazwie import w formacie PNG

    ok. 3 min
    Polecenie z arkusza

    Do bazy grzybobranie zaimportuj tabele z pliku dane.sql z rozpakowanego archiwum Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie PNG i nazwij import. Nie kadruj zrzutu. Powinien on obejmować cały ekran, z widocznym paskiem zadań. Na zrzucie powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel

    Zaznacz bazę grzybobranie, karta Import, Wybierz plik → plik SQL z archiwum (na egzaminie dane.sql, w archiwum z 2019 dane2.sql), Wykonaj. Plik z 2019 tworzy 12 tabel - poza trzema z obrazu 1 są tam tabele z innych zadań; zostawiasz je, zadanie ich nie dotyczy.

    Struktura z obrazu 1: tabela grzyby ma pola id, rodzina_id, potrawy_id, nazwa, potoczna, jadalny, miesiac_zbierania i nazwa_pliku. Pola rodzina_id i potrawy_id to klucze obce do tabel rodzina (1 pieczarkowate … 5 boczniakowate) i potrawy (1 sos … 4 zupa, 5 niejadalne). Pole jadalny ma wartość 1 dla grzyba jadalnego i 0 dla niejadalnego.

    Po imporcie widać komunikat o sukcesie i listę tabel w lewym panelu. Zrzut całego ekranu z paskiem zadań zapisujesz jako import.png.

    Typowe błędy

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

    Podgląd strony po tym kroku
    Podgląd strony po kroku 3
  4. Krok 4: Zapytanie 1: grzyby jadalne

    Kryteria: ,

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

    R.1.3 Utworzono zapytanie 1 wybierające jedynie pola id, nazwa oraz potoczna z tabeli grzyby jedynie dla grzybów jadalnych. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1; oraz wynik działania udokumentowano zrzutem, na którym widocznych jest dokładnie 7 rekordów o id: 1, 2, 3, 4, 6, 7, 8; jedynie pola id, nazwa, potoczna

    ok. 3 min
    Polecenie z arkusza

    Zapisz i wykonaj zapytania SQL działające na bazie danych. Zapytania zapisz w pliku kwerendy.txt. Wykonaj zrzuty ekranu przedstawiające wyniki działania zapytań. Zrzuty zapisz w formacie PNG i nadaj im nazwy kw1, kw2, kw3, kw4. Zrzuty powinny obejmować cały ekran monitora z widocznym paskiem zadań Zapytanie 1: wybierające jedynie pola id, nazwa oraz potoczna z tabeli grzyby jedynie dla grzybów jadalnych

    Karta SQL bazy grzybobranie:

    • SELECT id, nazwa, potoczna - „jedynie pola” wyklucza SELECT *,
    • FROM grzyby,
    • WHERE jadalny = 1 - grzyb jadalny ma w tym polu 1 (opis pod obrazem 1).

    Wynik: 7 wierszy o id 1, 2, 3, 4, 6, 7, 8 - brakuje tylko szatana (id 5). Zapytanie kopiujesz do pliku kwerendy.txt, a cały ekran z wynikiem zapisujesz jako kw1.png - w tym arkuszu zrzuty zapytań też są w PNG. To zapytanie wyśle skrypt 1.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: grzyby do zup z nazwą rodziny

    Kryteria:

    R.1.4 Utworzono zapytanie 2 wybierające jedynie pole potoczna z tabeli grzyby oraz odpowiadające mu pole nazwa z tabeli rodzina jedynie dla grzybów, do których przypisana jest potrawa zupa. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON rodzina_id = rodzina.id WHERE potrawy_id = 4; (lub INNER JOIN lub porównanie kluczy po WHERE) oraz wynik działania udokumentowano zrzutem, na którym widoczne są dokładnie 2 rekordy z danymi: prawdziwek, borowikowate, podgrzybek, borowikowate

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pole potoczna z tabeli grzyby oraz odpowiadające mu pole nazwa z tabeli rodzina jedynie dla grzybów, do których przypisana jest potrawa zupa

    • SELECT potoczna, rodzina.nazwa - pole nazwa jest w obu tabelach, więc trzeba wskazać, z której: rodzina.nazwa,
    • FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id - relacja: klucz obcy grzyba równa się kluczowi głównemu rodziny,
    • WHERE potrawy_id = 4 - zupa ma w tabeli potrawy identyfikator 4 (obraz 1).

    Wynik: 2 wiersze - prawdziwek, borowikowate i podgrzybek, borowikowate. Zrzut: kw2.png. To zapytanie wyśle skrypt 2.

    Typowe błędy

    WHERE potrawy = 'zupa' nie zadziała - tabela grzyby ma tylko numer potrawy. Bez rodzina. przed nazwa baza zgłosi błąd #1052 (kolumna niejednoznaczna).

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;
    
    -- Zapytanie 2
    SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: pliki zdjęć i nazwy grzybów

    Kryteria:

    R.1.5 Utworzono zapytanie 3 wybierające jedynie pola nazwa_pliku oraz nazwa z tabeli grzyby . W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa_pliku, nazwa FROM grzyby; oraz wynik działania udokumentowano zrzutem, na którym widoczne jest 8 rekordów, jedynie pola nazwa_pliku i nazwa

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pola nazwa_pliku oraz nazwa z tabeli grzyby

    Dwie kolumny, bez warunku - 8 wierszy, od pieczarka.jpg, pieczarka dwuzarodnikowa do trufla.jpg, trufla letnia. Zrzut: kw3.png. Skrypt 3 zrobi z każdego wiersza zdjęcie: nazwa pliku będzie źródłem, nazwa - dymkiem.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;
    
    -- Zapytanie 2
    SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;
    
    -- Zapytanie 3
    SELECT nazwa_pliku, nazwa FROM grzyby;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: kolumna opis w tabeli rodzina

    Kryteria:

    R.1.6 Utworzono zapytanie 4 dodające do tabeli rodzina pole opis typu napisowego. W pliku z kwerendami lub na zrzucie istnieje zapis np.: ALTER TABLE rodzina ADD opis TEXT; (lub ADD COLUMN, dowolne typy napisowe) oraz wynik działania udokumentowano zrzutem, na którym widoczny jest komunikat o poprawnie dodanej kolumnie lub struktura tabeli z dodaną kolumną

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: dodające do tabeli rodzina pole opis typu napisowego

    ALTER TABLE zmienia strukturę istniejącej tabeli, a ADD opis TEXT dodaje kolumnę opis typu tekstowego (klucz przyjmuje też ADD COLUMN i inne typy napisowe, np. VARCHAR(200)). Istniejące wiersze dostają w niej NULL.

    phpMyAdmin odpowiada „MySQL zwrócił pusty wynik” - zmiana struktury niczego nie wybiera. Na zrzucie kw4.png warto pokazać kartę Struktura tabeli rodzina z nową kolumną - klucz przyjmuje komunikat albo strukturę.

    Typowe błędy

    Drugie wykonanie tego samego ALTER TABLE kończy się błędem #1060 „Duplicate column name” - kolumna już istnieje.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;
    
    -- Zapytanie 2
    SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;
    
    -- Zapytanie 3
    SELECT nazwa_pliku, nazwa FROM grzyby;
    
    -- Zapytanie 4
    ALTER TABLE rodzina ADD opis TEXT;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

    Kroki 8-17 · po tym etapie masz 15 z 34 kryteriów (pkt)

    Krok 8: Kopia zdjęcia: podgrzybek-miniatura.jpg

    ok. 1 min
    Polecenie z arkusza

    Obraz podgrzybek.jpg należy skopiować na nowy i nazwać podgrzybek-miniatura.jpg (powinny być dwa pliki: podgrzybek.jpg i podgrzybek-miniatura.jpg).

    W folderze zdającego kopiujesz podgrzybek.jpg (Ctrl+C, Ctrl+V) i zmieniasz nazwę kopii na podgrzybek-miniatura.jpg. Oryginał zostaje bez zmian - do niego poprowadzi odnośnik z miniatury.

  9. Krok 9: Miniatura: wysokość 80 px z zachowaniem proporcji

    Kryteria:

    R.2.7 Obraz podgrzybek-miniatura.jpg przeskalowano z zachowaniem proporcji do wysokości 80 px, odpowiadająca temu szerokość wynosi 107 px (± 5 px)

    ok. 2 min
    Polecenie z arkusza

    Obraz podgrzybek-miniatura.jpg należy przeskalować z zachowaniem proporcji do wysokości 80 px

    W GIMP-ie otwierasz kopię, Obraz → Skaluj obraz, łańcuch proporcji zamknięty, wysokość 80 px - szerokość wychodzi 267 × 80 / 200 ≈ 107 px (klucz: 107 ±5). Zapis: Plik → Nadpisz podgrzybek-miniatura.jpg (format JPEG zostaje).

    Typowe błędy

    Skalowanie oryginału zamiast kopii - zostanie jeden mały plik, a odnośnik z miniatury otworzy tę samą miniaturę.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 9
  10. Krok 10: Plik grzyby.php i kodowanie polskich znaków

    Kryteria:

    R.2.1 Witrynę internetową zapisano w pliku grzyby.php oraz zastosowano właściwy standard kodowania polskich znaków

    ok. 2 min
    Polecenie z arkusza

    Składa się ze strony o nazwie grzyby.php Zastosowany właściwy standard kodowania polskich znaków

    Plik grzyby.php w folderze zdającego. Rozszerzenie .php, bo dojdą skrypty. <!DOCTYPE html> - HTML5, lang="pl" - treść po polsku, a <meta charset="UTF-8"> jako pierwszy element <head> mówi przeglądarce, jak odczytać polskie litery; plik trzeba też zapisać w UTF-8 (R.2.1). Stronę sprawdzasz przez http://localhost/…/grzyby.php.

    grzyby.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  11. Krok 11: Tytuł strony

    Kryteria:

    R.2.2 Nadano tytuł strony: "Grzybobranie”

    ok. 1 min
    Polecenie z arkusza

    Tytuł strony widoczny na karcie przeglądarki: „Grzybobranie”

    <title> w <head> - napis na karcie przeglądarki (R.2.2).

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

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

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

    <link rel="stylesheet" href="styl.css"> w <head>. Całe formatowanie ma pochodzić z tego pliku - bez atrybutów style w znacznikach (R.3.3).

    Typowe błędy

    Klucz odbiera R.3.3 także wtedy, gdy choć jeden selektor nie trafia w blok (np. kropka zamiast # przy identyfikatorze).

    grzyby.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  13. Krok 13: Bloki: tytułowy, miniatury, lewy, prawy i stopka

    Kryteria:

    R.2.3 Wygląd strony zdefiniowano dzieląc ją na bloki: tytułowy, miniatury, lewy, prawy, stopka. Zastosowano znaczniki sekcji

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: na górze: tytułowy i miniatury, poniżej: lewy i prawy, poniżej stopka. Podział zrealizowany za pomocą znaczników sekcji tak, aby układ bloków strony po uruchomieniu w przeglądarce, był zgodny z Obrazem 2

    Pięć bloków w kolejności z obrazu 2: cztery <section> z identyfikatorami title, thumbnail, left i right (nazwy po angielsku - arkusz ich nie narzuca) oraz <footer> (R.2.3). Na razie stoją jeden pod drugim; w pary ustawi je CSS.

    Typowe błędy

    Bloki z samych <div> nie spełniają R.2.3 - klucz wymaga znaczników sekcji.

    grzyby.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
        </section>
        <section id="thumbnail">
        </section>
        <section id="left">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  14. Krok 14: Blok tytułowy: nagłówek pierwszego stopnia

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku tytułowego: nagłówek pierwszego stopnia o treści „Czas na grzyby!”

    <h1> z wykrzyknikiem na końcu. Klucz R.2.4 sprawdza go razem z dwoma nagłówkami <h3> i akapitem w stopce.

    grzyby.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
        </section>
        <section id="left">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  15. Krok 15: Blok miniatury: miniatura będąca odnośnikiem

    Kryteria: ,

    R.2.6 W bloku miniatury umieszczono obraz podgrzybek-miniatura.jpg , z tekstem alternatywnym "Grzybobranie"

    R.3.2 Miniatura jest odnośnikiem, którego kliknięcie pobiera / otwiera plik podgrzybek.jpg

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku miniatury: obraz podgrzybek-miniatura.jpg z tekstem alternatywnym „Grzybobranie”, jest on odnośnikiem, który prowadzi do obrazu podgrzybek.jpg

    Obraz z src i alt (R.2.6) stoi wewnątrz znacznika <a>: całe zdjęcie staje się odnośnikiem. href="podgrzybek.jpg" prowadzi do pełnego zdjęcia - przeglądarka otworzy je w tej samej karcie (R.3.2). To typowy wzorzec galerii: mała miniatura na stronie, duży plik po kliknięciu.

    grzyby.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  16. Krok 16: Blok lewy: dwa nagłówki i lista punktowana

    Kryteria:

    R.2.5 W bloku lewym umieszczono listę punktowaną, zastosowano znaczniki <ul>, <li> oraz ich zamknięcia

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku lewego: Nagłówek trzeciego stopnia o treści „Grzyby jadalne” efekt działania skryptu 1 Nagłówek trzeciego stopnia o treści „Polecamy do zup” lista punktowana (nieuporządkowana), a w niej efekt działania skryptu 2

    Trzeciego stopnia to <h3>. Lista punktowana (nieuporządkowana) to <ul> (R.2.5) - na razie pusta: jej elementy <li> wypisze skrypt 2. Między nagłówkami zostaje miejsce na skrypt 1.

    Typowe błędy

    <ol> to lista numerowana - arkusz wymaga punktowanej.

    grzyby.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <h3>Polecamy do zup</h3>
            <ul>
            </ul>
        </section>
        <section id="right">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  17. Krok 17: Stopka: akapit z autorem

    Kryteria:

    R.2.4 W bloku tytułowym zapisano h1: "Czas na grzyby!" oraz w bloku lewym zapisano dwa h3: "Grzyby jadalne" i "Polecamy do zup" oraz w stopce zapisano akapit z numerem zdającego

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: akapit (paragraf) o treści „Autor: ”, dalej wstawiony numer zdającego

    Akapit <p> w stopce. Na egzaminie po dwukropku wpisujesz swój numer PESEL; w tym rozwiązaniu stoi tam imię i nazwisko autora, Bartosz Bryniarski. Nagłówki <h1>, dwa <h3> i akapit w stopce dają R.2.4.

    grzyby.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <h3>Polecamy do zup</h3>
            <ul>
            </ul>
        </section>
        <section id="right">
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  18. Etap 3: Styl CSS

    Kroki 18-26 · po tym etapie masz 26 z 34 kryteriów (pkt)

    Krok 18: Plik styl.css: krój Helvetica dla całej strony

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

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

    Nowy plik styl.css. Selektor uniwersalny * obejmuje każdy element strony (R.4.1). Windows nie ma kroju Helvetica - przeglądarka podstawi zastępczy krój bezszeryfowy, zwykle Arial, i tak też wygląda obraz 2.

    styl.css CSS nowy plik
    * {
        font-family: Helvetica;
    }
    Podgląd strony po tym kroku (grzyby.php)
  19. Krok 19: Blok tytułowy: tło, kolor tekstu, wymiary, wyśrodkowanie

    ok. 1 min
    Polecenie z arkusza

    Dla bloku tytułowego: kolor tła ForestGreen, biały kolor czcionki, wysokość 100 px, szerokość 80%, wyrównanie tekstu do środka

    Pięć właściwości z arkusza na #title. ForestGreen i white to nazwy kolorów CSS - wpisujesz je dosłownie.

    styl.css CSS zmienione: 7 linii
    * {
        font-family: Helvetica;
    }
    #title {
        background-color: ForestGreen;
        color: white;
        height: 100px;
        width: 80%;
        text-align: center;
    }
    Podgląd strony po tym kroku (grzyby.php)
  20. Krok 20: Blok miniatury: tło i wymiary

    ok. 1 min
    Polecenie z arkusza

    Dla bloku miniatury: kolor tła ForestGreen, wysokość 100 px, szerokość 20%

    80% + 20% = 100% - blok tytułowy i miniatury zajmą razem całą szerokość.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: Helvetica;
    }
    #title {
        background-color: ForestGreen;
        color: white;
        height: 100px;
        width: 80%;
        text-align: center;
    }
    #thumbnail {
        background-color: ForestGreen;
        height: 100px;
        width: 20%;
    }
    Podgląd strony po tym kroku (grzyby.php)
  21. Krok 21: Blok lewy: tło i wymiary

    ok. 1 min
    Polecenie z arkusza

    Dla bloku lewego: kolor tła Tan, wysokość 700 px, szerokość 25%

    Kolor Tan - beżowy z obrazu 2.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: Helvetica;
    }
    #title {
        background-color: ForestGreen;
        color: white;
        height: 100px;
        width: 80%;
        text-align: center;
    }
    #thumbnail {
        background-color: ForestGreen;
        height: 100px;
        width: 20%;
    }
    #left {
        background-color: Tan;
        height: 700px;
        width: 25%;
    }
    Podgląd strony po tym kroku (grzyby.php)
  22. Krok 22: Blok prawy: tło i wymiary

    Kryteria: ,

    R.4.4 Ustawiono wysokość 100px dla bloku tytułowego i bloku miniatury oraz 700px dla bloków lewego i prawego

    R.4.5 Ustawiono szerokość 20% dla bloku miniatury, 80% dla bloku tytułowego, 25% dla bloku lewego i 75% dla bloku prawego

    ok. 1 min
    Polecenie z arkusza

    Dla bloku prawego: kolor tła Tan, wysokość 700 px, szerokość 75%

    25% + 75% = 100%. Wysokości (R.4.4) i szerokości (R.4.5) czterech bloków są ustawione.

    Typowe błędy

    Bloki o sumie szerokości 100% nie mogą mieć marginesów, padding ani obramowań - każdy piksel więcej zepchnie drugi blok pod spód.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: Helvetica;
    }
    #title {
        background-color: ForestGreen;
        color: white;
        height: 100px;
        width: 80%;
        text-align: center;
    }
    #thumbnail {
        background-color: ForestGreen;
        height: 100px;
        width: 20%;
    }
    #left {
        background-color: Tan;
        height: 700px;
        width: 25%;
    }
    #right {
        background-color: Tan;
        height: 700px;
        width: 75%;
    }
    Podgląd strony po tym kroku (grzyby.php)
  23. Krok 23: Stopka: tło, kolor tekstu, wyśrodkowanie

    Kryteria: , ,

    R.4.2 Ustawiono kolor tła ForestGreen dla bloków tytułowego, miniatury i stopki oraz Tan dla bloków lewego i prawego

    R.4.3 Ustawiono biały kolor czcionki dla bloku tytułowego i stopki

    R.4.6 Ustawiono wyrównanie tekstu do środka dla bloku tytułowego i stopki

    ok. 1 min
    Polecenie z arkusza

    Dla stopki: kolor tła ForestGreen, biały kolor czcionki, wyrównanie tekstu do środka

    Kolory tła wszystkich bloków (R.4.2), biały tekst bloku tytułowego i stopki (R.4.3) oraz wyśrodkowanie tych dwóch bloków (R.4.6) są gotowe.

    styl.css CSS zmienione: 5 linii
    * {
        font-family: Helvetica;
    }
    #title {
        background-color: ForestGreen;
        color: white;
        height: 100px;
        width: 80%;
        text-align: center;
    }
    #thumbnail {
        background-color: ForestGreen;
        height: 100px;
        width: 20%;
    }
    #left {
        background-color: Tan;
        height: 700px;
        width: 25%;
    }
    #right {
        background-color: Tan;
        height: 700px;
        width: 75%;
    }
    footer {
        background-color: ForestGreen;
        color: white;
        text-align: center;
    }
    Podgląd strony po tym kroku (grzyby.php)
  24. Krok 24: Obrazy: marginesy, obramowanie i zaokrąglenie rogów

    Kryteria: , ,

    R.4.7 Ustawiono marginesy zewnętrzne (margin) 4px oraz wewnętrzne (padding) 5px dla selektora obrazu

    R.4.8 Ustawiono obramowanie 1px linią ciągłą o kolorze ForestGreen dla obrazów

    R.4.9 Ustawiono zaokrąglenie rogów 20px dla obrazów

    ok. 2 min
    Polecenie z arkusza

    Dla selektora obrazu: marginesy zewnętrzne 4 px, marginesy wewnętrzne 5 px, obramowanie 1 px linią ciągłą o kolorze ForestGreen, zaokrąglenie rogów 20 px

    „Selektor obrazu” to selektor znacznika img - reguła obejmie miniaturę i wszystkie zdjęcia, które wypisze skrypt 3:

    • margin: 4px - margines zewnętrzny, odstęp między zdjęciami,
    • padding: 5px - margines wewnętrzny, odstęp zdjęcia od obramowania (stąd jasny pasek tła między zdjęciem a ramką na obrazie 2) (R.4.7),
    • border: 1px solid ForestGreen - obramowanie (R.4.8),
    • border-radius: 20px - zaokrąglone rogi ramki (R.4.9).

    Miniatura ma 80 px, a z marginesami, padding i ramką zajmuje 80 + 2 × (4 + 5 + 1) = 100 px - dokładnie wysokość bloku miniatury.

    styl.css CSS zmienione: 6 linii
    * {
        font-family: Helvetica;
    }
    #title {
        background-color: ForestGreen;
        color: white;
        height: 100px;
        width: 80%;
        text-align: center;
    }
    #thumbnail {
        background-color: ForestGreen;
        height: 100px;
        width: 20%;
    }
    #left {
        background-color: Tan;
        height: 700px;
        width: 25%;
    }
    #right {
        background-color: Tan;
        height: 700px;
        width: 75%;
    }
    footer {
        background-color: ForestGreen;
        color: white;
        text-align: center;
    }
    img {
        margin: 4px;
        padding: 5px;
        border: 1px solid ForestGreen;
        border-radius: 20px;
    }
    Podgląd strony po tym kroku (grzyby.php)
  25. Krok 25: Obraz pod kursorem: obramowanie Maroon

    Kryteria:

    R.4.10 W momencie, gdy kursor znajdzie się na obrazie, jego kolor obramowania zmienia się na Maroon

    ok. 1 min
    Polecenie z arkusza

    W momencie, gdy kursor znajdzie się na obrazie, jego kolor obramowania zmienia się na Maroon

    Pseudoklasa :hover działa, gdy kursor jest nad elementem. Zmieniasz tylko kolor ramki - border-color - grubość i styl zostają z reguły img (R.4.10). Na obrazie 2 tak wygląda drugie zdjęcie.

    styl.css CSS zmienione: 3 linie
    * {
        font-family: Helvetica;
    }
    #title {
        background-color: ForestGreen;
        color: white;
        height: 100px;
        width: 80%;
        text-align: center;
    }
    #thumbnail {
        background-color: ForestGreen;
        height: 100px;
        width: 20%;
    }
    #left {
        background-color: Tan;
        height: 700px;
        width: 25%;
    }
    #right {
        background-color: Tan;
        height: 700px;
        width: 75%;
    }
    footer {
        background-color: ForestGreen;
        color: white;
        text-align: center;
    }
    img {
        margin: 4px;
        padding: 5px;
        border: 1px solid ForestGreen;
        border-radius: 20px;
    }
    img:hover {
        border-color: Maroon;
    }
    Podgląd strony po tym kroku (grzyby.php)
  26. Krok 26: Układ bloków zgodny z obrazem 2

    Kryteria:

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z Obrazem 2 w arkuszu egzaminacyjnym

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: na górze: tytułowy i miniatury, poniżej: lewy i prawy, poniżej stopka. Podział zrealizowany za pomocą znaczników sekcji tak, aby układ bloków strony po uruchomieniu w przeglądarce, był zgodny z Obrazem 2

    Wszystkie cztery sekcje dostają float: left. Opływanie układa je kolejno od lewej: tytułowy (80%) i miniatury (20%) wypełniają pierwszy rząd, więc lewy (25%) przechodzi do drugiego, a prawy (75%) staje obok niego. Stopka z clear: both zaczyna się pod blokami (R.3.1).

    Typowe błędy

    Bez clear: both stopka wsunie się pod pływające bloki i jej zielony pasek zniknie u góry strony.

    styl.css CSS zmienione: 6 linii
    * {
        font-family: Helvetica;
    }
    #title {
        background-color: ForestGreen;
        color: white;
        height: 100px;
        width: 80%;
        text-align: center;
    }
    #thumbnail {
        background-color: ForestGreen;
        height: 100px;
        width: 20%;
    }
    #left {
        background-color: Tan;
        height: 700px;
        width: 25%;
    }
    #right {
        background-color: Tan;
        height: 700px;
        width: 75%;
    }
    footer {
        background-color: ForestGreen;
        color: white;
        text-align: center;
    }
    img {
        margin: 4px;
        padding: 5px;
        border: 1px solid ForestGreen;
        border-radius: 20px;
    }
    img:hover {
        border-color: Maroon;
    }
    section {
        float: left;
    }
    footer {
        clear: both;
    }
    Podgląd strony po tym kroku (grzyby.php)
  27. Etap 4: Skrypty PHP

    Kroki 27-35 · po tym etapie masz 34 z 34 kryteriów (pkt)

    Krok 27: Miejsce skryptu 1 i połączenie z bazą

    Kryteria: ,

    R.5.1 Skrypt napisano w języku PHP i zawiera przynajmniej jedną zgodną ze składnią instrukcję

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

    ok. 3 min
    Polecenie z arkusza

    Napisane w języku PHP Skrypt łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie grzybobranie

    Skrypt 1 ma się wypisać pod nagłówkiem „Grzyby jadalne”, więc tam zaczynasz blok PHP, między <?php a ?> (R.5.1). Pierwsza instrukcja: mysqli_connect z tabeli 1 - kolejno serwer localhost, użytkownik root, puste hasło '' i baza grzybobranie (R.5.2). Połączenie trafia do zmiennej $connection; skrypty 2 i 3 skorzystają z tej samej zmiennej, bo PHP wykonuje bloki jednego pliku po kolei.

    Od tego kroku podgląd to wynik prawdziwego uruchomienia strony w PHP 8.2 - na razie wygląda jak wcześniej. Stronę sprawdzasz przez http://localhost/…, a nie otwierając plik z dysku.

    grzyby.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            </ul>
        </section>
        <section id="right">
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  28. Krok 28: Skrypt 1: zapytanie 1

    Kryteria:

    R.5.4 Skrypt 1 wysyła do bazy danych zapytanie 1 lub skrypt 2 wysyła zapytanie 2 lub skrypt 3 wysyła zapytanie 3

    ok. 1 min
    Polecenie z arkusza

    Działanie skryptu 1: Wysyła do bazy zapytanie 1

    mysqli_query wysyła zapytanie 1 z kwerendy.txt bez zmian; wynik trafia do $result - jeszcze nic nie jest wypisane (R.5.4).

    grzyby.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            $result = mysqli_query($connection, 'SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;');
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            </ul>
        </section>
        <section id="right">
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  29. Krok 29: Skrypt 1: każdy grzyb w osobnym akapicie

    Kryteria:

    R.5.5 Skrypt 1 wyświetla wszystkie pobrane z bazy danych wiersze w osobnych akapitach w formacie: <id>. <nazwa> (<potoczna>), gdzie pola zapisane w <> są pobrane z bazy danych

    ok. 3 min
    Polecenie z arkusza

    Kolejne wiersze są wyświetlane w osobnych akapitach w formacie: <id>. <nazwa> (<potoczna>), gdzie pola zapisane w <> są pobrane z bazy danych

    • mysqli_fetch_row($result) zwraca kolejny wiersz jako tablicę numerowaną od zera: $row[0] to id, $row[1] - nazwa, $row[2] - potoczna (kolejność z SELECT). Gdy wierszy zabraknie, zwraca false i pętla while się kończy,
    • echo "<p>$row[0]. $row[1] ($row[2])</p>"; - każdy wiersz w osobnym akapicie: numer, kropka, nazwa, a nazwa potoczna w nawiasie (R.5.5). W cudzysłowie "…" PHP podstawia wartości zmiennych.

    Podgląd: siedem akapitów, od 1. pieczarka dwuzarodnikowa (pieczarka) do 8. trufla letnia (trufla).

    grzyby.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            $result = mysqli_query($connection, 'SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<p>$row[0]. $row[1] ($row[2])</p>";
            }
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            </ul>
        </section>
        <section id="right">
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  30. Krok 30: Skrypt 2: zapytanie 2 wewnątrz listy

    ok. 1 min
    Polecenie z arkusza

    Działanie skryptu 2: Wysyła do bazy zapytanie 2

    Blok PHP skryptu 2 stoi wewnątrz <ul> - to, co wypisze, znajdzie się na liście. Zapytanie 2 z kwerendy.txt bez zmian, przez to samo połączenie $connection.

    grzyby.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            $result = mysqli_query($connection, 'SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<p>$row[0]. $row[1] ($row[2])</p>";
            }
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            <?php
            $result = mysqli_query($connection, 'SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;');
            ?>
            </ul>
        </section>
        <section id="right">
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  31. Krok 31: Skrypt 2: każdy wiersz jako element listy

    Kryteria:

    R.5.6 Skrypt 2 wyświetla każdy wiersz jako element listy, w formacie: <potoczna>, rodzina: <nazwa rodziny>, gdzie pola zapisane w <> są pobrane z bazy danych

    ok. 2 min
    Polecenie z arkusza

    wyświetla każdy wiersz jako elementy listy, w formacie: <potoczna>, rodzina: <nazwa rodziny>, gdzie pola zapisane w <> są pobrane z bazy danych

    Ta sama pętla co w skrypcie 1, ale każdy wiersz w znaczniku <li> - element listy punktowanej. Między polami stały tekst „, rodzina: ” (R.5.6). Podgląd: prawdziwek, rodzina: borowikowate i podgrzybek, rodzina: borowikowate.

    grzyby.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            $result = mysqli_query($connection, 'SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<p>$row[0]. $row[1] ($row[2])</p>";
            }
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            <?php
            $result = mysqli_query($connection, 'SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<li>$row[0], rodzina: $row[1]</li>";
            }
            ?>
            </ul>
        </section>
        <section id="right">
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  32. Krok 32: Skrypt 3: zapytanie 3 w bloku prawym

    ok. 1 min
    Polecenie z arkusza

    Działanie skryptu 3: Wysyła do bazy zapytanie 3

    Blok prawy zawiera tylko efekt skryptu 3. Zapytanie 3 bez zmian - dwie kolumny wszystkich grzybów.

    grzyby.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            $result = mysqli_query($connection, 'SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<p>$row[0]. $row[1] ($row[2])</p>";
            }
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            <?php
            $result = mysqli_query($connection, 'SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<li>$row[0], rodzina: $row[1]</li>";
            }
            ?>
            </ul>
        </section>
        <section id="right">
            <?php
            $result = mysqli_query($connection, 'SELECT nazwa_pliku, nazwa FROM grzyby;');
            ?>
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  33. Krok 33: Skrypt 3: zdjęcie dla każdego wiersza

    Kryteria:

    R.5.7 Skrypt 3 wyświetla obrazy, dla których nazwa pliku jest pobrana z bazy. Liczba obrazów jest równa liczbie zwróconych zapytaniem wierszy

    ok. 3 min
    Polecenie z arkusza

    Dla każdego wiersza wartości zwrócone zapytaniem: nazwa pliku i nazwa wykorzystane są do wyświetlenia obrazu, w ten sposób, że nazwa pliku jest źródłem obrazu

    Dla każdego wiersza echo wypisuje znacznik <img>: nazwa pliku $row[0] trafia do atrybutu src - pliki zdjęć leżą w folderze strony, więc sama nazwa wystarcza. Cudzysłowy wokół wartości atrybutów muszą być poprzedzone ukośnikiem \", bo cały napis echo też jest w cudzysłowie. Tekst alternatywny alt z nazwą grzyba przyda się czytnikom ekranu (arkusz go nie wymaga).

    Podgląd: osiem zdjęć - tyle, ile wierszy zwróciło zapytanie (R.5.7). Mają już ramki i zaokrąglone rogi z reguły img.

    Typowe błędy

    echo "<img src="$row[0]">"; bez ukośników przed cudzysłowami kończy napis w połowie - PHP zgłosi błąd składni i strona się nie wyświetli.

    grzyby.php PHP zmienione: 3 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            $result = mysqli_query($connection, 'SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<p>$row[0]. $row[1] ($row[2])</p>";
            }
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            <?php
            $result = mysqli_query($connection, 'SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<li>$row[0], rodzina: $row[1]</li>";
            }
            ?>
            </ul>
        </section>
        <section id="right">
            <?php
            $result = mysqli_query($connection, 'SELECT nazwa_pliku, nazwa FROM grzyby;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<img src=\"$row[0]\" alt=\"$row[1]\">";
            }
            ?>
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  34. Krok 34: Skrypt 3: nazwa grzyba w dymku

    Kryteria:

    R.5.8 W skrypcie 3 zwrócona zapytaniem nazwa jest zapisana jako atrybut title obrazu

    ok. 1 min
    Polecenie z arkusza

    a nazwa jest tytułem obrazu wyświetlanym jako dymek, gdy kursor myszy znajdzie się na elemencie (patrz obraz 2).

    Atrybut title - przeglądarka pokazuje jego treść w dymku po zatrzymaniu kursora nad elementem (R.5.8). Na obrazie 2 kursor stoi nad drugim zdjęciem i widać dymek „czubajka kania”.

    grzyby.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            $result = mysqli_query($connection, 'SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<p>$row[0]. $row[1] ($row[2])</p>";
            }
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            <?php
            $result = mysqli_query($connection, 'SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<li>$row[0], rodzina: $row[1]</li>";
            }
            ?>
            </ul>
        </section>
        <section id="right">
            <?php
            $result = mysqli_query($connection, 'SELECT nazwa_pliku, nazwa FROM grzyby;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<img src=\"$row[0]\" alt=\"$row[1]\" title=\"$row[1]\">";
            }
            ?>
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  35. Krok 35: Zamknięcie połączenia z bazą

    Kryteria:

    R.5.3 Po wykonaniu operacji na bazie zastosowano funkcję zamknięcia bazy

    ok. 1 min
    Polecenie z arkusza

    Na końcu działania skrypt zamyka połączenie z serwerem

    mysqli_close($connection) na końcu skryptu 3 - ostatniego miejsca, w którym strona korzysta z bazy (R.5.3). Strona spełnia wszystkie kryteria arkusza; podgląd pokazuje stan z obrazu 2. Dalej trzy kroki o zabezpieczeniach - klucz ich nie wymaga.

    Typowe błędy

    mysqli_close na końcu skryptu 1 zamknie połączenie, zanim skrypty 2 i 3 wyślą swoje zapytania - PHP 8 zgłosi błąd „mysqli object is already closed”.

    grzyby.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            $result = mysqli_query($connection, 'SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<p>$row[0]. $row[1] ($row[2])</p>";
            }
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            <?php
            $result = mysqli_query($connection, 'SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<li>$row[0], rodzina: $row[1]</li>";
            }
            ?>
            </ul>
        </section>
        <section id="right">
            <?php
            $result = mysqli_query($connection, 'SELECT nazwa_pliku, nazwa FROM grzyby;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<img src=\"$row[0]\" alt=\"$row[1]\" title=\"$row[1]\">";
            }
            mysqli_close($connection);
            ?>
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  36. Etap 5: Dobre praktyki (poza kluczem)

    Kroki 36-38 · po tym etapie masz 34 z 34 kryteriów (pkt)

    Krok 36: Zabezpieczenie: htmlspecialchars w atrybutach obrazu

    ok. 4 min
    Polecenie z arkusza

    Dla każdego wiersza wartości zwrócone zapytaniem: nazwa pliku i nazwa wykorzystane są do wyświetlenia obrazu, w ten sposób, że nazwa pliku jest źródłem obrazu, a nazwa jest tytułem obrazu wyświetlanym jako dymek, gdy kursor myszy znajdzie się na elemencie (patrz obraz 2).

    Skrypty wklejają tekst z bazy prosto do HTML-a. Sprawdzone na wersji z poprzednich kroków: wystarczy, że ktoś (np. przez panel administracyjny albo inny formularz) zapisze w tabeli nazwę grzyba z cudzysłowem, np. kania" onmouseover="…". Cudzysłów kończy wartość atrybutu title, a reszta napisu staje się nowym atrybutem - kodem JavaScript, który przeglądarka wykona u każdego, kto najedzie kursorem na zdjęcie (atak XSS). W teście ten kod podmieniał całą stronę na napis „Atak XSS”.

    Lekarstwo: htmlspecialchars($tekst, ENT_QUOTES) zamienia <, >, & oraz oba rodzaje cudzysłowów na encje (&quot; …). Przeglądarka pokaże je jako zwykłe znaki w dymku i atrybut się nie zamknie. Podgląd: ta sama zmieniona nazwa w bazie - w dymku drugiego zdjęcia widać cały napis, a kod się nie wykonuje. Stara linia zostaje w komentarzu. W bloku lewym widać jeszcze skutek tej samej nazwy w akapicie skryptu 1: fragment <h1>Atak XSS</h1> stał się prawdziwym nagłówkiem - to zabezpiecza następny krok.

    Typowe błędy

    Bez ENT_QUOTES starsze wersje PHP (przed 8.1) nie zamieniały apostrofu - atrybut w apostrofach (title='…') dało się zamknąć tak samo.

    grzyby.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            $result = mysqli_query($connection, 'SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<p>$row[0]. $row[1] ($row[2])</p>";
            }
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            <?php
            $result = mysqli_query($connection, 'SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;');
            while ($row = mysqli_fetch_row($result)) {
                echo "<li>$row[0], rodzina: $row[1]</li>";
            }
            ?>
            </ul>
        </section>
        <section id="right">
            <?php
            $result = mysqli_query($connection, 'SELECT nazwa_pliku, nazwa FROM grzyby;');
            while ($row = mysqli_fetch_row($result)) {
                // echo "<img src=\"$row[0]\" alt=\"$row[1]\" title=\"$row[1]\">";
                $file = htmlspecialchars($row[0], ENT_QUOTES);
                $name = htmlspecialchars($row[1], ENT_QUOTES);
                echo "<img src=\"$file\" alt=\"$name\" title=\"$name\">";
            }
            mysqli_close($connection);
            ?>
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  37. Krok 37: Zabezpieczenie: htmlspecialchars w skryptach 1 i 2

    ok. 2 min
    Polecenie z arkusza

    Kolejne wiersze są wyświetlane w osobnych akapitach w formacie: <id>. <nazwa> (<potoczna>), gdzie pola zapisane w <> są pobrane z bazy danych

    Ten sam problem dotyczy akapitów skryptu 1 i elementów listy skryptu 2: nazwa ze znacznikiem <script> zostałaby wykonana. Zasada jest jedna - każdy tekst z bazy zabezpieczasz w chwili wypisywania, niezależnie od tego, gdzie trafia. id jest liczbą z bazy i tego nie potrzebuje.

    Napisy są teraz sklejane kropką (.) zamiast podstawiane w cudzysłowie - wywołania funkcji nie da się wstawić do napisu "…". Podgląd: zmieniona nazwa w akapicie 2 wyświetla się dosłownie, z cudzysłowami.

    grzyby.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            $result = mysqli_query($connection, 'SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;');
            while ($row = mysqli_fetch_row($result)) {
                // echo "<p>$row[0]. $row[1] ($row[2])</p>";
                echo '<p>' . $row[0] . '. ' . htmlspecialchars($row[1]) . ' (' . htmlspecialchars($row[2]) . ')</p>';
            }
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            <?php
            $result = mysqli_query($connection, 'SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;');
            while ($row = mysqli_fetch_row($result)) {
                // echo "<li>$row[0], rodzina: $row[1]</li>";
                echo '<li>' . htmlspecialchars($row[0]) . ', rodzina: ' . htmlspecialchars($row[1]) . '</li>';
            }
            ?>
            </ul>
        </section>
        <section id="right">
            <?php
            $result = mysqli_query($connection, 'SELECT nazwa_pliku, nazwa FROM grzyby;');
            while ($row = mysqli_fetch_row($result)) {
                // echo "<img src=\"$row[0]\" alt=\"$row[1]\" title=\"$row[1]\">";
                $file = htmlspecialchars($row[0], ENT_QUOTES);
                $name = htmlspecialchars($row[1], ENT_QUOTES);
                echo "<img src=\"$file\" alt=\"$name\" title=\"$name\">";
            }
            mysqli_close($connection);
            ?>
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  38. Krok 38: Zabezpieczenie: bez komunikatów błędów dla odwiedzających

    ok. 3 min
    Polecenie z arkusza

    Skrypt łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie grzybobranie

    Sprawdzone na wersji podstawowej ze złym hasłem do bazy: PHP 8 rzuca wyjątek mysqli_sql_exception i strona kończy się komunikatem „Fatal error: Uncaught mysqli_sql_exception: Access denied for user 'root'@'localhost'” z pełną ścieżką pliku na serwerze i numerem linii. Taki komunikat podpowiada atakującemu nazwę użytkownika bazy, układ katalogów i miejsce w kodzie.

    Połączenie stoi teraz w bloku try, a catch (mysqli_sql_exception $exception) przechwytuje wyjątek i kończy stronę (exit) krótkim zdaniem „Baza grzybów jest chwilowo niedostępna.”. Szczegóły błędu zostają w zmiennej $exception - w prawdziwym serwisie zapisuje się je do dziennika na serwerze, a nie na stronę. Podgląd: zwykły stan strony (baza działa).

    grzyby.php PHP zmienione: 6 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Grzybobranie</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <section id="title">
            <h1>Czas na grzyby!</h1>
        </section>
        <section id="thumbnail">
            <a href="podgrzybek.jpg"><img src="podgrzybek-miniatura.jpg" alt="Grzybobranie"></a>
        </section>
        <section id="left">
            <h3>Grzyby jadalne</h3>
            <?php
            // $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            try {
                $connection = mysqli_connect('localhost', 'root', '', 'grzybobranie');
            } catch (mysqli_sql_exception $exception) {
                exit('<p>Baza grzybów jest chwilowo niedostępna.</p>');
            }
            $result = mysqli_query($connection, 'SELECT id, nazwa, potoczna FROM grzyby WHERE jadalny = 1;');
            while ($row = mysqli_fetch_row($result)) {
                // echo "<p>$row[0]. $row[1] ($row[2])</p>";
                echo '<p>' . $row[0] . '. ' . htmlspecialchars($row[1]) . ' (' . htmlspecialchars($row[2]) . ')</p>';
            }
            ?>
            <h3>Polecamy do zup</h3>
            <ul>
            <?php
            $result = mysqli_query($connection, 'SELECT potoczna, rodzina.nazwa FROM grzyby JOIN rodzina ON grzyby.rodzina_id = rodzina.id WHERE potrawy_id = 4;');
            while ($row = mysqli_fetch_row($result)) {
                // echo "<li>$row[0], rodzina: $row[1]</li>";
                echo '<li>' . htmlspecialchars($row[0]) . ', rodzina: ' . htmlspecialchars($row[1]) . '</li>';
            }
            ?>
            </ul>
        </section>
        <section id="right">
            <?php
            $result = mysqli_query($connection, 'SELECT nazwa_pliku, nazwa FROM grzyby;');
            while ($row = mysqli_fetch_row($result)) {
                // echo "<img src=\"$row[0]\" alt=\"$row[1]\" title=\"$row[1]\">";
                $file = htmlspecialchars($row[0], ENT_QUOTES);
                $name = htmlspecialchars($row[1], ENT_QUOTES);
                echo "<img src=\"$file\" alt=\"$name\" title=\"$name\">";
            }
            mysqli_close($connection);
            ?>
        </section>
        <footer>
            <p>Autor: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (grzyby.php)
  39. Etap 6: Oddanie pracy

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

    Krok 39: Zawartość folderu i nagranie płyty

    ok. 5 min
    Polecenie z arkusza

    UWAGA: katalog z rezultatami pracy oraz płytę należy opisać numerem, którym został podpisany arkusz, czyli numerem PESEL lub w przypadku jego braku numerem paszportu.

    W folderze z numerem zdającego muszą być: import.png, kw1.png - kw4.png, kwerendy.txt, grzyby.php, styl.css, wszystkie zdjęcia z archiwum oraz podgrzybek.jpg i podgrzybek-miniatura.jpg (plik SQL z archiwum może zostać). Strona korzysta z ośmiu zdjęć i miniatury - bez nich egzaminator zobaczy puste ramki. Nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego.

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

Podgląd

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

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

Potwierdzenie

Informacja