INF.03-01-23.06-SGCzerwiec 2023 · 36 kroków · ok. 72 min · 38 kryteriów z klucza
Rozwiązanie krok po kroku: Portal sklepu dla uczniów (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, trzema panelami, formularzem sprawdzania ceny 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 35%, 30% i 35% zostają na samych panelach - tak ocenia je klucz,
zmienne CSS dla kolorów z arkusza i panele nazwane po treści (#offers, #price-check, #contact),
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,
jedna lista towarów w stałej PRODUCTS: z niej powstają opcje listy rozwijalnej i z nią porównywany jest wybór z formularza (biała lista), a do bazy nazwa trafia zapytaniem przygotowanym,
bezpieczne wypisywanie (htmlspecialchars w funkcji e()) i komunikaty dla towaru spoza listy i dla awarii bazy.
Na końcu krótkie porównanie z wersją podstawową (zakładka obok). Baza i grafika pochodzą z prawdziwego archiwum materialy1, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2 na bazie z arkusza - od kroku z zapytaniem 2 po wysłaniu formularza z wybraną gumką do mazania, jak na obrazie 2.
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 sklep
Kryteria:
R.1.1 Wykonano import tabel do bazy danych o nazwie sklep , czynność udokumentowano plikiem o nazwie baza w formacie JPEG
ok. 2 min
Polecenie z arkusza
Utwórz nową bazę danych o nazwie sklep.
Rozpakuj materialy1.7z z pulpitu (hasło Z@dani3x - małpa zamiast „a”, cyfra 3 zamiast „e”) do folderu z numerem zdającego: sklep.sql i promocja.png. W phpMyAdmin Nowa → sklep albo zapytaniem:
CREATE DATABASE sklep CHARACTER SET utf8mb4 COLLATE utf8mb4_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
Kryteria:
R.1.1 Wykonano import tabel do bazy danych o nazwie sklep , czynność udokumentowano plikiem o nazwie baza w formacie JPEG
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). promocja ma wartość 0 albo 1, a idDostawcy wskazuje id dostawcy - na tej parze pól opiera się relacja.
Krok 3: Zrzut ekranu baza.jpg
Kryteria:
R.1.1 Wykonano import tabel do bazy danych o nazwie sklep , czynność udokumentowano plikiem o nazwie baza w formacie JPEG
ok. 2 min
Polecenie z arkusza
Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie JPEG i nazwij baza. 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ą baza - w tym arkuszu zrzut importu nie nazywa się import. Zrzuty zapytań to kwerenda1 - kwerenda4, też w JPEG, i muszą pokazywać także treść zapytania (phpMyAdmin wyświetla ją nad wynikiem).
Typowe błędy
Wykadrowany zrzut, zrzut bez paska zadań albo nazwa import.jpg zamiast baza.jpg - R.1.1 przepada.
Krok 4: Zapytanie 1: towary objęte promocją
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 pole nazwa z tabeli towary dla towarów objętych promocją. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT nazwa FROM towary WHERE promocja = 1; oraz wynik działania zapytania udokumentowano zrzutem ekranu, na którym widoczne są jedynie nazwy towarów: Gumka do mazania, Cienkopis, Pisaki 60 szt., Markery 4 szt.
ok. 2 min
Polecenie z arkusza
Zapytanie 1: wybierające jedynie pole nazwa z tabeli towary dla towarów objętych promocją.
„Objęte promocją” to w bazie promocja = 1. Każda klauzula w osobnej linii - łatwiej przeczytać i poprawić. Wynik: 4 wiersze - Gumka do mazania, Cienkopis, Pisaki 60 szt., Markery 4 szt. Zapytanie do kwerendy.txt, zrzut kwerenda1.jpg.
R.1.4 Zapisano zapytanie 2 wybierające jedynie pole cena z tabeli towary dla towaru Markery 4 szt. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT cena FROM towary WHERE nazwa = 'Markery 4 szt.'; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczna jest jedynie cena 22.4
ok. 1 min
Polecenie z arkusza
Zapytanie 2: wybierające jedynie pole cena z tabeli towary dla towaru Markery 4 szt.
Warunek porównuje cały napis, razem ze spacjami i kropką. Wynik: 22.4. Zrzut: kwerenda2.jpg. To samo zapytanie wyśle Skrypt 2 - z parametrem w miejscu nazwy.
Typowe błędy
nazwa = 'Markery 4 szt' bez kropki na końcu nie znajdzie towaru - wynik jest pusty.
Krok 6: Zapytanie 3: liczba towarów każdego dostawcy
Kryteria:
R.1.5 Zapisano zapytanie 3 wybierające jedynie pola id i nazwa z tabeli dostawcy oraz zliczające liczbę towarów dostarczanych przez każdego z dostawców. Nazwa kolumny (alias) zliczającej liczba_towarow. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT dostawcy.id, dostawcy.nazwa, COUNT(towary.id) AS liczba_towarow FROM dostawcy JOIN towary ON idDostawcy = dostawcy.id GROUP BY dostawcy.nazwa; (lub INNER JOIN, lub wersja bez JOIN z porównaniem kluczy po WHERE, w funkcji COUNT możliwa również *) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są dokładnie 3 rekordy, jedynie pola id, nazwa z tabeli dostawcy oraz odpowiadająca im liczba_towarow: Art. szkolne - 4, Papiernictwo - 4, Wszystko dla ucznia - 2
ok. 3 min
Polecenie z arkusza
Zapytanie 3: wybierające jedynie pola id oraz nazwa dla wszystkich rekordów z tabeli dostawcy oraz zliczające liczbę towarów dostarczanych przez każdego z dostawców. Nazwa kolumny (alias) dla zliczenia towarów: liczba_towarow. W zapytaniu 3 należy posłużyć się relacją.
aliasy tabeld i t - obie tabele mają kolumny id i nazwa, a krótki przedrostek mówi, z której tabeli pochodzi każde pole,
LEFT JOIN ... ON t.idDostawcy = d.id - relacja z obrazu 1. Arkusz chce „wszystkich rekordów z tabeli dostawcy”: złączenie lewostronne zostawia także dostawcę bez towarów, a COUNT(t.id) policzy mu wtedy 0 (COUNT(*) policzyłby 1 - pusty wiersz złączenia),
GROUP BY d.id, d.nazwa - grupa na dostawcę, po obu wybranych polach.
Wynik: 3 wiersze - Papiernictwo 4, Art. szkolne 4, Wszystko dla ucznia 2. Każdy dostawca ma tu towary, więc zwykły JOIN z przykładu w kluczu daje te same wiersze. Zrzut: kwerenda3.jpg.
Typowe błędy
SELECT id, nazwa ... bez przedrostków tabel kończy się błędem „column is ambiguous”.
kwerendy.txtTekstzmienione: 6 linii
-- Zapytanie 1SELECT nazwaFROM towaryWHERE promocja = 1;-- Zapytanie 2SELECT cenaFROM towaryWHERE nazwa = 'Markery 4 szt.';-- Zapytanie 3SELECT d.id, d.nazwa, COUNT(t.id) AS liczba_towarowFROM dostawcy AS dLEFT JOIN towary AS t ON t.idDostawcy = d.idGROUP BY d.id, d.nazwa;
Podgląd strony po tym kroku
Krok 7: Zapytanie 4: nowe pole informacje
Kryteria:
R.1.6 Zapisano zapytanie 4 modyfikujące strukturę tabeli dostawcy poprzez dodanie pola typu napisowego o nazwie informacje. W pliku z kwerendami lub na zrzucie istnieje zapis np.: ALTER TABLE dostawcy ADD COLUMN informacje TEXT; (możliwe również bez słowa COLUMN, dowolny typ tekstowy) oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest komunikat o poprawnie wykonanym zapytaniu lub zmieniona struktura tabeli
ok. 2 min
Polecenie z arkusza
Zapytanie 4: modyfikujące strukturę tabeli dostawcy, poprzez dodanie pola typu tekstowego o nazwie informacje.
ALTER TABLE zmienia strukturę tabeli, ADD COLUMN informacje TEXT dodaje pole tekstowe (sprawdzone na bazie z archiwum: tabela ma potem cztery kolumny). Zapytanie wykonaj raz - drugie wykonanie zgłosi, że kolumna już istnieje. Zrzut: kwerenda4.jpg z komunikatem albo strukturą tabeli.
kwerendy.txtTekstzmienione: 3 linie
-- Zapytanie 1SELECT nazwaFROM towaryWHERE promocja = 1;-- Zapytanie 2SELECT cenaFROM towaryWHERE nazwa = 'Markery 4 szt.';-- Zapytanie 3SELECT d.id, d.nazwa, COUNT(t.id) AS liczba_towarowFROM dostawcy AS dLEFT JOIN towary AS t ON t.idDostawcy = d.idGROUP BY d.id, d.nazwa;-- Zapytanie 4ALTER TABLE dostawcy ADD COLUMN informacje TEXT;
Podgląd strony po tym kroku
Etap 2: Grafika i strona HTML
Kroki 8-18 · po tym etapie masz 19 z 38 kryteriów (pkt)
Krok 8: promocja.png: 180 px szerokości z przezroczystością
Kryteria:
R.2.1 Obraz z pliku promocja.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
Przygotowanie grafiki: Plik promocja.png, wypakowany z archiwum, należy przeskalować z zachowaniem proporcji tak, aby jego szerokość wynosiła dokładnie 180 px. Plik powinien być zapisany z zachowaniem przezroczystości pod tą samą nazwą.
Grafika jest taka sama jak w wersji podstawowej. Obraz ma 378 × 340 px. W GIMP-ie Obraz → Skaluj obraz, łańcuch proporcji zamknięty, szerokość 180 - wysokość wyjdzie 162 px (340 × 180 / 378). Eksportuj jakopromocja.png: PNG zachowuje kanał przezroczystości.
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 9: 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
Cechy witryny: Składa się ze strony o nazwie index.php. Zapisany 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 ma rozszerzenie .php, bo za chwilę dojdzie do niego kod PHP; na egzaminie leży w htdocs XAMPP-a i otwierasz go przez http://localhost/....
Tytuł strony widoczny na karcie przeglądarki „Sklep dla uczniów”.
<title> (R.2.3) i viewport - telefon nie pomniejsza strony do rozmiaru ekranu komputera.
index.phpPHPzmienione: 2 linie
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Sklep dla uczniów</title></head><body></body></html>
Podgląd strony po tym kroku (index.php)
Krok 11: Podpięcie arkusza stylów
Kryteria:
R.3.5 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.5).
Typowe błędy
Klucz odbiera R.3.5 także wtedy, gdy choć jeden selektor nie trafia w blok (np. kropka zamiast #).
index.phpPHPzmienione: 1 linia
<!DOCTYPE html><htmllang="pl"><head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1"> <title>Sklep dla uczniów</title> <linkrel="stylesheet"href="styl.css"></head><body></body></html>
Podgląd strony po tym kroku (index.php)
Krok 12: Bloki: baner, trzy panele w main, stopka
Kryteria:
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 to <header>, stopka <footer>. Trzy panele stoją we wspólnym <main> - to główna treść strony i zarazem pojemnik, który w CSS ustawi je w rząd:
prawy <aside id="contact"> - kontakt, czyli 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 - po zmianie układu nazwa #left przestałaby pasować.
R.2.5 Zapisano w banerze nagłówek h1 "Dzisiejsze promocje naszego sklepu”; w panelach przynajmniej jeden z nagłówków h2: "Taniej o 30%”, "Sprawdź cenę”, "Kontakt"; w stopce nagłówek h4 z numerem zdającego oraz w panelu prawym zapisano paragraf z adresem e-mail
ok. 1 min
Polecenie z arkusza
Zawartość banera: nagłówek pierwszego stopnia o treści „Dzisiejsze promocje naszego sklepu”.
R.2.6 W bloku lewym umieszczono jedną listę numerowaną (w kodzie HTML lub skrypcie), użyto znaczników <ol>, <li>
ok. 1 min
Polecenie z arkusza
Zawartość bloku lewego: Nagłówek drugiego stopnia o treści „Taniej o 30%”. Lista numerowana (uporządkowana) zgodna z obrazem 2, elementy listy generowane są skryptem 1.
<h2> i pusta <ol> - elementy <li> wypisze Skrypt 1. Numerację literami (a, b, c) da CSS.
Krok 15: Panel środkowy: formularz z listą rozwijalną
Kryteria: , , , ,
R.2.5 Zapisano w banerze nagłówek h1 "Dzisiejsze promocje naszego sklepu”; w panelach przynajmniej jeden z nagłówków h2: "Taniej o 30%”, "Sprawdź cenę”, "Kontakt"; w stopce nagłówek h4 z numerem zdającego oraz w panelu prawym zapisano paragraf z adresem e-mail
R.2.7 W bloku środkowym umieszczono listę rozwijaną, zawierającą nazwy: Gumka do mazania, Cienkopis, Pisaki 60 szt., Markery 4 szt. użyto znaczników <select>, <option>
R.2.8 W bloku środkowym umieszczono przycisk SPRAWDŹ
R.3.2 Dane z formularza przesyłane są za pomocą metody POST do tego samego pliku (poprawne również bez atrybutu action)
R.3.3 Przycisk w formularzu realizuje wysyłanie danych do skryptu (input lub button z typem submit lub button bez typu)
ok. 3 min
Polecenie z arkusza
Zawartość bloku środkowego: Nagłówek drugiego stopnia o treści „Sprawdź cenę”. Formularz wysyłający dane do tego samego pliku metodą bezpieczną oraz składający się z: Listy rozwijalnej zawierającej nazwy towarów: „Gumka do mazania”, „Cienkopis”, „Pisaki 60 szt.”, „Markery 4 szt.”. Przycisku SPRAWDŹ, którego wybranie powoduje wysłanie danych do skryptu 2.
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 atrybutu action formularz wysyła dane do tego samego pliku,
<select name="product"> z czterema <option> (R.2.7). Opcja bez value wysyła swój tekst, czyli nazwę towaru - tej Skrypt 2 potrzebuje. aria-label="Towar" podaje czytnikowi ekranu nazwę listy, której na obrazie 2 nie ma żadnej widocznej etykiety,
<button type="submit"> z napisem SPRAWDŹ wysyła formularz (R.2.8, R.3.3).
Typowe błędy
method="get" też wysyła dane, ale klucz R.3.2 wymaga POST.
Pusty <div id="result"> pod formularzem - wypełni go Skrypt 2. To zwykły blok na wynik, a nie sekcja z własnym nagłówkiem, więc <div> jest tu właściwym znacznikiem.
Krok 17: Panel prawy: kontakt z odsyłaczem pocztowym i obraz
Kryteria: , ,
R.2.5 Zapisano w banerze nagłówek h1 "Dzisiejsze promocje naszego sklepu”; w panelach przynajmniej jeden z nagłówków h2: "Taniej o 30%”, "Sprawdź cenę”, "Kontakt"; w stopce nagłówek h4 z numerem zdającego oraz w panelu prawym zapisano paragraf z adresem e-mail
R.2.9 W bloku prawym umieszczono obraz promocja.png z tekstem alternatywnym "promocja"
R.3.4 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”. Paragraf (akapit) o treści „e-mail: [email protected]”, gdzie napis „[email protected]” jest odsyłaczem pocztowym łączącym z tym adresem. Obraz promocja.png z tekstem alternatywnym „promocja”.
Odnośnik z adresem zaczynającym się od mailto: otwiera program pocztowy z wpisanym odbiorcą (R.3.4); odnośnikiem jest sam adres. Pod spodem obraz z tekstem alternatywnym z arkusza (R.2.9).
R.2.5 Zapisano w banerze nagłówek h1 "Dzisiejsze promocje naszego sklepu”; w panelach przynajmniej jeden z nagłówków h2: "Taniej o 30%”, "Sprawdź cenę”, "Kontakt"; w stopce nagłówek h4 z numerem zdającego oraz w panelu prawym zapisano paragraf z adresem e-mail
ok. 1 min
Polecenie z arkusza
Zawartość stopki: nagłówek czwartego stopnia o treści „Autor strony: ”, dalej wstawiony numer zdającego.
<h4> w <footer> - w tym arkuszu stopka ma nagłówek, nie akapit. Na egzaminie po dwukropku wpisujesz swój numer zdającego (PESEL), tu stoi autor rozwiązania.
Kroki 19-26 · po tym etapie masz 30 z 38 kryteriów (pkt)
Krok 19: Plik styl.css: zmienne, krój i wyśrodkowanie
Kryteria:
R.4.1 Ustawiono domyślne formatowanie wszystkich selektorów (*) krój czcionki Garamond i wyrównanie tekstu do środka
ok. 2 min
Polecenie z arkusza
Domyślne formatowanie wszystkich selektorów: krój czcionki Garamond, wyrównanie tekstu do środka.
Trzy kolory z arkusza w zmiennych - #8C7B75 pojawia się w czterech regułach (baner, stopka, dwie ramki), więc zmiana odcienia to jedna linia. Selektor * z Garamondem i krojem zapasowym serif: gdy Garamonda nie ma w systemie, przeglądarka weźmie dowolny krój szeryfowy zamiast domyślnego (R.4.1).
Dla panelu lewego i prawego: kolor tła #BCAAA4, wysokość 400 px, szerokość 35%.
Selektory identyfikatorów (#). Szerokość zostaje na samych panelach, tak jak ją ocenia klucz (R.4.5) - pojemnik flex za chwilę liczy procenty od <main>, czyli od całej szerokości strony.
Typowe błędy
.offers (kropka) zamiast #offers nie trafia w panel - klucz R.3.5 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ł 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. Baner i stopka są poza tym pojemnikiem, więc stoją nad i pod nim same z siebie - nie ma float, więc nie ma też 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 ten margines pochłania clear, tu trzeba go wyzerować - stopka przylega do paneli jak na obrazie 2.
Dlaczego nie siatka? W siatce (grid) procent szerokości elementu liczy się od jego kolumny: panel z width: 35% w kolumnie szerokiej na 35% strony zająłby 12% strony. Szerokości trzeba by przenieść do grid-template-columns, a klucz z tego arkusza ocenia je na panelach. Flex liczy procenty od pojemnika, więc wymiary z arkusza działają wprost.
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 35% ani 30%.
Krok 24: Lista: numeracja literami, elementy do lewej
Kryteria:
R.4.7 Ustawiono styl selektora ol: małe litery (np. list-style-type: lower-alpha;) oraz styl selektora li: wyrównanie tekstu do lewej strony
ok. 1 min
Polecenie z arkusza
Dla selektora listy numerowanej: numeracja małymi literami. Dla selektora elementu listy: wyrównanie tekstu do lewej strony.
lower-alpha numeruje literami a, b, c. li { text-align: left; } nadpisuje wyśrodkowanie z * tylko dla elementów listy. Uwaga arkusza: wyłącznie selektory znaczników ol i li - nie #offers ol ani klasa (R.4.7).
Typowe błędy
Reguła #offers ol albo klasa listy nie spełnia R.4.7 - klucz wymaga samych selektorów ol i li.
R.4.8 Ustawiono obramowanie dla selektora img na 1px dashed #8C7B75 oraz obramowanie dla bloku z wynikiem działania skryptu 2: 1px solid #8C7B75
R.4.9 Ustawiono dla obrazu marginesy wewnętrzne (padding) na 5px oraz dla bloku z wynikami skryptu 2 jedynie margines zewnętrzny górny (np. margin-top) na 100px
ok. 1 min
Polecenie z arkusza
Dla bloku z wynikiem działania skryptu 2: margines zewnętrzny górny 100 px, obramowanie o szerokości 1 px linią ciągłą koloru #8C7B75.
margin-top: 100px - jedynie górny margines, jak chce klucz; linia ciągła to solid. Razem z regułą obrazu: obie ramki (R.4.8) i odstępy (R.4.9). Pusty blok to na razie pozioma kreska.
Kroki 27-34 · po tym etapie masz 38 z 38 kryteriów (pkt)
Krok 27: 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
Wymagania dotyczące skryptu: 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, którego łatwo nie zauważyć; FETCH_ASSOC - wiersze z nazwami kolumn,
$pdo = null - PDO nie ma funkcji „zamknij”: połączenie 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.
index.phpPHPzmienione: 12 linii
<?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, ]);$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$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>Sklep dla uczniów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Dzisiejsze promocje naszego sklepu</h1> </header> <main> <sectionid="offers"> <h2>Taniej o 30%</h2> <ol> </ol> </section> <sectionid="price-check"> <h2>Sprawdź cenę</h2> <formmethod="post"> <selectname="product"aria-label="Towar"> <option>Gumka do mazania</option> <option>Cienkopis</option> <option>Pisaki 60 szt.</option> <option>Markery 4 szt.</option> </select> <buttontype="submit">SPRAWDŹ</button> </form> <divid="result"> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> <imgsrc="promocja.png"alt="promocja"> </aside> </main> <footer> <h4>Autor strony: Bartosz Bryniarski</h4> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 28: Skrypt 1: zapytanie 1
Kryteria:
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(PDO::FETCH_COLUMN) zwraca od razu prostą listę nazw: ["Gumka do mazania", "Cienkopis", ...]. 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 - towary w promocji$promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$promoNames = [];$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>Sklep dla uczniów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Dzisiejsze promocje naszego sklepu</h1> </header> <main> <sectionid="offers"> <h2>Taniej o 30%</h2> <ol> </ol> </section> <sectionid="price-check"> <h2>Sprawdź cenę</h2> <formmethod="post"> <selectname="product"aria-label="Towar"> <option>Gumka do mazania</option> <option>Cienkopis</option> <option>Pisaki 60 szt.</option> <option>Markery 4 szt.</option> </select> <buttontype="submit">SPRAWDŹ</button> </form> <divid="result"> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> <imgsrc="promocja.png"alt="promocja"> </aside> </main> <footer> <h4>Autor strony: Bartosz Bryniarski</h4> </footer></body></html>
R.5.4 Skrypt 1 wyświetla nazwy towarów zwrócone zapytaniem (Gumka do mazania, Cienkopis, Pisaki 60 szt., Markery 4 szt.)
R.5.5 Wyświetlane przez skrypt 1 nazwy towarów są elementami listy numerowanej
ok. 3 min
Polecenie z arkusza
Nazwy towarów zwrócone zapytaniem wypisywane są jako elementy listy numerowanej.
foreach z dwukropkiem i endforeach - w HTML-u czytelniejsze niż klamry rozrzucone między znacznikami; jedna pętla na każdą nazwę,
<?= ... ?> to skrót od <?php echo ... ?>,
funkcja e() (na górze pliku) przepuszcza każdy tekst z bazy przez htmlspecialchars - znak < albo cudzysłów w nazwie towaru nie rozbije strony ani nie wstrzyknie do niej kodu.
W panelu lewym: a. Gumka do mazania, b. Cienkopis, c. Pisaki 60 szt., d. Markery 4 szt. - jak na obrazie 2 (R.5.4, R.5.5).
index.phpPHPzmienione: 8 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 - towary w promocji$promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$promoNames = [];$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>Sklep dla uczniów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Dzisiejsze promocje naszego sklepu</h1> </header> <main> <sectionid="offers"> <h2>Taniej o 30%</h2> <ol><?phpforeach ($promoNamesas$name): ?> <li><?= e($name) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="price-check"> <h2>Sprawdź cenę</h2> <formmethod="post"> <selectname="product"aria-label="Towar"> <option>Gumka do mazania</option> <option>Cienkopis</option> <option>Pisaki 60 szt.</option> <option>Markery 4 szt.</option> </select> <buttontype="submit">SPRAWDŹ</button> </form> <divid="result"> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> <imgsrc="promocja.png"alt="promocja"> </aside> </main> <footer> <h4>Autor strony: Bartosz Bryniarski</h4> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 30: Skrypt 2: wybór z formularza i lista towarów w jednym miejscu
ok. 4 min
Polecenie z arkusza
Skrypt 2: Pobiera z formularza nazwę towaru wybranego w liście rozwijalnej.
const PRODUCTS - cztery nazwy z arkusza w jednej stałej na górze pliku. Z niej pętla wypisuje teraz opcje listy rozwijalnej (wynik jest ten sam co wcześniej, R.2.7), a za chwilę - z nią porównany zostanie wybór,
filter_input(INPUT_POST, 'product') czyta pole formularza; przy pierwszym wejściu na stronę formularza jeszcze nie wysłano i funkcja zwraca null - bez ostrzeżenia „Undefined array key”, które dałoby $_POST['product'],
in_array(..., true) - biała lista: dalej przejdzie tylko nazwa, która jest na liście. Listę rozwijalną da się obejść (wysłane dane można podmienić w narzędziach przeglądarki), a sprawdzenie po stronie serwera - nie. true to porównanie ścisłe, bez zamiany typów,
selected przy opcji, która była wybrana - po wysłaniu lista pokazuje dalej ten sam towar, a nie wraca do pierwszego.
Typowe błędy
Bez sprawdzenia, czy formularz wysłano, pierwsze otwarcie strony z $_POST['product'] pokazuje ostrzeżenie „Undefined array key”.
index.phpPHPzmienione: 10 linii
<?phpconst PRODUCTS = ['Gumka do mazania', 'Cienkopis', 'Pisaki 60 szt.', 'Markery 4 szt.'];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 - towary w promocji$promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);// Skrypt 2: nazwa towaru z formularza - tylko z listy PRODUCTS$product = filter_input(INPUT_POST, 'product');$isKnownProduct = in_array($product, PRODUCTS, true);$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$promoNames = [];$product = null;$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>Sklep dla uczniów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Dzisiejsze promocje naszego sklepu</h1> </header> <main> <sectionid="offers"> <h2>Taniej o 30%</h2> <ol><?phpforeach ($promoNamesas$name): ?> <li><?= e($name) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="price-check"> <h2>Sprawdź cenę</h2> <formmethod="post"> <selectname="product"aria-label="Towar"><?phpforeach (PRODUCTS as$option): ?> <option<?= $option === $product ? ' selected' : ''?>><?= e($option) ?></option><?phpendforeach; ?> </select> <buttontype="submit">SPRAWDŹ</button> </form> <divid="result"> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> <imgsrc="promocja.png"alt="promocja"> </aside> </main> <footer> <h4>Autor strony: Bartosz Bryniarski</h4> </footer></body></html>
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 - nawet ze znakiem ' w środku (R.5.6).
zapytanie wykonuje się tylko dla towaru z listy (if ($isKnownProduct)),
fetchColumn() zwraca od razu pierwszą kolumnę pierwszego wiersza - samą cenę - albo false, gdy wiersza nie ma,
$statement = null zwalnia zapytanie przed zamknięciem połączenia.
Od tego kroku podgląd pokazuje stronę po wysłaniu formularza z wybraną gumką do mazania. Cena jest już pobrana, ale jeszcze niewypisana.
index.phpPHPzmienione: 8 linii
<?phpconst PRODUCTS = ['Gumka do mazania', 'Cienkopis', 'Pisaki 60 szt.', 'Markery 4 szt.'];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 - towary w promocji$promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);// Skrypt 2: nazwa towaru z formularza - tylko z listy PRODUCTS$product = filter_input(INPUT_POST, 'product');$isKnownProduct = in_array($product, PRODUCTS, true);$price = false;if ($isKnownProduct) {$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) {$promoNames = [];$product = null;$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>Sklep dla uczniów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Dzisiejsze promocje naszego sklepu</h1> </header> <main> <sectionid="offers"> <h2>Taniej o 30%</h2> <ol><?phpforeach ($promoNamesas$name): ?> <li><?= e($name) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="price-check"> <h2>Sprawdź cenę</h2> <formmethod="post"> <selectname="product"aria-label="Towar"><?phpforeach (PRODUCTS as$option): ?> <option<?= $option === $product ? ' selected' : ''?>><?= e($option) ?></option><?phpendforeach; ?> </select> <buttontype="submit">SPRAWDŹ</button> </form> <divid="result"> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> <imgsrc="promocja.png"alt="promocja"> </aside> </main> <footer> <h4>Autor strony: Bartosz Bryniarski</h4> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 32: Skrypt 2: cena niższa o 30%
Kryteria:
R.5.7 Skrypt 2 oblicza na podstawie zwróconej zapytaniem wartości cenę promocyjną obniżoną o 30% (np. *0.7). Zaokrąglenie nie jest błędem.
ok. 2 min
Polecenie z arkusza
Na podstawie ceny zwróconej zapytaniem oblicza cenę promocyjną jako wartość niższą o 30%.
Cena niższa o 30% to 70% ceny: $price * 0.7 (R.5.7). round(..., 2) zaokrągla wynik do groszy - mnożenie liczb zmiennoprzecinkowych potrafi dać np. 15.679999999999 zamiast 15.68 (klucz: zaokrąglenie nie jest błędem). Dla gumki: 3.2 × 0,7 = 2.24.
index.phpPHPzmienione: 3 linie
<?phpconst PRODUCTS = ['Gumka do mazania', 'Cienkopis', 'Pisaki 60 szt.', 'Markery 4 szt.'];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 - towary w promocji$promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);// Skrypt 2: nazwa towaru z formularza - tylko z listy PRODUCTS$product = filter_input(INPUT_POST, 'product');$isKnownProduct = in_array($product, PRODUCTS, true);$price = false;if ($isKnownProduct) {$statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');$statement->execute(['name' => $product]);$price = $statement->fetchColumn();$statement = null; }if ($price !== false) {$promoPrice = round($price * 0.7, 2); // cena niższa o 30% }$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$promoNames = [];$product = null;$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>Sklep dla uczniów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Dzisiejsze promocje naszego sklepu</h1> </header> <main> <sectionid="offers"> <h2>Taniej o 30%</h2> <ol><?phpforeach ($promoNamesas$name): ?> <li><?= e($name) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="price-check"> <h2>Sprawdź cenę</h2> <formmethod="post"> <selectname="product"aria-label="Towar"><?phpforeach (PRODUCTS as$option): ?> <option<?= $option === $product ? ' selected' : ''?>><?= e($option) ?></option><?phpendforeach; ?> </select> <buttontype="submit">SPRAWDŹ</button> </form> <divid="result"> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> <imgsrc="promocja.png"alt="promocja"> </aside> </main> <footer> <h4>Autor strony: Bartosz Bryniarski</h4> </footer></body></html>
R.5.8 Skrypt 2 wyświetla dane w bloku pod formularzem, w dwóch wierszach, zgodnie z formatem cena regularna: <cena_pobrana_z_bazy> cena w promocji 30%: <obliczona_cena>
ok. 2 min
Polecenie z arkusza
Pod formularzem wypisuje dane w bloku, zgodnie z obrazem 2, w formacie: cena regularna: <cena_pobrana_z_bazy> cena w promocji 30%: <obliczona_cena>.
Dwa wiersze w bloku #result, rozdzielone <br> - łamanie linii w jednym bloku tekstu, bez odstępów akapitów: „cena regularna: 3.2” i „cena w promocji 30%: 2.24” - jak na obrazie 2 (R.5.8). if ($price !== false) - przed wysłaniem formularza blok zostaje pusty. Format liczb jest taki jak w bazie i na obrazie 2 (kropka dziesiętna), bo arkusz podaje go wprost.
index.phpPHPzmienione: 4 linie
<?phpconst PRODUCTS = ['Gumka do mazania', 'Cienkopis', 'Pisaki 60 szt.', 'Markery 4 szt.'];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 - towary w promocji$promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);// Skrypt 2: nazwa towaru z formularza - tylko z listy PRODUCTS$product = filter_input(INPUT_POST, 'product');$isKnownProduct = in_array($product, PRODUCTS, true);$price = false;if ($isKnownProduct) {$statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');$statement->execute(['name' => $product]);$price = $statement->fetchColumn();$statement = null; }if ($price !== false) {$promoPrice = round($price * 0.7, 2); // cena niższa o 30% }$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$promoNames = [];$product = null;$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>Sklep dla uczniów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Dzisiejsze promocje naszego sklepu</h1> </header> <main> <sectionid="offers"> <h2>Taniej o 30%</h2> <ol><?phpforeach ($promoNamesas$name): ?> <li><?= e($name) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="price-check"> <h2>Sprawdź cenę</h2> <formmethod="post"> <selectname="product"aria-label="Towar"><?phpforeach (PRODUCTS as$option): ?> <option<?= $option === $product ? ' selected' : ''?>><?= e($option) ?></option><?phpendforeach; ?> </select> <buttontype="submit">SPRAWDŹ</button> </form> <divid="result"><?phpif ($price !== false): ?> cena regularna: <?= e($price) ?><br> cena w promocji 30%: <?= e($promoPrice) ?><?phpendif; ?> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> <imgsrc="promocja.png"alt="promocja"> </aside> </main> <footer> <h4>Autor strony: Bartosz Bryniarski</h4> </footer></body></html>
Podgląd strony po tym kroku (index.php)
Krok 34: Towar spoza listy i awaria bazy: komunikaty
ok. 3 min
Dodatek spoza arkusza - klucz go nie ocenia. Blok wyniku odpowiada teraz także w dwóch sytuacjach, których obraz 2 nie pokazuje:
towar spoza listy (np. podmieniony w narzędziach przeglądarki na ' OR 1=1 --) - zapytanie się nie wykonuje, a w bloku pojawia się „Wybierz towar z listy.”,
awaria bazy (catch) - komunikat z role="alert" (czytnik ekranu przeczyta go od razu), a reszta strony - formularz, kontakt, stopka - działa dalej, bo zmienne mają w catch wartości puste.
Sprawdzone na prawdziwym PHP: wszystkie cztery towary z listy (3.2 / 2.24, 2.5 / 1.75, 55 / 38.5, 22.4 / 15.68), napis spoza listy i złe hasło do bazy - za każdym razem bez ostrzeżeń PHP.
index.phpPHPzmienione: 6 linii
<?phpconst PRODUCTS = ['Gumka do mazania', 'Cienkopis', 'Pisaki 60 szt.', 'Markery 4 szt.'];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 - towary w promocji$promoNames = $pdo->query('SELECT nazwa FROM towary WHERE promocja = 1')->fetchAll(PDO::FETCH_COLUMN);// Skrypt 2: nazwa towaru z formularza - tylko z listy PRODUCTS$product = filter_input(INPUT_POST, 'product');$isKnownProduct = in_array($product, PRODUCTS, true);$price = false;if ($isKnownProduct) {$statement = $pdo->prepare('SELECT cena FROM towary WHERE nazwa = :name');$statement->execute(['name' => $product]);$price = $statement->fetchColumn();$statement = null; }if ($price !== false) {$promoPrice = round($price * 0.7, 2); // cena niższa o 30% }$pdo = null; // zamknięcie połączenia - ostatnia operacja na bazie} catch (PDOException $exception) {$promoNames = [];$product = null;$isKnownProduct = false;$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>Sklep dla uczniów</title> <linkrel="stylesheet"href="styl.css"></head><body> <header> <h1>Dzisiejsze promocje naszego sklepu</h1> </header> <main> <sectionid="offers"> <h2>Taniej o 30%</h2> <ol><?phpforeach ($promoNamesas$name): ?> <li><?= e($name) ?></li><?phpendforeach; ?> </ol> </section> <sectionid="price-check"> <h2>Sprawdź cenę</h2> <formmethod="post"> <selectname="product"aria-label="Towar"><?phpforeach (PRODUCTS as$option): ?> <option<?= $option === $product ? ' selected' : ''?>><?= e($option) ?></option><?phpendforeach; ?> </select> <buttontype="submit">SPRAWDŹ</button> </form> <divid="result"><?phpif (isset($databaseError)): ?> <prole="alert"><?= e($databaseError) ?></p><?phpelseif ($product !== null && !$isKnownProduct): ?> <prole="alert">Wybierz towar z listy.</p><?phpendif; ?><?phpif ($price !== false): ?> cena regularna: <?= e($price) ?><br> cena w promocji 30%: <?= e($promoPrice) ?><?phpendif; ?> </div> </section> <asideid="contact"> <h2>Kontakt</h2> <p>e-mail: <ahref="mailto:[email protected]">[email protected]</a></p> <imgsrc="promocja.png"alt="promocja"> </aside> </main> <footer> <h4>Autor strony: Bartosz Bryniarski</h4> </footer></body></html>
Krok 35 · po tym etapie masz 38 z 38 kryteriów (pkt)
Krok 35: Co jest inaczej i którą wersję wybrać na egzaminie
ok. 3 min
Obie wersje dają komplet 38 punktów. Różnice:
wersja podstawowa
wersja nowoczesna
układ
float: left paneli i clear stopki
<main> z display: flex
panele
#left, #middle, #right
#offers, #price-check, #contact (<aside>)
kolory
wpisane w każdą regułę
zmienne CSS
zapytanie 3
JOIN, GROUP BY dostawcy.nazwa
aliasy, LEFT JOIN, GROUP BY d.id, d.nazwa
biblioteka
mysqli (tabela 1 arkusza)
PDO z wyjątkami
gdzie są zapytania
skrypty rozrzucone po HTML-u
jeden blok na górze index.php
lista towarów
wpisana w HTML
stała PRODUCTS - opcje i biała lista
nazwa w zapytaniu 2
wklejona z $_POST
parametr :name
wypisywanie
dane z bazy wprost
e() - htmlspecialchars
zła nazwa, awaria bazy
ostrzeżenia PHP
komunikaty
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 - tu cały kod PHP zostaje w nim.
Na egzaminie wersja podstawowa trzyma się funkcji z tabeli 1 arkusza i łatwiej ją napisać bez dokumentacji pod ręką. Nowoczesna jest odporna na podmienione dane z formularza. Obie biblioteki klucz przyjmuje.
Krok 36 · po tym etapie masz 38 z 38 kryteriów (pkt)
Krok 36: 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: baza.jpg, index.php, kwerenda1.jpg, kwerenda2.jpg kwerenda3.jpg kwerenda4.jpg, kwerendy.txt, promocja.png, przeglądarka.txt, styl.css 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.