Przejdź do głównej treści
EE.09-01-20.06-SGCzerwiec 2020 · 39 kroków · ok. 77 min · 34 kryteria z klucza

Rozwiązanie krok po kroku: Organizer z wpisami zadań (Wersja podstawowa)

Organizer na sierpień 2020: baza egzamin6 z tabelą zadania (import pliku baza5.sql), cztery zapytania - wybór zadań z sierpnia, miesiąca i roku dla jednej daty, wpisów bez powtórzeń kończących się literą „o” (DISTINCT, LIKE) oraz zmiana wpisu jednego dnia (UPDATE). Do tego logo przeskalowane do 100 × 100 px, strona organizer.php z trzema blokami banera, formularzem i blokami kolejnych dni generowanymi przez skrypt, style w styl6.css oraz skrypt PHP, który wyświetla dni sierpnia, podpis miesiąca w stopce i zapisuje wpis z formularza do bazy.

To arkusz EE.09 z czerwca 2020 - kwalifikacji formuły 2017, którą zastąpiła INF.03. Z tego samego archiwum egzamin5.zip korzysta też arkusz EE.09-03 z tej sesji (kalendarz na lipiec) - baza jest ta sama, ale polecenia i klucz inne. Kroki idą zdanie po zdaniu za treścią arkusza: przy każdym jest polecenie z arkusza, a kod pokazuje dokładnie te linie, które je spełniają. Skrypt jest najpierw napisany najprościej, funkcjami mysqli z tabeli 1 arkusza, a na końcu sprawdzony na prawdziwym wstrzyknięciu SQL i zabezpieczony.

To jest wersja podstawowa. W zakładce „Nowoczesny kod (flex, grid, PDO)” jest drugi sposób rozwiązania tego samego arkusza: semantyczne znaczniki, układ bez float, PDO z zapytaniem przygotowanym na górze pliku i przekierowanie po zapisie - obie wersje są zgodne z kluczem.

Baza i grafiki pochodzą z oryginalnego archiwum CKE egzamin5.zip (załącznik do pobrania na stronie arkusza, hasło w treści zadania). Zapytania i zrzuty są policzone na MariaDB 10.4 - tej samej, która jest w XAMPP, a podgląd od kroków z PHP to wynik prawdziwego uruchomienia kodu w PHP 8.2.

