Przejdź do głównej treści
INF.03-04-24.06-SGCzerwiec 2024 · 39 kroków · ok. 78 min · 37 kryteriów z klucza

Rozwiązanie krok po kroku: Galeria zdjęć z bazą (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza galeria z czterema zapytaniami, dzięcioł przeskalowany do 520 × 346 px i strona galeria.php, na której zdjęcia z bazy pokazują tytuł, autora i odnośnik „Pobierz” po najechaniu kursorem - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • siatka CSS na całej stronie: grid-template-areas z kolumnami 15% 70% 15% (klucz R.4.4 przyjmuje je wprost) zamiast float i clear trzech bloków; bloki zdjęć zostają opływane (float), bo tak każe arkusz,
  • kolory w zmiennych CSS, przejście tylko dla przezroczystości (transition: opacity), opis zdjęcia widoczny także przy przechodzeniu klawiszem Tab (:focus-within),
  • karta zdjęcia jako <figure> z podpisem <figcaption> - znaczniki mówią, że tytuł, autor i odnośnik opisują to zdjęcie,
  • PHP na PDO nad HTML-em: oba zapytania wykonane i połączenie zamknięte, zanim zacznie się strona; szablon tylko wypisuje dane, kolumny po nazwach,
  • bezpieczeństwo i odporność: e() przy każdej wartości z bazy, rawurlencode() w adresie pliku, Skrypt 2 nie psuje strony, gdy żadne zdjęcie nie ma stu polubień, komunikat przy awarii bazy.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i zdjęcia pochodzą z prawdziwego archiwum pliki4, zapytania zostały wykonane na tej bazie w MariaDB z XAMPP-a, a podgląd strony od etapu PHP to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2.

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

  1. Etap 1: Baza danych

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

    Krok 1: Baza danych galeria

    ok. 2 min
    Polecenie z arkusza

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

    Archiwum pliki4.zip rozpakuj hasłem (G@LeriA) - razem z nawiasami okrągłymi - do folderu z numerem zdającego; arkusz zaleca, żeby zdjęcia leżały obok pliku strony. W phpMyAdmin Nowa → galeria, porównywanie utf8_unicode_ci, albo zapytaniem:

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

    Nazwa bazy musi być dokładnie galeria - skrypty łączą się z nią po nazwie (R.5.1).

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

    ok. 2 min
    Polecenie z arkusza

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

    Import → baza.sql. Dwie tabele z obrazu 1: autorzy (11 osób) i zdjecia (12 zdjęć z tytułem, nazwą pliku, liczbą polubień i kluczem obcym autorzy_id) - jeden autor, wiele zdjęć, czyli relacja 1..n.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

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

    ok. 2 min
    Polecenie z arkusza

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

    Cały ekran z paskiem zadań, zapis jako import.png. Uwaga na formaty: import w PNG, zrzuty zapytań w JPEG (kw1.jpg - kw4.jpg).

    Typowe błędy

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

  4. Krok 4: Zapytanie 1: zdjęcia z co najmniej 100 polubieniami

    Kryteria: ,

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

    R.1.3 Wykonano zapytanie 1 wybierające jedynie pola tytul i plik z tabeli zdjecia dla zdjęć z polubieniami większymi lub równymi 100. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT tytul, plik FROM zdjecia WHERE polubienia >= 100; oraz wynik działania udokumentowano zrzutem, na którym są widoczne dane: Slava Ukraini! kiev.jpg

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pola tytul i plik z tabeli zdjecia dla zdjęć z polubieniami większymi lub równymi 100

    „Większymi lub równymi” to >=. Wynik: jeden wiersz - Slava Ukraini! kiev.jpg, jak w kluczu. To zapytanie wyśle Skrypt 2. Zapytania trafiają do kwerendy.txt, zrzut do kw1.jpg.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT tytul, plik
    FROM zdjecia
    WHERE polubienia >= 100;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: zdjęcia z autorami według nazwiska

    Kryteria:

    R.1.4 Wykonano zapytanie 2 wybierające jedynie pola plik, tytul, polubienia z tabeli zdjecia oraz odpowiadające im pola imie i nazwisko z tabeli autorzy posortowane rosnąco według nazwiska. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT plik, tytul, polubienia, imie, nazwisko FROM zdjecia JOIN autorzy ON autorzy_id = autorzy.id ORDER BY nazwisko; (możliwe również INNER JOIN lub bez JOIN i porównanie kluczy po WHERE, opcjonalnie ASC przy sortowaniu) oraz wynik działania udokumentowano zrzutem, na którym jest widocznych dokładnie 12 rekordów, jedynie pola plik, tytul, polubienia, imie, nazwisko, wyniki są posortowane wg. nazwiska

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pola plik, tytul, polubienia z tabeli zdjecia oraz odpowiadające im pola imie i nazwisko z tabeli autorzy posortowane rosnąco według nazwiska. Należy posłużyć się relacją

    • aliasy tabel z (zdjęcia) i a (autorzy) - przy każdej kolumnie widać, skąd pochodzi,
    • JOIN autorzy AS a ON a.id = z.autorzy_id - relacja z obrazu 1: klucz obcy zdjęcia wskazuje autora,
    • ORDER BY a.nazwisko - rosnąco, bo to domyślny kierunek.

    Wynik: 12 wierszy, od trzech zdjęć Jolanty Białej. Kolejność zdjęć tej samej autorki nie jest ustalona - gwarantuje ją tylko ORDER BY; drugi klucz sortowania (np. , z.tytul) by ją ustalił, ale zmieniłby układ galerii z obrazu 2. Zrzut: kw2.jpg. To zapytanie wyśle Skrypt 1.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT tytul, plik
    FROM zdjecia
    WHERE polubienia >= 100;
    
    -- Zapytanie 2
    SELECT z.plik, z.tytul, z.polubienia, a.imie, a.nazwisko
    FROM zdjecia AS z
    JOIN autorzy AS a ON a.id = z.autorzy_id
    ORDER BY a.nazwisko;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: liczba zdjęć dla każdego imienia

    Kryteria:

    R.1.5 Wykonano zapytanie 3 wybierające jedynie pole imie oraz liczące ile jest zdjęć dla tego imienia. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT imie, COUNT(*) FROM zdjecia JOIN autorzy ON autorzy_id = autorzy.id GROUP BY imie; (lub dowolne pole zamiast *, możliwe również INNER JOIN lub bez JOIN i porównanie kluczy po WHERE) oraz wynik działania udokumentowano zrzutem, na którym są widoczne dane: Ewa 1; Ewelina 2; Jadwiga 4; Jan 2; Jolanta 3

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pole imie oraz liczące ile jest zdjęć dla tego imienia. Należy posłużyć się relacją

    Imię jest w tabeli autorów, zdjęcia w drugiej - stąd złączenie. GROUP BY a.imie tworzy grupę na imię, a COUNT(z.id) liczy zdjęcia w grupie (klucz dopuszcza dowolne pole zamiast *); alias liczba_zdjec nazywa kolumnę wyniku. Wynik: Ewa 1, Ewelina 2, Jadwiga 4, Jan 2, Jolanta 3 - autorzy bez zdjęć nie mają pary w złączeniu i nie trafiają do wyniku. Zrzut: kw3.jpg.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT tytul, plik
    FROM zdjecia
    WHERE polubienia >= 100;
    
    -- Zapytanie 2
    SELECT z.plik, z.tytul, z.polubienia, a.imie, a.nazwisko
    FROM zdjecia AS z
    JOIN autorzy AS a ON a.id = z.autorzy_id
    ORDER BY a.nazwisko;
    
    -- Zapytanie 3
    SELECT a.imie, COUNT(z.id) AS liczba_zdjec
    FROM autorzy AS a
    JOIN zdjecia AS z ON z.autorzy_id = a.id
    GROUP BY a.imie;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: nowa kolumna rozmiarPliku

    Kryteria:

    R.1.6 Wykonano zapytanie 4 modyfikujące strukturę tabeli zdjecia. Dodana jest kolumna rozmiarPliku typu całkowitego. W pliku z kwerendami lub na zrzucie istnieje zapis np.: ALTER TABLE zdjecia ADD COLUMN rozmiarPliku INT; (słowo COLUMN jest opcjonalne, dowolny typ całkowity) oraz wynik działania udokumentowano zrzutem, na którym jest widoczny komunikat o poprawnym wykonaniu zapytania lub struktura tabeli z dodanym polem rozmiarPliku typu całkowitego

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: modyfikujące strukturę tabeli zdjecia. Dodana jest kolumna rozmiarPliku typu całkowitego

    Strukturę zmienia ALTER TABLE ... ADD COLUMN. Typ INT UNSIGNED - całkowity bez liczb ujemnych, bo rozmiar pliku nie może być ujemny (klucz przyjmuje dowolny typ całkowity). W istniejących wierszach nowa kolumna ma NULL. Zrzut kw4.jpg: komunikat o wykonaniu albo struktura tabeli z rozmiarPliku int(10) unsigned. Skrypty wybierają konkretne kolumny, więc nowe pole im nie przeszkadza.

    Typowe błędy

    Drugie wykonanie kończy się błędem „Duplicate column name” - kolumna już istnieje.

    kwerendy.txt Tekst zmienione: 4 linie
    -- Zapytanie 1
    SELECT tytul, plik
    FROM zdjecia
    WHERE polubienia >= 100;
    
    -- Zapytanie 2
    SELECT z.plik, z.tytul, z.polubienia, a.imie, a.nazwisko
    FROM zdjecia AS z
    JOIN autorzy AS a ON a.id = z.autorzy_id
    ORDER BY a.nazwisko;
    
    -- Zapytanie 3
    SELECT a.imie, COUNT(z.id) AS liczba_zdjec
    FROM autorzy AS a
    JOIN zdjecia AS z ON z.autorzy_id = a.id
    GROUP BY a.imie;
    
    -- Zapytanie 4
    ALTER TABLE zdjecia
    ADD COLUMN rozmiarPliku INT UNSIGNED;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

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

    Krok 8: woodpecker.jpg: dokładnie 520 × 346 px

    Kryteria:

    R.2.1 Grafikę woodpecker.jpg przeskalowano do dokładnych wymiarów: szerokość 520 px i wysokość 346 px

    ok. 2 min
    Polecenie z arkusza

    Plik woodpecker.jpg, wypakowany z archiwum, należy przeskalować do dokładnych wymiarów szerokość 520 px i wysokość 346 px.

    Zdjęcie ma 640 × 428 px, pozostałe zdjęcia z archiwum - 520 × 346. W GIMP-ie Obraz → Skaluj obraz z rozłączonym łańcuchem proporcji i obiema wartościami: 520 i 346. Zamknięty łańcuch wyliczyłby wysokość 348 (inne proporcje), a arkusz wymaga dokładnych wymiarów. Eksport: Plik → Nadpisz woodpecker.jpg.

    Typowe błędy

    Zdjęcie 520 × 348 (z zamkniętym łańcuchem) nie spełnia warunku „dokładnych wymiarów”.

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

    Kryteria:

    R.2.2 Witrynę internetową zapisano w pliku galeria.php oraz zapisano jawnie standard kodowania polskich znaków i deklarację HTML5 <!DOCTYPE HTML> i zadeklarowano język witryny polski np.<HTML lang="pl">. Strona zawiera sekcje head i body

    ok. 2 min
    Polecenie z arkusza

    Składa się ze strony o nazwie galeria.php zapisanej w języku HTML5. Ustawiony język zawartości strony na polski. Jawnie zastosowany właściwy standard kodowania polskich znaków

    Plik galeria.php: <!DOCTYPE html>, <html lang="pl">, <meta charset="UTF-8"> jako pierwszy element <head> i sekcja <body> (R.2.2).

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

    Kryteria:

    R.2.3 Nadano tytuł stronie: "Galeria"

    ok. 1 min
    Polecenie z arkusza

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

    <title> w <head> (R.2.3), obok viewport, żeby telefon nie pomniejszał strony.

    galeria.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  11. Krok 11: Podpięcie arkusza stylów

    Kryteria:

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

    ok. 1 min
    Polecenie z arkusza

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

    Nazwa styl.css, nie style.css. R.3.3 przepada też wtedy, gdy któryś selektor jest źle zapisany, np. kropka zamiast # przy identyfikatorze.

    galeria.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  12. Krok 12: Bloki semantyczne

    Kryteria:

    R.2.4 Układ strony zdefiniowano dzieląc ją na bloki: baner, lewy, środkowy, prawy oraz stopkę. Zastosowano semantyczne znaczniki sekcji HTML5 zgodnie z ich znaczeniem, w tym do części nagłówkowej - header, stopki - footer, dopuszcza się zagnieżdżenie bloków div w znacznikach semantycznych

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: baner, poniżej trzy bloki: lewy, środkowy i prawy, na dole blok stopki. Podział zrealizowany wyłącznie za pomocą semantycznych znaczników sekcji języka HTML5 tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z obrazem 2

    Baner <header>, stopka <footer> (klucz R.2.4 wymienia je wprost). Galeria to główna treść - <main>. Bloki boczne niosą treść dodatkową, więc oba to <aside>, a identyfikatory nazywają je od treści, nie od położenia: topics (tematy) i favourite (najbardziej lubiane) - po przestawieniu bloków nazwy nadal będą prawdziwe.

    galeria.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
        </header>
        <aside id="topics">
        </aside>
        <main>
        </main>
        <aside id="favourite">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  13. Krok 13: Baner: nagłówek pierwszego stopnia

    ok. 1 min
    Polecenie z arkusza

    Zawartość banera: nagłówek pierwszego stopnia o treści „Zdjęcia”

    <h1> w banerze.

    galeria.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
        </aside>
        <main>
        </main>
        <aside id="favourite">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  14. Krok 14: Blok lewy: nagłówek i lista numerowana

    Kryteria:

    R.2.6 W bloku lewym umieszczono jedną listę numerowaną z pięcioma elementami, zastosowano znaczniki <ol> i <li>

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku lewego: Nagłówek drugiego stopnia o treści: „Tematy zdjęć”. Lista numerowana (uporządkowana) z elementami: Zwierzęta, Krajobrazy, Miasta, Przyroda, Samochody

    <h2> i lista uporządkowana <ol> z pięcioma <li> - numery doda przeglądarka (R.2.6).

    galeria.php PHP zmienione: 8 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
        </main>
        <aside id="favourite">
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  15. Krok 15: Blok prawy: nagłówek i napis o dużym znaczeniu

    Kryteria:

    R.2.9 W bloku prawym umieszczono napis o treści „Zobacz wszystkie nasze zdjęcia” zapisany w znaczniku semantycznym pisanym czcionką pogrubioną i oznaczającym tekst o dużym znaczeniu - znacznik <strong>

    ok. 2 min
    Polecenie z arkusza

    Zawartość bloku prawego: Nagłówek drugiego stopnia o treści: „Najbardziej lubiane”. Efekt działania skryptu 2. Napis o treści „Zobacz wszystkie nasze zdjęcia” zapisany w znaczniku semantycznym pisanym czcionką pogrubioną i oznaczającym tekst o dużym znaczeniu

    Tekst o dużym znaczeniu to <strong> (<b> też pogrubia, ale nic nie znaczy) - R.2.9. Między nagłówkiem a napisem stanie zdjęcie ze Skryptu 2. Blok środkowy w całości wypełni Skrypt 1.

    galeria.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
        </main>
        <aside id="favourite">
            <h2>Najbardziej lubiane</h2>
            <strong>Zobacz wszystkie nasze zdjęcia</strong>
        </aside>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  16. Krok 16: Stopka z autorem

    Kryteria:

    R.2.5 W banerze zapisano nagłówek h1: "Zdjęcia" , w bloku lewym h2: "Tematy zdjęć" , w bloku prawym h2: "Najbardziej lubiane" , w stopce h5 z numerem zdającego

    ok. 1 min
    Polecenie z arkusza

    Zawartość stopki: nagłówek piątego stopnia o treści „Stronę wykonał: ”, dalej wstawiony numer zdającego

    Nagłówek <h5>. Na egzaminie po „Stronę wykonał:” wpisujesz swój numer zdającego (PESEL); tu stoi autor rozwiązania, Bartosz Bryniarski. Strona ma już wszystkie nagłówki z klucza (R.2.5).

    galeria.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
        </main>
        <aside id="favourite">
            <h2>Najbardziej lubiane</h2>
            <strong>Zobacz wszystkie nasze zdjęcia</strong>
        </aside>
        <footer>
            <h5>Stronę wykonał: Bartosz Bryniarski</h5>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  17. Etap 3: Styl CSS

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

    Krok 17: Plik styl.css: zmienne i krój Helvetica

    Kryteria:

    R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (dla *): krój czcionki Helvetica

    ok. 2 min
    Polecenie z arkusza

    Domyślne formatowanie wszystkich selektorów: krój czcionki Helvetica

    Sienna występuje w banerze, stopce i odnośnikach, NavajoWhite w dwóch blokach - zapisane raz jako zmienne CSS na :root. Selektor * z Helvetiką (R.4.1); na Windows zwykle jej nie ma, więc dalej stoi Arial (taki krój widać na obrazie 2) i ogólny krój bezszeryfowy.

    styl.css CSS nowy plik
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    Podgląd strony po tym kroku (galeria.php)
  18. Krok 18: Układ bloków na siatce

    Kryteria:

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu. Nie należy brać pod uwagę bloków generowanych skryptem. (prawidłowo zastosowano właściwości CSS decydujące o układzie strony np. float i clear albo display flex albo grid)

    ok. 3 min
    Polecenie z arkusza

    Podział strony na bloki: baner, poniżej trzy bloki: lewy, środkowy i prawy, na dole blok stopki. Podział zrealizowany wyłącznie za pomocą semantycznych znaczników sekcji języka HTML5 tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z obrazem 2

    <body> staje się siatką. grid-template-columns: 15% 70% 15% to szerokości bloku lewego, środkowego i prawego z arkusza (klucz R.4.4 przyjmuje je w tej postaci), a grid-template-areas rysuje obraz 2 słowami: baner przez trzy kolumny, trzy bloki obok siebie, stopka przez trzy kolumny (R.3.1). Każdy blok trafia w swoje pole przez grid-area.

    Bez float stopka nie potrzebuje clear, a bloki nie „spadają” pod siebie, gdy suma szerokości z marginesami przekroczy 100%.

    styl.css CSS zmienione: 29 linii
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 15% 70% 15%;
        grid-template-areas:
            "header header    header"
            "topics main      favourite"
            "footer footer    footer";
    }
    
    header {
        grid-area: header;
    }
    
    #topics {
        grid-area: topics;
    }
    
    main {
        grid-area: main;
    }
    
    #favourite {
        grid-area: favourite;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (galeria.php)
  19. Krok 19: Baner i stopka: kolory, odstęp, wyśrodkowanie

    ok. 1 min
    Polecenie z arkusza

    Wspólne dla banera i stopki: kolor tła Sienna, biały kolor czcionki, marginesy wewnętrzne 10 px, wyrównanie tekstu do środka

    Jedna reguła dla header, footer - wspólne cechy zapisane raz, a położenie w siatce każdy blok ma w swojej regule.

    styl.css CSS zmienione: 8 linii
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 15% 70% 15%;
        grid-template-areas:
            "header header    header"
            "topics main      favourite"
            "footer footer    footer";
    }
    
    header,
    footer {
        background-color: var(--accent);
        color: var(--light-text);
        padding: 10px;
        text-align: center;
    }
    
    header {
        grid-area: header;
    }
    
    #topics {
        grid-area: topics;
    }
    
    main {
        grid-area: main;
    }
    
    #favourite {
        grid-area: favourite;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (galeria.php)
  20. Krok 20: Blok lewy i prawy: tło i wysokość

    ok. 1 min
    Polecenie z arkusza

    Wspólne dla bloku lewego i prawego: kolor tła NavajoWhite, szerokość 15%, wysokość 700 px

    Oba bloki boczne to <aside>, więc wspólne cechy łapie jeden selektor znacznika. Szerokość 15% dają kolumny siatki - width: 15% liczyłby się od kolumny i zwęził blok do 15% z 15%.

    Typowe błędy

    Procentowa szerokość elementu siatki liczy się od jego pola, nie od strony - width: 15% przy kolumnie 15% daje ok. 2% szerokości okna.

    styl.css CSS zmienione: 5 linii
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 15% 70% 15%;
        grid-template-areas:
            "header header    header"
            "topics main      favourite"
            "footer footer    footer";
    }
    
    header,
    footer {
        background-color: var(--accent);
        color: var(--light-text);
        padding: 10px;
        text-align: center;
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        background-color: var(--side-bg);
        height: 700px;
    }
    
    #topics {
        grid-area: topics;
    }
    
    main {
        grid-area: main;
    }
    
    #favourite {
        grid-area: favourite;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (galeria.php)
  21. Krok 21: Blok środkowy: wysokość i paski przewijania

    Kryteria: ,

    R.4.3 Ustawiono wysokość bloków lewego, środkowego i prawego na 700px

    R.4.5 Ustawiono wyrównanie tekstu do środka dla banera i stopki oraz zawsze widoczne paski przewijania (overflow: scroll;) dla bloku środkowego

    ok. 2 min
    Polecenie z arkusza

    Dla bloku środkowego: szerokość 70%, wysokość 700 px, zawsze widoczne paski przewijania

    „Zawsze widoczne” paski to overflow: scroll - rysowane od początku, nawet gdy treść się mieści (auto pokazałoby je tylko w razie potrzeby). Dwanaście zdjęć i tak nie zmieści się w 700 px. Wysokości trzech bloków są komplet (R.4.3), wyśrodkowanie banera i stopki razem z paskami tego bloku to R.4.5. Szerokość 70% - środkowa kolumna siatki.

    styl.css CSS zmienione: 2 linie
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 15% 70% 15%;
        grid-template-areas:
            "header header    header"
            "topics main      favourite"
            "footer footer    footer";
    }
    
    header,
    footer {
        background-color: var(--accent);
        color: var(--light-text);
        padding: 10px;
        text-align: center;
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        background-color: var(--side-bg);
        height: 700px;
    }
    
    #topics {
        grid-area: topics;
    }
    
    main {
        grid-area: main;
        height: 700px;
        overflow: scroll;
    }
    
    #favourite {
        grid-area: favourite;
    }
    
    footer {
        grid-area: footer;
    }
    Podgląd strony po tym kroku (galeria.php)
  22. Krok 22: Obrazy: szerokość 100%

    Kryteria:

    R.4.4 Ustawiono szerokość bloku lewego i prawego 15%, środkowego 70% (width lub grid- template-columns lub flex), selektora img 100%

    ok. 1 min
    Polecenie z arkusza

    Dla selektora obrazu: szerokość 100%

    Zgodnie z uwagą arkusza selektor to sam znacznik img (klucz nie uzna klasy). Obraz zajmie całą szerokość rodzica: karty zdjęcia w galerii albo bloku prawego. Szerokości są komplet: kolumny 15% 70% 15% i img 100% (R.4.4).

    styl.css CSS zmienione: 4 linie
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 15% 70% 15%;
        grid-template-areas:
            "header header    header"
            "topics main      favourite"
            "footer footer    footer";
    }
    
    header,
    footer {
        background-color: var(--accent);
        color: var(--light-text);
        padding: 10px;
        text-align: center;
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        background-color: var(--side-bg);
        height: 700px;
    }
    
    #topics {
        grid-area: topics;
    }
    
    main {
        grid-area: main;
        height: 700px;
        overflow: scroll;
    }
    
    #favourite {
        grid-area: favourite;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 100%;
    }
    Podgląd strony po tym kroku (galeria.php)
  23. Krok 23: Karta zdjęcia: obok siebie, szerokość, marginesy, pozycja względna

    Kryteria:

    R.4.7 Ustawiono dla bloku generowanego skryptem 1: bloki ustawione obok siebie, szerokość 46%, marginesy zewnętrzne (margin) 2% (np. dla klasy, id jest błędem )

    ok. 2 min
    Polecenie z arkusza

    Dla bloku generowanego skryptem 1 i zawierającego zdjęcie i opis: bloki ustawione obok siebie (opływanie), szerokość 46%, marginesy zewnętrzne 2%, pozycjonowanie względne

    Karty zdjęć wypisze Skrypt 1 z klasą photo (klucz: klasa, identyfikator byłby błędem - kart jest dwanaście).

    • Tu zostaje float: left: arkusz mówi wprost „opływanie”. Siatka w <main> dałaby ten sam wygląd, ale nie jest opływaniem, a punkt R.4.7 ma pierwszeństwo,
    • 46% + 2 × 2% marginesu = 50% - dwie karty w rzędzie; margin: 2% zastępuje też domyślny margines znacznika <figure> (40 px z boków),
    • position: relative nie przesuwa karty, ale robi z niej punkt odniesienia dla opisu pozycjonowanego absolute (R.4.7).
    styl.css CSS zmienione: 7 linii
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 15% 70% 15%;
        grid-template-areas:
            "header header    header"
            "topics main      favourite"
            "footer footer    footer";
    }
    
    header,
    footer {
        background-color: var(--accent);
        color: var(--light-text);
        padding: 10px;
        text-align: center;
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        background-color: var(--side-bg);
        height: 700px;
    }
    
    #topics {
        grid-area: topics;
    }
    
    main {
        grid-area: main;
        height: 700px;
        overflow: scroll;
    }
    
    #favourite {
        grid-area: favourite;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 100%;
    }
    
    .photo {
        float: left;
        width: 46%;
        margin: 2%;
        position: relative;
    }
    Podgląd strony po tym kroku (galeria.php)
  24. Krok 24: Opis zdjęcia: niewidoczny i pozycjonowany na zdjęciu

    Kryteria:

    R.4.9 Ustawiono pełną przezroczystość dla nagłówka trzeciego stopnia, paragrafu i odnośnika (opacity: 0;)

    ok. 2 min
    Polecenie z arkusza

    Wspólne dla nagłówka trzeciego stopnia, paragrafu, odnośnika: pełna przezroczystość (0), pozycjonowanie pozwalające przesuwać element względem rodzica

    :is(h3, p, a) zbiera trzy selektory w jeden: .photo :is(h3, p, a) znaczy to samo co .photo h3, .photo p, .photo a. Przedrostek .photo ogranicza styl do kart - <strong> i nagłówki bloków bocznych zostają widoczne. opacity: 0 - element jest w pełni przezroczysty, ale istnieje i reaguje na kliknięcie (R.4.9). Przesuwanie „względem rodzica” to position: absolute z tabeli 2 arkusza - liczy się od najbliższego przodka z pozycją, czyli od karty .photo.

    styl.css CSS zmienione: 5 linii
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 15% 70% 15%;
        grid-template-areas:
            "header header    header"
            "topics main      favourite"
            "footer footer    footer";
    }
    
    header,
    footer {
        background-color: var(--accent);
        color: var(--light-text);
        padding: 10px;
        text-align: center;
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        background-color: var(--side-bg);
        height: 700px;
    }
    
    #topics {
        grid-area: topics;
    }
    
    main {
        grid-area: main;
        height: 700px;
        overflow: scroll;
    }
    
    #favourite {
        grid-area: favourite;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 100%;
    }
    
    .photo {
        float: left;
        width: 46%;
        margin: 2%;
        position: relative;
    }
    
    .photo :is(h3, p, a) {
        opacity: 0;
        position: absolute;
    }
    Podgląd strony po tym kroku (galeria.php)
  25. Krok 25: Tytuł: 5% od góry

    ok. 1 min
    Polecenie z arkusza

    Dodatkowo dla nagłówka trzeciego stopnia: odległość od górnej krawędzi rodzica 5%

    top przy pozycji absolute to odległość od górnej krawędzi karty; procent liczy się od jej wysokości.

    styl.css CSS zmienione: 4 linie
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 15% 70% 15%;
        grid-template-areas:
            "header header    header"
            "topics main      favourite"
            "footer footer    footer";
    }
    
    header,
    footer {
        background-color: var(--accent);
        color: var(--light-text);
        padding: 10px;
        text-align: center;
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        background-color: var(--side-bg);
        height: 700px;
    }
    
    #topics {
        grid-area: topics;
    }
    
    main {
        grid-area: main;
        height: 700px;
        overflow: scroll;
    }
    
    #favourite {
        grid-area: favourite;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 100%;
    }
    
    .photo {
        float: left;
        width: 46%;
        margin: 2%;
        position: relative;
    }
    
    .photo :is(h3, p, a) {
        opacity: 0;
        position: absolute;
    }
    
    .photo h3 {
        top: 5%;
    }
    Podgląd strony po tym kroku (galeria.php)
  26. Krok 26: Paragraf: 30% od góry

    ok. 1 min
    Polecenie z arkusza

    Dodatkowo dla paragrafu: odległość od górnej krawędzi rodzica 30%

    Autor stanie pod tytułem, mniej więcej w jednej trzeciej wysokości zdjęcia.

    styl.css CSS zmienione: 4 linie
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 15% 70% 15%;
        grid-template-areas:
            "header header    header"
            "topics main      favourite"
            "footer footer    footer";
    }
    
    header,
    footer {
        background-color: var(--accent);
        color: var(--light-text);
        padding: 10px;
        text-align: center;
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        background-color: var(--side-bg);
        height: 700px;
    }
    
    #topics {
        grid-area: topics;
    }
    
    main {
        grid-area: main;
        height: 700px;
        overflow: scroll;
    }
    
    #favourite {
        grid-area: favourite;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 100%;
    }
    
    .photo {
        float: left;
        width: 46%;
        margin: 2%;
        position: relative;
    }
    
    .photo :is(h3, p, a) {
        opacity: 0;
        position: absolute;
    }
    
    .photo h3 {
        top: 5%;
    }
    
    .photo p {
        top: 30%;
    }
    Podgląd strony po tym kroku (galeria.php)
  27. Krok 27: Odnośnik: położenie, tło i odstęp

    Kryteria: , ,

    R.4.2 Ustawiono kolor tła Sienna i biały kolor czcionki dla banera i stopki, kolor tła: Navaj oWhite dla bloku lewego i prawego oraz kolor tła Sienna dla odnośnika

    R.4.6 Ustawiono marginesy wewnętrzne (padding) 10px dla banera i stopki, 15px dla odnośnika

    R.4.8 Ustawiono cechy pozycjonowania (position): relative dla bloku generowanego skryptem 1, absolute dla nagłówka trzeciego stopnia, paragrafu i odnośnika oraz odległości od górnej krawędzi (top) 5% dla nagłówka trzeciego stopnia, 30% dla paragrafu, 70% dla odnośnika oraz od lewej krawędzi (left) 70% dla odnośnika (kryterium należy uznać za spełnione, gdy poprawnie zdefiniowano formatowanie dla co najmniej 4 z 6 elementów)

    ok. 2 min
    Polecenie z arkusza

    Dodatkowo dla odnośnika: odległość od górnej krawędzi rodzica 70%, od lewej krawędzi 70%, kolor tła Sienna, marginesy wewnętrzne 15 px

    top i left stawiają odnośnik w prawym dolnym rogu zdjęcia, a tło Sienna z odstępem 15 px robi z niego przycisk (obraz 3). Pozycje opisu są komplet (R.4.8), tak samo kolory tła (R.4.2) i marginesy wewnętrzne (R.4.6).

    styl.css CSS zmienione: 7 linii
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 15% 70% 15%;
        grid-template-areas:
            "header header    header"
            "topics main      favourite"
            "footer footer    footer";
    }
    
    header,
    footer {
        background-color: var(--accent);
        color: var(--light-text);
        padding: 10px;
        text-align: center;
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        background-color: var(--side-bg);
        height: 700px;
    }
    
    #topics {
        grid-area: topics;
    }
    
    main {
        grid-area: main;
        height: 700px;
        overflow: scroll;
    }
    
    #favourite {
        grid-area: favourite;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 100%;
    }
    
    .photo {
        float: left;
        width: 46%;
        margin: 2%;
        position: relative;
    }
    
    .photo :is(h3, p, a) {
        opacity: 0;
        position: absolute;
    }
    
    .photo h3 {
        top: 5%;
    }
    
    .photo p {
        top: 30%;
    }
    
    .photo a {
        top: 70%;
        left: 70%;
        background-color: var(--accent);
        padding: 15px;
    }
    Podgląd strony po tym kroku (galeria.php)
  28. Krok 28: Karta pod kursorem: zdjęcie blednie, opis się pojawia

    Kryteria:

    R.4.10 W momencie, gdy kursor myszy znajdzie się na bloku generowanym skryptem 1: przezroczystość dla zdjęcia w tym bloku zmienia się na 0.3, przezroczystość dla nagłówka trzeciego stopnia, paragrafu i odnośnika zmienia się na 1, dla zdjęcia zdefiniowano płynne przejście transition: 0.5s ease

    ok. 3 min
    Polecenie z arkusza

    W momencie, gdy kursor myszy znajdzie się na bloku generowanym skryptem 1: Zdjęcia z bloku środkowego przyjmują wartość przezroczystości 0.3 i jest ona zmieniana płynnie przez 0.5 sekundy efektem ease. Nagłówka trzeciego stopnia: brak przezroczystości (1). Paragrafu: brak przezroczystości. Odnośnika: brak przezroczystości

    • .photo:hover img - obraz w karcie, nad którą stoi kursor: przezroczystość 0.3; opis w tej karcie dostaje 1 (R.4.10),
    • transition: opacity 0.5s ease stoi w zwykłej regule obrazu, więc zdjęcie blednie przez pół sekundy i tak samo wraca. Nazwa właściwości w transition mówi, co ma się zmieniać płynnie - bez niej przejście dostałaby każda zmiana obrazu, także szerokość,
    • :focus-within - ten sam efekt, gdy fokus jest wewnątrz karty: kto przechodzi po stronie klawiszem Tab, po dojściu do „Pobierz” widzi tytuł, autora i odnośnik. Arkusz tego nie wymaga, a bez tego odnośnik byłby niewidoczny dla osoby bez myszy.
    styl.css CSS zmienione: 14 linii
    :root {
        --accent: Sienna;
        --side-bg: NavajoWhite;
        --light-text: white;
    }
    
    * {
        font-family: Helvetica, Arial, sans-serif;
    }
    
    body {
        display: grid;
        grid-template-columns: 15% 70% 15%;
        grid-template-areas:
            "header header    header"
            "topics main      favourite"
            "footer footer    footer";
    }
    
    header,
    footer {
        background-color: var(--accent);
        color: var(--light-text);
        padding: 10px;
        text-align: center;
    }
    
    header {
        grid-area: header;
    }
    
    aside {
        background-color: var(--side-bg);
        height: 700px;
    }
    
    #topics {
        grid-area: topics;
    }
    
    main {
        grid-area: main;
        height: 700px;
        overflow: scroll;
    }
    
    #favourite {
        grid-area: favourite;
    }
    
    footer {
        grid-area: footer;
    }
    
    img {
        width: 100%;
    }
    
    .photo {
        float: left;
        width: 46%;
        margin: 2%;
        position: relative;
    }
    
    .photo :is(h3, p, a) {
        opacity: 0;
        position: absolute;
    }
    
    .photo h3 {
        top: 5%;
    }
    
    .photo p {
        top: 30%;
    }
    
    .photo a {
        top: 70%;
        left: 70%;
        background-color: var(--accent);
        padding: 15px;
    }
    
    .photo img {
        transition: opacity 0.5s ease;
    }
    
    .photo:hover img,
    .photo:focus-within img {
        opacity: 0.3;
    }
    
    .photo:hover :is(h3, p, a),
    .photo:focus-within :is(h3, p, a) {
        opacity: 1;
    }
    Podgląd strony po tym kroku (galeria.php)
  29. Etap 4: Skrypty PHP

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

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

    Kryteria:

    R.5.1 Skrypt zawiera instrukcje połączenia z serwerem bazy danych i wyboru bazy galeria

    ok. 4 min
    Polecenie z arkusza

    Skrypty łączą się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie galeria

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

    • DSN mysql:host=localhost;dbname=galeria;charset=utf8mb4, użytkownik root, puste hasło (R.5.1),
    • ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, FETCH_ASSOC - wiersze z nazwami kolumn,
    • $pdo = null zamyka połączenie; oba skrypty trafią przed tę linię,
    • try / catch - przy awarii szczegóły idą do dziennika serwera, a strona dostaje ogólny komunikat,
    • e() - htmlspecialchars(..., ENT_QUOTES) do każdej wartości z bazy wstawianej w HTML.

    Od tego kroku podgląd to wynik prawdziwego uruchomienia (baza po zapytaniu 4).

    galeria.php PHP zmienione: 21 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $photos = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=galeria;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać zdjęć. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
        </main>
        <aside id="favourite">
            <h2>Najbardziej lubiane</h2>
            <strong>Zobacz wszystkie nasze zdjęcia</strong>
        </aside>
        <footer>
            <h5>Stronę wykonał: Bartosz Bryniarski</h5>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  30. Krok 30: Skrypt 1: zapytanie 2

    Kryteria: ,

    R.5.3 Skrypt 1 zawiera instrukcje wysyłające do bazy danych zapytanie 2 lub skrypt 2: zapytanie 1

    R.5.9 Zastosowano znaczące nazewnictwo dla wszystkich zmiennych lub funkcji w języku polskim lub angielskim. Nazwy odzwierciedlają cel zastosowania

    ok. 2 min
    Polecenie z arkusza

    Skrypt 1: Wysyła do bazy danych zapytanie 2

    $pdo->query(...) wysyła zapytanie 2 - te same linie co w kwerendy.txt - a ->fetchAll() odbiera 12 wierszy do tablicy $photos (R.5.3). W zapytaniu nie ma wartości od użytkownika, więc query wystarcza. Nazwy zmiennych po angielsku mówią, co przechowują (R.5.9).

    galeria.php PHP zmienione: 8 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $photos = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=galeria;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 2
        $photos = $pdo->query(
            'SELECT z.plik, z.tytul, z.polubienia, a.imie, a.nazwisko
             FROM zdjecia AS z
             JOIN autorzy AS a ON a.id = z.autorzy_id
             ORDER BY a.nazwisko'
        )->fetchAll();
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać zdjęć. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
        </main>
        <aside id="favourite">
            <h2>Najbardziej lubiane</h2>
            <strong>Zobacz wszystkie nasze zdjęcia</strong>
        </aside>
        <footer>
            <h5>Stronę wykonał: Bartosz Bryniarski</h5>
        </footer>
    </body>
    </html>

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

  31. Krok 31: Skrypt 1: karta dla każdego wiersza

    Kryteria:

    R.5.4 Skrypt 1 tworzy element blokowy dla każdego zwróconego z bazy wiersza

    ok. 2 min
    Polecenie z arkusza

    Każdy zwrócony zapytaniem wiersz jest wyświetlany w osobnym bloku (obraz 3)

    W <main> pętla foreach ($photos as $photo): w składni alternatywnej - HTML karty zostaje zwykłym HTML-em, bez echo i ukośników przed cudzysłowami. Karta to <figure> - element blokowy przeznaczony na ilustrację z podpisem (R.5.4). Na razie dwanaście pustych kart.

    galeria.php PHP zmienione: 4 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $photos = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=galeria;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 2
        $photos = $pdo->query(
            'SELECT z.plik, z.tytul, z.polubienia, a.imie, a.nazwisko
             FROM zdjecia AS z
             JOIN autorzy AS a ON a.id = z.autorzy_id
             ORDER BY a.nazwisko'
        )->fetchAll();
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać zdjęć. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
            <?php foreach ($photos as $photo): ?>
                <figure class="photo">
                </figure>
            <?php endforeach; ?>
        </main>
        <aside id="favourite">
            <h2>Najbardziej lubiane</h2>
            <strong>Zobacz wszystkie nasze zdjęcia</strong>
        </aside>
        <footer>
            <h5>Stronę wykonał: Bartosz Bryniarski</h5>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  32. Krok 32: Skrypt 1: zdjęcie z tekstem alternatywnym

    Kryteria: ,

    R.2.7 W bloku środkowym umieszczono przynajmniej jeden obraz z tekstem alternatywnym "zdjęcie" (wygenerowany skryptem lub w HTML)

    R.5.5 W każdym bloku wyświetlono zdjęcie, którego źródłem jest pole <plik> z bazy

    ok. 2 min
    Polecenie z arkusza

    Każdy blok zawiera: Obraz, którego źródło zostało zwrócone zapytaniem, z tekstem alternatywnym „zdjęcie”

    Źródło z kolumny plik ($photo['plik'] - po nazwie, nie po numerze), tekst alternatywny - stałe słowo z arkusza. Podgląd wygląda już jak obraz 2: zdjęcia po dwa w rzędzie, z paskiem przewijania bloku środkowego (R.5.5, R.2.7).

    galeria.php PHP zmienione: 1 linia
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $photos = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=galeria;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 2
        $photos = $pdo->query(
            'SELECT z.plik, z.tytul, z.polubienia, a.imie, a.nazwisko
             FROM zdjecia AS z
             JOIN autorzy AS a ON a.id = z.autorzy_id
             ORDER BY a.nazwisko'
        )->fetchAll();
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać zdjęć. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
            <?php foreach ($photos as $photo): ?>
                <figure class="photo">
                    <img src="<?= e($photo['plik']) ?>" alt="zdjęcie">
                </figure>
            <?php endforeach; ?>
        </main>
        <aside id="favourite">
            <h2>Najbardziej lubiane</h2>
            <strong>Zobacz wszystkie nasze zdjęcia</strong>
        </aside>
        <footer>
            <h5>Stronę wykonał: Bartosz Bryniarski</h5>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  33. Krok 33: Skrypt 1: tytuł w nagłówku trzeciego stopnia

    ok. 2 min
    Polecenie z arkusza

    Nagłówek trzeciego stopnia z tytułem zwróconym zapytaniem

    Opis zdjęcia trafia do podpisu <figcaption> - znacznik mówi, że tytuł, autor i odnośnik dotyczą tego zdjęcia. Pierwszy jest <h3> z tytułem. W podglądzie go nie widać: styl ukrywa opis do czasu najechania kursorem (opacity: 0). Pozycjonowanie się nie zmienia - absolute liczy się od najbliższego przodka z pozycją, czyli nadal od karty.

    galeria.php PHP zmienione: 3 linie
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $photos = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=galeria;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 2
        $photos = $pdo->query(
            'SELECT z.plik, z.tytul, z.polubienia, a.imie, a.nazwisko
             FROM zdjecia AS z
             JOIN autorzy AS a ON a.id = z.autorzy_id
             ORDER BY a.nazwisko'
        )->fetchAll();
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać zdjęć. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
            <?php foreach ($photos as $photo): ?>
                <figure class="photo">
                    <img src="<?= e($photo['plik']) ?>" alt="zdjęcie">
                    <figcaption>
                        <h3><?= e($photo['tytul']) ?></h3>
                    </figcaption>
                </figure>
            <?php endforeach; ?>
        </main>
        <aside id="favourite">
            <h2>Najbardziej lubiane</h2>
            <strong>Zobacz wszystkie nasze zdjęcia</strong>
        </aside>
        <footer>
            <h5>Stronę wykonał: Bartosz Bryniarski</h5>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  34. Krok 34: Skrypt 1: autor i „Wiele osób polubiło” przy ponad 40 polubieniach

    Kryteria:

    R.5.7 Gdy zwrócona zapytaniem wartość polubień jest większa niż 40 wyświetlony jest tekst: "Wiele osób polubiło ten obraz" , w przeciwnym wypadku tekst nie jest wyświetlony

    ok. 3 min
    Polecenie z arkusza

    W przypadku, gdy zwrócona wartość polubień jest większa niż 40: paragraf o treści: „Autor: <imię> <nazwisko>. Wiele osób polubiło ten obraz”, łamanie linii po kropce. W przeciwnym wypadku paragraf o treści: „Autor: <imię> <nazwisko>”, gdzie pola w nawiasach <> zostały pobrane z bazy danych

    Oba warianty paragrafu zaczynają się tak samo, więc jest jeden <p>, a warunek dopisuje tylko różnicę: kropkę, łamanie linii <br> i drugie zdanie. Próg 40 stoi w stałej POPULAR_LIKES na górze pliku - liczba ma nazwę i jedno miejsce do zmiany.

    „Większa niż” to > - „Urocze jeziorko” z dokładnie 40 polubieniami dostaje sam podpis. Sprawdzone na bazie z arkusza: dłuższy napis ma 5 zdjęć (Kijów, Praga, Wrocław, motyl i dzięcioł), krótszy - 7 (R.5.7).

    galeria.php PHP zmienione: 6 linii
    <?php
    const POPULAR_LIKES = 40;
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $photos = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=galeria;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 2
        $photos = $pdo->query(
            'SELECT z.plik, z.tytul, z.polubienia, a.imie, a.nazwisko
             FROM zdjecia AS z
             JOIN autorzy AS a ON a.id = z.autorzy_id
             ORDER BY a.nazwisko'
        )->fetchAll();
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać zdjęć. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
            <?php foreach ($photos as $photo): ?>
                <figure class="photo">
                    <img src="<?= e($photo['plik']) ?>" alt="zdjęcie">
                    <figcaption>
                        <h3><?= e($photo['tytul']) ?></h3>
                        <p>
                            Autor: <?= e($photo['imie']) ?> <?= e($photo['nazwisko']) ?><?php if ($photo['polubienia'] > POPULAR_LIKES): ?>.<br>
                            Wiele osób polubiło ten obraz<?php endif; ?>
                        </p>
                    </figcaption>
                </figure>
            <?php endforeach; ?>
        </main>
        <aside id="favourite">
            <h2>Najbardziej lubiane</h2>
            <strong>Zobacz wszystkie nasze zdjęcia</strong>
        </aside>
        <footer>
            <h5>Stronę wykonał: Bartosz Bryniarski</h5>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  35. Krok 35: Skrypt 1: odnośnik „Pobierz” z atrybutem download

    Kryteria: ,

    R.3.2 Zapisano na stronie przynajmniej jeden odnośnik o treści „Pobierz" , wskazujący na dowolny plik graficzny. Po kliknięciu na odnośnik, zdjęcie jest pobierane - atrybut download (wygenerowana skryptem lub w HTML)

    R.5.6 W każdym bloku wyświetlono tytuł, imię i nazwisko oraz odnośnik prowadzący do grafiki, której nazwa pliku jest zwrócona z bazy

    ok. 2 min
    Polecenie z arkusza

    Odnośnik o treści „Pobierz” wskazujący na nazwę pliku zwróconą zapytaniem. Po kliknięciu na odnośnik, zdjęcie jest pobierane

    Atrybut download każe zapisać plik na dysku zamiast otwierać go w przeglądarce; działa dla plików z tego samego serwera (R.3.2). Nazwa pliku przechodzi przez rawurlencode() - spacja czy polska litera w nazwie stałyby się bezpiecznym fragmentem adresu - a potem przez e() jak każdy atrybut. Każda karta ma teraz zdjęcie, tytuł, autora i odnośnik (R.5.6).

    galeria.php PHP zmienione: 1 linia
    <?php
    const POPULAR_LIKES = 40;
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $photos = [];
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=galeria;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 2
        $photos = $pdo->query(
            'SELECT z.plik, z.tytul, z.polubienia, a.imie, a.nazwisko
             FROM zdjecia AS z
             JOIN autorzy AS a ON a.id = z.autorzy_id
             ORDER BY a.nazwisko'
        )->fetchAll();
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać zdjęć. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
            <?php foreach ($photos as $photo): ?>
                <figure class="photo">
                    <img src="<?= e($photo['plik']) ?>" alt="zdjęcie">
                    <figcaption>
                        <h3><?= e($photo['tytul']) ?></h3>
                        <p>
                            Autor: <?= e($photo['imie']) ?> <?= e($photo['nazwisko']) ?><?php if ($photo['polubienia'] > POPULAR_LIKES): ?>.<br>
                            Wiele osób polubiło ten obraz<?php endif; ?>
                        </p>
                        <a href="<?= e(rawurlencode($photo['plik'])) ?>" download>Pobierz</a>
                    </figcaption>
                </figure>
            <?php endforeach; ?>
        </main>
        <aside id="favourite">
            <h2>Najbardziej lubiane</h2>
            <strong>Zobacz wszystkie nasze zdjęcia</strong>
        </aside>
        <footer>
            <h5>Stronę wykonał: Bartosz Bryniarski</h5>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  36. Krok 36: Skrypt 2: zdjęcie z tytułem jako tekstem alternatywnym

    Kryteria: , ,

    R.2.8 W bloku prawym umieszczono obraz z tekstem alternatywnym (wygenerowany skryptem lub w HTML)

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

    R.5.8 Skrypt 2 wyświetla obraz, którego źródło zwrócone zapytaniem 1, wraz z tekstem alternatywnym pobranym z pola tytuł

    ok. 3 min
    Polecenie z arkusza

    Skrypt 2: Wysyła do bazy danych zapytanie 1. Wyświetla obraz, którego źródło zostało zwrócone zapytaniem, wraz z tekstem alternatywnym pobranym z pola tytul. Na końcu jest zamykane połączenie z serwerem.

    Zapytanie 1 zwraca jeden wiersz, więc zamiast fetchAll jest fetch() - od razu ten wiersz albo false, gdy żadne zdjęcie nie ma stu polubień. W szablonie <?php if ($favourite): ?> wypisuje obraz tylko wtedy, gdy wiersz jest: źródło z plik, tekst alternatywny z tytul (R.5.8, R.2.8). Gdyby stu polubień dobiło kilka zdjęć, fetch() weźmie pierwsze.

    Sprawdzone uruchomieniem na zmienionej bazie: przy 99 polubieniach blok prawy ma sam nagłówek i napis, bez ostrzeżeń PHP, a tytuł z cudzysłowem (Kijów "nocą") trafia do alt jako &quot; i nie psuje znacznika. Połączenie zamyka $pdo = null zaraz po obu zapytaniach - ostatnia operacja na bazie (R.5.2).

    galeria.php PHP zmienione: 7 linii
    <?php
    const POPULAR_LIKES = 40;
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $photos = [];
    $favourite = false;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=galeria;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 2
        $photos = $pdo->query(
            'SELECT z.plik, z.tytul, z.polubienia, a.imie, a.nazwisko
             FROM zdjecia AS z
             JOIN autorzy AS a ON a.id = z.autorzy_id
             ORDER BY a.nazwisko'
        )->fetchAll();
    
        // Skrypt 2: zapytanie 1
        $favourite = $pdo->query('SELECT tytul, plik FROM zdjecia WHERE polubienia >= 100')->fetch();
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać zdjęć. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
            <?php foreach ($photos as $photo): ?>
                <figure class="photo">
                    <img src="<?= e($photo['plik']) ?>" alt="zdjęcie">
                    <figcaption>
                        <h3><?= e($photo['tytul']) ?></h3>
                        <p>
                            Autor: <?= e($photo['imie']) ?> <?= e($photo['nazwisko']) ?><?php if ($photo['polubienia'] > POPULAR_LIKES): ?>.<br>
                            Wiele osób polubiło ten obraz<?php endif; ?>
                        </p>
                        <a href="<?= e(rawurlencode($photo['plik'])) ?>" download>Pobierz</a>
                    </figcaption>
                </figure>
            <?php endforeach; ?>
        </main>
        <aside id="favourite">
            <h2>Najbardziej lubiane</h2>
            <?php if ($favourite): ?>
                <img src="<?= e($favourite['plik']) ?>" alt="<?= e($favourite['tytul']) ?>">
            <?php endif; ?>
            <strong>Zobacz wszystkie nasze zdjęcia</strong>
        </aside>
        <footer>
            <h5>Stronę wykonał: Bartosz Bryniarski</h5>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  37. Krok 37: Komunikat, gdy baza nie odpowiada

    ok. 2 min
    Polecenie z arkusza

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

    Na początku bloku środkowego akapit z $dbError - tylko gdy catch coś do niego wpisał. Sprawdzone uruchomieniem z błędną nazwą bazy: strona się wyświetla, w bloku środkowym stoi „Nie udało się pobrać zdjęć…”, blok prawy ma sam nagłówek i napis, a w treści nie ma nazwy bazy ani kodu błędu SQL. To zwykły akapit spoza kart, więc nie dotyczy go styl ukrywający opisy.

    galeria.php PHP zmienione: 3 linie
    <?php
    const POPULAR_LIKES = 40;
    
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $photos = [];
    $favourite = false;
    $dbError = '';
    
    try {
        $pdo = new PDO('mysql:host=localhost;dbname=galeria;charset=utf8mb4', 'root', '', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
    
        // Skrypt 1: zapytanie 2
        $photos = $pdo->query(
            'SELECT z.plik, z.tytul, z.polubienia, a.imie, a.nazwisko
             FROM zdjecia AS z
             JOIN autorzy AS a ON a.id = z.autorzy_id
             ORDER BY a.nazwisko'
        )->fetchAll();
    
        // Skrypt 2: zapytanie 1
        $favourite = $pdo->query('SELECT tytul, plik FROM zdjecia WHERE polubienia >= 100')->fetch();
    
        $pdo = null;
    } catch (PDOException $exception) {
        error_log($exception->getMessage());
        $dbError = 'Nie udało się pobrać zdjęć. Spróbuj ponownie za chwilę.';
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Galeria</title>
        <link rel="stylesheet" href="styl.css">
    </head>
    <body>
        <header>
            <h1>Zdjęcia</h1>
        </header>
        <aside id="topics">
            <h2>Tematy zdjęć</h2>
            <ol>
                <li>Zwierzęta</li>
                <li>Krajobrazy</li>
                <li>Miasta</li>
                <li>Przyroda</li>
                <li>Samochody</li>
            </ol>
        </aside>
        <main>
            <?php if ($dbError !== ''): ?>
                <p><?= e($dbError) ?></p>
            <?php endif; ?>
            <?php foreach ($photos as $photo): ?>
                <figure class="photo">
                    <img src="<?= e($photo['plik']) ?>" alt="zdjęcie">
                    <figcaption>
                        <h3><?= e($photo['tytul']) ?></h3>
                        <p>
                            Autor: <?= e($photo['imie']) ?> <?= e($photo['nazwisko']) ?><?php if ($photo['polubienia'] > POPULAR_LIKES): ?>.<br>
                            Wiele osób polubiło ten obraz<?php endif; ?>
                        </p>
                        <a href="<?= e(rawurlencode($photo['plik'])) ?>" download>Pobierz</a>
                    </figcaption>
                </figure>
            <?php endforeach; ?>
        </main>
        <aside id="favourite">
            <h2>Najbardziej lubiane</h2>
            <?php if ($favourite): ?>
                <img src="<?= e($favourite['plik']) ?>" alt="<?= e($favourite['tytul']) ?>">
            <?php endif; ?>
            <strong>Zobacz wszystkie nasze zdjęcia</strong>
        </aside>
        <footer>
            <h5>Stronę wykonał: Bartosz Bryniarski</h5>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (galeria.php)
  38. Etap 5: Porównanie z wersją podstawową

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

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

    ok. 2 min

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

    wersja podstawowawersja nowoczesna
    układ blokówfloat: left + clear: both, szerokości w widthgrid-template-areas, kolumny 15% 70% 15%
    karty zdjęć<div class="photo">, float<figure class="photo"> z <figcaption>, float (arkusz)
    style opisutrzy selektory po przecinku, transition: 0.5s ease:is(h3, p, a), transition: opacity 0.5s ease, :focus-within
    zapytaniapełne nazwy tabel, COUNT(*), INTaliasy z i a, COUNT(z.id) AS liczba_zdjec, INT UNSIGNED
    bazamysqli_connect, mysqli_fetch_row, $row[2]PDO z wyjątkami, fetchAll / fetch, $photo['polubienia']
    gdzie PHPdwa bloki w środku HTML-a, echo z ukośnikamidane na górze pliku, karty w zwykłym HTML-u
    brak zdjęcia ze 100 polubieniamiostrzeżenia PHP na stronieblok bez obrazu
    dane na stroniewprost (w dobrych praktykach htmlspecialchars)e() przy każdej wartości, rawurlencode() w adresie

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i jest szybsza do napisania. Nowoczesna oddziela pobieranie danych od wyglądu i łatwiej ją czytać - karta zdjęcia stoi w szablonie jako zwykły HTML, a nie w napisach echo. Z obu dróg warto zapamiętać jedno: procentowe szerokości elementów siatki liczą się od ich kolumny, więc przy grid szerokości bloków zapisuje się w grid-template-columns, a nie w width.

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

  39. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

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

    W przeglądarka.txt nazwa przeglądarki, w której strona była sprawdzana przez http://localhost/.... W folderze z numerem zdającego: galeria.php, import.png, kw1.jpg - kw4.jpg, kwerendy.txt, przeglądarka.txt, styl.css i wszystkie zdjęcia z archiwum (w tym przeskalowany woodpecker.jpg). Nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.