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.
Suwak i strzałki obok niego - albo ←→ na klawiaturze - przechodzą krok po kroku; wyjaśnienie, pliki i podgląd zmieniają się razem z krokiem.
Lista „Etap” przenosi od razu do wybranego etapu.
„Rozwiązuję samodzielnie” chowa rozwiązanie kroku - najpierw własna próba, potem odsłonięcie.
„Tryb prezentacji” pokazuje same kroki, bez menu strony - na rzutnik.
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).
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 „ł”.
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).
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.txtTekstnowy plik
-- Zapytanie 1SELECT LOWER(nazwa) AS wojewodztwoFROM wojewodztwa;
Podgląd strony po tym kroku
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.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT LOWER(nazwa) AS wojewodztwoFROM wojewodztwa;-- Zapytanie 2SELECT COUNT(*) AS liczba_miastFROM miastaWHERE id_wojewodztwa = 1;
Podgląd strony po tym kroku
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 tabelm i w - krótko i jednoznacznie: m.nazwa to miasto, w.nazwa województwo,
aliasy kolumnmiasto 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.txtTekstzmienione: 7 linii
-- Zapytanie 1SELECT LOWER(nazwa) AS wojewodztwoFROM wojewodztwa;-- Zapytanie 2SELECT COUNT(*) AS liczba_miastFROM miastaWHERE id_wojewodztwa = 1;-- Zapytanie 3SELECT m.nazwa AS miasto, w.nazwa AS wojewodztwoFROM miasta AS mJOIN wojewodztwa AS w ON w.id = m.id_wojewodztwaWHERE m.nazwa LIKE 'Lu%'ORDER BY m.nazwa;
Podgląd strony po tym kroku
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.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT LOWER(nazwa) AS wojewodztwoFROM wojewodztwa;-- Zapytanie 2SELECT COUNT(*) AS liczba_miastFROM miastaWHERE id_wojewodztwa = 1;-- Zapytanie 3SELECT m.nazwa AS miasto, w.nazwa AS wojewodztwoFROM miasta AS mJOIN wojewodztwa AS w ON w.id = m.id_wojewodztwaWHERE m.nazwa LIKE 'Lu%'ORDER BY m.nazwa;-- Zapytanie 4SELECT w.nazwa AS wojewodztwo, COUNT(*) AS `Liczba miast`FROM wojewodztwa AS wJOIN miasta AS m ON m.id_wojewodztwa = w.idGROUP BY w.id, w.nazwa;
Podgląd strony po tym kroku
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 jakobaner.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
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 → Nowy192 × 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 jakofav.png.
Podgląd strony po tym kroku
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.
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.
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):
<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.
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,
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.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Wyszukiwarka miast</title> <linkrel="icon"href="fav.png"type="image/png"> <linkrel="stylesheet"href="style.css"></head><body> <divclass="container"> <header> <imgsrc="baner.jpg"alt="Polska"> </header> <sectionclass="search"> <h4id="search-title">Podaj początek nazwy miasta</h4> <formmethod="post"> <inputtype="text"name="city"aria-labelledby="search-title"required> <buttontype="submit">Szukaj</button> </form> </section> <mainclass="results"> <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1> </main> <footerclass="info"> </footer> </div></body></html>
Podgląd strony po tym kroku (index.php)
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.phpPHPzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Wyszukiwarka miast</title> <linkrel="icon"href="fav.png"type="image/png"> <linkrel="stylesheet"href="style.css"></head><body> <divclass="container"> <header> <imgsrc="baner.jpg"alt="Polska"> </header> <sectionclass="search"> <h4id="search-title">Podaj początek nazwy miasta</h4> <formmethod="post"> <inputtype="text"name="city"aria-labelledby="search-title"required> <buttontype="submit">Szukaj</button> </form> </section> <mainclass="results"> <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1> </main> <footerclass="info"> <p>Egzamin INF.03</p> <p>Autor: Bartosz Bryniarski</p> </footer> </div></body></html>
Podgląd strony po tym kroku (index.php)
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).
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).
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.
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.
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 klasafilter, 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”.
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.
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.
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.
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.phpPHPzmienione: 9 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Wyszukiwarka miast</title> <linkrel="icon"href="fav.png"type="image/png"> <linkrel="stylesheet"href="style.css"></head><body> <divclass="container"> <header> <imgsrc="baner.jpg"alt="Polska"> </header> <sectionclass="search"> <h4id="search-title">Podaj początek nazwy miasta</h4> <formmethod="post"> <inputtype="text"name="city"aria-labelledby="search-title"required> <buttontype="submit">Szukaj</button> </form> </section> <mainclass="results"> <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1><?phpif ($filter !== null): ?> <pclass="filter"><?= e($filter) ?></p><?phpendif; ?> </main> <footerclass="info"> <p>Egzamin INF.03</p> <p>Autor: Bartosz Bryniarski</p> </footer> </div></body></html>
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.phpPHPzmienione: 12 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Wyszukiwarka miast</title> <linkrel="icon"href="fav.png"type="image/png"> <linkrel="stylesheet"href="style.css"></head><body> <divclass="container"> <header> <imgsrc="baner.jpg"alt="Polska"> </header> <sectionclass="search"> <h4id="search-title">Podaj początek nazwy miasta</h4> <formmethod="post"> <inputtype="text"name="city"aria-labelledby="search-title"required> <buttontype="submit">Szukaj</button> </form> </section> <mainclass="results"> <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1><?phpif ($filter !== null): ?> <pclass="filter"><?= e($filter) ?></p> <table> <tr> <th>Miasto</th> <th>Województwo</th> </tr><?phpforeach ($citiesas$city): ?> <tr> <td><?= e($city['miasto']) ?></td> <td><?= e($city['wojewodztwo']) ?></td> </tr><?phpendforeach; ?> </table><?phpendif; ?> </main> <footerclass="info"> <p>Egzamin INF.03</p> <p>Autor: Bartosz Bryniarski</p> </footer> </div></body></html>
Podgląd strony po tym kroku (index.php)
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.phpPHPzmienione: 11 linii
<?phpfunctione(string $text): string{returnhtmlspecialchars($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><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Wyszukiwarka miast</title> <linkrel="icon"href="fav.png"type="image/png"> <linkrel="stylesheet"href="style.css"></head><body> <divclass="container"> <header> <imgsrc="baner.jpg"alt="Polska"> </header> <sectionclass="search"> <h4id="search-title">Podaj początek nazwy miasta</h4> <formmethod="post"> <inputtype="text"name="city"aria-labelledby="search-title"required> <buttontype="submit">Szukaj</button> </form> </section> <mainclass="results"> <h1>Wyniki wyszukiwania miast z uwzględnieniem filtra:</h1><?phpif ($filter !== null && $filter !== ''): ?> <pclass="filter"><?= e($filter) ?></p> <table> <tr> <th>Miasto</th> <th>Województwo</th> </tr><?phpforeach ($citiesas$city): ?> <tr> <td><?= e($city['miasto']) ?></td> <td><?= e($city['wojewodztwo']) ?></td> </tr><?phpendforeach; ?> </table><?phpendif; ?><?phpif ($message !== ''): ?> <p><?= e($message) ?></p><?phpelseif ($filter !== null && $cities === []): ?> <p>Brak miast zaczynających się od „<?= e($filter) ?>”.</p><?phpendif; ?> </main> <footerclass="info"> <p>Egzamin INF.03</p> <p>Autor: Bartosz Bryniarski</p> </footer> </div></body></html>
Podgląd strony po tym kroku (index.php)
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 podstawowa
wersja nowoczesna
układ
float: left (bloki lewe) i float: right (prawy), szerokości w width
grid-template-areas w kontenerze, szerokości w kolumnach
znaczniki
trzy <section>
<section>, <main> (wyniki), <footer> (autor)
baza
mysqli_connect przed warunkiem, mysqli_fetch_row
PDO tylko po wysłaniu formularza, fetchAll, kolumny po aliasach
szablon foreach / endforeach, e() przy każdej wartości
puste pole, %, atak
wszystkie miasta (także 919 przy wstrzyknięciu)
komunikat albo zero wyników
awaria bazy
biała strona z błędem
komunikat, 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.
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.
Symulacja ekranu w przeglądarce - w Android Studio / Visual Studio wygląd może się nieco różnić.
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: Bartosz Bryniarski. 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.