Przejdź do głównej treści
INF.03-07-25.01-SGStyczeń 2025 · 29 kroków · ok. 65 min · 38 kryteriów z klucza

Rozwiązanie krok po kroku: Wyszukiwarka polskich miast (Nowoczesny kod (grid, PDO))

To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza wykaz z czterema zapytaniami, baner z mapy, favicon i strona index.php, która po wysłaniu formularza pokazuje filtr i tabelę miast z województwami - i tak samo spełnia każde kryterium klucza. Inna jest droga:

  • siatka CSS z nazwanymi obszarami: grid-template-areas rysuje układ z ilustracji 4 wprost w kodzie (nagłówek na górze, dwa bloki lewe jeden pod drugim, prawy na wysokość obu), a szerokości 30% i 70% stoją w grid-template-columns - klucz R.4.4 przyjmuje je w tej postaci,
  • znaczniki od treści: blok wyników to <main>, blok z autorem to <footer>,
  • PHP na PDO z obsługą formularza nad HTML-em: filter_input, trim, puste pole kończy się podpowiedzią, a nie listą wszystkich 919 miast,
  • zapytanie przygotowane od pierwszej linii: cząstka z formularza nie zmieni treści zapytania (w wersji podstawowej wpisanie x' OR 1=1 OR 'a'=' wypisuje całą bazę), a % i _ wpisane przez użytkownika są zwykłymi znakami,
  • htmlspecialchars na wszystkim, co trafia na stronę, i try/catch przy awarii bazy.

Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i mapa pochodzą z prawdziwego archiwum pliki11, zapytania zostały wykonane na tej bazie w MariaDB z XAMPP-a, a podgląd strony to wynik prawdziwego uruchomienia kodu z danego kroku w PHP 8.2 - po wysłaniu formularza z „Po”, jak na ilustracji 5.