Jak liczą się punkty. Klucz ma 34 kryteria (R.1.1 - R.5.8), każde za 1 punkt. Próg zdania części praktycznej to 75%, czyli 26 punktów - można stracić 8 kryteriów i nadal zdać. Przy każdym kroku jest napisane, które kryteria spełnia; egzaminator ocenia spełnienie kryteriów z klucza, nie zgodność z tym kodem.

  1. Etap 1: Baza danych

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

    Krok 1: Folder zdającego i archiwum egzamin5.zip

    ok. 2 min
    Polecenie z arkusza

    Na pulpicie znajdziesz archiwum ZIP o nazwie egzamin5.zip zabezpieczone hasłem: kaL(end)arz% Archiwum należy rozpakować. Na pulpicie konta Egzamin utwórz folder. Jako nazwy folderu użyj swojego numeru PESEL. Umieść w tym folderze rozpakowane pliki.

    Na pulpicie tworzysz folder nazwany numerem PESEL - w tym rozwiązaniu w jego miejscu stoi imię i nazwisko autora, Bartosz Bryniarski. Archiwum egzamin5.zip rozpakowujesz (np. 7-Zipem), podając hasło kaL(end)arz% - wielkość liter, nawiasy i znak procentu mają znaczenie. W środku są trzy pliki: baza5.sql (tabela do importu), logo1.png i logo2.png. W tym zadaniu potrzebne jest tylko logo2.png - logo1.png należy do bliźniaczego arkusza z kalendarzem.

    Do tego folderu trafia wszystko, co powstanie: zrzuty, kwerendy.txt, plik strony i plik stylów.

    Typowe błędy

    Pliki zapisane poza folderem zdającego (np. tylko w htdocs) nie trafią na płytę - a ocenia się wyłącznie to, co jest na płycie.

  2. Krok 2: XAMPP i pusta baza egzamin6

    ok. 2 min
    Polecenie z arkusza

    Uruchom usługi MySQL i Apache za pomocą XAMPP Control Panel. Za pomocą narzędzia phpMyAdmin wykonaj podane operacje na bazie danych: Utwórz bazę danych o nazwie egzamin6

    W XAMPP Control Panel klikasz Start przy Apache i MySQL, potem w przeglądarce otwierasz http://localhost/phpmyadmin. Nowa w lewym panelu, nazwa egzamin6, Utwórz.

    Uwaga na numer: archiwum nazywa się egzamin5.zip, plik baza5.sql, a baza ma się nazywać egzamin6 - tak jest w arkuszu i w kluczu (R.1.1, R.5.1). Te trzy numery celowo się różnią, łatwo wpisać odruchowo egzamin5.

    Typowe błędy

    Baza egzamin5 zamiast egzamin6 to strata R.1.1 i R.5.1 - skrypt łączy się z bazą po nazwie.

  3. Krok 3: Import pliku baza5.sql i zrzut import.png

    Kryteria:

    R.1.1 Wykonano import tabel do bazy danych egzamin6 , czynność udokumentowano plikiem o nazwie import w formacie PNG. Zrzut zawiera cały obszar ekranu monitora z widocznym paskiem zadań

    ok. 3 min
    Polecenie z arkusza

    Do bazy egzamin6 zaimportuj tabele z pliku baza5.sql z rozpakowanego archiwum. Wykonaj zrzut ekranu po imporcie. Zrzut zapisz w formacie PNG pod nazwą import w folderze z numerem PESEL. Nie kadruj zrzutu. Zrzut powinien obejmować cały ekran monitora, z widocznym paskiem zadań. Na zrzucie powinny być widoczne elementy wskazujące na poprawnie wykonany import tabel

    Zaznacz bazę egzamin6, karta Import, Wybierz plik → baza5.sql, Wykonaj. Plik zawiera polecenie CREATE TABLE zadania i dane, więc tabela powstaje razem z importem. Po imporcie kliknij tabelę zadania (karta Przeglądaj): 63 wiersze - każdy dzień lipca i sierpnia 2020 (miesiac to lipiec albo sierpien) oraz jeden dzień listopada. Zrzut całego ekranu z paskiem zadań: import.png.

    Zwróć uwagę na pisownię miesięcy w danych: sierpien bez litery „ń”. Arkusz pisze „sierpień”, ale zapytanie musi porównywać z tym, co jest w tabeli.

    Typowe błędy

    Zrzut importu w PNG (import.png), zrzuty kwerend w JPG - zły format albo wykadrowany zrzut nie jest oceniany.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 3
  4. Krok 4: Zapytanie 1: zadania z sierpnia

    Kryteria: ,

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

    R.1.3 Utworzono zapytanie 1 wybierające jedynie pola dataZadania, miesiac i wpis z tabeli zadania dla zadań, których miesiąc to sierpień. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien'; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczne są jedynie pola dataZadania, miesiac i wpis. Rekordy mają daty jedynie z sierpnia przy czym jest widoczna albo poprawna kwerenda albo dokładnie 31 rekordów (daty od 1-31 sierpnia)

    ok. 3 min
    Polecenie z arkusza

    Zapytanie 1: wybierające jedynie pola dataZadania, miesiac i wpis z tabeli zadania dla zadań, których miesiąc to sierpień

    Karta SQL bazy egzamin6. Zapytanie po kolei:

    • SELECT dataZadania, miesiac, wpis - trzy kolumny z arkusza, w tej kolejności (skrypt wyświetli je w bloku dnia),
    • FROM zadania,
    • WHERE miesiac = 'sierpien' - napis w apostrofach, pisany tak jak w tabeli, bez „ń”.

    Wynik: 31 wierszy, od 2020-08-01 (Remont) do 2020-08-31 - klucz wymaga dokładnie tylu. Zapytanie zapisujesz w pliku kwerendy.txt, a cały ekran - jako kw1.jpg.

    Typowe błędy

    WHERE miesiac = 'sierpień' (z „ń”) zwraca zero wierszy, bo w tabeli jest sierpien. Pusty wynik to sygnał, żeby zajrzeć do danych, a nie zmieniać warunek na LIKE '%sierp%'.

    kwerendy.txt Tekst nowy plik
    -- Zapytanie 1
    SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 4
  5. Krok 5: Zapytanie 2: miesiąc i rok dla 1 sierpnia

    Kryteria:

    R.1.4 Utworzono zapytanie 2 wybierające jedynie pola miesiac i rok z tabeli zadania dla zadania z datą 2020-08-01. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08- 01'; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest dokładnie jeden rekord z danymi: sierpien 2020

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 2: wybierające jedynie pola miesiac i rok z tabeli zadania dla zadania z datą 2020-08-01

    SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08-01' - datę w SQL zapisuje się jak napis, w formacie RRRR-MM-DD. Wynik: dokładnie jeden wiersz: sierpien, 2020. Zrzut: kw2.jpg. Tego zapytania użyje skrypt 2 do podpisu w stopce.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';
    
    -- Zapytanie 2
    SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08-01';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 5
  6. Krok 6: Zapytanie 3: wpisy bez powtórzeń kończące się na „o”

    Kryteria:

    R.1.5 Utworzono zapytanie 3 wybierające jedynie pole wpis i wyświetlające je bez powtórzeń, z tabeli zadania dla wpisów kończących się literą „o”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: SELECT DISTINCT wpis FROM zadania WHERE wpis LIKE "%o"; oraz wynik działania zapytania udokumentowano zrzutem, na którym widoczny jest jedynie jeden rekord: Mielno

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 3: wybierające jedynie pole wpis i wyświetlające je bez powtórzeń, z tabeli zadania dla wpisów kończących się literą „o”

    • SELECT DISTINCT wpis - DISTINCT usuwa powtórzenia: wpis „Mielno” stoi przy sześciu dniach lipca, a w wyniku pojawi się raz,
    • FROM zadania,
    • WHERE wpis LIKE '%o' - % to dowolny ciąg znaków (także pusty), a po nim litera o na samym końcu.

    Wynik: jeden wiersz - Mielno, dokładnie jak w kluczu. Zrzut: kw3.jpg.

    Typowe błędy

    LIKE 'o%' szuka wpisów zaczynających się od „o”, a LIKE '%o%' - zawierających „o” gdziekolwiek (Projekt z programowania, Rower, Sopot...). Bez DISTINCT wynik ma sześć razy „Mielno”.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';
    
    -- Zapytanie 2
    SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08-01';
    
    -- Zapytanie 3
    SELECT DISTINCT wpis FROM zadania WHERE wpis LIKE '%o';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 6
  7. Krok 7: Zapytanie 4: nowy wpis dla 27 sierpnia

    Kryteria:

    R.1.6 Utworzono zapytanie 4 zmieniające pole wpis w tabeli zadania dla zadania z datą 2020- 08-27, nowy wpis to „Wycieczka: Karkonosze”. W pliku z kwerendami lub na zrzucie istnieje zapis np.: UPDATE zadania SET wpis='Wycieczka: Karkonosze' WHERE dataZadania='2020-08-27'; oraz wynik działania zapytania udokumentowano zrzutem wskazującym na poprawne wykonanie zapytania

    ok. 2 min
    Polecenie z arkusza

    Zapytanie 4: zmieniające pole wpis w tabeli zadania dla zadania z datą 2020-08-27, nowy wpis to „Wycieczka: Karkonosze”

    • UPDATE zadania - zmiana istniejących wierszy,
    • SET wpis = 'Wycieczka: Karkonosze' - nowa wartość kolumny,
    • WHERE dataZadania = '2020-08-27' - tylko ten jeden dzień.

    phpMyAdmin odpowiada „1 wiersz zmieniony” - zrzut kw4.jpg. Wpis 27 sierpnia był wcześniej pusty, teraz zobaczysz go na stronie w bloku tego dnia. Plik kwerendy.txt ma komplet czterech zapytań. Z tego zapytania skrypt zrobi zapis wpisu z formularza.

    Typowe błędy

    UPDATE bez WHERE zmienia wszystkie 63 wiersze - phpMyAdmin wykona je bez pytania. Przed uruchomieniem sprawdź, czy warunek jest na miejscu.

    kwerendy.txt Tekst zmienione: 3 linie
    -- Zapytanie 1
    SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';
    
    -- Zapytanie 2
    SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08-01';
    
    -- Zapytanie 3
    SELECT DISTINCT wpis FROM zadania WHERE wpis LIKE '%o';
    
    -- Zapytanie 4
    UPDATE zadania SET wpis = 'Wycieczka: Karkonosze' WHERE dataZadania = '2020-08-27';
    Podgląd strony po tym kroku
    Podgląd strony po kroku 7
  8. Etap 2: Grafika i strona HTML

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

    Krok 8: Grafika: logo2.png 100 × 100 px

    Kryteria:

    R.2.1 Plik logo2.png przeskalowano do wymiarów 100 px na 100 px (± 2 px)

    ok. 2 min
    Polecenie z arkusza

    Plik logo2.png, wypakowany z archiwum, należy przeskalować dokładnie do wymiarów 100 px na 100 px

    Oryginał ma 300 × 300 px, czyli jest kwadratem - zmniejszenie do 100 × 100 px zachowuje proporcje samo z siebie. W GIMP-ie Obraz → Skaluj obraz, szerokość i wysokość 100 px, potem Plik → Nadpisz logo2.png (zwykłe Zapisz zapisuje format .xcf). Plik ma przezroczyste tło - format PNG je zachowa (R.2.1).

    Typowe błędy

    Wymiary ustawione tylko atrybutem width w HTML-u nie zmieniają pliku - klucz sprawdza rozmiar samego logo2.png.

    Podgląd strony po tym kroku
    Podgląd strony po kroku 8
  9. Krok 9: Plik organizer.php

    ok. 1 min
    Polecenie z arkusza

    Składa się ze strony o nazwie organizer.php

    Plik organizer.php w folderze zdającego, obok logo2.png. Rozszerzenie .php, bo dojdzie skrypt - na razie to szkielet HTML5: <!DOCTYPE html> i lang="pl", czyli informacja dla przeglądarki i czytników ekranu, że treść jest po polsku.

    organizer.php PHP nowy plik
    <!DOCTYPE html>
    <html lang="pl">
    <head>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  10. Krok 10: Kodowanie polskich znaków

    Kryteria:

    R.2.2 Witrynę internetową zapisano w pliku PHP o nazwie organizer oraz zastosowano właściwy standard kodowania polskich znaków

    ok. 1 min
    Polecenie z arkusza

    Zastosowany właściwy standard kodowania polskich znaków

    <meta charset="UTF-8"> jako pierwszy element <head>, a plik zapisany w UTF-8 (w Notepad++ menu Kodowanie). Klucz ocenia nazwę pliku i kodowanie razem (R.2.2).

    organizer.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  11. Krok 11: Tytuł strony

    Kryteria:

    R.2.3 Nadano tytuł strony: "Organizer"

    ok. 1 min
    Polecenie z arkusza

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

    <title>Organizer</title> w <head> - napis na karcie przeglądarki (R.2.3). Nie myl go z nagłówkiem w banerze („MÓJ ORGANIZER”).

    organizer.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  12. Krok 12: Podpięcie arkusza stylów styl6.css

    Kryteria:

    R.3.4 Strona zawiera działające połączenie z zewnętrznym arkuszem stylów o nazwie styl6.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 styl6.css prawidłowo połączony z kodem strony

    <link rel="stylesheet" href="styl6.css"> w <head>. Nazwa z cyfrą 6 - jak numer bazy, a nie jak archiwum. Plik stylów powstanie po HTML-u (R.3.4).

    Typowe błędy

    Klucz uznaje R.3.4 tylko wtedy, gdy całe formatowanie pochodzi z tego pliku i każdy selektor trafia w swój blok (kropka zamiast # odbiera punkt).

    organizer.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  13. Krok 13: Bloki: trzy banery, blok główny, stopka

    Kryteria:

    R.2.4 Wygląd strony zdefiniowano dzieląc ją na bloki: trzy bloki banera, poniżej blok główny, na dole blok stopki. Zastosowano znaczniki sekcji

    ok. 2 min
    Polecenie z arkusza

    Podział strony na bloki: trzy bloki banera, poniżej blok główny, w którym znajdują się bloki wyświetlające dane kolejnych dni, na dole blok stopki.

    Pięć bloków w kolejności z obrazu 2: trzy bloki banera i blok główny jako <section> z identyfikatorami banner-title, banner-form, banner-logo i main (nazwy po angielsku - arkusz ich nie narzuca), na dole <footer>. Bloki kolejnych dni nie są pisane w HTML-u - wygeneruje je skrypt wewnątrz bloku głównego. Na razie bloki są puste i stoją jeden pod drugim; obok siebie ustawi je CSS (R.2.4).

    organizer.php PHP zmienione: 10 linii
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
        </section>
        <section id="banner-form">
        </section>
        <section id="banner-logo">
        </section>
        <section id="main">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  14. Krok 14: Pierwszy blok banera: nagłówek „MÓJ ORGANIZER”

    ok. 1 min
    Polecenie z arkusza

    Zawartość pierwszego bloku banera: nagłówek drugiego stopnia o treści „MÓJ ORGANIZER”

    Drugiego stopnia to <h2>, nie <h1> - nagłówek pierwszego stopnia arkusz przeznacza na podpis miesiąca w stopce. Punkt R.2.5 przyjdzie razem z tamtym nagłówkiem i paragrafem stopki.

    organizer.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
        </section>
        <section id="banner-logo">
        </section>
        <section id="main">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  15. Krok 15: Drugi blok banera: formularz metodą POST

    Kryteria: ,

    R.2.6 W drugim bloku banera umieszczono formularz, zastosowano znacznik <form> i jego zamknięcie

    R.3.3 Zdefinowano dla formularza metodę POST

    ok. 1 min
    Polecenie z arkusza

    Zawartość drugiego bloku banera: formularz wysyłający dane metodą POST do tej samej strony

    <form method="post"> bez atrybutu action wysyła dane do adresu, z którego został wczytany - czyli do organizer.php. Znacznik ma zamknięcie </form> (R.2.6), a metoda POST przesyła wpis w treści żądania, nie w adresie strony; w PHP odczytasz go z tablicy $_POST (R.3.3).

    Typowe błędy

    Formularz bez method używa domyślnie GET - wpis trafi do $_GET, a skrypt czytający $_POST nie dostanie niczego.

    organizer.php PHP zmienione: 2 linie
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
            </form>
        </section>
        <section id="banner-logo">
        </section>
        <section id="main">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  16. Krok 16: Pole edycyjne „Wpis wydarzenia: ”

    ok. 1 min
    Polecenie z arkusza

    Pole edycyjne podpisane: „Wpis wydarzenia: ”

    Podpis wprost w formularzu, za nim <input type="text">. Atrybut name="wpis" to klucz, pod którym tekst trafi do PHP: $_POST['wpis'] - nazwa taka sama jak kolumna w bazie, łatwo się nie pomylić.

    organizer.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
            </form>
        </section>
        <section id="banner-logo">
        </section>
        <section id="main">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  17. Krok 17: Przycisk „ZAPISZ”

    Kryteria: ,

    R.2.7 W formularzu umieszczono pole do wprowadzania danych oraz przycisk "ZAPISZ"

    R.3.2 Zastosowano przycisk do wysyłania danych z formularza

    ok. 1 min
    Polecenie z arkusza

    Przycisk wysyłający zawartość formularza z etykietą „ZAPISZ”

    <input type="submit" value="ZAPISZ"> - przycisk typu submit wysyła formularz, a napis to wartość value. Formularz ma pole i przycisk (R.2.7), a przycisk naprawdę wysyła dane (R.3.2).

    Typowe błędy

    <input type="button"> wygląda tak samo, ale niczego nie wysyła - brak punktu R.3.2, a skrypt zapisu nigdy się nie uruchomi.

    organizer.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
        </section>
        <section id="main">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  18. Krok 18: Trzeci blok banera: logo z tekstem alternatywnym

    Kryteria:

    R.2.8 W trzecim bloku banera umieszczono obraz logo2.png wraz z tekstem alternatywnym "Mój organizer"

    ok. 1 min
    Polecenie z arkusza

    Zawartość trzeciego bloku banera: logo2.png z tekstem alternatywnym o treści: „Mój organizer”

    <img> z src (przeskalowany plik z folderu zdającego) i alt - tekst, który przeczyta czytnik ekranu i który pokaże się, gdy obrazu zabraknie (R.2.8). W HTML5 <img> nie ma znacznika zamykającego ani ukośnika na końcu.

    organizer.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
        </section>
        <footer>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  19. Krok 19: Stopka: paragraf z autorem strony

    ok. 1 min
    Polecenie z arkusza

    Paragraf (akapit) o treści: „Stronę wykonał: ”, dalej wstawiony numer PESEL zdającego

    Na egzaminie po dwukropku wpisujesz swój numer PESEL; w tym rozwiązaniu w jego miejscu stoi imię i nazwisko autora: Bartosz Bryniarski. To ma być paragraf <p>. Nad nim w stopce stanie jeszcze efekt skryptu 2 - nagłówek z miesiącem i rokiem.

    organizer.php PHP zmienione: 1 linia
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
        </section>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  20. Etap 3: Styl CSS

    Kroki 20-27 · po tym etapie masz 25 z 34 kryteriów (pkt)

    Krok 20: Plik styl6.css: krój Georgia dla całej strony

    Kryteria:

    R.4.1 Ustawiono domyślny krój czcionki dla całej strony na Georgia (dla selektora * lub body, lub html, lub kontenera całej strony)

    ok. 1 min
    Polecenie z arkusza

    Domyślne dla całej strony: krój czcionki Georgia

    Nowy plik styl6.css w folderze zdającego. Pierwsza reguła: selektor uniwersalny * z font-family: Georgia. Selektor * obejmuje każdy element, także pole tekstowe i przycisk „ZAPISZ”, które kroju po body nie dziedziczą - na obrazie 2 przycisk też jest pisany Georgią. Klucz R.4.1 przyjmuje *, body i html.

    styl6.css CSS nowy plik
    * {
        font-family: Georgia;
    }
    Podgląd strony po tym kroku (organizer.php)
  21. Krok 21: Pierwszy i drugi blok banera

    ok. 2 min
    Polecenie z arkusza

    Dla pierwszego i drugiego bloku banera: kolor tła #3CB371, biały kolor czcionki, wysokość 100 px, szerokość 40%

    Dwa selektory identyfikatorów rozdzielone przecinkiem dostają jedną wspólną regułę. #3CB371 to zieleń z arkusza zapisana szesnastkowo (czerwony 3C, zielony B3, niebieski 71).

    styl6.css CSS zmienione: 6 linii
    * {
        font-family: Georgia;
    }
    #banner-title, #banner-form {
        background-color: #3CB371;
        color: white;
        height: 100px;
        width: 40%;
    }
    Podgląd strony po tym kroku (organizer.php)
  22. Krok 22: Trzeci blok banera

    Kryteria: ,

    R.4.4 Ustawiono wysokość dla wszystkich bloków banera na 100px

    R.4.5 Ustawiono szerokość pierwszego i drugiego bloku banera na 40%, trzeciego bloku banera na 20%

    ok. 1 min
    Polecenie z arkusza

    Dla trzeciego bloku banera: kolor tła #3CB371, wysokość 100 px, szerokość 20%

    Bez koloru tekstu - w tym bloku jest tylko logo. Szerokości 40% + 40% + 20% = 100%, więc trzy bloki zmieszczą się w jednym rzędzie. Wszystkie bloki banera mają wysokość (R.4.4) i szerokość (R.4.5).

    Typowe błędy

    Bloki o sumie szerokości 100% nie mogą mieć obramowania ani padding - każdy dodatkowy piksel zepchnie logo do następnego rzędu.

    styl6.css CSS zmienione: 5 linii
    * {
        font-family: Georgia;
    }
    #banner-title, #banner-form {
        background-color: #3CB371;
        color: white;
        height: 100px;
        width: 40%;
    }
    #banner-logo {
        background-color: #3CB371;
        height: 100px;
        width: 20%;
    }
    Podgląd strony po tym kroku (organizer.php)
  23. Krok 23: Blok jednego dnia

    Kryteria:

    R.4.8 Ustawiono dla bloku z danymi z kalendarza: kolor tła #FFDAB9, szerokość 170px, wyskość 90px, marginesy zewnętrzne 5px, obramowanie o szerokości 1px linią ciągłą o kolorze #3CB371

    ok. 2 min
    Polecenie z arkusza

    Dla bloku wyświetlającego dane jednego dnia: kolor tła #FFDAB9, szerokość 170 px, wysokość 90 px, marginesy zewnętrzne 5 px, obramowanie o szerokości 1 px, linią ciągłą o kolorze #3CB371

    Bloków dni będzie 31, więc selektor klasy .day - identyfikator może wystąpić na stronie tylko raz. Skrypt dopisze każdemu blokowi class="day". margin: 5px to margines zewnętrzny ze wszystkich stron, a border: 1px solid #3CB371 - szerokość, styl linii (ciągła) i kolor w jednej właściwości (R.4.8).

    Na razie żadnego bloku dnia nie ma, więc podgląd się nie zmienia - klucz pozwala sprawdzić to kryterium w pliku CSS.

    styl6.css CSS zmienione: 7 linii
    * {
        font-family: Georgia;
    }
    #banner-title, #banner-form {
        background-color: #3CB371;
        color: white;
        height: 100px;
        width: 40%;
    }
    #banner-logo {
        background-color: #3CB371;
        height: 100px;
        width: 20%;
    }
    .day {
        background-color: #FFDAB9;
        width: 170px;
        height: 90px;
        margin: 5px;
        border: 1px solid #3CB371;
    }
    Podgląd strony po tym kroku (organizer.php)
  24. Krok 24: Stopka: tło i kolor tekstu

    Kryteria: ,

    R.4.2 Ustawiono kolor tła #3CB371 dla trzech bloków banera i stopki

    R.4.3 Ustawiono biały kolor czcionki dla pierwszego i drugiego bloku banera oraz dla stopki

    ok. 1 min
    Polecenie z arkusza

    Dla stopki: kolor tła #3CB371, biały kolor czcionki

    Ta sama zieleń co w banerze. Tło trzech bloków banera i stopki (R.4.2) oraz biały tekst w dwóch pierwszych blokach banera i stopce (R.4.3) są ustawione.

    styl6.css CSS zmienione: 4 linie
    * {
        font-family: Georgia;
    }
    #banner-title, #banner-form {
        background-color: #3CB371;
        color: white;
        height: 100px;
        width: 40%;
    }
    #banner-logo {
        background-color: #3CB371;
        height: 100px;
        width: 20%;
    }
    .day {
        background-color: #FFDAB9;
        width: 170px;
        height: 90px;
        margin: 5px;
        border: 1px solid #3CB371;
    }
    footer {
        background-color: #3CB371;
        color: white;
    }
    Podgląd strony po tym kroku (organizer.php)
  25. Krok 25: Nagłówek szóstego stopnia do prawej

    Kryteria:

    R.4.6 Ustawiono wyrównanie tekstu do prawej strony dla znacznika h6

    ok. 1 min
    Polecenie z arkusza

    Dla znacznika nagłówka szóstego stopnia: wyrównanie tekstu do prawej strony

    Selektor znacznika h6 - w nim skrypt wypisze datę i miesiąc każdego dnia, wyrównane do prawej jak na obrazie 3 (R.4.6).

    styl6.css CSS zmienione: 3 linie
    * {
        font-family: Georgia;
    }
    #banner-title, #banner-form {
        background-color: #3CB371;
        color: white;
        height: 100px;
        width: 40%;
    }
    #banner-logo {
        background-color: #3CB371;
        height: 100px;
        width: 20%;
    }
    .day {
        background-color: #FFDAB9;
        width: 170px;
        height: 90px;
        margin: 5px;
        border: 1px solid #3CB371;
    }
    footer {
        background-color: #3CB371;
        color: white;
    }
    h6 {
        text-align: right;
    }
    Podgląd strony po tym kroku (organizer.php)
  26. Krok 26: Formularz: marginesy wewnętrzne 30 px

    Kryteria:

    R.4.7 Ustawiono marginesy wewnętrzne (padding) na 30px dla znacznika formularza

    ok. 1 min
    Polecenie z arkusza

    Dla znacznika formularza: marginesy wewnętrzne 30 px

    padding: 30px - margines wewnętrzny z czterech stron. Pole i przycisk odsuwają się od krawędzi bloku i stoją mniej więcej w połowie wysokości banera (R.4.7).

    styl6.css CSS zmienione: 3 linie
    * {
        font-family: Georgia;
    }
    #banner-title, #banner-form {
        background-color: #3CB371;
        color: white;
        height: 100px;
        width: 40%;
    }
    #banner-logo {
        background-color: #3CB371;
        height: 100px;
        width: 20%;
    }
    .day {
        background-color: #FFDAB9;
        width: 170px;
        height: 90px;
        margin: 5px;
        border: 1px solid #3CB371;
    }
    footer {
        background-color: #3CB371;
        color: white;
    }
    h6 {
        text-align: right;
    }
    form {
        padding: 30px;
    }
    Podgląd strony po tym kroku (organizer.php)
  27. Krok 27: Układ bloków zgodny z obrazem 2

    Kryteria:

    R.3.1 Układ bloków po uruchomieniu strony w przeglądarce jest w pełni zgodny z Obrazem 2 w arkuszu egzaminacyjnym (uwaga! Blok główny nie musi zawierać bloków kalendarza)

    ok. 3 min
    Polecenie z arkusza

    Podział zrealizowany za pomocą znaczników sekcji tak, aby po uruchomieniu w przeglądarce wygląd układu bloków był zgodny z Obrazem 2

    • trzy bloki banera dostają float: left i stają obok siebie (40% + 40% + 20%),
    • blok główny z clear: both zaczyna się dopiero pod nimi,
    • bloki dni też pływają w lewo: każdy zajmuje 170 + 2 + 10 = 182 px, więc przeglądarka układa ich w rzędzie tyle, ile się zmieści, a resztę przenosi do następnego rzędu - jak kafelki kalendarza na obrazie 2,
    • stopka z clear: both staje pod ostatnim rzędem dni.

    Bloki dni pojawią się dopiero ze skryptem; klucz przy R.3.1 zaznacza, że blok główny nie musi ich zawierać, żeby układ był uznany. Wersja nowoczesna ustawia te same bloki bez float.

    Typowe błędy

    Bez clear przy stopce zielony pasek stopki wsunie się pod pływające bloki dni i schowa za nimi.

    styl6.css CSS zmienione: 6 linii
    * {
        font-family: Georgia;
    }
    #banner-title, #banner-form {
        background-color: #3CB371;
        color: white;
        height: 100px;
        width: 40%;
    }
    #banner-logo {
        background-color: #3CB371;
        height: 100px;
        width: 20%;
    }
    .day {
        background-color: #FFDAB9;
        width: 170px;
        height: 90px;
        margin: 5px;
        border: 1px solid #3CB371;
    }
    footer {
        background-color: #3CB371;
        color: white;
    }
    h6 {
        text-align: right;
    }
    form {
        padding: 30px;
    }
    #banner-title, #banner-form, #banner-logo, .day {
        float: left;
    }
    #main, footer {
        clear: both;
    }
    Podgląd strony po tym kroku (organizer.php)
  28. Etap 4: Skrypt PHP

    Kroki 28-35 · po tym etapie masz 34 z 34 kryteriów (pkt)

    Krok 28: Połączenie z bazą egzamin6

    Kryteria:

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

    ok. 3 min
    Polecenie z arkusza

    Skrypt łączy się z serwerem bazodanowym na localhost, użytkownik root bez hasła, baza danych o nazwie egzamin6

    Na samej górze organizer.php, przed <!DOCTYPE html>, piszesz pierwszy blok PHP. Funkcja mysqli_connect z tabeli 1 arkusza przyjmuje kolejno serwer localhost, użytkownika root, puste hasło '' i nazwę bazy egzamin6 - czwarty argument od razu wybiera bazę (R.5.1). Połączenie trafia do zmiennej $connection, z której skorzystają wszystkie zapytania na stronie.

    Dlaczego na górze: z połączenia korzystają trzy miejsca strony (zapis z formularza, blok główny i stopka), a w tym bloku za chwilę stanie też obsługa formularza - zapis musi wykonać się przed odczytem dni, żeby nowy wpis był od razu widoczny. Od tego kroku podgląd to wynik prawdziwego uruchomienia w PHP 8.2; stronę sprawdzasz przez http://localhost/…, a nie otwierając plik z dysku.

    organizer.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'egzamin6');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
        </section>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  29. Krok 29: Skrypt 1: zapytanie 1 i blok dla każdego wiersza

    Kryteria: ,

    R.5.3 Skrypt 1 wysyła do bazy danych zapytanie 1 lub Skrypt 2 wysyła do bazy zapytanie 2

    R.5.4 Skrypt 1 definiuje w bloku głównym blok (za pomocą znaczników sekcji), a w nim wyświetla dane przesłane skryptem. Liczba wygenerowanych bloków odpowiada liczbie wierszy zwróconych zapytaniem

    ok. 4 min
    Polecenie z arkusza

    Działanie skryptu 1: Wysyła do bazy danych zapytanie 1. Definiuje blok (za pomocą znacznika sekcji), a w nim wyświetla dane przesłane skryptem dotyczące jednego wiersza.

    Blok PHP wewnątrz <section id="main">:

    • mysqli_query($connection, "SELECT ...") - zapytanie 1 z kwerendy.txt, bez zmian (R.5.3); wynik trafia do $result,
    • while ($row = mysqli_fetch_row($result)) - mysqli_fetch_row zwraca kolejny wiersz jako tablicę numerowaną od zera, a gdy wierszy zabraknie - false, i pętla się kończy,
    • w każdym obrocie echo wypisuje otwarcie i zamknięcie znacznika sekcji z klasą day - tą samą, którą ma reguła w CSS.

    Zapytanie zwraca 31 wierszy, więc pętla obraca się 31 razy i na stronie jest 31 bloków - po jednym na każdy wiersz (R.5.4). Bloki są jeszcze puste, ale już widać kafelki z obramowaniem ułożone w rzędy. Napis w echo jest w apostrofach, bo w środku stoją cudzysłowy atrybutu class.

    organizer.php PHP zmienione: 7 linii
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'egzamin6');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
            <?php
            $result = mysqli_query($connection, "SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';");
            while ($row = mysqli_fetch_row($result)) {
                echo '<section class="day">';
                echo '</section>';
            }
            ?>
        </section>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  30. Krok 30: Skrypt 1: data i miesiąc w nagłówku szóstego stopnia

    ok. 2 min
    Polecenie z arkusza

    W nagłówku szóstego stopnia data zadania i miesiąc oddzielone przecinkiem

    Wiersz z zapytania 1 ma trzy kolumny w kolejności z SELECT: $row[0] to dataZadania, $row[1] - miesiac, $row[2] - wpis. Nagłówek <h6> dostaje datę, przecinek ze spacją i miesiąc: 2020-08-01, sierpien, jak na obrazie 3.

    Napis jest w cudzysłowie "…", więc PHP podstawia w nim wartości zmiennych; zapis $row[0] bez apostrofów wokół indeksu jest w takim napisie poprawny. Reguła h6 z CSS wyrównuje daty do prawej.

    organizer.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'egzamin6');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
            <?php
            $result = mysqli_query($connection, "SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';");
            while ($row = mysqli_fetch_row($result)) {
                echo '<section class="day">';
                echo "<h6>$row[0], $row[1]</h6>";
                echo '</section>';
            }
            ?>
        </section>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  31. Krok 31: Skrypt 1: wpis w paragrafie

    Kryteria:

    R.5.5 Data zadania i miesiac są zapisana w nagłówku h6 oraz pole wpis jest zapisane w paragrafie

    ok. 2 min
    Polecenie z arkusza

    Pole wpis w paragrafie (akapicie)

    Trzecia kolumna, $row[2], w paragrafie <p>. Dni bez wpisu mają w bazie pusty napis, więc ich paragraf jest pusty, a blok pokazuje samą datę - tak jak dni 5, 6 czy 9 sierpnia na obrazie 2. Data w <h6> i wpis w <p> dają R.5.5. Blok 27 sierpnia ma już wpis „Wycieczka: Karkonosze” z zapytania 4.

    organizer.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'egzamin6');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
            <?php
            $result = mysqli_query($connection, "SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';");
            while ($row = mysqli_fetch_row($result)) {
                echo '<section class="day">';
                echo "<h6>$row[0], $row[1]</h6>";
                echo "<p>$row[2]</p>";
                echo '</section>';
            }
            ?>
        </section>
        <footer>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  32. Krok 32: Skrypt 2: miesiąc i rok w nagłówku pierwszego stopnia

    Kryteria: ,

    R.5.6 Skrypt 2 wyświetla tekst: „miesiąc: <nazwa_miesiąca>, rok: <rok>”, gdzie pola w nawiasach <> oznaczają dane pobrane skryptem

    R.2.5 W pierwszym bloku banera zapisano nagłówek h2: "MÓJ OGRANIZER" oraz w stopce nagłówek h1 (w skrypcie lub HTML) oraz zapisano przynajmniej jeden paragraf (znacznik <p>)

    ok. 3 min
    Polecenie z arkusza

    Działanie skryptu 2: Wysyła do bazy danych zapytanie 2. Wyświetla w nagłówku pierwszego stopnia tekst: „miesiąc: <nazwa_miesiąca>, rok: <rok>”, gdzie pola w nawiasach <…> oznaczają dane pobrane skryptem

    Drugi blok PHP w stopce, nad paragrafem autora:

    • zapytanie 2 przez to samo połączenie $connection,
    • zapytanie zwraca dokładnie jeden wiersz, więc wystarczy jedno wywołanie mysqli_fetch_row - bez pętli,
    • echo nagłówka <h1> ze stałym tekstem i dwiema wartościami: $row[0] (miesiąc) i $row[1] (rok).

    Na stronie: „miesiąc: sierpien, rok: 2020” (R.5.6). Nazwa miesiąca pochodzi z bazy, dlatego bez „ń” - tak samo jest na obrazie 2. Strona ma teraz <h2> w banerze, <h1> w stopce i paragraf (R.2.5).

    organizer.php PHP zmienione: 5 linii
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'egzamin6');
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
            <?php
            $result = mysqli_query($connection, "SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';");
            while ($row = mysqli_fetch_row($result)) {
                echo '<section class="day">';
                echo "<h6>$row[0], $row[1]</h6>";
                echo "<p>$row[2]</p>";
                echo '</section>';
            }
            ?>
        </section>
        <footer>
            <?php
            $result = mysqli_query($connection, "SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08-01';");
            $row = mysqli_fetch_row($result);
            echo "<h1>miesiąc: $row[0], rok: $row[1]</h1>";
            ?>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  33. Krok 33: Odczyt wpisu z formularza

    Kryteria:

    R.5.7 W skrypcie działającym na danych przesłanych przez formularz zastosowano tablicę $_POST, zapis musi być poprawny składniowo i logicznie

    ok. 2 min
    Polecenie z arkusza

    Działanie skryptu, który odbiera dane przesłane z formularza. Skrypt pobiera dane z pola edycyjnego

    W bloku na górze pliku, pod połączeniem:

    • isset($_POST['wpis']) - prawdziwe dopiero po kliknięciu „ZAPISZ”. Przy zwykłym wejściu na stronę formularz nie był wysłany i ta część się nie wykonuje,
    • $wpis = $_POST['wpis']; - tekst z pola o nazwie wpis do zmiennej. Kluczem w tablicy $_POST jest atrybut name pola (R.5.7).

    Podgląd: formularz wysłany ze słowem „Basen” - skrypt je odczytał, ale jeszcze nigdzie nie zapisał, więc strona wygląda jak wcześniej.

    organizer.php PHP zmienione: 3 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'egzamin6');
    if (isset($_POST['wpis'])) {
        $wpis = $_POST['wpis'];
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
            <?php
            $result = mysqli_query($connection, "SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';");
            while ($row = mysqli_fetch_row($result)) {
                echo '<section class="day">';
                echo "<h6>$row[0], $row[1]</h6>";
                echo "<p>$row[2]</p>";
                echo '</section>';
            }
            ?>
        </section>
        <footer>
            <?php
            $result = mysqli_query($connection, "SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08-01';");
            $row = mysqli_fetch_row($result);
            echo "<h1>miesiąc: $row[0], rok: $row[1]</h1>";
            ?>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>

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

  34. Krok 34: Zapis wpisu: zmodyfikowane zapytanie 4

    Kryteria:

    R.5.8 Skrypt obsługujący formularz uaktualnia dane w bazie danych. Wpis pobrany z formularza jest wpisany do rekordu o dacie 2020-08-27. Należy sprawdzić w bazie danych czy po wciśnięciu przycisku baza została uaktualniona, nie jest wymagane uaktualnienie na stronie

    ok. 3 min
    Polecenie z arkusza

    Następnie uaktualnia dane w bazie danych. Aby uprościć zadanie skrypt wprowadza wpis zawsze do rekordu o dacie 2020-08-27. Do uaktualnienia danych wykorzystaj zapytanie 4, modyfikując w nim wartość pola wpis

    Zapytanie 4 z kwerendy.txt, w którym w miejsce stałego tekstu „Wycieczka: Karkonosze” stoi '$wpis'. Data zostaje: arkusz każe zawsze zapisywać do 27 sierpnia. Całe zapytanie jest w cudzysłowie, więc PHP podstawia wartość zmiennej, a apostrofy wewnątrz to ograniczniki napisu w SQL.

    Sprawdzenie, jak każe klucz (R.5.8): po wpisaniu „Basen” i kliknięciu „ZAPISZ” w tabeli zadania wiersz z datą 2020-08-27 ma wpis Basen. Ponieważ zapis stoi na górze pliku, przed skryptem 1, nowy wpis widać od razu w bloku 27 sierpnia - klucz tego nie wymaga, ale to wygodne przy sprawdzaniu.

    Typowe błędy

    Wpis z literami „ś” czy „ż” (np. „Śnieżka”) kończy się błędem „Incorrect string value”: tabela z archiwum ma kodowanie latin1, które tych liter nie zna, a MariaDB z XAMPP działa w trybie ścisłym. To cecha bazy z archiwum, nie błąd skryptu - klucz sprawdza import pliku bez zmian, więc tabeli nie przerabiaj, a zapis sprawdź słowem bez polskich liter.

    organizer.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'egzamin6');
    if (isset($_POST['wpis'])) {
        $wpis = $_POST['wpis'];
        mysqli_query($connection, "UPDATE zadania SET wpis = '$wpis' WHERE dataZadania = '2020-08-27';");
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
            <?php
            $result = mysqli_query($connection, "SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';");
            while ($row = mysqli_fetch_row($result)) {
                echo '<section class="day">';
                echo "<h6>$row[0], $row[1]</h6>";
                echo "<p>$row[2]</p>";
                echo '</section>';
            }
            ?>
        </section>
        <footer>
            <?php
            $result = mysqli_query($connection, "SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08-01';");
            $row = mysqli_fetch_row($result);
            echo "<h1>miesiąc: $row[0], rok: $row[1]</h1>";
            ?>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  35. Krok 35: Zamknięcie połączenia z bazą

    Kryteria:

    R.5.2 Ostatnią operacją na bazie jest jej zamknięcie (zgodne ze składnią)

    ok. 1 min
    Polecenie z arkusza

    Na końcu działania skrypt zamyka połączenie z serwerem

    mysqli_close($connection) w bloku PHP stopki, po zapytaniu 2 - to ostatnie zapytanie na stronie, więc zamknięcie jest ostatnią operacją na bazie (R.5.2). Strona spełnia wszystkie kryteria arkusza. Dalej trzy kroki o zabezpieczeniach - klucz ich nie wymaga.

    Typowe błędy

    mysqli_close zaraz po połączeniu albo w bloku głównym sprawia, że zapytanie 2 w stopce dostaje zamknięte połączenie i PHP kończy się błędem.

    organizer.php PHP zmienione: 1 linia
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'egzamin6');
    if (isset($_POST['wpis'])) {
        $wpis = $_POST['wpis'];
        mysqli_query($connection, "UPDATE zadania SET wpis = '$wpis' WHERE dataZadania = '2020-08-27';");
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
            <?php
            $result = mysqli_query($connection, "SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';");
            while ($row = mysqli_fetch_row($result)) {
                echo '<section class="day">';
                echo "<h6>$row[0], $row[1]</h6>";
                echo "<p>$row[2]</p>";
                echo '</section>';
            }
            ?>
        </section>
        <footer>
            <?php
            $result = mysqli_query($connection, "SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08-01';");
            $row = mysqli_fetch_row($result);
            echo "<h1>miesiąc: $row[0], rok: $row[1]</h1>";
            mysqli_close($connection);
            ?>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  36. Etap 5: Dobre praktyki (poza kluczem)

    Kroki 36-38 · po tym etapie masz 34 z 34 kryteriów (pkt)

    Krok 36: Zabezpieczenie: zapytanie przygotowane dla UPDATE

    ok. 4 min
    Polecenie z arkusza

    Do uaktualnienia danych wykorzystaj zapytanie 4, modyfikując w nim wartość pola wpis

    Tekst z formularza trafia do zapytania wprost - to SQL injection. Sprawdzone na wersji z poprzednich kroków:

    • wpis Hakowanie' -- (z apostrofem i dwoma myślnikami ze spacją) zamienia zapytanie w UPDATE zadania SET wpis = 'Hakowanie' -- ' WHERE .... Dwa myślniki ze spacją zaczynają w SQL komentarz, więc warunek WHERE znika i baza zmienia wszystkie 63 wiersze tabeli - cały lipiec, sierpień i listopad dostały wpis „Hakowanie”,
    • zwykły wpis z apostrofem, np. Kino 'Helios', rozbija składnię zapytania - PHP 8.1 i nowsze zgłaszają wtedy wyjątek mysqli_sql_exception, a zamiast strony widać „Fatal error”.

    Lekarstwo to zapytanie przygotowane: mysqli_prepare z jednym znakiem ? w miejscu wpisu, mysqli_stmt_bind_param z typem 's' (napis) i mysqli_stmt_execute. Baza dostaje treść zapytania i dane osobno, więc dane nigdy nie staną się kodem SQL. Podgląd: ten sam „wpis-atak” zapisał się jako zwykły tekst - tylko w bloku 27 sierpnia. Stara linia zostaje w komentarzu.

    Typowe błędy

    Data 2020-08-27 jest stała, więc może zostać w treści zapytania. Znak ? zastępuje tylko wartości pochodzące od użytkownika.

    organizer.php PHP zmienione: 4 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'egzamin6');
    if (isset($_POST['wpis'])) {
        $wpis = $_POST['wpis'];
        // mysqli_query($connection, "UPDATE zadania SET wpis = '$wpis' WHERE dataZadania = '2020-08-27';");
        $statement = mysqli_prepare($connection, "UPDATE zadania SET wpis = ? WHERE dataZadania = '2020-08-27';");
        mysqli_stmt_bind_param($statement, 's', $wpis);
        mysqli_stmt_execute($statement);
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
            <?php
            $result = mysqli_query($connection, "SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';");
            while ($row = mysqli_fetch_row($result)) {
                echo '<section class="day">';
                echo "<h6>$row[0], $row[1]</h6>";
                echo "<p>$row[2]</p>";
                echo '</section>';
            }
            ?>
        </section>
        <footer>
            <?php
            $result = mysqli_query($connection, "SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08-01';");
            $row = mysqli_fetch_row($result);
            echo "<h1>miesiąc: $row[0], rok: $row[1]</h1>";
            mysqli_close($connection);
            ?>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  37. Krok 37: Zabezpieczenie: htmlspecialchars przy wyświetlaniu wpisu

    ok. 3 min
    Polecenie z arkusza

    Pole wpis w paragrafie (akapicie)

    Zapytanie przygotowane chroni bazę, ale wpis wraca potem na stronę - i każdy, kto otworzy organizer, zobaczy go jako kod HTML. Sprawdzone: wpis <b style="color:red">PILNE</b> wyświetlał się w bloku 27 sierpnia jako czerwone, pogrubione słowo. Tak samo przeszedłby znacznik <script> - kod JavaScript uruchamiałby się u każdego odwiedzającego (stored XSS, zapisany atak na przeglądarki).

    Funkcja htmlspecialchars zamienia <, >, & i cudzysłowy na encje (np. < na &lt;), więc przeglądarka pokazuje znaki dosłownie, zamiast je wykonywać. Stosuje się ją przy wypisywaniu danych, które przyszły od użytkownika - tutaj to kolumna wpis. Daty i nazwy miesięcy pochodzą z importu, a nie z formularza. Podgląd: wpis widać dosłownie, ze znacznikami.

    organizer.php PHP zmienione: 2 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'egzamin6');
    if (isset($_POST['wpis'])) {
        $wpis = $_POST['wpis'];
        // mysqli_query($connection, "UPDATE zadania SET wpis = '$wpis' WHERE dataZadania = '2020-08-27';");
        $statement = mysqli_prepare($connection, "UPDATE zadania SET wpis = ? WHERE dataZadania = '2020-08-27';");
        mysqli_stmt_bind_param($statement, 's', $wpis);
        mysqli_stmt_execute($statement);
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
            <?php
            $result = mysqli_query($connection, "SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';");
            while ($row = mysqli_fetch_row($result)) {
                echo '<section class="day">';
                echo "<h6>$row[0], $row[1]</h6>";
                // echo "<p>$row[2]</p>";
                echo '<p>' . htmlspecialchars($row[2]) . '</p>';
                echo '</section>';
            }
            ?>
        </section>
        <footer>
            <?php
            $result = mysqli_query($connection, "SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08-01';");
            $row = mysqli_fetch_row($result);
            echo "<h1>miesiąc: $row[0], rok: $row[1]</h1>";
            mysqli_close($connection);
            ?>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  38. Krok 38: Zabezpieczenie: pusty formularz nie kasuje wpisu

    ok. 3 min
    Polecenie z arkusza

    Skrypt pobiera dane z pola edycyjnego

    isset jest prawdziwe także wtedy, gdy pole jest puste - po kliknięciu „ZAPISZ” klucz wpis zawsze jest w $_POST. Sprawdzone: kliknięcie przycisku bez wpisania tekstu zapisało pusty napis i skasowało „Wycieczka: Karkonosze” z 27 sierpnia.

    Warunek dostaje drugi człon: trim($_POST['wpis']) !== '' - trim zdejmuje spacje z brzegów, więc wpis z samych spacji też się nie liczy. Do bazy idzie wersja przycięta. Podgląd: formularz wysłany z trzema spacjami - wpis 27 sierpnia został.

    W prawdziwym serwisie doszłaby jeszcze kontrola długości wpisu (blok ma 170 × 90 px) i ochrona przed wysłaniem formularza z obcej strony (token CSRF) - klucz ich nie ocenia.

    organizer.php PHP zmienione: 4 linie
    <?php
    $connection = mysqli_connect('localhost', 'root', '', 'egzamin6');
    // if (isset($_POST['wpis'])) {
    //     $wpis = $_POST['wpis'];
    if (isset($_POST['wpis']) && trim($_POST['wpis']) !== '') {
        $wpis = trim($_POST['wpis']);
        // mysqli_query($connection, "UPDATE zadania SET wpis = '$wpis' WHERE dataZadania = '2020-08-27';");
        $statement = mysqli_prepare($connection, "UPDATE zadania SET wpis = ? WHERE dataZadania = '2020-08-27';");
        mysqli_stmt_bind_param($statement, 's', $wpis);
        mysqli_stmt_execute($statement);
    }
    ?>
    <!DOCTYPE html>
    <html lang="pl">
    <head>
        <meta charset="UTF-8">
        <title>Organizer</title>
        <link rel="stylesheet" href="styl6.css">
    </head>
    <body>
        <section id="banner-title">
            <h2>MÓJ ORGANIZER</h2>
        </section>
        <section id="banner-form">
            <form method="post">
                Wpis wydarzenia: <input type="text" name="wpis">
                <input type="submit" value="ZAPISZ">
            </form>
        </section>
        <section id="banner-logo">
            <img src="logo2.png" alt="Mój organizer">
        </section>
        <section id="main">
            <?php
            $result = mysqli_query($connection, "SELECT dataZadania, miesiac, wpis FROM zadania WHERE miesiac = 'sierpien';");
            while ($row = mysqli_fetch_row($result)) {
                echo '<section class="day">';
                echo "<h6>$row[0], $row[1]</h6>";
                // echo "<p>$row[2]</p>";
                echo '<p>' . htmlspecialchars($row[2]) . '</p>';
                echo '</section>';
            }
            ?>
        </section>
        <footer>
            <?php
            $result = mysqli_query($connection, "SELECT miesiac, rok FROM zadania WHERE dataZadania = '2020-08-01';");
            $row = mysqli_fetch_row($result);
            echo "<h1>miesiąc: $row[0], rok: $row[1]</h1>";
            mysqli_close($connection);
            ?>
            <p>Stronę wykonał: Bartosz Bryniarski</p>
        </footer>
    </body>
    </html>
    Podgląd strony po tym kroku (organizer.php)
  39. Etap 6: Oddanie pracy

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

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

    ok. 5 min
    Polecenie z arkusza

    Po zakończeniu pracy utwórz plik tekstowy. Zapisz w nim nazwę przeglądarki internetowej, w której weryfikowałeś poprawność działania witryny. Nazwij plik przeglądarka.txt i zapisz go w folderze z numerem PESEL. Nagraj płytę z rezultatami pracy.

    W przeglądarka.txt wpisujesz nazwę przeglądarki, w której sprawdzasz stronę przez http://localhost/….

    W folderze z numerem zdającego muszą być: import.png, kw1.jpg, kw2.jpg, kw3.jpg, kw4.jpg, kwerendy.txt, logo2.png, organizer.php, przeglądarka.txt i styl6.css (pliki z archiwum mogą zostać). Nagraj płytę, sprawdź jej odczyt, opisz ją numerem PESEL i zostaw w pudełku na stanowisku razem z arkuszem.

    przeglądarka.txt Tekst nowy plik
    Google Chrome

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

Podgląd

To jedno z możliwych rozwiązań. Egzaminator ocenia spełnienie kryteriów z klucza odpowiedzi, a nie zgodność z tym kodem - inna, poprawna droga też daje punkty.

Autor rozwiązania: . Licencja CC BY-NC 4.0 - kopiowanie i przeróbki do celów niekomercyjnych, z podaniem autora i źródła (link do tej strony) (zasady wykorzystania). Treść arkusza i klucza oceniania pochodzi z CKE.

Potwierdzenie

Informacja