INF.03-02-23.06-SGCzerwiec 2023 · 42 kroki · ok. 80 min · 39 kryteriów z klucza
Rozwiązanie krok po kroku: Portal hurtowni papierniczej (Nowoczesny kod (flex, PDO))
To drugi sposób rozwiązania tego samego arkusza. Wynik jest ten sam - baza sklep z czterema zapytaniami, przeskalowana grafika z przezroczystym tłem i strona index.php z banerem, tabelą cen, formularzem liczącym wartość zakupów, kontaktem i stopką - i tak samo spełnia każde kryterium klucza. Inna jest droga:
flexbox zamiast float: trzy panele stoją w znaczniku <main> ustawionym w rząd, bez clear przy stopce. Szerokości 25%, 50% i 25% zostają na samych panelach - tak ocenia je klucz,
zmienne CSS dla kolorów z arkusza, wiersze formularza w blokach zamiast <br>, pole liczbowe przyjmujące tylko liczby od 1 i pamiętające wpisaną wartość,
PHP na PDO: wszystkie dane strona pobiera na górze pliku index.php, przed HTML-em, w bloku try, a w HTML-u zostają tylko pętle i wypisywanie,
sprawdzanie danych z formularza: liczba sztuk przez filter_var (liczba całkowita od 1), nazwa towaru z listą w stałej PRODUCTS, a do bazy nazwa trafia zapytaniem przygotowanym,
bezpieczne wypisywanie (htmlspecialchars w funkcji e()) i komunikaty zamiast błędów PHP przy pustym polu, towarze spoza listy i awarii bazy.
Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafika pochodzą z prawdziwego archiwum materialy2, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 na bazie po zapytaniach 1-4 - od Skryptu 2 po wysłaniu formularza z obrazu 2 (Zeszyt 32 kartki, 2 sztuki).
Jak liczą się punkty. Klucz ma 39 kryteriów (R.1.1 - R.5.8), każde za 1 punkt. Próg zdania to 75%, czyli 30 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-8 · po tym etapie masz 6 z 39 kryteriów (pkt)
Krok 1: Baza danych sklep
ok. 2 min
Polecenie z arkusza
Utwórz nową bazę danych o nazwie sklep
Rozpakuj materialy2.7z z pulpitu (hasło Zad@n!e#2 - wielkie Z, małpa zamiast „a”, wykrzyknik zamiast „i”) do folderu z numerem zdającego: sklep.sql i zakupy.png. W phpMyAdmin Nowa → sklep albo zapytaniem:
CREATE DATABASE sklep CHARACTER SET utf8 COLLATE utf8_polish_ci;
Arkusz nie podaje porównywania, więc wybór należy do Ciebie. Tabele z sklep.sql mają zestaw znaków zapisany we własnej definicji, więc ustawienie bazy ich nie zmienia.
Typowe błędy
Skrypt łączy się z bazą po nazwie - literówka w nazwie bazy zabiera też punkty za część PHP (R.5.1).
Krok 2: Import tabel z pliku sklep.sql
ok. 2 min
Polecenie z arkusza
Do bazy zaimportuj tabele z pliku sklep.sql, z wcześniej rozpakowanego archiwum
Zaznaczona baza sklep, Import → sklep.sql → Wykonaj. Dwie tabele z obrazu 1: dostawcy (3 rekordy) i towary (10 rekordów). idDostawcy wskazuje id dostawcy - to relacja z arkusza. Cena ma typ float, dlatego wyniki pokazują 4.5, a nie 4.50.
Krok 3: Zrzut ekranu import.jpg
Kryteria:
R.1.1 Wykonano import tabel do bazy danych o nazwie sklep , czynność udokumentowano plikiem o nazwie import w formacie JPEG
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie JPEG 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ń, w JPEG, pod nazwą import. Na zrzucie komunikat o zakończonym imporcie albo obie tabele w drzewie bazy sklep.
Typowe błędy
Wykadrowany zrzut albo zrzut bez paska zadań nie jest oceniany (uwaga w kluczu przy R.1).
Krok 4: Plik kwerendy.txt i zasady zrzutów
ok. 1 min
Polecenie z arkusza
Wykonaj zapytania SQL działające na bazie danych sklep. Zapytania zapisz w pliku kwerendy.txt. Wykonaj zrzuty ekranu przedstawiające wyniki działania kwerend. Zrzuty zapisz w formacie JPEG i nadaj im nazwy kwerenda1, kwerenda2, kwerenda3, kwerenda4. Zrzuty powinny obejmować cały ekran monitora z widocznym paskiem zadań oraz zawierać wykonane zapytanie
Każde zapytanie wykonaj w zakładce SQL bazy sklep i skopiuj do kwerendy.txt. phpMyAdmin pokazuje treść wykonanego zapytania nad wynikiem, więc zrzut całego ekranu od razu ją zawiera. Nazwy: kwerenda1.jpg - kwerenda4.jpg. Każda klauzula w osobnej linii - łatwiej przeczytać i poprawić.
Krok 5: Zapytanie 1: cztery pierwsze towary
Kryteria: ,
R.1.2 Zapisano plik kwerendy.txt zawierający co najmniej jedno zapytanie SQL, wynikające z treści zadania
R.1.3 Zapisano zapytanie 1 wybierające jedynie pola nazwa i cena z tabeli towary i wyświetlające jedynie cztery pierwsze zwrócone zapytaniem rekordy. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa, cena FROM towary LIMIT 4; oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym widoczne są dokładnie 4 rekordy: Zeszyt 60 kartek, 4.5; Zeszyt 32 kartki, 1.2; Cyrkiel, 12.4; Linijka 30 cm, 7.2
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie pola nazwa i cena z tabeli towary i wyświetlające jedynie cztery pierwsze zwrócone zapytaniem rekordy
ORDER BY id - kolejność według klucza głównego. Bez sortowania baza może zwrócić wiersze w dowolnej kolejności (zależy od planu zapytania), a wtedy „cztery pierwsze” nie są niczym ustalone,
LIMIT 4 - z posortowanego wyniku zostają cztery pierwsze rekordy.
Wynik: Zeszyt 60 kartek 4.5, Zeszyt 32 kartki 1.2, Cyrkiel 12.4, Linijka 30 cm 7.2 - jak w kluczu. Zrzut: kwerenda1.jpg. To zapytanie wyśle Skrypt 1.
Typowe błędy
WHERE id <= 4 daje na tej bazie ten sam wynik, ale nie jest „czterema pierwszymi rekordami” - klucz wymaga zapytania uniwersalnego.
kwerendy.txtTekstnowy plik
-- Zapytanie 1SELECT nazwa, cenaFROM towaryORDER BY idLIMIT 4;
Podgląd strony po tym kroku
Krok 6: Zapytanie 2: cena ekierki
Kryteria:
R.1.4 Zapisano zapytanie 2 wybierające jedynie pole cena z tabeli towary dla towaru Ekierka. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT cena FROM towary WHERE nazwa = 'Ekierka'; oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym widoczna jest jedynie cena 5.5
ok. 1 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie pole cena z tabeli towary dla towaru Ekierka
Jedno pole i warunek na całej nazwie. Wynik: 5.5. Zrzut: kwerenda2.jpg. Skrypt 2 wyśle to zapytanie z parametrem w miejscu nazwy.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT nazwa, cenaFROM towaryORDER BY idLIMIT 4;-- Zapytanie 2SELECT cenaFROM towaryWHERE nazwa = 'Ekierka';
Podgląd strony po tym kroku
Krok 7: Zapytanie 3: nowa nazwa dostawcy
Kryteria:
R.1.5 Zapisano zapytanie 3 aktualizujące dane w tabeli dostawcy , rekord o id równym 2 w polu nazwa przyjmuje nową wartość „Artykuly szkolne”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: UPDATE dostawcy SET nazwa = "Artykuly szkolne" WHERE id = 2; oraz wynik działania zapytania udokumentowano zrzutem wskazującym na poprawne wykonanie zapytania
ok. 2 min
Polecenie z arkusza
Zapytanie 3: aktualizujące dane w tabeli dostawcy, rekord o id równym 2 w polu nazwa przyjmuje nową wartość „Artykuly szkolne”
UPDATE ... SET ... WHERE id = 2 - nowa wartość tylko w rekordzie 2 (dotąd „Art. szkolne”), bez polskiej litery, dokładnie jak w arkuszu. Warunek po kluczu głównym trafia zawsze w jeden rekord. phpMyAdmin odpowiada „1 wiersz zmieniony” - to pokazuje zrzut kwerenda3.jpg.
Typowe błędy
UPDATE bez WHERE zmienia nazwę wszystkich trzech dostawców.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT nazwa, cenaFROM towaryORDER BY idLIMIT 4;-- Zapytanie 2SELECT cenaFROM towaryWHERE nazwa = 'Ekierka';-- Zapytanie 3UPDATE dostawcySET nazwa = 'Artykuly szkolne'WHERE id = 2;
Podgląd strony po tym kroku
Krok 8: Zapytanie 4: towary w promocji i poza nią
Kryteria:
R.1.6 Zapisano zapytanie 4 wybierające jedynie pole promocja z tabeli towary oraz zliczające liczbę towarów objętych i towarów nieobjętych promocją. Nazwa kolumny (alias) zliczającej podsumowanie. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT promocja, COUNT(*) AS podsumowanie FROM towary GROUP BY promocja; (w COUNT możliwa również nazwa dowolnego pola tabeli) oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym widoczne są jedynie dwa rekordy z danymi: 0 i 6 oraz 1 i 4
ok. 2 min
Polecenie z arkusza
Zapytanie 4: wybierające jedynie pole promocja dla wszystkich rekordów z tabeli towary oraz zliczające liczbę towarów objętych i towarów nieobjętych promocją. Nazwa kolumny (alias) dla zliczenia towarów: podsumowanie.
GROUP BY promocja - jedna grupa dla wartości 0 i jedna dla 1, a COUNT(*) liczy wiersze grupy pod aliasem podsumowanie. Wynik: 0 - 6 i 1 - 4. Zrzut: kwerenda4.jpg.
Typowe błędy
Bez GROUP BY wychodzi jeden wiersz z liczbą 10.
kwerendy.txtTekstzmienione: 5 linii
-- Zapytanie 1SELECT nazwa, cenaFROM towaryORDER BY idLIMIT 4;-- Zapytanie 2SELECT cenaFROM towaryWHERE nazwa = 'Ekierka';-- Zapytanie 3UPDATE dostawcySET nazwa = 'Artykuly szkolne'WHERE id = 2;-- Zapytanie 4SELECT promocja, COUNT(*) AS podsumowanieFROM towaryGROUP BY promocja;
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 9-22 · po tym etapie masz 19 z 39 kryteriów (pkt)
Krok 9: zakupy.png: 180 px szerokości z przezroczystością
Kryteria:
R.2.1 Obraz z pliku zakupy.png przeskalowano do szerokości 180 px, odpowiadająca temu wysokość wynosi 162 px (± 5 px) oraz obraz ma tło przezroczyste
ok. 2 min
Polecenie z arkusza
Plik zakupy.png, wypakowany z archiwum, należy przeskalować z zachowaniem proporcji tak, aby jego szerokość wynosiła dokładnie 180 px. Plik należy zapisać z zachowaniem przezroczystości pod tą samą nazwą
Grafika jest taka sama jak w wersji podstawowej. Obraz ma 377 × 340 px. W GIMP-ie Obraz → Skaluj obraz, łańcuch proporcji zamknięty, szerokość 180 - wysokość wyjdzie 162 px. Eksportuj jakozakupy.png: PNG zachowuje przezroczystość.
Typowe błędy
Eksport do JPEG albo spłaszczenie obrazu zamienia przezroczyste tło na białe - R.2.1 wymaga przezroczystości.
Podgląd strony po tym kroku
Krok 10: Plik index.php: HTML5, język, kodowanie
Kryteria:
R.2.2 Witrynę internetową zapisano w pliku index.php, zastosowano deklarację języka HTML5 <!DOCTYPE html>, zadeklarowano język witryny polski np. <html lang = "pl"> oraz zastosowano właściwy standard kodowania polskich znaków
ok. 2 min
Polecenie z arkusza
Składa się ze strony o nazwie index.php. Zapisana w języku HTML5. Ustawiony język zawartości strony na polski. Jawnie zastosowany właściwy standard kodowania polskich znaków
<!DOCTYPE html>, <html lang="pl"> i <meta charset="UTF-8"> jako pierwszy element <head> - razem R.2.2. Plik leży w htdocs XAMPP-a i otwierasz go przez http://localhost/....
R.3.6 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl.css , formatowanie pochodzi jedynie z tego arkusza Uwaga: kryterium to nie jest spełnione również, gdy 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
Całe formatowanie pochodzi ze styl.css - bez atrybutów style w HTML-u (R.3.6).
Typowe błędy
Klucz odbiera R.3.6 także wtedy, gdy choć jeden selektor nie trafia w blok (np. kropka zamiast #).
R.2.4 Wygląd strony zdefiniowano dzieląc ją na bloki: baner, panele lewy, środkowy, prawy oraz stopkę. Zastosowano znaczniki sekcji
ok. 2 min
Polecenie z arkusza
Podział strony na bloki: na górze baner, poniżej bloki: lewy, środkowy i prawy oraz na dole blok stopki. Podział zrealizowany za pomocą znaczników sekcji tak, aby wygląd układu bloków strony po uruchomieniu w przeglądarce, był zgodny z Obrazem 2
Baner <header>, stopka <footer>, a trzy panele we wspólnym <main> - to główna treść strony i pojemnik, który w CSS ustawi je w rząd:
lewy <section id="prices"> - ceny,
środkowy <section id="order"> - koszt zakupów,
prawy <aside id="contact"> - kontakt, treść dodatkowa obok głównej.
<section> i <aside> to znaczniki sekcji HTML5 (R.2.4). Identyfikatory mówią, co jest w panelu, a nie gdzie stoi.
Zawartość bloku lewego: Nagłówek drugiego stopnia o treści „Nasze ceny”. Tabela składająca się z czterech wierszy i dwóch kolumn o zawartości generowanej skryptem 1
<h2> i pusta <table> - wiersze <tr> z dwiema komórkami <td> wypisze Skrypt 1. Na obrazie 2 tabela nie ma wiersza nagłówkowego, więc nie ma też <th>.
Krok 16: Panel środkowy: nagłówek i formularz POST
Kryteria:
R.3.2 Dane z formularza przesyłane są za pomocą metody POST do tego samego pliku (poprawne również bez atrybutu action)
ok. 2 min
Polecenie z arkusza
Zawartość bloku środkowego: Nagłówek drugiego stopnia o treści „Koszt zakupów”. Formularz zgodny z obrazem 2, wysyłający dane do tego samego pliku metodą bezpieczną.
method="post" - „metodę bezpieczną” arkusz rozumie jako POST: dane idą w treści żądania, a nie w adresie strony (w specyfikacji HTTP słowo „bezpieczna” znaczy co innego, ale klucz R.3.2 wymaga POST). Bez action formularz wysyła dane do tego samego pliku. Z <h1>, trzema <h2>, <h4> w stopce i akapitem w prawym panelu złoży się R.2.5.
Typowe błędy
method="get" też wysyła dane, ale klucz R.3.2 wymaga POST.
Krok 17: Napis „wybierz artykuł: ” i lista rozwijalna
Kryteria:
R.2.7 W bloku środkowym umieszczono jedną listę rozwijaną, zawierającą nazwy produktów: Zeszyt 60 kartek, Zeszyt 32 kartki, Cyrkiel, Linijka 30 cm. Zastosowano znaczniki <select>, <option>
ok. 2 min
Polecenie z arkusza
Napis „wybierz artykuł: ”. Listę rozwijalną zawierającą nazwy towarów z tabeli: Zeszyt 60 kartek, Zeszyt 32 kartki, Cyrkiel, Linijka 30 cm.
napis to <label for="product"> - kliknięcie napisu przenosi kursor do listy, a czytnik ekranu odczyta go jako nazwę listy,
<select name="product"> z czterema <option> (R.2.7) - opcja bez value wysyła swój tekst, czyli nazwę towaru,
napis z listą stoją we wspólnym <div> - blok sam zaczyna nową linię, więc kolejne pole stanie pod spodem bez <br>.
Typowe błędy
Literówka w nazwie („Linijka 30cm” bez spacji) sprawi, że zapytanie w Skrypcie 2 nic nie znajdzie dla tego towaru.
Pole edycyjne typu numerycznego poprzedzone napisem „liczba sztuk: ”
type="number" przyjmuje tylko liczby (R.3.4). min="1" i required - przeglądarka nie wyśle formularza z pustym polem, zerem ani liczbą ujemną i sama pokaże komunikat. To wygoda dla użytkownika, nie zabezpieczenie: żądanie da się wysłać z pominięciem formularza, dlatego liczbę sprawdzi jeszcze Skrypt 2.
R.2.8 W bloku środkowym umieszczono pole edycyjne oraz przycisk OBLICZ
R.3.3 Przycisk w formularzu realizuje wysyłanie danych do skryptu (input lub button z typem submit lub button bez typu)
ok. 1 min
Polecenie z arkusza
Przycisk „OBLICZ”, którego wybranie powoduje przesłanie danych do skryptu 2
<button type="submit"> wysyła formularz (R.2.8, R.3.3). <button>, a nie <input type="submit">: arkusz nadaje marginesy selektorowi input, a przycisk zbudowany z input dostałby je razem z polem.
Krok 21: Panel prawy: nagłówek, obraz i odsyłacz pocztowy
Kryteria: ,
R.2.9 W bloku prawym umieszczono obraz zakupy.png z tekstem alternatywnym "hurtownia"
R.3.5 Napis [email protected] jest odsyłaczem pocztowym, w definicji zastosowano "mailto:"
ok. 2 min
Polecenie z arkusza
Zawartość bloku prawego: Nagłówek drugiego stopnia o treści „Kontakt”. Obraz zakupy.png z tekstem alternatywnym „hurtownia”. Paragraf (akapit) „e-mail: [email protected]”, gdzie [email protected] jest odsyłaczem pocztowym łączącym z tym adresem
Obraz z tekstem alternatywnym z arkusza (R.2.9), pod nim akapit z odnośnikiem mailto: - kliknięcie otwiera program pocztowy z wpisanym odbiorcą (R.3.5).
Typowe błędy
href="[email protected]" bez mailto: przeglądarka potraktuje jak plik w bieżącym folderze.
R.2.5 Zapisano w banerze nagłówek h1 "Hurtownia z najlepszymi cenami”, w panelach przynajmniej jeden z nagłówków h2: "Nasze ceny”, "Koszt zakupów”, "Kontakt" i nagłówek h4 z numerem zdającego w stopce oraz w panelu prawym zapisano paragraf
ok. 1 min
Polecenie z arkusza
Zawartość stopki: nagłówek czwartego stopnia o treści „Witrynę wykonał: ”, dalej wstawiony numer zdającego
<h4> w <footer>. Na egzaminie po dwukropku wpisujesz swój numer zdającego (PESEL), tu stoi autor rozwiązania. Strona ma już komplet nagłówków i akapit z kryterium R.2.5.
Kroki 23-31 · po tym etapie masz 30 z 39 kryteriów (pkt)
Krok 23: Plik styl.css: zmienne, krój Georgia i wyśrodkowanie
Kryteria:
R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (*) krój czcionki Georgia i wyrównanie tekstu do środka
ok. 2 min
Polecenie z arkusza
Domyślne formatowanie wszystkich selektorów: krój czcionki Georgia i wyrównanie tekstu do środka
Trzy kolory z arkusza w zmiennych - #90CAF9 pojawia się aż w czterech regułach (panel, dwie ramki, kolor wiersza), więc zmiana odcienia to jedna linia. Selektor * z Georgią i krojem zapasowym serif (R.4.1).
Dla panelu lewego i prawego: kolor tła #EFEFEF wysokość 400 px, szerokość 25%
Selektory identyfikatorów (#). Szerokość zostaje na samych panelach, tak jak ją ocenia klucz - pojemnik flex liczy procenty od <main>, czyli od całej szerokości strony.
Typowe błędy
.prices (kropka) zamiast #prices nie trafia w panel - klucz R.3.6 wprost wymienia ten błąd.
R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest zgodny z obrazem 2 w arkuszu (prawidłowo zastosowano właściwości CSS układające bloki na stronie, np. float i clear albo display z wartością flex lub grid albo position)
ok. 2 min
Polecenie z arkusza
Podział strony na bloki: na górze baner, poniżej bloki: lewy, środkowy i prawy oraz na dole blok stopki. Podział zrealizowany za pomocą znaczników sekcji tak, aby wygląd układu bloków strony po uruchomieniu w przeglądarce, był zgodny z Obrazem 2
display: flex na <main> ustawia trzy panele w rzędzie, a baner i stopka stoją nad i pod nim same z siebie - bez float i bez clear (R.3.1).
footer h4 { margin-top: 0; } - górny margines nagłówka stopki „wychodzi” poza stopkę (marginesy stykających się bloków się sklejają) i między panelami a stopką zostałby biały pas. W wersji z float pochłania go clear, tu trzeba go wyzerować.
Dlaczego nie siatka? W siatce procent szerokości elementu liczy się od jego kolumny: panel z width: 25% w kolumnie szerokiej na 25% strony zająłby 6% strony. Szerokości trzeba by przenieść do grid-template-columns, a klucz z tego arkusza ocenia je na panelach.
Typowe błędy
Margines albo ramka dodana panelom przekracza 100% szerokości - flex ściśnie wtedy panele i żaden nie będzie miał dokładnie 25% ani 50%.
R.4.7 Ustawiono styl selektora table: margines zewnętrzny lewy (np. margin-left) 10px, szerokość 90% oraz ustawiono dla selektora komórki tabeli marginesy wewnętrzne (padding) 5px
R.4.8 Ustawiono obramowanie tabeli oraz komórki tabeli na 1px solid #90CAF9
ok. 1 min
Polecenie z arkusza
Dla selektora komórek tabeli: marginesy wewnętrzne 5 px, obramowanie komórek tabeli linią ciągłą o szerokości 1 px i kolorze #90CAF9
Komórka to td, marginesy wewnętrzne to padding. Bez border-collapse każda komórka ma osobną ramkę z odstępem od ramki tabeli - tak wygląda tabela na obrazie 2. Z poprzednią regułą: R.4.7 i R.4.8.
Typowe błędy
Reguła #prices td albo klasa komórki nie spełnia R.4.7 - klucz wymaga samego selektora znacznika.
R.4.10 Ustawiono margines zewnętrzny górny i dolny (np. margin-top i margin-bottom) 15px dla selektora input
ok. 1 min
Polecenie z arkusza
Dla selektora pola edycyjnego: margines zewnętrzny górny i dolny 15 px
margin-block: 15px - jedna właściwość ustawia oba marginesy w kierunku bloku, czyli górny i dolny (w tekście poziomym), i niczego nie zmienia po bokach. Selektor znacznika input (R.4.10).
Typowe błędy
margin: 15px ustawia także marginesy boczne - klucz mówi o górnym i dolnym. Gdy nie masz pewności co do margin-block, wpisz margin-top i margin-bottom - to samo, tylko dłużej.
Kroki 32-40 · po tym etapie masz 39 z 39 kryteriów (pkt)
Krok 32: Połączenie z bazą na górze pliku i zamknięcie
Kryteria: ,
R.5.1 Zastosowano funkcję realizującą połączenie z serwerem bazy danych na localhost, użytkownik root, bez hasła, baza danych sklep
R.5.2 Ostatnią operacją na bazie jest zamknięcie połączenia z serwerem bazy danych
ok. 4 min
Polecenie z arkusza
Napisany w języku PHP, umieszczony w pliku index.php. Skrypt łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie sklep. Na końcu działania skrypt zamyka połączenie z serwerem
Wszystkie dane strona pobierze przed HTML-em, w jednym bloku PHP na samej górze index.php - kod rozmawiający z bazą jest w jednym miejscu, a połączenie zamyka się, zanim przeglądarka dostanie pierwszy znacznik.
new PDO(...) - serwer localhost, baza sklep, kodowanie utf8mb4, użytkownik root bez hasła (R.5.1). ERRMODE_EXCEPTION - błąd bazy rzuca wyjątek, zamiast zwracać false; FETCH_ASSOC - wiersze z nazwami kolumn,
$pdo = null - połączenie PDO zamyka się, gdy znika ostatnie odwołanie do obiektu. Stoi na końcu bloku, więc pozostanie ostatnią operacją na bazie (R.5.2),
try / catch (PDOException ...) - gdy baza nie działa, uczeń zobaczy zwykłe zdanie, a nie komunikat PHP ze szczegółami serwera.
PDO zamiast mysqli z tabeli 1 - klucz przyjmuje obie biblioteki (uwaga przy R.5). Od tego kroku podgląd to wynik prawdziwego uruchomienia strony.
R.5.3 Skrypt 1 zawiera instrukcję wysyłającą do bazy danych zapytanie 1
ok. 2 min
Polecenie z arkusza
Skrypt 1: Wysyła do bazy danych zapytanie 1
Zapytanie bez danych od użytkownika, więc wystarczy query. fetchAll() zwraca od razu wszystkie cztery wiersze jako tablice z kluczami nazwa i cena. W catch lista dostaje wartość pustą, żeby HTML niżej nie trafił na niezdefiniowaną zmienną (R.5.3). Na stronie jeszcze nic nie widać.
index.phpPHPzmienione: 4 linie
<?phptry {$pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - cztery pierwsze towary$priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$priceList = [];$databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Hurtownia szkolna</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Hurtownia z najlepszymi cenami</h1> </header> <main> <sectionid="prices"> <h2>Nasze ceny</h2> <table> </table> </section> <sectionid="order"> <h2>Koszt zakupów</h2> <formmethod="post"> <div> <labelfor="product">wybierz artykuł: </label> <selectid="product"name="product"> <option>Zeszyt 60 kartek</option> <option>Zeszyt 32 kartki</option> <option>Cyrkiel</option> <option>Linijka 30 cm</option> </select> </div> <div> <labelfor="amount">liczba sztuk: </label> <inputtype="number"id="amount"name="amount"min="1"required> </div> <buttontype="submit">OBLICZ</button> </form> <divid="result"> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <imgsrc="zakupy.png"alt="hurtownia"> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> </aside> </main> <footer> <h4>Witrynę wykonał: Bartosz Bryniarski</h4> </footer></body></html>
R.2.6 W bloku lewym umieszczono tabelę o 2 kolumnach (w kodzie HTML lub skrypcie), zastosowano znaczniki <table>, <tr>, <td>
R.5.4 Skrypt 1 wyświetla dane zwrócone zapytaniem w tabeli, każdy rekord w osobnym wierszu
ok. 3 min
Polecenie z arkusza
Wyświetla dane pobrane zapytaniem wewnątrz komórek tabeli. W pierwszej kolumnie tabeli wypisywane są nazwy czterech towarów, w drugiej kolumnie odpowiadające im ceny (zgodnie z obrazem 2)
foreach z dwukropkiem i endforeach - jedna pętla na rekord, każdy rekord to osobny wiersz <tr> (R.5.4),
pola po nazwach kolumn ($item['nazwa'], $item['cena']), nie po numerach - zmiana kolejności pól w SELECT niczego nie psuje,
funkcja e() (na górze pliku) przepuszcza każdą daną z bazy przez htmlspecialchars - znak < albo & w nazwie towaru nie rozbije strony. <?= ... ?> to skrót od <?php echo ... ?>.
Tabela ma cztery wiersze i dwie kolumny (R.2.6): Zeszyt 60 kartek 4.5, Zeszyt 32 kartki 1.2, Cyrkiel 12.4, Linijka 30 cm 7.2 - jak na obrazie 2. Po najechaniu kursorem wiersz zmienia kolor.
index.phpPHPzmienione: 11 linii
<?phpfunctione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - cztery pierwsze towary$priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$priceList = [];$databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Hurtownia szkolna</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Hurtownia z najlepszymi cenami</h1> </header> <main> <sectionid="prices"> <h2>Nasze ceny</h2> <table><?phpforeach ($priceListas$item): ?> <tr> <td><?= e($item['nazwa']) ?></td> <td><?= e($item['cena']) ?></td> </tr><?phpendforeach; ?> </table> </section> <sectionid="order"> <h2>Koszt zakupów</h2> <formmethod="post"> <div> <labelfor="product">wybierz artykuł: </label> <selectid="product"name="product"> <option>Zeszyt 60 kartek</option> <option>Zeszyt 32 kartki</option> <option>Cyrkiel</option> <option>Linijka 30 cm</option> </select> </div> <div> <labelfor="amount">liczba sztuk: </label> <inputtype="number"id="amount"name="amount"min="1"required> </div> <buttontype="submit">OBLICZ</button> </form> <divid="result"> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <imgsrc="zakupy.png"alt="hurtownia"> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> </aside> </main> <footer> <h4>Witrynę wykonał: Bartosz Bryniarski</h4> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 35: Skrypt 2: nazwa towaru z listy
Kryteria:
R.5.5 Skrypt 2 zawiera instrukcję pobierającą dane z tablicy globalnej odpowiadającej zastosowanej metodzie formularza ($_POST, $_GET, $_REQUEST)
ok. 4 min
Polecenie z arkusza
Skrypt 2: Pobiera z formularza nazwę towaru wybraną w liście rozwijalnej
$_POST['product'] ?? null - wybór z tablicy globalnej $_POST (R.5.5: klucz wymaga instrukcji czytającej tablicę odpowiadającą metodzie formularza, dlatego tu nie ma filter_input, które czyta dane z pominięciem tej tablicy). Operator ?? daje null, gdy formularza jeszcze nie wysłano - bez ostrzeżenia „Undefined array key”,
const PRODUCTS - cztery nazwy z arkusza w jednej stałej. Z niej pętla wypisuje opcje listy (wynik ten sam co wcześniej, R.2.7), a in_array(..., true) sprawdza, czy wysłana nazwa jest na tej liście (biała lista). Listę rozwijalną da się obejść, wysyłając żądanie z pominięciem formularza - sprawdzenie po stronie serwera nie,
selected przy opcji, która była wybrana - po wysłaniu lista pokazuje ten sam towar, jak na obrazie 2.
Od tego kroku podgląd pokazuje stronę po wysłaniu formularza z obrazu 2 (Zeszyt 32 kartki, 2 sztuki).
index.phpPHPzmienione: 9 linii
<?phpconst PRODUCTS = ['Zeszyt 60 kartek', 'Zeszyt 32 kartki', 'Cyrkiel', 'Linijka 30 cm'];// Skrypt 2: dane z formularza$product = $_POST['product'] ?? null;$isKnownProduct = in_array($product, PRODUCTS, true);functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - cztery pierwsze towary$priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$priceList = [];$databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Hurtownia szkolna</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Hurtownia z najlepszymi cenami</h1> </header> <main> <sectionid="prices"> <h2>Nasze ceny</h2> <table><?phpforeach ($priceListas$item): ?> <tr> <td><?= e($item['nazwa']) ?></td> <td><?= e($item['cena']) ?></td> </tr><?phpendforeach; ?> </table> </section> <sectionid="order"> <h2>Koszt zakupów</h2> <formmethod="post"> <div> <labelfor="product">wybierz artykuł: </label> <selectid="product"name="product"><?phpforeach (PRODUCTS as$option): ?> <option<?= $option === $product ? ' selected' : ''?>><?= e($option) ?></option><?phpendforeach; ?> </select> </div> <div> <labelfor="amount">liczba sztuk: </label> <inputtype="number"id="amount"name="amount"min="1"required> </div> <buttontype="submit">OBLICZ</button> </form> <divid="result"> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <imgsrc="zakupy.png"alt="hurtownia"> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> </aside> </main> <footer> <h4>Witrynę wykonał: Bartosz Bryniarski</h4> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 36: Skrypt 2: liczba sztuk z pola
ok. 3 min
Polecenie z arkusza
Pobiera z formularza liczbę wpisaną w polu edycyjnym
filter_var(..., FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]) - wartość z $_POST zamienia się w liczbę całkowitą, ale tylko wtedy, gdy jest liczbą całkowitą od 1 wzwyż. Puste pole, „-3”, „2,5” albo „abc” dają false - nic z tego nie trafi do mnożenia,
pole liczbowe dostaje value ze sprawdzoną liczbą - po kliknięciu OBLICZ zostaje w nim 2, jak na obrazie 2. Wypisywana jest wartość po filter_var, czyli zawsze liczba całkowita albo nic - surowy tekst z formularza nie wraca na stronę.
Typowe błędy
Samo $_POST['amount'] w mnożeniu przy pustym polu kończy się w PHP 8 błędem krytycznym „Unsupported operand types: string * string” - strona urywa się w połowie.
R.5.6 Skrypt 2 zawiera instrukcję wysyłającą do bazy danych zapytanie 2 zmodyfikowane tak, że w sekcji warunku nazwa towaru jest zgodna z wyborem w liście rozwijanej
ok. 3 min
Polecenie z arkusza
Wysyła do bazy danych zapytanie 2, zmodyfikowane w ten sposób, że w sekcji warunku nazwa towaru jest zgodna z wyborem w liście rozwijalnej
Zapytanie przygotowane: w miejscu nazwy stoi parametr :name, a wartość podaje execute(["name" => $product]). Baza dostaje treść zapytania i dane osobno, więc nazwa z formularza nigdy nie stanie się częścią kodu SQL (R.5.6).
zapytanie wykonuje się tylko dla towaru z listy i poprawnej liczby sztuk,
fetchColumn() zwraca samą cenę pierwszego wiersza albo false, gdy wiersza nie ma,
$statement = null zwalnia zapytanie przed zamknięciem połączenia.
index.phpPHPzmienione: 10 linii
<?phpconst PRODUCTS = ['Zeszyt 60 kartek', 'Zeszyt 32 kartki', 'Cyrkiel', 'Linijka 30 cm'];// Skrypt 2: dane z formularza$product = $_POST['product'] ?? null;$isKnownProduct = in_array($product, PRODUCTS, true);$amount = filter_var($_POST['amount'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - cztery pierwsze towary$priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();// Skrypt 2: zapytanie 2 - cena wybranego towaru$price = false;if ($isKnownProduct && $amount !== false) {$statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');$statement->execute(['name' => $product]);$price = $statement->fetchColumn();$statement = null; }$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$priceList = [];$price = false;$databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Hurtownia szkolna</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Hurtownia z najlepszymi cenami</h1> </header> <main> <sectionid="prices"> <h2>Nasze ceny</h2> <table><?phpforeach ($priceListas$item): ?> <tr> <td><?= e($item['nazwa']) ?></td> <td><?= e($item['cena']) ?></td> </tr><?phpendforeach; ?> </table> </section> <sectionid="order"> <h2>Koszt zakupów</h2> <formmethod="post"> <div> <labelfor="product">wybierz artykuł: </label> <selectid="product"name="product"><?phpforeach (PRODUCTS as$option): ?> <option<?= $option === $product ? ' selected' : ''?>><?= e($option) ?></option><?phpendforeach; ?> </select> </div> <div> <labelfor="amount">liczba sztuk: </label> <inputtype="number"id="amount"name="amount"min="1"requiredvalue="<?= $amount === false ? '' : $amount?>"> </div> <buttontype="submit">OBLICZ</button> </form> <divid="result"> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <imgsrc="zakupy.png"alt="hurtownia"> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> </aside> </main> <footer> <h4>Witrynę wykonał: Bartosz Bryniarski</h4> </footer></body></html>
R.5.7 Skrypt 2 oblicza wartość zakupów przez pomnożenie ceny zwróconej zapytaniem z liczbą pobraną z pola edycyjnego
ok. 2 min
Polecenie z arkusza
Oblicza wartość zakupów na podstawie ceny towaru zwróconej zapytaniem i liczby sztuk tego towaru pobranej z pola edycyjnego
Wartość zakupów to cena razy liczba sztuk: $price * $amount (R.5.7). round(..., 2) zaokrągla do groszy - mnożenie liczb zmiennoprzecinkowych potrafi dać np. 3.5999999999999996 zamiast 3.6. Dla zeszytu za 1.2 i 2 sztuk: 2.4.
index.phpPHPzmienione: 3 linie
<?phpconst PRODUCTS = ['Zeszyt 60 kartek', 'Zeszyt 32 kartki', 'Cyrkiel', 'Linijka 30 cm'];// Skrypt 2: dane z formularza$product = $_POST['product'] ?? null;$isKnownProduct = in_array($product, PRODUCTS, true);$amount = filter_var($_POST['amount'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - cztery pierwsze towary$priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();// Skrypt 2: zapytanie 2 - cena wybranego towaru$price = false;if ($isKnownProduct && $amount !== false) {$statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');$statement->execute(['name' => $product]);$price = $statement->fetchColumn();$statement = null; }if ($price !== false) {$total = round($price * $amount, 2); // wartość zakupów }$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$priceList = [];$price = false;$databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Hurtownia szkolna</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Hurtownia z najlepszymi cenami</h1> </header> <main> <sectionid="prices"> <h2>Nasze ceny</h2> <table><?phpforeach ($priceListas$item): ?> <tr> <td><?= e($item['nazwa']) ?></td> <td><?= e($item['cena']) ?></td> </tr><?phpendforeach; ?> </table> </section> <sectionid="order"> <h2>Koszt zakupów</h2> <formmethod="post"> <div> <labelfor="product">wybierz artykuł: </label> <selectid="product"name="product"><?phpforeach (PRODUCTS as$option): ?> <option<?= $option === $product ? ' selected' : ''?>><?= e($option) ?></option><?phpendforeach; ?> </select> </div> <div> <labelfor="amount">liczba sztuk: </label> <inputtype="number"id="amount"name="amount"min="1"requiredvalue="<?= $amount === false ? '' : $amount?>"> </div> <buttontype="submit">OBLICZ</button> </form> <divid="result"> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <imgsrc="zakupy.png"alt="hurtownia"> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> </aside> </main> <footer> <h4>Witrynę wykonał: Bartosz Bryniarski</h4> </footer></body></html>
R.5.8 Kwota wyświetlona jest pod formularzem w formacie: „wartość zakupów: <obliczona_kwota>”
ok. 2 min
Polecenie z arkusza
Obliczona kwota wyświetlana jest pod formularzem w formacie: „wartość zakupów: <obliczona_kwota>”, zgodnie z obrazem 2.
Napis z arkusza i kwota w bloku #result pod formularzem: „wartość zakupów: 2.4” (R.5.8). if ($price !== false) - przed wysłaniem formularza blok zostaje pusty. Format liczby jak na obrazie 2 (kropka dziesiętna).
index.phpPHPzmienione: 3 linie
<?phpconst PRODUCTS = ['Zeszyt 60 kartek', 'Zeszyt 32 kartki', 'Cyrkiel', 'Linijka 30 cm'];// Skrypt 2: dane z formularza$product = $_POST['product'] ?? null;$isKnownProduct = in_array($product, PRODUCTS, true);$amount = filter_var($_POST['amount'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - cztery pierwsze towary$priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();// Skrypt 2: zapytanie 2 - cena wybranego towaru$price = false;if ($isKnownProduct && $amount !== false) {$statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');$statement->execute(['name' => $product]);$price = $statement->fetchColumn();$statement = null; }if ($price !== false) {$total = round($price * $amount, 2); // wartość zakupów }$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$priceList = [];$price = false;$databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Hurtownia szkolna</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Hurtownia z najlepszymi cenami</h1> </header> <main> <sectionid="prices"> <h2>Nasze ceny</h2> <table><?phpforeach ($priceListas$item): ?> <tr> <td><?= e($item['nazwa']) ?></td> <td><?= e($item['cena']) ?></td> </tr><?phpendforeach; ?> </table> </section> <sectionid="order"> <h2>Koszt zakupów</h2> <formmethod="post"> <div> <labelfor="product">wybierz artykuł: </label> <selectid="product"name="product"><?phpforeach (PRODUCTS as$option): ?> <option<?= $option === $product ? ' selected' : ''?>><?= e($option) ?></option><?phpendforeach; ?> </select> </div> <div> <labelfor="amount">liczba sztuk: </label> <inputtype="number"id="amount"name="amount"min="1"requiredvalue="<?= $amount === false ? '' : $amount?>"> </div> <buttontype="submit">OBLICZ</button> </form> <divid="result"><?phpif ($price !== false): ?> wartość zakupów: <?= e($total) ?><?phpendif; ?> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <imgsrc="zakupy.png"alt="hurtownia"> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> </aside> </main> <footer> <h4>Witrynę wykonał: Bartosz Bryniarski</h4> </footer></body></html>
Dodatek spoza arkusza - klucz go nie ocenia. Blok wyniku odpowiada teraz także w sytuacjach, których obraz 2 nie pokazuje:
zła liczba sztuk (puste pole, „-3”, „2.5”, „abc” - wysłane z pominięciem formularza, bo required i min blokują je w przeglądarce) - „Podaj liczbę sztuk większą od zera.”,
towar spoza listy (np. ' OR '1'='1) - zapytanie się nie wykonuje, w bloku „Wybierz towar z listy.”,
awaria bazy (catch) - komunikat z role="alert", a reszta strony działa dalej.
Sprawdzone na prawdziwym PHP 8.2: cztery towary z listy (np. Cyrkiel × 3 = 37.2), cztery złe liczby, napis spoza listy i złe hasło do bazy - za każdym razem cała strona, bez ostrzeżeń PHP.
index.phpPHPzmienione: 7 linii
<?phpconst PRODUCTS = ['Zeszyt 60 kartek', 'Zeszyt 32 kartki', 'Cyrkiel', 'Linijka 30 cm'];// Skrypt 2: dane z formularza$product = $_POST['product'] ?? null;$isKnownProduct = in_array($product, PRODUCTS, true);$amount = filter_var($_POST['amount'] ?? null, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);functione(string|int|float|null$value): string{returnhtmlspecialchars((string) $value, ENT_QUOTES);}try {$pdo = new PDO('mysql:host=localhost;dbname=sklep;charset=utf8mb4', 'root', '', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]);// Skrypt 1: zapytanie 1 - cztery pierwsze towary$priceList = $pdo->query('SELECT nazwa, cena FROM towary ORDER BY id LIMIT 4')->fetchAll();// Skrypt 2: zapytanie 2 - cena wybranego towaru$price = false;if ($isKnownProduct && $amount !== false) {$statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');$statement->execute(['name' => $product]);$price = $statement->fetchColumn();$statement = null; }if ($price !== false) {$total = round($price * $amount, 2); // wartość zakupów }$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$priceList = [];$price = false;$databaseError = 'Nie udało się pobrać danych z bazy. Spróbuj odświeżyć stronę za chwilę.';}?><!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Hurtownia szkolna</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Hurtownia z najlepszymi cenami</h1> </header> <main> <sectionid="prices"> <h2>Nasze ceny</h2> <table><?phpforeach ($priceListas$item): ?> <tr> <td><?= e($item['nazwa']) ?></td> <td><?= e($item['cena']) ?></td> </tr><?phpendforeach; ?> </table> </section> <sectionid="order"> <h2>Koszt zakupów</h2> <formmethod="post"> <div> <labelfor="product">wybierz artykuł: </label> <selectid="product"name="product"><?phpforeach (PRODUCTS as$option): ?> <option<?= $option === $product ? ' selected' : ''?>><?= e($option) ?></option><?phpendforeach; ?> </select> </div> <div> <labelfor="amount">liczba sztuk: </label> <inputtype="number"id="amount"name="amount"min="1"requiredvalue="<?= $amount === false ? '' : $amount?>"> </div> <buttontype="submit">OBLICZ</button> </form> <divid="result"><?phpif (isset($databaseError)): ?> <prole="alert"><?= e($databaseError) ?></p><?phpelseif ($product !== null && !$isKnownProduct): ?> <prole="alert">Wybierz towar z listy.</p><?phpelseif ($product !== null && $amount === false): ?> <prole="alert">Podaj liczbę sztuk większą od zera.</p><?phpendif; ?><?phpif ($price !== false): ?> wartość zakupów: <?= e($total) ?><?phpendif; ?> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <imgsrc="zakupy.png"alt="hurtownia"> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> </aside> </main> <footer> <h4>Witrynę wykonał: Bartosz Bryniarski</h4> </footer></body></html>
Krok 41 · po tym etapie masz 39 z 39 kryteriów (pkt)
Krok 41: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 3 min
Obie wersje dają komplet 39 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ
float: left paneli i clear stopki
<main> z display: flex
formularz
<br> między polami
wiersze w <div>, min, required, zapamiętany wybór
zapytanie 1
LIMIT 4
ORDER BY id LIMIT 4 - kolejność ustalona
biblioteka
mysqli (tabela 1 arkusza)
PDO z wyjątkami
gdzie są zapytania
skrypty w tabeli i pod formularzem
jeden blok na górze index.php
liczba sztuk
$_POST wprost do mnożenia
$_POST przez filter_var (liczba od 1)
nazwa w zapytaniu 2
wklejona z $_POST
biała lista + parametr :name
wypisywanie
dane z bazy wprost
e() - htmlspecialchars
puste pole, zła nazwa
błąd krytyczny PHP / „wartość zakupów: 0”
komunikaty
Klucz wymaga tu odczytu z tablicy $_POST (R.5.5), dlatego obie wersje ją czytają. Dane logowania do bazy w prawdziwym projekcie trzyma się zwykle w osobnym pliku (np. config.php), ale arkusz każe umieścić skrypt w pliku index.php.
Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 i łatwiej ją napisać bez dokumentacji. Nowoczesna nie wywraca się na pustym polu i podmienionych danych. Obie biblioteki klucz przyjmuje.
Krok 42 · po tym etapie masz 39 z 39 kryteriów (pkt)
Krok 42: Plik przeglądarka.txt i nagranie płyty
ok. 5 min
Polecenie z arkusza
Po zakończeniu pracy utwórz w folderze z numerem zdającego plik tekstowy o nazwie przeglądarka.txt. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowana była poprawność działania witryny. Nagraj płytę z rezultatami pracy. W folderze z numerem zdającego powinny się znajdować następujące pliki: import.jpg, index.php, kwerenda1.jpg, kwerenda2.jpg, kwerenda3.jpg, kwerenda4.jpg, kwerendy.txt, przeglądarka.txt, styl.css, zakupy.png oraz ewentualnie inne przygotowane pliki.
W przeglądarka.txt nazwa przeglądarki, w której sprawdzana była strona przez http://localhost/.... Sprawdź listę plików z arkusza, nagraj płytę, sprawdź jej odczyt i opisz ją numerem zdającego.
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.