Jak liczą się punkty. Klucz ma 38 kryteriów (R.1.1 - R.5.8), każde za 1 punkt. Próg zdania to 75%, czyli 29 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 38 kryteriów (pkt)

    Krok 1: Baza danych wykaz

    ok. 2 min
    Polecenie z arkusza

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

    Archiwum pliki11.7z rozpakuj hasłem @PolSki3Miast@ do folderu z numerem zdającego: wykaz.sql i mapa_polski.jpg. W phpMyAdmin Nowa → wykaz, porównywanie utf8_unicode_ci, albo:

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

    Nazwa bazy musi być dokładnie wykaz - skrypt łączy się z nią po nazwie (R.5.2).

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

    ok. 2 min
    Polecenie z arkusza

    Do utworzonej bazy zaimportuj tabele z pliku wykaz.sql z rozpakowanego archiwum.

    Import → wykaz.sql. Dwie tabele z ilustracji 1: wojewodztwa (16) i miasta (919, klucz obcy id_wojewodztwa). Plik tworzy tabele z CHARSET=utf8, ale bez porównywania, więc dostają domyślne dla tego zestawu znaków utf8_general_ci - niezależnie od porównywania wybranego dla bazy. Wróci to przy sortowaniu nazw z „ł”.

  3. Krok 3: Zrzut ekranu import.png

    Kryteria:

    R.1.1 Wykonany import tabeli do bazy danych wykaz, co jest udokumentowane w pliku import.png

    ok. 2 min
    Polecenie z arkusza

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

    Cały ekran z paskiem zadań, zapis jako import.png.

    Typowe błędy

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

  4. Krok 4: Zapytanie 1: województwa małymi literami

    Kryteria: ,

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

    R.1.3 Wykonane zapytanie 1 wybierające jedynie nazwy województw, zapisane małymi literami. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT LOWER(nazwa) FROM wojewodztwa; (poprawne też LCASE ) oraz wynik działania udokumentowany zrzutem, na którym widocznych jest dokładnie 16 rekordów z nazwami województw zapisanymi małymi literami

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie nazwy województw, wszystkie litery w nazwach województw są zamienione na małe.

    Nazwy są w bazie wielkimi literami („DOLNOŚLĄSKIE”). LOWER() (albo LCASE()) zamienia wszystkie litery na małe - także polskie. Alias AS wojewodztwo daje kolumnie wyniku czytelny nagłówek zamiast LOWER(nazwa). Wynik: 16 wierszy. Zapytania zapisuj w kwerendy.txt, zrzut jako kw1.png.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT LOWER(nazwa) AS wojewodztwo
    FROM wojewodztwa;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: liczba miast województwa 1

    Kryteria:

    R.1.4 Wykonane zapytanie 2 obliczające liczbę miast, dla których id_wojewodztwa = 1. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT COUNT(nazwa) FROM miasta WHERE id_wojewodztwa = 1; (COUNT poprawne też z * lub innym polem ) oraz wynik działania udokumentowany zrzutem, na którym widoczna jest jedynie wartość 91

    ok. 1 min
    Polecenie z arkusza

    Zapytanie 2: obliczające liczbę miast dla których id_wojewodztwa jest równe jeden.

    COUNT(*) liczy wiersze spełniające warunek - gwiazdka mówi „policz wiersze”, a nie wartości jednej kolumny (COUNT(nazwa) pominąłby wiersze z NULL w nazwie). Wynik: 91. Zrzut: kw2.png.

    kwerendy.txt Tekst zmienione: 5 linii
    -- Zapytanie 1
    SELECT LOWER(nazwa) AS wojewodztwo
    FROM wojewodztwa;
    
    -- Zapytanie 2
    SELECT COUNT(*) AS liczba_miast
    FROM miasta
    WHERE id_wojewodztwa = 1;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: miasta na „Lu” z województwami

    Kryteria:

    R.1.5 Wykonane zapytanie 3 wybierające jedynie nazwy miast zaczynające się od „Lu” i odpowiadające im nazwy województw, posortowane alfabetycznie po nazwie miasta. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT miasta.nazwa, wojewodztwa.nazwa FROM miasta JOIN wojewodztwa ON wojewodztwa.id = id_wojewodztwa WHERE miasta.nazwa LIKE "Lu%" ORDER BY miasta.nazwa; (opcjonalnie ASC, możliwe też INNER JOIN lub porównanie kluczy po WHERE ) oraz wynik działania udokumentowany zrzutem, na którym widocznych jest dokładnie 15 wierszy z nazwami miast i województw, nazwy miast rozpoczynają się od "Lu"

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie nazwy miast zaczynające się od cząstki „Lu” i odpowiadające im nazwy województw, posortowane alfabetycznie po nazwie miasta. Należy posłużyć się relacją.

    Obie tabele mają kolumnę nazwa, więc aliasy robią tu dwie rzeczy naraz:

    • aliasy tabel m i w - krótko i jednoznacznie: m.nazwa to miasto, w.nazwa województwo,
    • aliasy kolumn miasto i wojewodztwo - w wyniku nie ma dwóch kolumn o tej samej nazwie, a w PHP odczyta się je po nazwie ($city['miasto']).

    JOIN ... ON w.id = m.id_wojewodztwa to relacja, LIKE 'Lu%' - nazwa zaczyna się od „Lu”, ORDER BY m.nazwa - alfabetycznie. Wynik: 15 wierszy, od Lubaczowa. Zrzut: kw3.png. To zapytanie, z cząstką z formularza zamiast „Lu”, wyśle skrypt.

    Typowe błędy

    Samo nazwa bez tabeli albo aliasu kończy się błędem „Column 'nazwa' in field list is ambiguous”.

    kwerendy.txt Tekst zmienione: 7 linii
    -- Zapytanie 1
    SELECT LOWER(nazwa) AS wojewodztwo
    FROM wojewodztwa;
    
    -- Zapytanie 2
    SELECT COUNT(*) AS liczba_miast
    FROM miasta
    WHERE id_wojewodztwa = 1;
    
    -- Zapytanie 3
    SELECT m.nazwa AS miasto, w.nazwa AS wojewodztwo
    FROM miasta AS m
    JOIN wojewodztwa AS w ON w.id = m.id_wojewodztwa
    WHERE m.nazwa LIKE 'Lu%'
    ORDER BY m.nazwa;
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: liczba miast w każdym województwie

    Kryteria:

    R.1.6 Wykonane zapytanie 4 wybierające jedynie nazwy województw i odpowiadającą im liczbę miast, które się w nich znajdują. Kolumna z liczbą miast jest nazwana (alias) „Liczba miast”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT wojewodztwa.nazwa, COUNT(miasta.nazwa) AS "Liczba miast" FROM wojewodztwa JOIN miasta ON wojewodztwa.id = miasta.id_wojewodztwa GROUP BY wojewodztwa.nazwa; (AS opcjonalnie, możliwe również INNER JOIN, poprawne też porównanie kluczy po WHERE ) oraz wynik działania udokumentowany zrzutem, na którym jest dokładnie 16 wierszy z nazwą województw i odpowiadającą im liczbą miast, np. 91 dla Dolnośląskiego 52 dla Kujawsko-Pomorskiego. Kolumna z liczbą nazwana (alias) jest "Liczba miast"

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 4: wybierające jedynie nazwy województw i odpowiadającą im liczbę miast, które się w nich znajdują. Kolumna z liczbą miast powinna mieć nadany (alias) „Liczba miast”. Należy posłużyć się relacją.

    Złączenie, GROUP BY w.id, w.nazwa - grupa na województwo po kluczu (dwa województwa o tej samej nazwie nie skleiłyby się w jedno), z nazwą na liście grupowania dla serwerów z ONLY_FULL_GROUP_BY - i COUNT(*) w każdej grupie. Alias ze spacją w odwrotnych apostrofach. Wynik: 16 wierszy, np. dolnośląskie 91, kujawsko-pomorskie 52 - jak w kluczu. Zrzut: kw4.png.

    kwerendy.txt Tekst zmienione: 6 linii
    -- Zapytanie 1
    SELECT LOWER(nazwa) AS wojewodztwo
    FROM wojewodztwa;
    
    -- Zapytanie 2
    SELECT COUNT(*) AS liczba_miast
    FROM miasta
    WHERE id_wojewodztwa = 1;
    
    -- Zapytanie 3
    SELECT m.nazwa AS miasto, w.nazwa AS wojewodztwo
    FROM miasta AS m
    JOIN wojewodztwa AS w ON w.id = m.id_wojewodztwa
    WHERE m.nazwa LIKE 'Lu%'
    ORDER BY m.nazwa;
    
    -- Zapytanie 4
    SELECT w.nazwa AS wojewodztwo, COUNT(*) AS `Liczba miast`
    FROM wojewodztwa AS w
    JOIN miasta AS m ON m.id_wojewodztwa = w.id
    GROUP BY w.id, w.nazwa;
    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 18 z 38 kryteriów (pkt)

    Krok 8: Baner: górna część mapy

    Kryteria:

    R.2.1 Na grafice baner.jpg widać górną część mapy z Morzem Bałtyckim. Obraz ma dokładnie: szerokość 1406 px, wysokość 270 px

    ok. 3 min
    Polecenie z arkusza

    Przygotowanie pliku z banerem strony: Plik mapa_polski.jpg należy wykadrować tak, że widoczna jest jedynie górna część mapy o wysokości 270 px. Nazwa pliku wynikowego baner.jpg.

    Grafika jest taka sama w obu wersjach. Mapa ma 1406 × 1262 px. W GIMP-ie zaznaczenie prostokątne 1406 × 270 od punktu 0, 0 i Obraz → Przytnij do zaznaczenia (albo Obraz → Rozmiar płótna z wysokością 270 i przesunięciem 0 oraz Przytnij do płótna). Zostaje pas z Morzem Bałtyckim. Eksportuj jako baner.jpg: dokładnie 1406 × 270 px (R.2.1).

    Typowe błędy

    Skalowanie zamiast kadrowania zmniejsza całą mapę - klucz wymaga szerokości 1406 px i widoku samej górnej części.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Favicon fav.png: czerwone M

    Kryteria: ,

    R.2.2 Obraz fav.png ma wymiary dokładnie 192 px na 192 px oraz tło przezroczyste

    R.2.3 Na obrazie faviconu widoczna jest litera M zapisana czcionką szeryfową w kolorze czerwonym. Litera M wypełnia min. 90% obrazu, przynajmniej w jednej z osi

    ok. 3 min
    Polecenie z arkusza

    Przygotowanie faviconu: wysokość i szerokość 192 px. Tło przezroczyste. Obraz wypełnia litera M koloru czerwonego zapisana dowolną czcionką szeryfową. Litera M wypełnia cały obraz, przynajmniej w jednej z osi (pionowej lub poziomej). Nazwa fav.png.

    Plik → Nowy 192 × 192 z wypełnieniem „Przezroczystość”. Tekst: litera M, czcionka szeryfowa (np. Times New Roman Bold), kolor ff0000, rozmiar dobrany tak, żeby litera sięgała prawie od krawędzi do krawędzi - tu 183 z 192 px w poziomie; klucz wymaga co najmniej 90% (R.2.2, R.2.3). Eksportuj jako fav.png.

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

    Kryteria:

    R.2.4 Witryna internetowa zapisana w pliku index.php oraz zapisany jawnie standard kodowania polskich znaków, deklaracja HTML5 <!DOCTYPE HTML>, zadeklarowany język witryny: polski np. <html lang="pl">. Strona zawiera sekcje head i body

    ok. 1 min
    Polecenie z arkusza

    Cechy witryny: Składa się ze strony o nazwie index.php. Zapisana w języku HTML5. Zadeklarowany polski język zawartości witryny. Jawnie zastosowany właściwy standard kodowania polskich znaków.

    Szkielet od razu z lang="pl" i <meta charset="UTF-8"> jako pierwszym elementem <head> (R.2.4). Jedna strona index.php - formularz i skrypt w tym samym pliku. Bez kodu PHP podgląd pokazuje ją wprost.

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

    Kryteria: ,

    R.2.5 Na karcie przeglądarki widoczne są favicon oraz tytuł strony "Wyszukiwarka miast"

    R.3.2 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie style.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

    Tytuł strony widoczny na karcie przeglądarki: „Wyszukiwarka miast”. Arkusz stylów w pliku o nazwie style.css prawidłowo połączony z kodem strony. Favicon o nazwie fav.png widoczny na karcie przeglądarki.

    <title> i ikona karty <link rel="icon"> z typem pliku (R.2.5) - przeglądarka zmniejszy obraz 192 px do rozmiaru ikony sama. <link> do style.css (R.3.2) i viewport dla telefonów.

    index.php PHP zmienione: 4 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wyszukiwarka miast</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  12. Krok 12: Kontener i bloki semantyczne

    Kryteria:

    R.2.6 Zdefiniowany układ strony, dzielący ją na bloki: nagłówkowy, dwa lewe i prawy przy pomocy semantycznych znaczników sekcji języka HTML5, zgodnie z ich znaczeniem, w tym do części nagłówkowej - header. Dopuszcza się dodatkowe użycie bloków div lub section

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: kontener zawartości, a w nim blok nagłówkowy, poniżej dwa bloki lewe jeden pod drugim i obok blok prawy. Podział zrealizowany 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 ilustracją 4.

    Kontener <div class="container">, a w nim znaczniki dobrane do treści bloków (R.2.6):

    • <header> - baner,
    • <section class="search"> - wyszukiwarka (lewy górny),
    • <main class="results"> - wyniki to główna treść strony, po którą użytkownik przychodzi (prawy),
    • <footer class="info"> - napis o egzaminie i autor, czyli informacje o stronie; <footer> nie musi stać na dole - znaczy „stopka tej części”, a nie „ostatni blok”.

    Kolejność w HTML-u idzie za treścią arkusza; o położeniu zdecydują nazwane obszary siatki w CSS.

    index.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>Wyszukiwarka miast</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
            </header>
            <section class="search">
            </section>
            <main class="results">
            </main>
            <footer class="info">
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  13. Krok 13: Nagłówek: baner

    Kryteria:

    R.2.9 W bloku nagłówkowym umieszczony obraz banera z tekstem alternatywnym Polska

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku nagłówkowego: obraz z pliku baner.jpg z tekstem alternatywnym „Polska”.

    Obraz z tekstem alternatywnym z arkusza (R.2.9).

    index.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>Wyszukiwarka miast</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Polska">
            </header>
            <section class="search">
            </section>
            <main class="results">
            </main>
            <footer class="info">
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  14. Krok 14: Blok lewy górny: formularz

    Kryteria: , , ,

    R.2.7 Umieszczone w bloku lewym górnym <h4>: "Podaj początek nazwy miasta", w bloku prawym <h1>: "Wyniki wyszukiwania miast z uwzględnieniem filtra:" oraz w lewym dolnym paragraf: "Egzamin INF.03" oraz drugi paragraf z numerem zdającego

    R.2.10 W bloku lewym górnym umieszczony formularz składający się z pola edycyjnego i przycisku Szukaj

    R.3.3 Formularz wysyła dane metodą POST do tego samego pliku (poprawne również bez atrybutu action)

    R.3.4 Przycisk realizuje funkcję wysłania danych z formularza (input lub button z typem submit lub button bez typu)

    ok. 3 min
    Polecenie z arkusza

    Zawartość bloku lewego-górnego: Nagłówek czwartego stopnia o treści „Podaj początek nazwy miasta”. Formularz wysyłający dane do tego samego pliku metodą bezpieczną, o polach: Pole edycyjne. Przycisk „Szukaj” wysyłający dane formularza do skryptu.

    • <form method="post"> bez action wysyła dane do tego samego pliku (R.3.3). „Metoda bezpieczna” w rozumieniu arkusza to POST - dane idą w treści żądania, a nie w adresie strony; w specyfikacji HTTP „bezpieczne” są co prawda metody, które niczego nie zmieniają na serwerze (GET i HEAD), ale punkt daje method="post",
    • pole name="city" - pod tą nazwą skrypt znajdzie wartość; aria-labelledby wskazuje nagłówek jako etykietę pola dla czytnika ekranu, a required nie pozwoli wysłać pustego pola,
    • przycisk type="submit" wysyła formularz (R.3.4).
    index.php PHP zmienione: 5 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wyszukiwarka miast</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Polska">
            </header>
            <section class="search">
                <h4 id="search-title">Podaj początek nazwy miasta</h4>
                <form method="post">
                    <input type="text" name="city" aria-labelledby="search-title" required>
                    <button type="submit">Szukaj</button>
                </form>
            </section>
            <main class="results">
            </main>
            <footer class="info">
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  15. Krok 15: Blok prawy: nagłówek wyników

    Kryteria:

    R.2.7 Umieszczone w bloku lewym górnym <h4>: "Podaj początek nazwy miasta", w bloku prawym <h1>: "Wyniki wyszukiwania miast z uwzględnieniem filtra:" oraz w lewym dolnym paragraf: "Egzamin INF.03" oraz drugi paragraf z numerem zdającego

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku prawego: Nagłówek pierwszego stopnia o treści „Wyniki wyszukiwania miast z uwzględnieniem filtra:”.

    <h1>; pod nim stanie wynik skryptu.

    index.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>Wyszukiwarka miast</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Polska">
            </header>
            <section class="search">
                <h4 id="search-title">Podaj początek nazwy miasta</h4>
                <form method="post">
                    <input type="text" name="city" aria-labelledby="search-title" required>
                    <button type="submit">Szukaj</button>
                </form>
            </section>
            <main class="results">
                <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1>
            </main>
            <footer class="info">
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  16. Krok 16: Blok lewy dolny: dwa akapity

    Kryteria:

    R.2.7 Umieszczone w bloku lewym górnym <h4>: "Podaj początek nazwy miasta", w bloku prawym <h1>: "Wyniki wyszukiwania miast z uwzględnieniem filtra:" oraz w lewym dolnym paragraf: "Egzamin INF.03" oraz drugi paragraf z numerem zdającego

    ok. 1 min
    Polecenie z arkusza

    Zawartość bloku lewego-dolnego: Paragraf (akapit) zawierający napis „Egzamin INF.03”. Paragraf z napisem „Autor: ”, dalej wstawiony numer zdającego.

    Dwa <p>. Na egzaminie po „Autor:” wpisujesz swój numer zdającego (PESEL).

    index.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>Wyszukiwarka miast</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Polska">
            </header>
            <section class="search">
                <h4 id="search-title">Podaj początek nazwy miasta</h4>
                <form method="post">
                    <input type="text" name="city" aria-labelledby="search-title" required>
                    <button type="submit">Szukaj</button>
                </form>
            </section>
            <main class="results">
                <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1>
            </main>
            <footer class="info">
                <p>Egzamin INF.03</p>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  17. Etap 3: Styl CSS

    Kroki 17-22 · po tym etapie masz 29 z 38 kryteriów (pkt)

    Krok 17: Plik style.css: zmienne, krój, tło i wyśrodkowanie

    Kryteria: , ,

    R.4.1 Ustawiony domyślnie dla wszystkich selektorów (*): krój czcionki Helvetica

    R.4.2 Ustawiony kolor tła LightSalmon dla ciała strony oraz DarkSalmon dla bloków lewego- dolnego i prawego

    R.4.5 Ustawione wyrównanie tekstu do środka dla ciała strony

    ok. 2 min
    Polecenie z arkusza

    Domyślne dla wszystkich selektorów: krój czcionki Helvetica. Dla ciała strony: kolor tła LightSalmon, wyrównanie tekstu do środka.

    Kolor paneli (DarkSalmon - dwa bloki) i ramek (Maroon) w zmiennych CSS; nazwa --panel mówi, do czego kolor służy. Potem * z krojem Helvetica (R.4.1) i body z tłem i wyśrodkowaniem (R.4.2, R.4.5).

    style.css CSS nowy plik
    :root {
        --panel: DarkSalmon;
        --border: Maroon;
    }
    
    * {
        font-family: Helvetica;
    }
    
    body {
        background-color: LightSalmon;
        text-align: center;
    }
    Podgląd strony po tym kroku (index.php)
  18. Krok 18: Kontener: siatka z nazwanymi obszarami

    Kryteria: ,

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z ilustracją 4 w arkuszu egzaminacyjnym (prawidłowo zastosowano właściwości CSS decydujące o układzie strony, np. float albo display flex albo grid albo position)

    R.4.4 Ustawiona szerokość obu bloków lewych 30%, prawego 70% (width lub grid-template- columns lub flex) oraz dla selektora img szerokość 100% i selektora table 70%

    ok. 3 min
    Polecenie z arkusza

    Poniżej dwa bloki lewe jeden pod drugim i obok blok prawy. Podział zrealizowany tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z ilustracją 4.

    Układ z ilustracji 4 zapisany jako mapa: każdy napis w grid-template-areas to jeden wiersz siatki, a każde słowo - jedna komórka.

    • "header header" - nagłówek zajmuje obie kolumny,
    • "search results" i "info results" - po lewej wyszukiwarka nad informacjami, po prawej results w obu wierszach, czyli na wysokość dwóch lewych bloków,
    • grid-template-columns: 30% 70% - szerokości kolumn, a zarazem szerokości bloków lewych i prawego; klucz R.4.4 przyjmuje je zapisane właśnie tu.

    Nagłówek dostaje swój obszar (grid-area: header); pozostałe bloki - w swoich regułach w kolejnych krokach. Wyrównane spacjami słowa w mapie nie mają znaczenia dla przeglądarki, ale pozwalają zobaczyć układ w kodzie (R.3.1).

    style.css CSS zmienione: 13 linii
    :root {
        --panel: DarkSalmon;
        --border: Maroon;
    }
    
    * {
        font-family: Helvetica;
    }
    
    body {
        background-color: LightSalmon;
        text-align: center;
    }
    
    .container {
        display: grid;
        grid-template-columns: 30% 70%;
        grid-template-areas:
            "header header"
            "search results"
            "info   results";
    }
    
    header {
        grid-area: header;
    }
    Podgląd strony po tym kroku (index.php)
  19. Krok 19: Trzy bloki: obszary, wysokości, tło, przewijanie

    Kryteria: , ,

    R.4.2 Ustawiony kolor tła LightSalmon dla ciała strony oraz DarkSalmon dla bloków lewego- dolnego i prawego

    R.4.3 Ustawiona wysokość 400px bloku lewego-górnego, 100px lewego-dolnego i 500px prawego

    R.4.7 Ustawione paski przewijania (overflow) na auto dla bloku prawego

    ok. 2 min
    Polecenie z arkusza

    Dla bloku lewego-górnego: szerokość 30%, wysokość 400 px. Dla bloku lewego-dolnego: kolor tła DarkSalmon, szerokość 30%, wysokość 100 px. Dla bloku prawego: kolor tła DarkSalmon, szerokość 70%, wysokość 500 px, paski przewijania widoczne tylko w przypadku, gdy zawartość nie mieści się.

    Każdy blok wskazuje swój obszar z mapy (grid-area) i dostaje wysokość z arkusza (R.4.3): 400 + 100 px po lewej to dokładnie 500 px bloku prawego, więc wiersze siatki wyrównują się same. Tło DarkSalmon ze zmiennej (R.4.2) i overflow: auto - pasek tylko przy długiej liście miast (R.4.7).

    Szerokości 30% i 70% stoją w kolumnach siatki - nie dopisuj ich jako width (R.4.4).

    Typowe błędy

    width: 30% na bloku w kolumnie siatki 30% daje 9% strony - procent szerokości elementu siatki liczy się od jego kolumny.

    style.css CSS zmienione: 18 linii
    :root {
        --panel: DarkSalmon;
        --border: Maroon;
    }
    
    * {
        font-family: Helvetica;
    }
    
    body {
        background-color: LightSalmon;
        text-align: center;
    }
    
    .container {
        display: grid;
        grid-template-columns: 30% 70%;
        grid-template-areas:
            "header header"
            "search results"
            "info   results";
    }
    
    header {
        grid-area: header;
    }
    
    .search {
        grid-area: search;
        height: 400px;
    }
    
    .info {
        grid-area: info;
        background-color: var(--panel);
        height: 100px;
    }
    
    .results {
        grid-area: results;
        background-color: var(--panel);
        height: 500px;
        overflow: auto;
    }
    Podgląd strony po tym kroku (index.php)
  20. Krok 20: Baner na całą szerokość

    Kryteria:

    R.4.4 Ustawiona szerokość obu bloków lewych 30%, prawego 70% (width lub grid-template- columns lub flex) oraz dla selektora img szerokość 100% i selektora table 70%

    ok. 1 min
    Polecenie z arkusza

    Dla selektora obrazu: szerokość 100%.

    Selektor znacznika img, jak wymaga uwaga arkusza (R.4.4). display: block usuwa szparę pod obrazem, którą przeglądarka zostawia pod elementem liniowym na „ogonki” liter.

    style.css CSS zmienione: 5 linii
    :root {
        --panel: DarkSalmon;
        --border: Maroon;
    }
    
    * {
        font-family: Helvetica;
    }
    
    body {
        background-color: LightSalmon;
        text-align: center;
    }
    
    .container {
        display: grid;
        grid-template-columns: 30% 70%;
        grid-template-areas:
            "header header"
            "search results"
            "info   results";
    }
    
    header {
        grid-area: header;
    }
    
    .search {
        grid-area: search;
        height: 400px;
    }
    
    .info {
        grid-area: info;
        background-color: var(--panel);
        height: 100px;
    }
    
    .results {
        grid-area: results;
        background-color: var(--panel);
        height: 500px;
        overflow: auto;
    }
    
    img {
        display: block;
        width: 100%;
    }
    Podgląd strony po tym kroku (index.php)
  21. Krok 21: Akapit filtra: klasa i kolor pod kursorem

    Kryteria: ,

    R.4.9 Ustawiony styl jedynie dla paragrafu filtra: czcionka pochylona o rozmiarze 200%, marginesy wewnętrzne 10px

    R.4.10 Gdy kursor myszy znajdzie się na paragrafie z frazą, jego kolor czcionki zmienia się na GhostWhite

    ok. 2 min
    Polecenie z arkusza

    Jedynie dla paragrafu ze skryptu wyświetlającego tekst filtra: czcionka pochylona o rozmiarze 200%, marginesy wewnętrzne 10 px. Gdy kursor myszy znajdzie się na paragrafie filtra jego kolor czcionki zmienia się na GhostWhite.

    „Jedynie dla tego paragrafu” - na stronie są też inne akapity (w stopce bloku lewego), więc styl dostaje klasa filter, którą nada akapitowi szablon (R.4.9, R.4.10).

    Typowe błędy

    Selektor p zmieniłby też „Egzamin INF.03” i „Autor:” - klucz mówi „jedynie dla paragrafu filtra”.

    style.css CSS zmienione: 10 linii
    :root {
        --panel: DarkSalmon;
        --border: Maroon;
    }
    
    * {
        font-family: Helvetica;
    }
    
    body {
        background-color: LightSalmon;
        text-align: center;
    }
    
    .container {
        display: grid;
        grid-template-columns: 30% 70%;
        grid-template-areas:
            "header header"
            "search results"
            "info   results";
    }
    
    header {
        grid-area: header;
    }
    
    .search {
        grid-area: search;
        height: 400px;
    }
    
    .info {
        grid-area: info;
        background-color: var(--panel);
        height: 100px;
    }
    
    .results {
        grid-area: results;
        background-color: var(--panel);
        height: 500px;
        overflow: auto;
    }
    
    img {
        display: block;
        width: 100%;
    }
    
    .filter {
        font-style: italic;
        font-size: 200%;
        padding: 10px;
    }
    
    .filter:hover {
        color: GhostWhite;
    }
    Podgląd strony po tym kroku (index.php)
  22. Krok 22: Tabela: ramka kropkowana, szerokość, wyśrodkowanie

    Kryteria: , ,

    R.4.4 Ustawiona szerokość obu bloków lewych 30%, prawego 70% (width lub grid-template- columns lub flex) oraz dla selektora img szerokość 100% i selektora table 70%

    R.4.6 Ustawione marginesy zewnętrzne (margin) na auto dla tabeli

    R.4.8 Ustawione obramowanie dla tabeli i selektora td: 1px dotted Maroon

    ok. 2 min
    Polecenie z arkusza

    Dla selektora tabeli i selektora komórek tabeli (bez nagłówkowych): obramowanie linią kropkowaną o grubości 1 px i kolorze Maroon. Dodatkowo dla selektora tabeli: szerokość 70%, marginesy zewnętrzne automatycznie wyliczane przez przeglądarkę.

    Selektory znaczników table i td - bez th, bo arkusz mówi „bez nagłówkowych”. Linia kropkowana to dotted, a margin: auto przy szerokości 70% wyśrodkowuje tabelę (R.4.4, R.4.6, R.4.8). Tabelę wypisze skrypt.

    style.css CSS zmienione: 10 linii
    :root {
        --panel: DarkSalmon;
        --border: Maroon;
    }
    
    * {
        font-family: Helvetica;
    }
    
    body {
        background-color: LightSalmon;
        text-align: center;
    }
    
    .container {
        display: grid;
        grid-template-columns: 30% 70%;
        grid-template-areas:
            "header header"
            "search results"
            "info   results";
    }
    
    header {
        grid-area: header;
    }
    
    .search {
        grid-area: search;
        height: 400px;
    }
    
    .info {
        grid-area: info;
        background-color: var(--panel);
        height: 100px;
    }
    
    .results {
        grid-area: results;
        background-color: var(--panel);
        height: 500px;
        overflow: auto;
    }
    
    img {
        display: block;
        width: 100%;
    }
    
    .filter {
        font-style: italic;
        font-size: 200%;
        padding: 10px;
    }
    
    .filter:hover {
        color: GhostWhite;
    }
    
    table,
    td {
        border: 1px dotted var(--border);
    }
    
    table {
        width: 70%;
        margin: auto;
    }
    Podgląd strony po tym kroku (index.php)
  23. Etap 4: Skrypt PHP

    Kroki 23-27 · po tym etapie masz 38 z 38 kryteriów (pkt)

    Krok 23: Czy wysłano formularz? Filtr w akapicie

    Kryteria: ,

    R.5.4 W skrypcie zapisana instrukcja warunkowa sprawdzająca czy formularz wysyła dane. Wszystkie operacje obsługujące formularz i zapytanie są wykonywane tylko wtedy, gdy warunek jest spełniony

    R.5.6 Skrypt wyświetla w paragrafie frazę pobraną z formularza

    ok. 3 min
    Polecenie z arkusza

    Skrypt sprawdza czy z formularza zostały wysłane dane i jeżeli tak to wykonuje czynności: Wyświetla w paragrafie wyszukiwany filtr, który został przesłany z formularza.

    Obsługa formularza stoi przed <!DOCTYPE html>, a w HTML-u zostaje samo wypisywanie:

    • filter_input(INPUT_POST, 'city') - wartość pola albo null, gdy formularza nie wysłano (pierwsze wejście na stronę); bez ostrzeżenia „Undefined array key”, które dałoby gołe $_POST['city'],
    • if ($filter !== null) - wszystko, co dotyczy wyszukiwania, dzieje się tylko po wysłaniu (R.5.4); trim usuwa przypadkowe spacje,
    • w bloku prawym ten sam warunek w składni alternatywnej (if (...): ... endif;) i akapit z klasą filter, a w nim filtr przepuszczony przez e() - tekst od użytkownika nigdy nie trafia na stronę jako HTML (R.5.6),
    • funkcja e() to krótka nazwa htmlspecialchars(..., ENT_QUOTES).

    Podgląd pokazuje stan po wysłaniu „Po”. Pierwsze wejście na stronę (bez formularza) - sam nagłówek, sprawdzone.

    index.php PHP zmienione: 17 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $filter = filter_input(INPUT_POST, 'city');
    $cities = [];
    $message = '';
    
    if ($filter !== null) {
        $filter = trim($filter);
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Wyszukiwarka miast</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Polska">
            </header>
            <section class="search">
                <h4 id="search-title">Podaj początek nazwy miasta</h4>
                <form method="post">
                    <input type="text" name="city" aria-labelledby="search-title" required>
                    <button type="submit">Szukaj</button>
                </form>
            </section>
            <main class="results">
                <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1>
                <?php if ($filter !== null): ?>
                    <p class="filter"><?= e($filter) ?></p>
                <?php endif; ?>
            </main>
            <footer class="info">
                <p>Egzamin INF.03</p>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  24. Krok 24: Połączenie PDO tylko po wysłaniu formularza

    Kryteria: , , ,

    R.5.1 Skrypt jest napisany w języku PHP i zawiera przynajmniej jedną zgodną z treścią zadania instrukcję

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

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

    R.5.8 W skryptach zastosowana przynajmniej jedna zmienna lub funkcja oraz nazwy wszystkich funkcji i zmiennych są w języku polskim lub angielskim i odzwierciedlają cel zastosowania

    ok. 3 min
    Polecenie z arkusza

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

    Połączenie wewnątrz warunku: bez wysłanego formularza strona nie potrzebuje bazy, więc jej nie otwiera (klucz R.5.4: wszystkie operacje związane z zapytaniem tylko po spełnieniu warunku).

    • DSN 'mysql:host=localhost;dbname=wykaz;charset=utf8mb4' z użytkownikiem root i pustym hasłem (R.5.1, R.5.2),
    • ERRMODE_EXCEPTION i FETCH_ASSOC - wyjątki przy błędach, wiersze z nazwami kolumn,
    • $pdo = null zamyka połączenie; zapytanie trafi przed tę linię, więc zamknięcie zostanie ostatnią operacją na bazie (R.5.3),
    • catch (PDOException $exception) - szczegóły do dziennika serwera, a dla użytkownika ogólny komunikat w $message.

    Nazwy po angielsku mówią, co przechowują (R.5.8).

    index.php PHP zmienione: 10 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $filter = filter_input(INPUT_POST, 'city');
    $cities = [];
    $message = '';
    
    if ($filter !== null) {
        $filter = trim($filter);
        try {
            $pdo = new PDO('mysql:host=localhost;dbname=wykaz;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());
            $message = 'Wyszukiwarka jest chwilowo niedostępna. 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>Wyszukiwarka miast</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Polska">
            </header>
            <section class="search">
                <h4 id="search-title">Podaj początek nazwy miasta</h4>
                <form method="post">
                    <input type="text" name="city" aria-labelledby="search-title" required>
                    <button type="submit">Szukaj</button>
                </form>
            </section>
            <main class="results">
                <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1>
                <?php if ($filter !== null): ?>
                    <p class="filter"><?= e($filter) ?></p>
                <?php endif; ?>
            </main>
            <footer class="info">
                <p>Egzamin INF.03</p>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>

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

  25. Krok 25: Zapytanie 3 z cząstką z formularza - przygotowane

    Kryteria:

    R.5.5 Skrypt zawiera instrukcję wysyłającą do bazy danych zmodyfikowane zapytanie 3 w ten sposób, że wybierane są miasta rozpoczynające się od frazy przesłanej przez formularz

    ok. 4 min
    Polecenie z arkusza

    Wysyła do bazy danych zmodyfikowane zapytanie 3, w ten sposób że wybierane są miasta rozpoczynające się od cząstki przesłanej przez formularz.

    Zapytanie 3 jako zapytanie przygotowane: w miejscu 'Lu%' stoi parametr nazwany :pattern, a wartość przychodzi osobno w execute([...]) (R.5.5). Baza najpierw dostaje treść zapytania, potem dane - tekst z formularza nie może jej już zmienić. Wpisanie x' OR 1=1 OR 'a'=' szuka teraz miast o takiej dziwnej nazwie (zero wyników), zamiast wypisać całą bazę.

    Wzorzec buduje PHP: addcslashes($filter, '%_\\') poprzedza ukośnikiem znaki, które w LIKE mają specjalne znaczenie (% - dowolny ciąg, _ - jeden znak, \ - sam ukośnik), a na końcu doklejany jest jeden % - „zaczyna się od”. Bez tego wpisanie samego % zwróciłoby wszystkie miasta. fetchAll() odbiera wynik do $cities.

    index.php PHP zmienione: 9 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $filter = filter_input(INPUT_POST, 'city');
    $cities = [];
    $message = '';
    
    if ($filter !== null) {
        $filter = trim($filter);
        try {
            $pdo = new PDO('mysql:host=localhost;dbname=wykaz;charset=utf8mb4', 'root', '', [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]);
            $statement = $pdo->prepare(
                'SELECT m.nazwa AS miasto, w.nazwa AS wojewodztwo
                 FROM miasta AS m
                 JOIN wojewodztwa AS w ON w.id = m.id_wojewodztwa
                 WHERE m.nazwa LIKE :pattern
                 ORDER BY m.nazwa'
            );
            $statement->execute(['pattern' => addcslashes($filter, '%_\\') . '%']);
            $cities = $statement->fetchAll();
            $pdo = null;
        } catch (PDOException $exception) {
            error_log($exception->getMessage());
            $message = 'Wyszukiwarka jest chwilowo niedostępna. 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>Wyszukiwarka miast</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Polska">
            </header>
            <section class="search">
                <h4 id="search-title">Podaj początek nazwy miasta</h4>
                <form method="post">
                    <input type="text" name="city" aria-labelledby="search-title" required>
                    <button type="submit">Szukaj</button>
                </form>
            </section>
            <main class="results">
                <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1>
                <?php if ($filter !== null): ?>
                    <p class="filter"><?= e($filter) ?></p>
                <?php endif; ?>
            </main>
            <footer class="info">
                <p>Egzamin INF.03</p>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>

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

  26. Krok 26: Tabela miast z województwami

    Kryteria: ,

    R.2.8 W bloku prawym umieszczona tabela o dwóch kolumnach i pierwszym wierszu zawierającym komórki nagłówkowe. Zastosowane znaczniki <table>, <tr>, <th>, <td> (w skrypcie lub HTML)

    R.5.7 Skrypt wyświetla zwrócone miasta i województwa w osobnych wierszach tabeli

    ok. 3 min
    Polecenie z arkusza

    Tworzy tabelę z wierszem składającym się z komórek nagłówkowych: „Miasto” , „Województwo”. Wyświetla zwrócone miasta i województwa w osobnych wierszach tabeli – patrz ilustracja 5.

    Pod akapitem filtra tabela: wiersz nagłówkowy <th> raz, a wiersze z danymi w pętli foreach ($cities as $city): - każda wartość po nazwie kolumny z aliasu ($city['miasto']) i przez e(). HTML tabeli stoi w szablonie, a nie w napisach echo, więc edytor koloruje go jak zwykły HTML (R.2.8, R.5.7).

    Podgląd: 14 miast na „Po” - jak na ilustracji 5, łącznie z kolejnością „Poznań”, a dopiero potem „Połaniec”: tabele mają porównywanie utf8_general_ci, w którym „ł” stoi za „z”.

    index.php PHP zmienione: 12 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $filter = filter_input(INPUT_POST, 'city');
    $cities = [];
    $message = '';
    
    if ($filter !== null) {
        $filter = trim($filter);
        try {
            $pdo = new PDO('mysql:host=localhost;dbname=wykaz;charset=utf8mb4', 'root', '', [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]);
            $statement = $pdo->prepare(
                'SELECT m.nazwa AS miasto, w.nazwa AS wojewodztwo
                 FROM miasta AS m
                 JOIN wojewodztwa AS w ON w.id = m.id_wojewodztwa
                 WHERE m.nazwa LIKE :pattern
                 ORDER BY m.nazwa'
            );
            $statement->execute(['pattern' => addcslashes($filter, '%_\\') . '%']);
            $cities = $statement->fetchAll();
            $pdo = null;
        } catch (PDOException $exception) {
            error_log($exception->getMessage());
            $message = 'Wyszukiwarka jest chwilowo niedostępna. 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>Wyszukiwarka miast</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Polska">
            </header>
            <section class="search">
                <h4 id="search-title">Podaj początek nazwy miasta</h4>
                <form method="post">
                    <input type="text" name="city" aria-labelledby="search-title" required>
                    <button type="submit">Szukaj</button>
                </form>
            </section>
            <main class="results">
                <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1>
                <?php if ($filter !== null): ?>
                    <p class="filter"><?= e($filter) ?></p>
                    <table>
                        <tr>
                            <th>Miasto</th>
                            <th>Województwo</th>
                        </tr>
                        <?php foreach ($cities as $city): ?>
                            <tr>
                                <td><?= e($city['miasto']) ?></td>
                                <td><?= e($city['wojewodztwo']) ?></td>
                            </tr>
                        <?php endforeach; ?>
                    </table>
                <?php endif; ?>
            </main>
            <footer class="info">
                <p>Egzamin INF.03</p>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  27. Krok 27: Pusty filtr, brak wyników, awaria bazy

    ok. 3 min
    Polecenie z arkusza

    Skrypt sprawdza czy z formularza zostały wysłane dane i jeżeli tak to wykonuje czynności.

    Trzy sytuacje, które wersja podstawowa przemilcza, sprawdzone uruchomieniem:

    • pole z samymi spacjami - po trim zostaje pusty napis i zamiast listy wszystkich 919 miast pojawia się „Wpisz początek nazwy miasta.” (atrybut required zatrzyma puste pole już w przeglądarce, ale serwer nie może na tym polegać),
    • zero wyników - „Brak miast zaczynających się od „…”” zamiast pustej tabeli; tak kończy się też próba wstrzyknięcia x' OR 1=1 OR 'a'=' i wpisanie samego %,
    • awaria bazy (sprawdzone z błędną nazwą bazy) - „Wyszukiwarka jest chwilowo niedostępna…”, bez nazwy bazy i kodu błędu SQL na stronie.

    Akapit z filtrem i tabela pokazują się tylko wtedy, gdy filtr coś zawiera (warunek w szablonie dostał $filter !== ''); komunikaty stoją pod nimi. Podgląd: wynik dla „Po”.

    index.php PHP zmienione: 11 linii
    <?php
    function e(string $text): string
    {
        return htmlspecialchars($text, ENT_QUOTES);
    }
    
    $filter = filter_input(INPUT_POST, 'city');
    $cities = [];
    $message = '';
    
    if ($filter !== null) {
        $filter = trim($filter);
    }
    
    if ($filter === '') {
        $message = 'Wpisz początek nazwy miasta.';
    } elseif ($filter !== null) {
        try {
            $pdo = new PDO('mysql:host=localhost;dbname=wykaz;charset=utf8mb4', 'root', '', [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]);
            $statement = $pdo->prepare(
                'SELECT m.nazwa AS miasto, w.nazwa AS wojewodztwo
                 FROM miasta AS m
                 JOIN wojewodztwa AS w ON w.id = m.id_wojewodztwa
                 WHERE m.nazwa LIKE :pattern
                 ORDER BY m.nazwa'
            );
            $statement->execute(['pattern' => addcslashes($filter, '%_\\') . '%']);
            $cities = $statement->fetchAll();
            $pdo = null;
        } catch (PDOException $exception) {
            error_log($exception->getMessage());
            $message = 'Wyszukiwarka jest chwilowo niedostępna. 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>Wyszukiwarka miast</title>
        <link rel="icon" href="fav.png" type="image/png">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div class="container">
            <header>
                <img src="baner.jpg" alt="Polska">
            </header>
            <section class="search">
                <h4 id="search-title">Podaj początek nazwy miasta</h4>
                <form method="post">
                    <input type="text" name="city" aria-labelledby="search-title" required>
                    <button type="submit">Szukaj</button>
                </form>
            </section>
            <main class="results">
                <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1>
                <?php if ($filter !== null && $filter !== ''): ?>
                    <p class="filter"><?= e($filter) ?></p>
                    <table>
                        <tr>
                            <th>Miasto</th>
                            <th>Województwo</th>
                        </tr>
                        <?php foreach ($cities as $city): ?>
                            <tr>
                                <td><?= e($city['miasto']) ?></td>
                                <td><?= e($city['wojewodztwo']) ?></td>
                            </tr>
                        <?php endforeach; ?>
                    </table>
                <?php endif; ?>
                <?php if ($message !== ''): ?>
                    <p><?= e($message) ?></p>
                <?php elseif ($filter !== null && $cities === []): ?>
                    <p>Brak miast zaczynających się od „<?= e($filter) ?>”.</p>
                <?php endif; ?>
            </main>
            <footer class="info">
                <p>Egzamin INF.03</p>
                <p>Autor: Bartosz Bryniarski</p>
            </footer>
        </div>
    </body>
    </html>
    Podgląd strony po tym kroku (index.php)
  28. Etap 5: Porównanie z wersją podstawową

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

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

    ok. 2 min

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

    wersja podstawowawersja nowoczesna
    układfloat: left (bloki lewe) i float: right (prawy), szerokości w widthgrid-template-areas w kontenerze, szerokości w kolumnach
    znacznikitrzy <section><section>, <main> (wyniki), <footer> (autor)
    bazamysqli_connect przed warunkiem, mysqli_fetch_rowPDO tylko po wysłaniu formularza, fetchAll, kolumny po aliasach
    dane z formularza$_POST["city"] wklejone w zapytaniefilter_input, trim, zapytanie przygotowane, addcslashes
    wypisywanieecho z HTML-em w napisachszablon foreach / endforeach, e() przy każdej wartości
    puste pole, %, atakwszystkie miasta (także 919 przy wstrzyknięciu)komunikat albo zero wyników
    awaria bazybiała strona z błędemkomunikat, szczegóły w error_log

    Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i każde kryterium R.5 ma w niej jedną, łatwą do wskazania linię. Nowoczesna jest dłuższa o kilka linii, ale tylko ona nadaje się do prawdziwej wyszukiwarki w internecie - tam pole formularza wypełniają także ludzie szukający dziury w zapytaniu.

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

  29. Etap 6: Oddanie pracy

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

    Krok 29: 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 folderze z numerem zdającego, powinny znajdować się pliki: baner.jpg, fav.png, import.png, index.php, kw1.png, kw2.png, kw3.png, kw4.png, kwerendy.txt, przeglądarka.txt, style.css, ewentualnie inne przygotowane pliki.

    W przeglądarka.txt wpisz nazwę przeglądarki, w której strona była sprawdzana przez http://localhost/.... Sprawdź listę plików z arkusza, nagraj płytę i sprawdź jej odczyt.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

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.