<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>POST - Kama Kaczmarek</title>
	<atom:link href="https://kamakaczmarek.net/tag/post/feed/" rel="self" type="application/rss+xml" />
	<link>https://kamakaczmarek.net/tag/post/</link>
	<description>Strona z materiałami dla uczniów i nauczycieli</description>
	<lastBuildDate>Mon, 02 Nov 2026 08:52:10 +0000</lastBuildDate>
	<language>pl-PL</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>13. PHP &#8211; odbieranie danych z przeglądarki &#8211; formularze html, metody GET i POST. Wysyłanie danych do przeglądarki.</title>
		<link>https://kamakaczmarek.net/php-odbieranie-danych-z-przegladarki-formularze-html-metody-get-i-post-wysylanie-danych-do-przegladarki/</link>
		
		<dc:creator><![CDATA[Kama]]></dc:creator>
		<pubDate>Sat, 01 Dec 2018 20:26:13 +0000</pubDate>
				<category><![CDATA[aplikacje]]></category>
		<category><![CDATA[dla ucznia]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[formularze]]></category>
		<category><![CDATA[GET]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[POST]]></category>
		<guid isPermaLink="false">https://kamakaczmarek.net/?p=2328</guid>

					<description><![CDATA[<p>Szybki quiz – przypomnienie poprzedniej lekcji Lekcja 12: wyjątki (throw, try, catch, finally). Odpowiedz z pamięci, bez zaglądania do kodu. 1. Co zostanie wypisane? 2. Znajdź błąd. Dlaczego ten kod nie zatrzymuje zapisu ujemnej nagrody? 3. Dla każdej sytuacji odpowiedz: wyjątek czy zwykła wartość zwrócona z funkcji? a) pliku data/zadania.csv nie ma na dysku,b) plik [&#8230;]</p>
<p>Artykuł <a href="https://kamakaczmarek.net/php-odbieranie-danych-z-przegladarki-formularze-html-metody-get-i-post-wysylanie-danych-do-przegladarki/">13. PHP &#8211; odbieranie danych z przeglądarki &#8211; formularze html, metody GET i POST. Wysyłanie danych do przeglądarki.</a> pochodzi z serwisu <a href="https://kamakaczmarek.net">Kama Kaczmarek</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">Szybki quiz – przypomnienie poprzedniej lekcji</h2>



<p class="wp-block-paragraph">Lekcja 12: wyjątki (<code class="prettyprint lang-sql" style="font-size: 0.85em;">throw</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">catch</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">finally</code>). Odpowiedz z pamięci, bez zaglądania do kodu.</p>



<p class="wp-block-paragraph"><strong>1.</strong> Co zostanie wypisane?</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">function sprawdzNagrode(int $nagroda): int
{
    if ($nagroda &lt; 0) {
        throw new Exception("Nagroda nie może być ujemna.");
    }
    return $nagroda * 2;
}

try {
    echo "A";
    echo sprawdzNagrode(-3);
    echo "B";
} catch (Exception $wyjatek) {
    echo "C";
}
echo "D";</code></pre>



<p class="wp-block-paragraph"><strong>2.</strong> Znajdź błąd. Dlaczego ten kod nie zatrzymuje zapisu ujemnej nagrody?</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">if ($nagroda &lt; 0) {
    new Exception("Nagroda nie może być ujemna.");
}
$xp = $xp + $nagroda;</code></pre>



<p class="wp-block-paragraph"><strong>3.</strong> Dla każdej sytuacji odpowiedz: <strong>wyjątek</strong> czy <strong>zwykła wartość zwrócona z funkcji</strong>?</p>



<p class="wp-block-paragraph">a) pliku <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code> nie ma na dysku,<br />b) plik zawiera tylko wiersz nagłówka,<br />c) wiersz CSV ma 5 pól zamiast 6,<br />d) login nie przechodzi walidacji w <code class="prettyprint lang-sql" style="font-size: 0.85em;">walidujLogin()</code>.</p>



<p class="wp-block-paragraph"><strong>4.</strong> W <code class="prettyprint lang-sql" style="font-size: 0.85em;">wczytajZadania()</code> <code class="prettyprint lang-sql" style="font-size: 0.85em;">fclose()</code> znalazło się w bloku <code class="prettyprint lang-sql" style="font-size: 0.85em;">finally</code>, a nie za pętlą czytającą plik. Co by się stało, gdyby zostało za pętlą, a w jej środku poleciał wyjątek?</p>



<p class="wp-block-paragraph"><strong>5.</strong> Czy <code class="prettyprint lang-sql" style="font-size: 0.85em;">catch (Exception $wyjatek)</code> przechwyci <code class="prettyprint lang-sql" style="font-size: 0.85em;">TypeError</code> (np. gdy do funkcji z parametrem <code class="prettyprint lang-sql" style="font-size: 0.85em;">int</code> przekażesz tekst <code class="prettyprint lang-sql" style="font-size: 0.85em;">"abc"</code>)? Uzasadnij.</p>



<p class="wp-block-paragraph"><strong>6.</strong> Uzupełnij i odpowiedz: w bloku <code class="prettyprint lang-sql" style="font-size: 0.85em;">catch</code> wypisujemy użytkownikowi <code class="prettyprint lang-sql" style="font-size: 0.85em;">echo $wyjatek-&gt;________;</code>. Których metod wyjątku <strong>nie</strong> pokazujemy na stronie i dlaczego?</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>Odpowiedzi</strong></summary>
<ol class="wp-block-list">
<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">ACD</code>. <code class="prettyprint lang-sql" style="font-size: 0.85em;">throw</code> przerywa funkcję, reszta bloku <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code> (czyli <code class="prettyprint lang-sql" style="font-size: 0.85em;">echo "B"</code>) jest pomijana, PHP skacze do <code class="prettyprint lang-sql" style="font-size: 0.85em;">catch</code>, a po nim program działa dalej.</li>



<li>Brak słowa <code class="prettyprint lang-sql" style="font-size: 0.85em;">throw</code>. Samo <code class="prettyprint lang-sql" style="font-size: 0.85em;">new Exception(...)</code> tworzy obiekt i go porzuca — program leci dalej i dodaje ujemną nagrodę. Poprawnie: <code class="prettyprint lang-sql" style="font-size: 0.85em;">throw new Exception("...");</code>.</li>



<li>a) wyjątek, b) zwykła wartość — pusty plik to normalny stan („Brak zadań.”), c) wyjątek — dane uszkodzone, d) zwykła wartość <code class="prettyprint lang-sql" style="font-size: 0.85em;">true</code>/<code class="prettyprint lang-sql" style="font-size: 0.85em;">false</code> — zły login to nie awaria.</li>



<li>Wyjątek przerwałby pętlę i przeskoczył do <code class="prettyprint lang-sql" style="font-size: 0.85em;">catch</code>, więc <code class="prettyprint lang-sql" style="font-size: 0.85em;">fclose()</code> za pętlą nigdy by się nie wykonało — plik zostałby otwarty. <code class="prettyprint lang-sql" style="font-size: 0.85em;">finally</code> wykonuje się zawsze, także po wyjątku.</li>



<li>Nie. <code class="prettyprint lang-sql" style="font-size: 0.85em;">TypeError</code> należy do rodziny <code class="prettyprint lang-sql" style="font-size: 0.85em;">Error</code>, a nie <code class="prettyprint lang-sql" style="font-size: 0.85em;">Exception</code> — <code class="prettyprint lang-sql" style="font-size: 0.85em;">catch (Exception ...)</code> go nie złapie. To zwykle błąd w kodzie, który poprawiamy, a nie łapiemy.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">getMessage()</code>. Nie pokazujemy <code class="prettyprint lang-sql" style="font-size: 0.85em;">getFile()</code> ani <code class="prettyprint lang-sql" style="font-size: 0.85em;">getTraceAsString()</code> — ujawniają ścieżki na serwerze i strukturę kodu.</li>
</ol>
</details>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><a href="#TaskQuest">TaskQuest</a></p>
<p><a href="#dodatek">Dodatek</a></p>



<div data-wp-interactive="core/file" class="wp-block-file"><object data-wp-bind--hidden="!state.hasPdfPreview" hidden class="wp-block-file__embed" data="https://kamakaczmarek.net/wp-content/uploads/JAK-DZIALAJA-FORMULARZE.pdf" type="application/pdf" style="width:100%;height:660px" aria-label="Osadzone z JAK DZIAŁAJĄ FORMULARZE."></object><a id="wp-block-file--media-bdefae1e-e2f4-40e0-8547-b6cafa6fe046" href="https://kamakaczmarek.net/wp-content/uploads/JAK-DZIALAJA-FORMULARZE.pdf">JAK DZIAŁAJĄ FORMULARZE</a><a href="https://kamakaczmarek.net/wp-content/uploads/JAK-DZIALAJA-FORMULARZE.pdf" class="wp-block-file__button wp-element-button" download aria-describedby="wp-block-file--media-bdefae1e-e2f4-40e0-8547-b6cafa6fe046">Pobierz</a></div>



<h2 class="wp-block-heading">Przykład z formularzem</h2>



<p class="wp-block-paragraph">Poniżej znajduje się przykład przedstawiający zapis danych do pliku z formularza. Formularz przesyła dane metodą POST.</p>



<p>Dane zapisywane do pliku są zbierane w zmiennej $info.</p>



<p class="wp-block-paragraph">Plik <em>formularz.php</em></p>



<pre class="wp-block-code prettyprint"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php
// Debugowanie - wyświetl dane POST
/*
echo "&lt;pre&gt;";
print_r($_POST);
echo "&lt;/pre&gt;";
*/

$info = '';
$errors = &#91;];

if ($_SERVER&#91;'REQUEST_METHOD'] === 'POST' &amp;&amp; isset($_POST&#91;'przycisk'])) {
    // Walidacja
    if (empty($_POST&#91;'imie'])) {
        $errors&#91;] = "Imię jest wymagane";
    }
    
    if (empty($_POST&#91;'nazwisko'])) {
        $errors&#91;] = "Nazwisko jest wymagane";
    }
    
    // Jeśli są błędy, wyświetl
    if (!empty($errors)) {
        echo "&lt;div style='color: red; border: 1px solid red; padding: 10px;'&gt;";
        echo "&lt;strong&gt;Błędy:&lt;/strong&gt;&lt;ul&gt;";
        foreach ($errors as $error) {
            echo "&lt;li&gt;$error&lt;/li&gt;";
        }
        echo "&lt;/ul&gt;&lt;/div&gt;";
    } else {
        // Pobierz dane
        $imie = trim($_POST&#91;'imie']);
        $nazwisko = trim($_POST&#91;'nazwisko']);
        
        echo "&lt;p&gt;Witaj $nazwisko $imie na mojej stronie.&lt;/p&gt;";
        
        // Zbierz informacje
        $info = "&lt;div style='border: 1px solid #ccc; padding: 15px; margin: 10px 0;'&gt;";
        $info .= "&lt;h3&gt;Przekazane dane:&lt;/h3&gt;";
        
        // Wiadomość
        if (!empty($_POST&#91;'wiadomosc'])) {
            $wiadomosc = htmlspecialchars($_POST&#91;'wiadomosc'], ENT_QUOTES, 'UTF-8');
            $info .= "&lt;p&gt;&lt;strong&gt;Wiadomość:&lt;/strong&gt;&lt;br&gt;" . nl2br($wiadomosc) . "&lt;/p&gt;";
        }
        
        // Płeć
        if (!empty($_POST&#91;'plec'])) {
            $plec_map = &#91;'k' =&gt; 'Kobieta', 'm' =&gt; 'Mężczyzna', 'i' =&gt; 'Inne'];
            $plec = $plec_map&#91;$_POST&#91;'plec']] ?? 'Nieznana';
            $info .= "&lt;p&gt;&lt;strong&gt;Płeć:&lt;/strong&gt; $plec&lt;/p&gt;";
        }
        
        // Gry (checkboxy pojedyncze)
        $gry = &#91;];
        if (!empty($_POST&#91;'gra1'])) $gry&#91;] = $_POST&#91;'gra1'];
        if (!empty($_POST&#91;'gra2'])) $gry&#91;] = $_POST&#91;'gra2'];
        if (!empty($_POST&#91;'gra3'])) $gry&#91;] = $_POST&#91;'gra3'];
        
        if (!empty($gry)) {
            $info .= "&lt;p&gt;&lt;strong&gt;Ulubione gry:&lt;/strong&gt; " . 
                     implode(', ', array_map('htmlspecialchars', $gry)) . "&lt;/p&gt;";
        }
        
        // Jedzenie (checkboxy jako tablica)
        if (!empty($_POST&#91;'food']) &amp;&amp; is_array($_POST&#91;'food'])) {
            $food = array_map('htmlspecialchars', $_POST&#91;'food']);
            $info .= "&lt;p&gt;&lt;strong&gt;Ulubione jedzenie:&lt;/strong&gt; " . 
                     implode(', ', $food) . "&lt;/p&gt;";
        }
        
        // Klasa (select)
        if (!empty($_POST&#91;'klasa'])) {
            $klasa = htmlspecialchars($_POST&#91;'klasa']);
            $info .= "&lt;p&gt;&lt;strong&gt;Klasa:&lt;/strong&gt; $klasa&lt;/p&gt;";
        }
        
        // Hobby (select multiple)
        if (!empty($_POST&#91;'hobby']) &amp;&amp; is_array($_POST&#91;'hobby'])) {
            $hobby = array_map('htmlspecialchars', $_POST&#91;'hobby']);
            $info .= "&lt;p&gt;&lt;strong&gt;Hobby:&lt;/strong&gt; " . 
                     implode(', ', $hobby) . "&lt;/p&gt;";
        }
        
        $info .= "&lt;p&gt;&lt;em&gt;Data zapisu: " . date('Y-m-d H:i:s') . "&lt;/em&gt;&lt;/p&gt;";
        $info .= "&lt;/div&gt;";
        
        echo $info;
        
        // Zapis do pliku
        $file = "dane.txt";
        $f = fopen($file, 'a');
        
        if ($f !== false) {
            flock($f, LOCK_EX);
            //do pliku zapisujemy surowe dane
            fwrite($f, htmlspecialchars_decode(strip_tags($info)) . "\n" . str_repeat('-', 80) . "\n");
            /*
            Kolejność ma znaczenie: najpierw `strip_tags` (usuwa znaczniki dopisane przez nasz kod, a dane   użytkownika są jeszcze zakodowane, więc `&lt;b&gt;` wpisane przez niego zostaje), potem `htmlspecialchars_decode` (przywraca jego tekst w pierwotnej postaci). W odwrotnej kolejności `strip_tags` wycięłoby także tekst użytkownika.

`htmlspecialchars_decode()` jest odwrotnością `htmlspecialchars()`. Do pliku zapisujemy dane w pierwotnej postaci, a kodujemy je dopiero przy wyświetlaniu.
            */
            fflush($f);
            flock($f, LOCK_UN);
            fclose($f);
            
            echo "&lt;p style='color: green;'&gt;Dane zapisane do pliku!&lt;/p&gt;";
        } else {
            echo "&lt;p style='color: red;'&gt;Błąd zapisu do pliku&lt;/p&gt;";
        }
    }
}
?&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang="pl"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;Formularz&lt;/title&gt;
    &lt;style&gt;
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
        }
        form {
            background: #f5f5f5;
            padding: 20px;
            border-radius: 5px;
        }
        label {
            display: block;
            margin-top: 10px;
            font-weight: bold;
        }
        input&#91;type="text"],
        textarea,
        select {
            width: 100%;
            padding: 8px;
            margin-top: 5px;
            box-sizing: border-box;
        }
        button {
            margin-top: 20px;
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            cursor: pointer;
            font-size: 16px;
        }
        button:hover {
            background: #0056b3;
        }
        .checkbox-group,
        .radio-group {
            margin: 10px 0;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;Formularz rejestracyjny&lt;/h1&gt;
    
    &lt;form method="POST"&gt;
        &lt;label for="nazwisko"&gt;Nazwisko: *&lt;/label&gt;
        &lt;input type="text" id="nazwisko" name="nazwisko" required&gt;
        
        &lt;label for="imie"&gt;Imię: *&lt;/label&gt;
        &lt;input type="text" id="imie" name="imie" required&gt;
        
        &lt;label for="wiadomosc"&gt;Wiadomość:&lt;/label&gt;
        &lt;textarea id="wiadomosc" name="wiadomosc" rows="5"&gt;&lt;/textarea&gt;
        
        &lt;div class="radio-group"&gt;
            &lt;label&gt;Płeć:&lt;/label&gt;
            &lt;input type="radio" name="plec" value="k" id="plec_k"&gt;
            &lt;label for="plec_k" style="display: inline; font-weight: normal;"&gt;Kobieta&lt;/label&gt;
            
            &lt;input type="radio" name="plec" value="m" id="plec_m"&gt;
            &lt;label for="plec_m" style="display: inline; font-weight: normal;"&gt;Mężczyzna&lt;/label&gt;
            
            &lt;input type="radio" name="plec" value="i" id="plec_i"&gt;
            &lt;label for="plec_i" style="display: inline; font-weight: normal;"&gt;Inne&lt;/label&gt;
        &lt;/div&gt;
        
        &lt;div class="checkbox-group"&gt;
            &lt;label&gt;Ulubione gry:&lt;/label&gt;
            &lt;input type="checkbox" name="gra1" value="CS" id="gra1"&gt;
            &lt;label for="gra1" style="display: inline; font-weight: normal;"&gt;CS&lt;/label&gt;
            
            &lt;input type="checkbox" name="gra2" value="LOL" id="gra2"&gt;
            &lt;label for="gra2" style="display: inline; font-weight: normal;"&gt;LOL&lt;/label&gt;
            
            &lt;input type="checkbox" name="gra3" value="WOW" id="gra3"&gt;
            &lt;label for="gra3" style="display: inline; font-weight: normal;"&gt;WOW&lt;/label&gt;
        &lt;/div&gt;
        
        &lt;div class="checkbox-group"&gt;
            &lt;label&gt;Ulubione jedzenie:&lt;/label&gt;
            &lt;input type="checkbox" name="food&#91;]" value="pizza" id="food1"&gt;
            &lt;label for="food1" style="display: inline; font-weight: normal;"&gt;Pizza&lt;/label&gt;
            
            &lt;input type="checkbox" name="food&#91;]" value="bigos" id="food2"&gt;
            &lt;label for="food2" style="display: inline; font-weight: normal;"&gt;Bigos&lt;/label&gt;
            
            &lt;input type="checkbox" name="food&#91;]" value="jajecznica" id="food3"&gt;
            &lt;label for="food3" style="display: inline; font-weight: normal;"&gt;Jajecznica&lt;/label&gt;
            
            &lt;input type="checkbox" name="food&#91;]" value="lody" id="food4"&gt;
            &lt;label for="food4" style="display: inline; font-weight: normal;"&gt;Lody&lt;/label&gt;
        &lt;/div&gt;
        
        &lt;label for="klasa"&gt;Klasa:&lt;/label&gt;
        &lt;select name="klasa" id="klasa"&gt;
            &lt;option value=""&gt;-- Wybierz --&lt;/option&gt;
            &lt;option value="1"&gt;1&lt;/option&gt;
            &lt;option value="2"&gt;2&lt;/option&gt;
            &lt;option value="3"&gt;3&lt;/option&gt;
            &lt;option value="4"&gt;4&lt;/option&gt;
        &lt;/select&gt;
        
        &lt;label for="hobby"&gt;Hobby (przytrzymaj Ctrl aby wybrać wiele):&lt;/label&gt;
        &lt;select name="hobby&#91;]" id="hobby" multiple size="4"&gt;
            &lt;option value="IT"&gt;IT&lt;/option&gt;
            &lt;option value="PHP"&gt;PHP&lt;/option&gt;
            &lt;option value="sport"&gt;Sport&lt;/option&gt;
            &lt;option value="ryby"&gt;Wędkarstwo&lt;/option&gt;
        &lt;/select&gt;
        
        &lt;button type="submit" name="przycisk"&gt;Wyślij&lt;/button&gt;
    &lt;/form&gt;
    
    &lt;p style="margin-top: 20px;"&gt;
        &lt;a href="podglad.php" target="_blank"&gt;<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Podgląd zapisanych danych&lt;/a&gt;
    &lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>



<p class="wp-block-paragraph">Plik <em>podglad.php</em></p>



<pre class="wp-block-code prettyprint"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php
$file = 'dane.txt';

if (!file_exists($file) || filesize($file) == 0) {
    echo "Brak zapisanych danych.";
    echo "&lt;br&gt;&lt;br&gt;&lt;a href='formularz.php'&gt;Dodaj dane&lt;/a&gt;";
    exit;
}

$zawartosc = file_get_contents($file);
?&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang="pl"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Podgląd danych&lt;/title&gt;
    &lt;style&gt;
        body {
            font-family: Arial, sans-serif;
            max-width: 900px;
            margin: 50px auto;
            padding: 20px;
        }
        .content {
            background: #f9f9f9;
            padding: 20px;
            border: 1px solid #ddd;
            white-space: pre-wrap;
        }
        .buttons {
            margin-top: 20px;
        }
        a {
            display: inline-block;
            padding: 10px 20px;
            margin-right: 10px;
            background: #007bff;
            color: white;
            text-decoration: none;
            border-radius: 5px;
        }
        a:hover {
            background: #0056b3;
        }
        .delete {
            background: #dc3545;
        }
        .delete:hover {
            background: #c82333;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Zapisane dane&lt;/h1&gt;
    
    &lt;div class="content"&gt;
        &lt;?= nl2br(htmlspecialchars($zawartosc)) ?&gt;
    &lt;/div&gt;
    
    &lt;div class="buttons"&gt;
        &lt;a href="formularz.php"&gt;Dodaj kolejne dane&lt;/a&gt;
        &lt;a href="del.php" class="delete" 
           onclick="return confirm('Czy na pewno usunąć wszystkie dane?')"&gt;
            Usuń plik
        &lt;/a&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>



<p class="wp-block-paragraph">Plik <em>del.php</em></p>



<pre class="wp-block-code prettyprint"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php
$file = 'dane.txt';

if (file_exists($file)) {
    if (unlink($file)) {
        $message = "Plik został usunięty";
        $status = "success";
    } else {
        $message = "Błąd podczas usuwania pliku";
        $status = "error";
    }
} else {
    $message = "Plik nie istnieje";
    $status = "warning";
}
?&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang="pl"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Usuwanie pliku&lt;/title&gt;
    &lt;style&gt;
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 50px auto;
            padding: 20px;
            text-align: center;
        }
        .message {
            padding: 20px;
            margin: 20px 0;
            border-radius: 5px;
            font-size: 18px;
        }
        .success {
            background: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        .error {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        .warning {
            background: #fff3cd;
            color: #856404;
            border: 1px solid #ffeeba;
        }
        a {
            display: inline-block;
            margin-top: 20px;
            padding: 10px 20px;
            background: #007bff;
            color: white;
            text-decoration: none;
            border-radius: 5px;
        }
        a:hover {
            background: #0056b3;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div class="message &lt;?= $status ?&gt;"&gt;
        &lt;?= $message ?&gt;
    &lt;/div&gt;
    
    &lt;a href="formularz.php"&gt;Powrót do formularza&lt;/a&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>



<h2 class="wp-block-heading">Przykład &#8211; upload plików</h2>



<h4 class="wp-block-heading">Formularz uploadu</h4>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;!-- WAŻNE: enctype="multipart/form-data" --&gt;
&lt;form method="POST" enctype="multipart/form-data"&gt;
    &lt;label&gt;Wybierz plik:&lt;/label&gt;
    &lt;input type="file" name="plik" accept="image/*,application/pdf"&gt;
    
    &lt;button type="submit" name="upload"&gt;Prześlij&lt;/button&gt;
&lt;/form&gt;</code></pre>



<h4 class="wp-block-heading">Obsługa uploadu</h4>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php
if (isset($_POST&#91;'upload']) &amp;&amp; isset($_FILES&#91;'plik'])) {
    $file = $_FILES&#91;'plik'];
    
    // Sprawdź błędy
    if ($file&#91;'error'] !== UPLOAD_ERR_OK) {
        echo "Błąd uploadu: " . $file&#91;'error'];
        exit;
    }
    
    // Sprawdź rozmiar (5MB max)
    $maxSize = 5 * 1024 * 1024;
    if ($file&#91;'size'] &gt; $maxSize) {
        echo "Plik za duży (max 5MB)";
        exit;
    }
    
    // Sprawdź rozszerzenie
    $allowedExt = &#91;'jpg', 'jpeg', 'png', 'gif', 'pdf'];
    $ext = strtolower(pathinfo($file&#91;'name'], PATHINFO_EXTENSION));
    
    if (!in_array($ext, $allowedExt)) {
        echo "Niedozwolone rozszerzenie: $ext";
        exit;
    }
    
    // Utwórz katalog jeśli nie istnieje
    $uploadDir = 'uploads/';
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }
    
    // Generuj unikalną nazwę
    $newName = uniqid() . '.' . $ext;
    $destination = $uploadDir . $newName;
    
    // Przenieś plik
    if (move_uploaded_file($file&#91;'tmp_name'], $destination)) {
        echo "Plik przesłany: $destination";
    } else {
        echo "Błąd przenoszenia pliku";
    }
}
?&gt;</code></pre>



<h2 class="wp-block-heading">Zadania:</h2>



<p><span style="text-decoration: underline;"><strong>W każdym zadaniu należy sprawdzić, czy dane istnieją. Używaj metody bezpiecznej, chyba że rodzaj zadania wymaga innego podejścia.</strong></span></p>
<p><strong>Zadanie 1:</strong> Stwórz formularz, który na podstawie podanych przez użytkownika ilości kolumn i wierszy, narysuje tabelę w HTMLu. Wypełnij pola tabeli losowymi kolorami.</p>
<p><strong>Zadanie 2:</strong> Stwórz formularz, który po kliknięciu przycisku będzie losował liczby totolotka (zakres 1-49), umożliw podanie użytkownikowi ilości zestawów do wygenerowania. (np. wpisuje 3 &#8211; więc 3 razy generuje się zestaw itd.).</p>
<p><strong>Zadanie 3:</strong> Stwórz formularz, który będzie zawierał listę rzeczy do kupienia. W tabeli wyświetl produkt, ilość i cenę. Produkt ma zawierać checkboxa oraz nazwę; ilość i cena mogą być edytowane (po pierwszym uruchomieniu wypełnione dowolnymi wartościami &#8211; ty decydujesz). Zadeklaruj przynajmniej 6 produktów do wyboru. Po wybraniu produktów (zaznaczeniu checkboxów) i wysłaniu formularza wyświetli się paragon, tak jak w sklepie (poszukaj jakiegoś w plecaku i wykorzystaj za wzór).</p>
<p><strong>Zadanie 4: </strong>Stwórz grę &#8222;papier, kamień, nożyce&#8221;. Informację o wyniku przechowuj w zmiennej sesyjnej. Jeżeli nie znasz jeszcze sesji to możesz wynik danego gracza przekazywać w polu ukrytym</p>
<p>np. &lt;input type=&#8221;hidden&#8221; name=&#8221;licznik_player&#8221; value=&#8221;&lt;?=$licznik_player?&gt;&#8221;&gt;</p>
<p><strong>Zadanie 5: </strong>Stwórz rozbudowany formularz danych użytkownika w PHP, który umożliwia:</p>
<ol data-start="440" data-end="676">
<li data-start="440" data-end="533">zapisanie wprowadzonych danych do <strong data-start="571" data-end="591">pliku tekstowego</strong>,</li>
<li data-start="595" data-end="676">
<p data-start="598" data-end="676">odczytanie zapisanych danych i ich <strong data-start="633" data-end="673">czytelne wyświetlenie w przeglądarce</strong>.</p>
</li>
</ol>
<p data-start="1101" data-end="1135">Formularz powinien zawierać m.in.:</p>
<h4 data-start="1137" data-end="1157">Dane osobowe</h4>
<ul data-start="1158" data-end="1457">
<li data-start="1158" data-end="1193">
<p data-start="1160" data-end="1193">Imię i nazwisko (pole tekstowe)</p>
</li>
<li data-start="1194" data-end="1230">
<p data-start="1196" data-end="1230">Adres e-mail (pole typu <code class="prettyprint lang-sql" style="font-size: 0.85em;">email</code>)</p>
</li>
<li data-start="1231" data-end="1263">
<p data-start="1233" data-end="1263">Hasło (pole typu <code class="prettyprint lang-sql" style="font-size: 0.85em;">password</code>)</p>
</li>
<li data-start="1264" data-end="1300">
<p data-start="1266" data-end="1300">Numer telefonu (pole typu <code class="prettyprint lang-sql" style="font-size: 0.85em;">tel</code>)</p>
</li>
<li data-start="1301" data-end="1338">
<p data-start="1303" data-end="1338">Data urodzenia (pole typu <code class="prettyprint lang-sql" style="font-size: 0.85em;">date</code>)</p>
</li>
<li data-start="1339" data-end="1368">
<p data-start="1341" data-end="1368">Wiek (pole typu <code class="prettyprint lang-sql" style="font-size: 0.85em;">number</code>)</p>
</li>
<li data-start="1369" data-end="1420">
<p data-start="1371" data-end="1420">Płeć (radio button: mężczyzna / kobieta / inne)</p>
</li>
<li data-start="1421" data-end="1457">
<p data-start="1423" data-end="1457">Zdjęcie profilowe (upload pliku) &#8211; opcjonalne</p>
</li>
</ul>
<h4 data-start="1459" data-end="1480">Dane adresowe</h4>
<ul data-start="1481" data-end="1646">
<li data-start="1481" data-end="1503">
<p data-start="1483" data-end="1503">Ulica i numer domu</p>
</li>
<li data-start="1504" data-end="1520">
<p data-start="1506" data-end="1520">Kod pocztowy</p>
</li>
<li data-start="1521" data-end="1531">
<p data-start="1523" data-end="1531">Miasto</p>
</li>
<li data-start="1532" data-end="1574">
<p data-start="1534" data-end="1574">Województwo (lista rozwijana <code class="prettyprint lang-sql" style="font-size: 0.85em;">select</code>)</p>
</li>
<li data-start="1575" data-end="1610">
<p data-start="1577" data-end="1610">Kraj (lista rozwijana <code class="prettyprint lang-sql" style="font-size: 0.85em;">select</code>)</p>
</li>
<li data-start="1611" data-end="1646">
<p data-start="1613" data-end="1646">Współrzędne GPS (pole tekstowe)</p>
</li>
</ul>
<h4 data-start="1648" data-end="1669">Dane zawodowe</h4>
<ul data-start="1670" data-end="1853">
<li data-start="1670" data-end="1684">
<p data-start="1672" data-end="1684">Stanowisko</p>
</li>
<li data-start="1685" data-end="1700">
<p data-start="1687" data-end="1700">Nazwa firmy</p>
</li>
<li data-start="1701" data-end="1740">
<p data-start="1703" data-end="1740">Doświadczenie zawodowe (liczba lat)</p>
</li>
<li data-start="1741" data-end="1815">
<p data-start="1743" data-end="1815">Branża (lista rozwijana z możliwością wyboru kilku opcji – <code class="prettyprint lang-sql" style="font-size: 0.85em;">multiple</code>)</p>
</li>
<li data-start="1816" data-end="1853">
<p data-start="1818" data-end="1853">Przedział wynagrodzenia (<code class="prettyprint lang-sql" style="font-size: 0.85em;">range</code>)</p>
</li>
</ul>
<h4 data-start="1855" data-end="1892">Preferencje i zainteresowania</h4>
<ul data-start="1893" data-end="2246">
<li data-start="1893" data-end="1922">
<p data-start="1895" data-end="1922">Ulubione kolory (<code class="prettyprint lang-sql" style="font-size: 0.85em;">color</code>)</p>
</li>
<li data-start="1923" data-end="1961">
<p data-start="1925" data-end="1961">Ulubione dni tygodnia (<code class="prettyprint lang-sql" style="font-size: 0.85em;">checkbox</code>)</p>
</li>
<li data-start="1962" data-end="1991">
<p data-start="1964" data-end="1991">Ulubiona godzina (<code class="prettyprint lang-sql" style="font-size: 0.85em;">time</code>)</p>
</li>
<li data-start="1992" data-end="2022">
<p data-start="1994" data-end="2022">Ulubiony miesiąc (<code class="prettyprint lang-sql" style="font-size: 0.85em;">month</code>)</p>
</li>
<li data-start="2060" data-end="2097">
<p data-start="2062" data-end="2097">Adres strony internetowej (<code class="prettyprint lang-sql" style="font-size: 0.85em;">url</code>)</p>
</li>
<li data-start="2098" data-end="2140">
<p data-start="2100" data-end="2140">Krótki opis zainteresowań (<code class="prettyprint lang-sql" style="font-size: 0.85em;">textarea</code>)</p>
</li>
<li data-start="2141" data-end="2169">
<p data-start="2143" data-end="2169">Upload pliku CV (<code class="prettyprint lang-sql" style="font-size: 0.85em;">file</code>) &#8211; opcjonalnie</p>
</li>
<li data-start="2170" data-end="2206">
<p data-start="2172" data-end="2206">Zgoda na newsletter (<code class="prettyprint lang-sql" style="font-size: 0.85em;">checkbox</code>)</p>
</li>
<li data-start="2207" data-end="2246">
<p data-start="2209" data-end="2246">Ocena poziomu satysfakcji (<code class="prettyprint lang-sql" style="font-size: 0.85em;">range</code>)</p>
</li>
</ul>
<h4 data-start="2248" data-end="2269">Inne elementy</h4>
<ul data-start="2270" data-end="2409">
<li data-start="2270" data-end="2327">
<p data-start="2272" data-end="2327">Pole ukryte (<code class="prettyprint lang-sql" style="font-size: 0.85em;">hidden</code>) – np. data wysłania formularza</p>
</li>
<li data-start="2328" data-end="2375">
<p data-start="2330" data-end="2375">Przycisk wyczyszczenia formularza (<code class="prettyprint lang-sql" style="font-size: 0.85em;">reset</code>)</p>
</li>
<li data-start="2376" data-end="2409">
<p data-start="2378" data-end="2409">Przycisk wysyłania (<code class="prettyprint lang-sql" style="font-size: 0.85em;">submit</code>)</p>
</li>
</ul>
<hr data-start="2411" data-end="2414" />
<p data-start="2416" data-end="2445"><strong>Zapis danych do pliku</strong></p>
<p data-start="2446" data-end="2652">Po przesłaniu formularza dane użytkownika powinny zostać zapisane do pliku tekstowego (np. <code class="prettyprint lang-sql" style="font-size: 0.85em;">dane.txt</code>).<br data-start="2549" data-end="2552" />Każdy wiersz w pliku powinien zawierać dane jednego użytkownika, rozdzielone średnikami (<code class="prettyprint lang-sql" style="font-size: 0.85em;">;</code>) — np.:</p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr"><code class="prettyprint lang-sql" style="font-size: 0.85em;">Jan Kowalski;jan.kowalski@example.com;<span class="hljs-number">35</span>;mężczyzna;Warszawa;Programista;IT;<span class="hljs-comment">#3498db;Tak;2025-10-27</span>
</code></div>
</div>
<p data-start="2761" data-end="2812">Do zapisu możesz wykorzystać funkcje PHP takie jak:</p>
<ul data-start="2813" data-end="2860">
<li data-start="2813" data-end="2827">
<p data-start="2815" data-end="2827"><code class="prettyprint lang-sql" style="font-size: 0.85em;">fopen()</code>,</p>
</li>
<li data-start="2828" data-end="2844">
<p data-start="2830" data-end="2844"><code class="prettyprint lang-sql" style="font-size: 0.85em;">fputcsv()</code> &#8211; poczytaj o tej funkcji w dokumentacji technicznej</p>
</li>
<li data-start="2845" data-end="2860">
<p data-start="2847" data-end="2860"><code class="prettyprint lang-sql" style="font-size: 0.85em;">fclose()</code>.</p>
</li>
</ul>
<hr data-start="2862" data-end="2865" />
<p data-start="2867" data-end="2901"><strong>Odczyt danych (<code class="prettyprint lang-sql" style="font-size: 0.85em;">dane.php</code>)</strong></p>
<p data-start="2902" data-end="3046">Dane powinny wyświetlać się w formie tabeli HTML. Tabela powinna:</p>
<ul data-start="3064" data-end="3219">
<li data-start="3064" data-end="3089">
<p data-start="3066" data-end="3089">mieć nagłówki kolumn,</p>
</li>
<li data-start="3090" data-end="3143">
<p data-start="3092" data-end="3143">prezentować wszystkie zapisane dane użytkowników,</p>
</li>
<li data-start="3144" data-end="3219">
<p data-start="3146" data-end="3219">wyświetlać komunikat <em data-start="3167" data-end="3193">„Brak zapisanych danych”</em>, jeśli plik jest pusty.</p>
</li>
</ul>
<hr data-start="3221" data-end="3224" />
<p data-start="3226" data-end="3255"><strong>Wymagania techniczne</strong></p>
<ul data-start="3256" data-end="3643">
<li data-start="3256" data-end="3344">
<p data-start="3258" data-end="3344">Walidacja pól formularza po stronie HTML5 (np. <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">min</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">max</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">pattern</code>).</p>
</li>
<li data-start="3345" data-end="3444">
<p data-start="3347" data-end="3444">Dane od użytkownika wypisywane na stronie (komunikat, wartości w polach po błędzie) są kodowane funkcją htmlspecialchars() w miejscu wypisania, a do pliku zapisujemy dane bez kodowania.</p>
</li>
<li data-start="3445" data-end="3540">
<p data-start="3447" data-end="3540">Użytkownik po przesłaniu formularza powinien zobaczyć komunikat o poprawnym zapisie danych.</p>
</li>
<li data-start="3541" data-end="3643">
<p data-start="3543" data-end="3643">Pliki przesyłane przez użytkownika (np. zdjęcie, CV) powinny być zapisywane w katalogu <code class="prettyprint lang-sql" style="font-size: 0.85em;">uploads/</code>.</p>
</li>
</ul>



<h1 id="TaskQuest" class="wp-block-heading">TaskQuest 1.3 — użytkownik dodaje zadania ze strony</h1>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>KARTA PRACY</strong></summary>
<p class="wp-block-paragraph">Po Lekcji 12 TaskQuest bezpiecznie wczytuje zadania z <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code> i głośno zgłasza awarie. Ale żeby dodać zadanie, Kama nadal musi otworzyć plik CSV w edytorze kodu i ręcznie wpisać wiersz — pilnując średników, cudzysłowów, formatu daty i kolejnego <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code>. Żaden użytkownik prawdziwej aplikacji tak nie pracuje.</p>



<p class="wp-block-paragraph">Dziś TaskQuest dostaje <strong>pierwszy formularz</strong>: użytkownik wpisuje zadanie na stronie, klika „Dodaj zadanie”, a PHP sprawdza dane i zapisuje je do pliku. Przy okazji lista zadań nauczy się <strong>filtrowania</strong> przez adres strony (<code class="prettyprint lang-sql" style="font-size: 0.85em;">?filtr=aktywne</code>).</p>



<h3 class="wp-block-heading">Cel lekcji</h3>



<p class="wp-block-paragraph">Nauczyć TaskQuest:</p>



<ul class="wp-block-list">
<li>odbierać dane z formularza HTML (<code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST</code>) i z adresu strony (<code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code>),</li>



<li>odróżniać pierwsze wejście na stronę od wysłania formularza (<code class="prettyprint lang-sql" style="font-size: 0.85em;">$_SERVER['REQUEST_METHOD']</code>),</li>



<li>bezpiecznie sięgać po dane, których może nie być (operator <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code>),</li>



<li>sprawdzać dane od użytkownika <strong>po stronie serwera</strong> i pokazywać wszystkie błędy naraz,</li>



<li>dopisywać nowe zadanie do pliku (wczytaj → dodaj → zapisz) z nowym, unikalnym <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code>,</li>



<li>nie dodawać tego samego zadania dwa razy po odświeżeniu strony (przekierowanie po zapisie),</li>



<li>świadomie wybierać metodę: <strong>GET do czytania, POST do zmieniania danych</strong>.</li>
</ul>



<p class="wp-block-paragraph">Efekt: na stronie głównej jest link „+ Dodaj zadanie” prowadzący do nowej strony <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code>. Po poprawnym wypełnieniu formularza zadanie ląduje w <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code>, użytkownik wraca na stronę główną i widzi komunikat „Zadanie zostało dodane.”. Lista zadań ma trzy widoki: wszystkie, aktywne i wykonane.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>Uwaga — co jest, a czego jeszcze nie ma:</strong></p>



<ul class="wp-block-list">
<li>Nie ma logowania ani sesji. Komunikat „Zadanie zostało dodane.” przekazujemy w adresie strony (<code class="prettyprint lang-sql" style="font-size: 0.85em;">?dodano=1</code>), a nie „w pamięci” aplikacji — sesje to Lekcja 14.</li>



<li>Nadal <strong>nie używamy <code class="prettyprint lang-sql" style="font-size: 0.85em;">htmlspecialchars()</code></strong>. Od dziś tekst wpisany przez użytkownika trafia na stronę, więc to już realny problem bezpieczeństwa — zobaczysz go w Eksperymencie 5, a naprawimy go w lekcji o bezpieczeństwie (22). Aplikacja działa na Twoim komputerze i tylko Ty wpisujesz dane — dlatego na razie możemy to odłożyć.</li>



<li>Formularz tylko <strong>dodaje</strong> zadania. Oznaczanie jako wykonane to zadanie końcowe A; edycja i usuwanie — przy bazie danych (Lekcja 19).</li>



<li>Przesyłanie plików (upload) i pozostałe typy pól (checkbox, radio, select) poznasz w ćwiczeniach z tematu, ale TaskQuest ich dziś nie potrzebuje.</li>



<li>Dane grywalizacji (<code class="prettyprint lang-sql" style="font-size: 0.85em;">$xp</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">$streak</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">$dataOstatniejAktywnosci</code>) nadal są symulowane w <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code>.</li>
</ul>
</blockquote>



<h1 class="wp-block-heading">Rozgrzewka — 4 pytania z Lekcji 12</h1>



<p class="wp-block-paragraph">Odpowiedz z pamięci, zanim zaczniesz:</p>



<ol class="wp-block-list">
<li>W bloku <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code> są trzy linie kodu, a wyjątek poleciał w drugiej. Co stanie się z trzecią linią i dokąd „przeskoczy” PHP?</li>



<li>Dlaczego pusty plik z zadaniami <strong>nie</strong> rzuca wyjątku, a brak pliku — rzuca?</li>



<li>Po co w <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> jest <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadania = [];</code> <strong>przed</strong> blokiem <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code>?</li>



<li>Dokończ regułę: „Kto wykrywa problem — &#8230;, kto wie, co z problemem zrobić — &#8230;”.</li>
</ol>



<h1 class="wp-block-heading">1. Wracamy do naszego TaskQuest</h1>



<p class="wp-block-paragraph">Aplikacja czyta zadania z pliku, a funkcja <code class="prettyprint lang-sql" style="font-size: 0.85em;">zapiszZadania()</code> istnieje od Lekcji 11 — ale <strong>nikt jej jeszcze nie wywołuje ze strony</strong>. Dziś wreszcie dostanie swoje miejsce w aplikacji. <strong>Wszystkie zmienne i funkcje z Lekcji 12 zostają bez zmian.</strong></p>



<h3 class="wp-block-heading">Docelowa struktura projektu</h3>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">taskquest/
├── index.php                        (zmiana: link do formularza, komunikat po dodaniu, filtr GET)
├── dodaj-zadanie.php                NOWY — formularz dodawania zadania + jego obsługa
├── diagnostyka.php                  (zmiana: jedna nowa pozycja na liście wymaganych plików)
├── config/
│   └── config.php                   bez zmian
├── includes/
│   ├── header.php                   (zmiana: klasa .sukces + style formularza)
│   ├── footer.php                   (zmiana: "TaskQuest 1.3")
│   ├── funkcje_grywalizacji.php     bez zmian
│   ├── funkcje_tekstowe.php         bez zmian
│   ├── funkcje_terminow.php         bez zmian
│   ├── funkcje_zadan.php            (zmiana: dwie nowe funkcje na końcu pliku)
│   ├── funkcje_diagnostyczne.php    bez zmian
│   └── funkcje_plikow.php           bez zmian
└── data/
    └── zadania.csv                  (zmieni się jego zawartość — dopiszą się zadania z formularza)</code></pre>



<h3 class="wp-block-heading">Co gdzie trafi</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Plik</th><th>Co się zmienia</th></tr></thead><tbody><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_zadan.php</code></td><td>nowe funkcje <code class="prettyprint lang-sql" style="font-size: 0.85em;">walidujNoweZadanie()</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">nastepneIdZadania()</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code></td><td>nowa strona: formularz, sprawdzenie danych, zapis, przekierowanie</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code></td><td>link „+ Dodaj zadanie”, komunikat o dodaniu, wybór widoku listy przez <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/header.php</code></td><td>klasa <code class="prettyprint lang-sql" style="font-size: 0.85em;">.sukces</code> i kilka reguł dla formularza</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/footer.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">diagnostyka.php</code></td><td>numer wersji, nowy plik na liście wymaganych</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Ciekawostka: w nowej stronie wrócą nazwy <code class="prettyprint lang-sql" style="font-size: 0.85em;">$tytulZadania</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">$opisZadania</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">$terminZadania</code>, które usunęliśmy w Lekcji 9 (dane zadań przeniosły się wtedy do kluczy tablicy <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadania</code>). Teraz dostają nowe, konkretne znaczenie: <strong>dane nowego zadania wpisane w formularzu</strong>, zanim trafią do listy.</p>



<h3 class="wp-block-heading">Przygotowanie (zrób to teraz)</h3>



<ol class="wp-block-list">
<li><strong>Zrób kopię zapasową</strong> całego katalogu <code class="prettyprint lang-sql" style="font-size: 0.85em;">taskquest/</code> z Lekcji 12 — np. jako <code class="prettyprint lang-sql" style="font-size: 0.85em;">taskquest_lekcja12/</code> (obok, nie w środku). Przyda się do testu regresji.</li>



<li>Zrób kopię pliku <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code> <strong>poza</strong> katalogiem <code class="prettyprint lang-sql" style="font-size: 0.85em;">taskquest/</code> (np. na pulpicie, jako <code class="prettyprint lang-sql" style="font-size: 0.85em;">zadania_kopia.csv</code>). Dziś będziemy dopisywać do niego testowe zadania, a na koniec wrócimy do oryginału.</li>



<li>Sprawdź, czy <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> pokazuje 5 zadań, a <code class="prettyprint lang-sql" style="font-size: 0.85em;">diagnostyka.php</code> — 11 × <code class="prettyprint lang-sql" style="font-size: 0.85em;">[OK]</code>.</li>
</ol>



<h1 class="wp-block-heading">2. Problem w aplikacji</h1>



<p class="wp-block-paragraph">Kama chce dodać zadanie „Powtórka z formularzy” na przyszły piątek, za 30 XP. Dziś musi:</p>



<ol class="wp-block-list">
<li>otworzyć <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code> w edytorze,</li>



<li>sprawdzić, jakie jest największe <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code>, i wymyślić kolejne,</li>



<li>wpisać wiersz, nie gubiąc żadnego średnika i pamiętając o formacie <code class="prettyprint lang-sql" style="font-size: 0.85em;">RRRR-MM-DD</code>,</li>



<li>odświeżyć stronę i mieć nadzieję, że niczego nie zepsuła (jeśli zepsuła — od Lekcji 12 przynajmniej zobaczy ramkę z numerem linii).</li>
</ol>



<p class="wp-block-paragraph">Chcemy, żeby wyglądało to tak:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">formularz w przeglądarce ──(kliknięcie "Dodaj zadanie")──► PHP odbiera dane
                                   │
                         czy dane są poprawne?
                      ┌────────────┴────────────┐
                     NIE                        TAK
                      │                          │
      formularz jeszcze raz,            wczytajZadania() → dopisz → zapiszZadania()
       z listą błędów                           │
       i wpisanymi danymi            powrót na stronę główną
                                     + "Zadanie zostało dodane."</code></pre>



<p class="wp-block-paragraph">Do tego Kama ma już 5 zadań i chciałaby czasem widzieć <strong>tylko aktywne</strong>. To zupełnie inny rodzaj prośby: niczego nie zmienia w danych, tylko wybiera, <em>co pokazać</em>.</p>



<p class="wp-block-paragraph">Potrzebujemy więc:</p>



<ol class="wp-block-list">
<li>sposobu, żeby przeglądarka <strong>wysłała dane</strong> do skryptu PHP,</li>



<li>sposobu, żeby PHP te dane <strong>odebrał</strong>,</li>



<li>sposobu, żeby PHP odróżnił „ktoś dopiero otworzył formularz” od „ktoś go właśnie wysłał”,</li>



<li><strong>sprawdzenia danych</strong> — przeglądarka potrafi wysłać wszystko,</li>



<li>dwóch rodzajów próśb: „pokaż mi” i „zmień dane”.</li>
</ol>



<p class="wp-block-paragraph">To właśnie formularze HTML i metody GET oraz POST.</p>



<h1 class="wp-block-heading">3. Nowy mechanizm PHP: formularze, GET i POST</h1>



<h2 class="wp-block-heading">3.1 Jak przeglądarka rozmawia z PHP</h2>



<p class="wp-block-paragraph">Za każdym razem, gdy wpisujesz adres, klikasz link albo wysyłasz formularz, przeglądarka wysyła do serwera <strong>żądanie</strong> (ang. <em>request</em>). Serwer uruchamia skrypt PHP <strong>od zera</strong>, skrypt generuje HTML, a serwer odsyła go jako <strong>odpowiedź</strong> (ang. <em>response</em>). Potem skrypt się kończy i wszystkie jego zmienne znikają.</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">PRZEGLĄDARKA                                 SERWER (Apache + PHP)
     │  żądanie: GET /taskquest/index.php          │
     │ ──────────────────────────────────────────► │  uruchamia index.php od początku
     │                                             │  (zmienne powstają...)
     │  odpowiedź: HTML strony                     │
     │ ◄────────────────────────────────────────── │  (...i znikają po zakończeniu)</code></pre>



<p class="wp-block-paragraph">Każde żądanie ma <strong>metodę</strong>. Dwie najważniejsze to:</p>



<ul class="wp-block-list">
<li><strong>GET</strong> — „daj mi stronę”. Tak działa wpisanie adresu i kliknięcie linku. Dane (jeśli są) jadą <strong>w adresie</strong>, po znaku <code class="prettyprint lang-sql" style="font-size: 0.85em;">?</code>.</li>



<li><strong>POST</strong> — „przyjmij ode mnie dane”. Dane jadą <strong>w treści żądania</strong>, nie w adresie.</li>
</ul>



<h2 class="wp-block-heading">3.2 Pierwszy formularz — metoda GET</h2>



<p class="wp-block-paragraph"><strong>BRUDNOPIS.</strong> Utwórz w katalogu <code class="prettyprint lang-sql" style="font-size: 0.85em;">taskquest/</code> plik <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php</code> — to plik do eksperymentów, nie część aplikacji. Cała sekcja 3 i 4 dzieje się w tym pliku.</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;!DOCTYPE html&gt;
&lt;html lang="pl"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Brudnopis — formularz&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;form method="get" action="brudnopis.php"&gt;
        &lt;label for="imie"&gt;Imię&lt;/label&gt;
        &lt;input type="text" id="imie" name="imie"&gt;

        &lt;label for="xp"&gt;XP&lt;/label&gt;
        &lt;input type="number" id="xp" name="xp"&gt;

        &lt;button type="submit"&gt;Wyślij&lt;/button&gt;
    &lt;/form&gt;

    &lt;pre&gt;&lt;?php var_dump($_GET); ?&gt;&lt;/pre&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>



<p class="wp-block-paragraph">Otwórz <code class="prettyprint lang-sql" style="font-size: 0.85em;">http://localhost/taskquest/brudnopis.php</code>, wpisz <code class="prettyprint lang-sql" style="font-size: 0.85em;">Kama</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">50</code>, kliknij „Wyślij”. Zwróć uwagę na trzy rzeczy:</p>



<ol class="wp-block-list">
<li><strong>Adres zmienił się</strong> na <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php?imie=Kama&amp;xp=50</code>. Dane są widoczne w pasku adresu.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">var_dump($_GET)</code> pokazuje tablicę asocjacyjną: <code class="prettyprint lang-sql" style="font-size: 0.85em;">["imie"] =&gt; "Kama"</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">["xp"] =&gt; "50"</code>.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">xp</code> to <strong><code class="prettyprint lang-sql" style="font-size: 0.85em;">string(2) "50"</code></strong>, a nie liczba — mimo że pole ma <code class="prettyprint lang-sql" style="font-size: 0.85em;">type="number"</code>. <strong>Z formularza zawsze przychodzą teksty.</strong></li>
</ol>



<p class="wp-block-paragraph">Co tu się dzieje:</p>



<ul class="wp-block-list">
<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">method="get"</code> — przeglądarka dokleja dane do adresu,</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">action="brudnopis.php"</code> — do którego skryptu wysłać dane (tu: do tego samego pliku),</li>



<li>atrybut <strong><code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code></strong> pola staje się <strong>kluczem</strong> w tablicy <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code>. Pole bez <code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code> w ogóle nie zostanie wysłane (atrybut <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> służy tylko do połączenia z <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;label for="..."&gt;</code>),</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code> to tablica, którą PHP wypełnia <strong>sam</strong>, zanim uruchomi Twój kod.</li>
</ul>



<p class="wp-block-paragraph">Teraz <strong>wpisz adres ręcznie</strong>, bez formularza: <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php?imie=Ola&amp;xp=abc&amp;bonus=1</code>. PHP przyjął wszystko — także <code class="prettyprint lang-sql" style="font-size: 0.85em;">abc</code> w polu liczbowym i dodatkowy klucz <code class="prettyprint lang-sql" style="font-size: 0.85em;">bonus</code>, którego w formularzu nie ma. Zapamiętaj: <strong>formularz to tylko jeden ze sposobów wysłania danych. Użytkownik może wysłać, co zechce.</strong></p>



<p class="wp-block-paragraph">Na koniec otwórz <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php</code> bez niczego po <code class="prettyprint lang-sql" style="font-size: 0.85em;">?</code>. <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code> jest pustą tablicą: <code class="prettyprint lang-sql" style="font-size: 0.85em;">array(0) {}</code>.</p>



<h2 class="wp-block-heading">3.3 Metoda POST</h2>



<p class="wp-block-paragraph">Zmień w formularzu <code class="prettyprint lang-sql" style="font-size: 0.85em;">method="get"</code> na <code class="prettyprint lang-sql" style="font-size: 0.85em;">method="post"</code>, a <code class="prettyprint lang-sql" style="font-size: 0.85em;">var_dump($_GET)</code> na <code class="prettyprint lang-sql" style="font-size: 0.85em;">var_dump($_POST)</code>. Wyślij formularz jeszcze raz.</p>



<ul class="wp-block-list">
<li>Adres <strong>się nie zmienił</strong> — nie ma w nim <code class="prettyprint lang-sql" style="font-size: 0.85em;">?imie=...</code>.</li>



<li>Dane są w <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST</code>, zbudowanej dokładnie tak samo jak <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code>.</li>
</ul>



<p class="wp-block-paragraph">Gdzie się podziały? Otwórz narzędzia deweloperskie (<strong>F12</strong>) → karta <strong>Sieć</strong> (<em>Network</em>) → wyślij formularz → kliknij żądanie <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php</code> → karta <strong>Ładunek</strong> (<em>Payload</em>). Tam są. <strong>POST nie jest „tajny”</strong> — dane po prostu nie jadą w adresie. Każdy, kto ma dostęp do przeglądarki albo sieci, może je zobaczyć lub zmienić. (Szyfrowanie połączenia zapewnia HTTPS, nie metoda POST.)</p>



<p class="wp-block-paragraph">Teraz naciśnij <strong>F5</strong>. Przeglądarka pyta, czy wysłać dane ponownie. Zapamiętaj to okienko — w TaskQuest oznaczałoby <strong>drugie takie samo zadanie w pliku</strong>. Wrócimy do tego w Kroku 4.</p>



<h2 class="wp-block-heading">3.4 Pierwsze wejście czy wysłany formularz?</h2>



<p class="wp-block-paragraph">Ten sam plik <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php</code> jest otwierany na dwa sposoby: najpierw zwykłym wejściem (GET — pusty formularz), potem po kliknięciu przycisku (POST — dane do sprawdzenia). PHP musi je odróżnić. Podmień zawartość <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php</code> na:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php

echo "Metoda: " . $_SERVER&#91;'REQUEST_METHOD'] . "&lt;br&gt;";
echo "Imię: " . $_POST&#91;'imie'] . "&lt;br&gt;";
?&gt;
&lt;form method="post" action="brudnopis.php"&gt;
    &lt;input type="text" name="imie"&gt;
    &lt;button type="submit"&gt;Wyślij&lt;/button&gt;
&lt;/form&gt;</code></pre>



<p class="wp-block-paragraph">Przy pierwszym wejściu zobaczysz <code class="prettyprint lang-sql" style="font-size: 0.85em;">Metoda: GET</code> oraz ostrzeżenie <code class="prettyprint lang-sql" style="font-size: 0.85em;">Warning: Undefined array key "imie"</code> — przy zwykłym wejściu klucza <code class="prettyprint lang-sql" style="font-size: 0.85em;">imie</code> w <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST</code> <strong>nie ma</strong>. Po wysłaniu: <code class="prettyprint lang-sql" style="font-size: 0.85em;">Metoda: POST</code> i wpisane imię.</p>



<p class="wp-block-paragraph">Dwa narzędzia, które to porządkują:</p>



<p class="wp-block-paragraph"><strong>1. <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_SERVER['REQUEST_METHOD']</code></strong> — tekst <code class="prettyprint lang-sql" style="font-size: 0.85em;">'GET'</code> albo <code class="prettyprint lang-sql" style="font-size: 0.85em;">'POST'</code>. Kod obsługujący formularz umieszczamy w:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">if ($_SERVER&#91;'REQUEST_METHOD'] === 'POST') {
    // formularz został wysłany — dopiero tu sięgamy po $_POST
}</code></pre>



<p class="wp-block-paragraph"><strong>2. Operator <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code></strong> — „weź wartość z lewej, a jeśli jej nie ma (klucz nie istnieje albo jest <code class="prettyprint lang-sql" style="font-size: 0.85em;">null</code>), weź tę z prawej”:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">$imie = $_POST&#91;'imie'] ?? '';</code></pre>



<p class="wp-block-paragraph">To krótszy zapis tego samego, co:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">if (isset($_POST&#91;'imie'])) {
    $imie = $_POST&#91;'imie'];
} else {
    $imie = '';
}</code></pre>



<p class="wp-block-paragraph">Nawet wewnątrz <code class="prettyprint lang-sql" style="font-size: 0.85em;">if (... === 'POST')</code> używamy <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code> — bo ktoś może wysłać POST <strong>bez</strong> jakiegoś pola (np. usunąć je z formularza w F12). Zmień drugą linię na <code class="prettyprint lang-sql" style="font-size: 0.85em;">echo "Imię: " . ($_POST['imie'] ?? '(brak)') . "&lt;br&gt;";</code> i sprawdź pierwsze wejście — ostrzeżenie zniknęło.</p>



<h2 class="wp-block-heading">3.5 GET czy POST?</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Prośba</th><th>Metoda</th><th>Dlaczego</th></tr></thead><tbody><tr><td>pokaż listę zadań / tylko aktywne</td><td><strong>GET</strong></td><td>niczego nie zmienia; adres z <code class="prettyprint lang-sql" style="font-size: 0.85em;">?filtr=aktywne</code> można zapisać w zakładkach, wysłać koledze, odświeżyć bez skutków ubocznych</td></tr><tr><td>wyszukaj zadania ze słowem „PHP”</td><td><strong>GET</strong></td><td>j.w. — to tylko czytanie</td></tr><tr><td>dodaj zadanie</td><td><strong>POST</strong></td><td>zmienia dane; odświeżenie, link w historii albo robot indeksujący stronę nie mogą przypadkiem dodać zadania</td></tr><tr><td>oznacz zadanie jako wykonane</td><td><strong>POST</strong></td><td>zmienia dane</td></tr><tr><td>logowanie</td><td><strong>POST</strong></td><td>hasło w adresie trafiłoby do historii przeglądarki i logów serwera</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Reguła na cały kurs:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>GET — gdy tylko czytamy. POST — gdy coś zmieniamy.</strong></p>
</blockquote>



<h2 class="wp-block-heading">3.6 Pola formularza i walidacja w przeglądarce</h2>



<p class="wp-block-paragraph">W formularzu TaskQuest użyjemy czterech rodzajów pól:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Pole</th><th>Co wysyła</th><th>Przydatne atrybuty</th></tr></thead><tbody><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="text"&gt;</code></td><td>tekst</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">maxlength="100"</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;textarea&gt;</code></td><td>tekst, może mieć wiele linii (wartość wpisujemy <strong>między</strong> znacznikami, nie w <code class="prettyprint lang-sql" style="font-size: 0.85em;">value</code>)</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">rows</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">maxlength</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="date"&gt;</code></td><td>zawsze tekst <code class="prettyprint lang-sql" style="font-size: 0.85em;">RRRR-MM-DD</code> — <strong>niezależnie</strong> od tego, jak przeglądarka wyświetla datę (np. <code class="prettyprint lang-sql" style="font-size: 0.85em;">02.10.2026</code>)</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="number"&gt;</code></td><td>tekst z liczbą</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">min</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">max</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Atrybuty <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">min</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">max</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">maxlength</code> to <strong>walidacja w przeglądarce</strong>: przeglądarka nie wyśle formularza, dopóki pola nie będą poprawne. To wygoda dla użytkownika (szybka informacja bez przeładowania strony), <strong>ale nie ochrona</strong>. Obejść ją można na kilka sposobów:</p>



<ul class="wp-block-list">
<li>dopisać do znacznika <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;form&gt;</code> atrybut <strong><code class="prettyprint lang-sql" style="font-size: 0.85em;">novalidate</code></strong> — przeglądarka przestaje sprawdzać pola (użyjemy go w testach),</li>



<li>usunąć atrybut w narzędziach deweloperskich (F12 → Elementy),</li>



<li>wysłać dane bez przeglądarki (np. programem <code class="prettyprint lang-sql" style="font-size: 0.85em;">curl</code>) albo — przy GET — po prostu wpisać adres.</li>
</ul>



<p class="wp-block-paragraph">Dlatego <strong>PHP zawsze sprawdza dane sam</strong>, nawet jeśli formularz ma <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code>.</p>



<h2 class="wp-block-heading">3.7 Błąd użytkownika to nie awaria</h2>



<p class="wp-block-paragraph">W Lekcji 12 ustaliliśmy: wyjątek to sygnał „nie potrafię zrobić tego, o co mnie poproszono”, a zły login to <strong>zwykła odpowiedź</strong> (<code class="prettyprint lang-sql" style="font-size: 0.85em;">false</code>), nie awaria. Z formularzem jest tak samo:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Sytuacja</th><th>Jak ją zgłaszamy</th><th>Dlaczego</th></tr></thead><tbody><tr><td>pusty tytuł, XP = 500, termin 31 lutego</td><td><strong>lista komunikatów</strong> (tablica <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bledy</code>)</td><td>to oczekiwana sytuacja — ludzie się mylą; formularz ma im pomóc to poprawić</td></tr><tr><td>plik z zadaniami nie istnieje, nie da się go zapisać</td><td><strong>wyjątek</strong> (<code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code> / <code class="prettyprint lang-sql" style="font-size: 0.85em;">catch</code>)</td><td>aplikacja nie może wykonać zadania</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Jest też powód praktyczny: wyjątek przerywa sprawdzanie na <strong>pierwszym</strong> problemie. Użytkownik, który zrobił trzy błędy, poprawiałby je wtedy po jednym, wysyłając formularz trzy razy. Tablica błędów pozwala pokazać <strong>wszystkie naraz</strong>:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">$bledy = &#91;];

if (/* tytuł pusty */) {
    $bledy&#91;] = "Tytuł jest wymagany.";
}

if (/* XP złe */) {
    $bledy&#91;] = "XP musi być z zakresu 1–100.";
}

if (count($bledy) === 0) {
    // wszystko w porządku — można zapisywać
}</code></pre>



<h3 class="wp-block-heading">Typowe błędy przy formularzach</h3>



<ul class="wp-block-list">
<li><strong>Brak atrybutu <code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code></strong> w polu — pole nie zostanie wysłane, a w PHP pojawi się <code class="prettyprint lang-sql" style="font-size: 0.85em;">Undefined array key</code>.</li>



<li><strong>Sięganie po <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['...']</code> bez <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code></strong> przy pierwszym wejściu na stronę — ostrzeżenie <code class="prettyprint lang-sql" style="font-size: 0.85em;">Undefined array key</code>.</li>



<li><strong>Traktowanie danych z formularza jak liczb</strong> — <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['xp']</code> to zawsze tekst. <code class="prettyprint lang-sql" style="font-size: 0.85em;">(int) "12abc"</code> daje <code class="prettyprint lang-sql" style="font-size: 0.85em;">12</code>, a <code class="prettyprint lang-sql" style="font-size: 0.85em;">(int) "dużo"</code> daje <code class="prettyprint lang-sql" style="font-size: 0.85em;">0</code> — rzutowanie „po cichu” zamienia śmieci w liczbę.</li>



<li><strong>Poleganie tylko na <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">min</code>/<code class="prettyprint lang-sql" style="font-size: 0.85em;">max</code></strong> — przeglądarkę da się ominąć.</li>



<li><strong>Użycie GET do zmiany danych</strong> — link lub odświeżenie strony zmienia dane bez wiedzy użytkownika.</li>



<li><strong>Wpisanie wartości <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;textarea&gt;</code> w atrybut <code class="prettyprint lang-sql" style="font-size: 0.85em;">value</code></strong> — <code class="prettyprint lang-sql" style="font-size: 0.85em;">textarea</code> nie ma atrybutu <code class="prettyprint lang-sql" style="font-size: 0.85em;">value</code>, tekst wpisuje się między <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;textarea&gt;</code> a <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;/textarea&gt;</code>.</li>



<li><strong>Literówka w nazwie</strong>: <code class="prettyprint lang-sql" style="font-size: 0.85em;">name="tytul"</code> w HTML, a <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['tytuł']</code> albo <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['title']</code> w PHP — klucze muszą być identyczne.</li>
</ul>



<h1 class="wp-block-heading">4. SAMODZIELNIE — formularz, który sprawdza dane</h1>



<p class="wp-block-paragraph"><strong>BRUDNOPIS.</strong> To ostatnie ćwiczenie w <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php</code>, zanim zaczniemy zmieniać projekt. Zastąp zawartość pliku kodem, który:</p>



<ol class="wp-block-list">
<li>wyświetla formularz (metoda POST, wysyłany do <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php</code>) z dwoma polami: <code class="prettyprint lang-sql" style="font-size: 0.85em;">tytul</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">xp</code> — <strong>oba jako <code class="prettyprint lang-sql" style="font-size: 0.85em;">type="text"</code> i bez <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code></strong>, żeby przeglądarka niczego nie blokowała i całą pracę musiał wykonać PHP,</li>



<li>przy wysłaniu formularza (i tylko wtedy!) sprawdza dane:
<ul class="wp-block-list">
<li>tytuł (po <code class="prettyprint lang-sql" style="font-size: 0.85em;">trim()</code>) nie może być pusty → <code class="prettyprint lang-sql" style="font-size: 0.85em;">Tytuł jest wymagany.</code>,</li>



<li>XP musi składać się z samych cyfr → <code class="prettyprint lang-sql" style="font-size: 0.85em;">XP musi być liczbą całkowitą.</code>, a jeśli jest liczbą, musi być z zakresu 1–100 → <code class="prettyprint lang-sql" style="font-size: 0.85em;">XP musi być z zakresu 1–100.</code>,</li>
</ul>
</li>



<li>gdy są błędy — wypisuje je jako listę <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;ul&gt;</code>, gdy ich nie ma — wypisuje <code class="prettyprint lang-sql" style="font-size: 0.85em;">Przyjęto zadanie: &lt;tytuł&gt; (&lt;xp&gt; XP)</code>,</li>



<li><strong>po wysłaniu formularz nie jest pusty</strong> — pola zawierają to, co wpisał użytkownik (żeby przy błędzie nie musiał pisać wszystkiego od nowa).</li>
</ol>



<p class="wp-block-paragraph"><strong>Efekt:</strong></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Wpisane dane</th><th>Oczekiwany wynik</th></tr></thead><tbody><tr><td>pierwsze wejście (GET)</td><td>sam pusty formularz, brak komunikatów, <strong>brak ostrzeżeń PHP</strong></td></tr><tr><td>tytuł pusty, XP puste</td><td>dwa błędy: tytuł i „liczbą całkowitą”</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Nauka</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">150</code></td><td>jeden błąd: zakres 1–100; w polu tytułu nadal <code class="prettyprint lang-sql" style="font-size: 0.85em;">Nauka</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">   </code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">abc</code></td><td>dwa błędy (same spacje to pusty tytuł)</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Nauka PHP</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">30</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Przyjęto zadanie: Nauka PHP (30 XP)</code></td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><strong>Jak sprawdzić:</strong> każdy wiersz tabeli. Szczególnie pierwszy — jeśli przy pierwszym wejściu widzisz <code class="prettyprint lang-sql" style="font-size: 0.85em;">Warning</code>, sięgasz po <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST</code> za wcześnie albo bez <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code>.</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>HINT</strong></summary>
<p class="wp-block-paragraph">Kod PHP ustaw <strong>na górze pliku</strong>, przed HTML. Zmienne <code class="prettyprint lang-sql" style="font-size: 0.85em;">$tytulZadania</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">$xpZadania</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bledy</code> muszą istnieć także przy pierwszym wejściu — nadaj im wartości początkowe (puste) <strong>przed</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">if</code>. Dzięki temu w HTML możesz ich zawsze bezpiecznie użyć, np. w atrybucie <code class="prettyprint lang-sql" style="font-size: 0.85em;">value</code>. Do sprawdzenia „samych cyfr” masz wzorzec z Lekcji 12.</p>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>POMOC</strong></summary>
<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php

$tytulZadania = '';
$xpZadania = '';
$bledy = &#91;];
$przyjete = false;

if ($_SERVER&#91;'REQUEST_METHOD'] === 'POST') {
    $tytulZadania = trim($_POST&#91;'tytul'] ?? '');
    // ... to samo dla xp ...

    // ... sprawdzenia, każde dopisuje komunikat do $bledy ...

    $przyjete = count($bledy) === 0;
}
?&gt;
&lt;!-- HTML: komunikat "Przyjęto" (gdy $przyjete), lista błędów (gdy są), formularz --&gt;
&lt;input type="text" id="tytul" name="tytul" value="&lt;?= $tytulZadania; ?&gt;"&gt;</code></pre>



<p class="wp-block-paragraph">Listę błędów zbudujesz pętlą <code class="prettyprint lang-sql" style="font-size: 0.85em;">foreach</code> wewnątrz <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;ul&gt;</code> — tak samo jak wchodzisz i wychodzisz z PHP przy <code class="prettyprint lang-sql" style="font-size: 0.85em;">if</code> w <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code>: <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php foreach (...) { ?&gt; ... &lt;?php } ?&gt;</code>.</p>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>ROZWIĄZANIE</strong></summary>
<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php

$tytulZadania = '';
$xpZadania = '';
$bledy = &#91;];
$przyjete = false;

if ($_SERVER&#91;'REQUEST_METHOD'] === 'POST') {
    $tytulZadania = trim($_POST&#91;'tytul'] ?? '');
    $xpZadania = trim($_POST&#91;'xp'] ?? '');

    if ($tytulZadania === '') {
        $bledy&#91;] = "Tytuł jest wymagany.";
    }

    if (!preg_match('/^\d+$/', $xpZadania)) {
        $bledy&#91;] = "XP musi być liczbą całkowitą.";
    } elseif ((int) $xpZadania &lt; 1 || (int) $xpZadania &gt; 100) {
        $bledy&#91;] = "XP musi być z zakresu 1–100.";
    }

    $przyjete = count($bledy) === 0;
}
?&gt;
&lt;!DOCTYPE html&gt;
&lt;html lang="pl"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Brudnopis — formularz&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;?php if ($przyjete) { ?&gt;
    &lt;p&gt;Przyjęto zadanie: &lt;?= $tytulZadania; ?&gt; (&lt;?= $xpZadania; ?&gt; XP)&lt;/p&gt;
    &lt;?php } ?&gt;

    &lt;?php if (count($bledy) &gt; 0) { ?&gt;
    &lt;ul&gt;
        &lt;?php foreach ($bledy as $blad) { ?&gt;
        &lt;li&gt;&lt;?= $blad; ?&gt;&lt;/li&gt;
        &lt;?php } ?&gt;
    &lt;/ul&gt;
    &lt;?php } ?&gt;

    &lt;form method="post" action="brudnopis.php"&gt;
        &lt;label for="tytul"&gt;Tytuł&lt;/label&gt;
        &lt;input type="text" id="tytul" name="tytul" value="&lt;?= $tytulZadania; ?&gt;"&gt;

        &lt;label for="xp"&gt;XP&lt;/label&gt;
        &lt;input type="text" id="xp" name="xp" value="&lt;?= $xpZadania; ?&gt;"&gt;

        &lt;button type="submit"&gt;Sprawdź&lt;/button&gt;
    &lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>



<p class="wp-block-paragraph">Dlaczego XP sprawdzamy <strong>najpierw</strong> regexem, a dopiero potem zakresem (<code class="prettyprint lang-sql" style="font-size: 0.85em;">elseif</code>)? Bo <code class="prettyprint lang-sql" style="font-size: 0.85em;">(int) "12abc"</code> to <code class="prettyprint lang-sql" style="font-size: 0.85em;">12</code> — przeszłoby sprawdzenie zakresu, choć użytkownik wpisał bzdurę. A <code class="prettyprint lang-sql" style="font-size: 0.85em;">(int) "dużo"</code> to <code class="prettyprint lang-sql" style="font-size: 0.85em;">0</code> — użytkownik dostałby mylący komunikat o zakresie zamiast informacji, że to w ogóle nie jest liczba. Sprawdzisz to w Eksperymencie 4.</p>



<p class="wp-block-paragraph">Ten schemat — <strong>wartości początkowe → <code class="prettyprint lang-sql" style="font-size: 0.85em;">if (POST)</code> → odczyt z <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code> → sprawdzenia do tablicy <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bledy</code> → HTML z listą błędów i wypełnionym formularzem</strong> — powtórzymy za chwilę w TaskQuest.</p>
</details>



<h1 class="wp-block-heading">5. Włączamy formularz do TaskQuest — krok po kroku</h1>



<p class="wp-block-paragraph"><strong>PROJEKT.</strong> Od tego miejsca kończymy pracę w <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php</code> (zostaw go na razie — przyda się w testach) i zmieniamy pliki prawdziwej aplikacji w katalogu <code class="prettyprint lang-sql" style="font-size: 0.85em;">taskquest/</code>. Kolejność: najpierw funkcje (logika bez HTML), potem nowa strona, na końcu połączenie jej ze stroną główną.</p>



<h2 class="wp-block-heading">Krok 1 — SAMODZIELNIE: <code class="prettyprint lang-sql" style="font-size: 0.85em;">walidujNoweZadanie()</code></h2>



<p class="wp-block-paragraph"><strong>Plik projektu: <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_zadan.php</code>.</strong> Na <strong>końcu</strong> pliku (pod <code class="prettyprint lang-sql" style="font-size: 0.85em;">sortujPoTerminie()</code>) dopisz funkcję:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">function walidujNoweZadanie(string $tytulZadania, string $opisZadania, string $terminZadania, string $xpZadania): array</code></pre>



<p class="wp-block-paragraph">Funkcja dostaje <strong>już oczyszczone</strong> teksty z formularza i zwraca tablicę komunikatów o błędach. Pusta tablica oznacza: dane są poprawne. Funkcja <strong>niczego nie wyświetla</strong> i <strong>nie rzuca wyjątków</strong> (patrz 3.7).</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Pole</th><th>Reguła</th><th>Komunikat (dokładnie taki)</th></tr></thead><tbody><tr><td>tytuł</td><td>nie może być pusty</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Tytuł jest wymagany.</code></td></tr><tr><td>tytuł</td><td>najwyżej 100 znaków (sprawdzaj tylko, gdy nie jest pusty)</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Tytuł może mieć najwyżej 100 znaków.</code></td></tr><tr><td>opis</td><td>nieobowiązkowy, ale najwyżej 300 znaków</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Opis może mieć najwyżej 300 znaków.</code></td></tr><tr><td>termin</td><td>kształt <code class="prettyprint lang-sql" style="font-size: 0.85em;">RRRR-MM-DD</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Termin musi być datą w formacie RRRR-MM-DD.</code></td></tr><tr><td>termin</td><td>dzień musi istnieć w kalendarzu (sprawdzaj tylko, gdy kształt jest poprawny)</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Taki dzień nie istnieje w kalendarzu.</code></td></tr><tr><td>XP</td><td>same cyfry</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">XP musi być liczbą całkowitą.</code></td></tr><tr><td>XP</td><td>zakres 1–100 (sprawdzaj tylko, gdy to liczba)</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">XP musi być z zakresu 1–100.</code></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Długość tekstu licz przez <code class="prettyprint lang-sql" style="font-size: 0.85em;">mb_strlen()</code> (Lekcja 8) — <code class="prettyprint lang-sql" style="font-size: 0.85em;">strlen()</code> policzyłby polskie litery podwójnie.</p>



<p class="wp-block-paragraph">Do sprawdzenia, czy dzień istnieje, użyj wbudowanej funkcji <code class="prettyprint lang-sql" style="font-size: 0.85em;">checkdate($miesiac, $dzien, $rok)</code> — przyjmuje trzy liczby całkowite i zwraca <code class="prettyprint lang-sql" style="font-size: 0.85em;">true</code> albo <code class="prettyprint lang-sql" style="font-size: 0.85em;">false</code> (np. <code class="prettyprint lang-sql" style="font-size: 0.85em;">checkdate(2, 31, 2026)</code> → <code class="prettyprint lang-sql" style="font-size: 0.85em;">false</code>). Trzy liczby z tekstu <code class="prettyprint lang-sql" style="font-size: 0.85em;">2026-02-31</code> wydobędziesz przez <code class="prettyprint lang-sql" style="font-size: 0.85em;">explode('-', $terminZadania)</code> i rzutowanie <code class="prettyprint lang-sql" style="font-size: 0.85em;">(int)</code>. (Jeśli robiłaś/robiłeś zadanie końcowe A z Lekcji 12, znasz już ten pomysł.)</p>



<p class="wp-block-paragraph">Zauważ: w komunikatach <strong>nie powtarzamy tego, co wpisał użytkownik</strong> (np. „Termin 2026-02-31 &#8230;”). Tekst od użytkownika trafia na stronę tylko tam, gdzie to konieczne — dlaczego to ważne, zobaczysz w Eksperymencie 5.</p>



<p class="wp-block-paragraph"><strong>Jak sprawdzić:</strong> w <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php</code> (podmień zawartość):</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php

require_once __DIR__ . '/includes/funkcje_zadan.php';

print_r(walidujNoweZadanie('Nauka PHP', '', '2026-10-02', '20'));
print_r(walidujNoweZadanie('', '', '2026-02-31', 'dużo'));</code></pre>



<p class="wp-block-paragraph">Pierwsze wywołanie: <code class="prettyprint lang-sql" style="font-size: 0.85em;">Array ( )</code>. Drugie: trzy komunikaty — o tytule, o kalendarzu i o liczbie całkowitej. Pełny zestaw przypadków jest w Teście 1.</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>HINT</strong></summary>
<p class="wp-block-paragraph">Pary reguł dla jednego pola („pusty?” → „za długi?”, „kształt?” → „kalendarz?”, „cyfry?” → „zakres?”) to naturalne miejsce na <code class="prettyprint lang-sql" style="font-size: 0.85em;">if</code> / <code class="prettyprint lang-sql" style="font-size: 0.85em;">elseif</code> albo <code class="prettyprint lang-sql" style="font-size: 0.85em;">if</code> / <code class="prettyprint lang-sql" style="font-size: 0.85em;">else</code> z zagnieżdżonym <code class="prettyprint lang-sql" style="font-size: 0.85em;">if</code>. Druga reguła ma sens tylko wtedy, gdy pierwsza przeszła. Wzorzec daty masz w <code class="prettyprint lang-sql" style="font-size: 0.85em;">sprawdzWierszZadania()</code>.</p>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>POMOC</strong></summary>
<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">function walidujNoweZadanie(string $tytulZadania, string $opisZadania, string $terminZadania, string $xpZadania): array
{
    $bledy = &#91;];

    if ($tytulZadania === '') {
        $bledy&#91;] = "Tytuł jest wymagany.";
    } elseif (/* za długi */) {
        // ...
    }

    // opis ...

    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $terminZadania)) {
        // ...
    } else {
        $czesciDaty = explode('-', $terminZadania);   // &#91;0] =&gt; rok, &#91;1] =&gt; miesiąc, &#91;2] =&gt; dzień

        // checkdate(miesiąc, dzień, rok) — uwaga na kolejność!
    }

    // XP ...

    return $bledy;
}</code></pre>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>ROZWIĄZANIE</strong></summary>
<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">function walidujNoweZadanie(string $tytulZadania, string $opisZadania, string $terminZadania, string $xpZadania): array
{
    $bledy = &#91;];

    if ($tytulZadania === '') {
        $bledy&#91;] = "Tytuł jest wymagany.";
    } elseif (mb_strlen($tytulZadania) &gt; 100) {
        $bledy&#91;] = "Tytuł może mieć najwyżej 100 znaków.";
    }

    if (mb_strlen($opisZadania) &gt; 300) {
        $bledy&#91;] = "Opis może mieć najwyżej 300 znaków.";
    }

    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $terminZadania)) {
        $bledy&#91;] = "Termin musi być datą w formacie RRRR-MM-DD.";
    } else {
        $czesciDaty = explode('-', $terminZadania);

        if (!checkdate((int) $czesciDaty&#91;1], (int) $czesciDaty&#91;2], (int) $czesciDaty&#91;0])) {
            $bledy&#91;] = "Taki dzień nie istnieje w kalendarzu.";
        }
    }

    if (!preg_match('/^\d+$/', $xpZadania)) {
        $bledy&#91;] = "XP musi być liczbą całkowitą.";
    } elseif ((int) $xpZadania &lt; 1 || (int) $xpZadania &gt; 100) {
        $bledy&#91;] = "XP musi być z zakresu 1–100.";
    }

    return $bledy;
}</code></pre>



<p class="wp-block-paragraph">Porównaj z <code class="prettyprint lang-sql" style="font-size: 0.85em;">sprawdzWierszZadania()</code> z Lekcji 12: sprawdzenia są podobne (kształt daty, same cyfry), ale <strong>sposób zgłaszania inny</strong>. Tam uszkodzony plik to awaria → wyjątek. Tu pomyłka użytkownika to normalna sytuacja → lista komunikatów.</p>
</details>



<h2 class="wp-block-heading">Krok 2 — SAMODZIELNIE: <code class="prettyprint lang-sql" style="font-size: 0.85em;">nastepneIdZadania()</code></h2>



<p class="wp-block-paragraph"><strong>Plik projektu: <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_zadan.php</code>.</strong> Nowe zadanie potrzebuje <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code>, którego nie ma jeszcze żadne inne zadanie. Dopisz nową funkcję <strong>między</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">sortujPoTerminie()</code> a <code class="prettyprint lang-sql" style="font-size: 0.85em;">walidujNoweZadanie()</code> (dla PHP kolejność funkcji w pliku nie ma znaczenia — to tylko porządek dla czytającego):</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">function nastepneIdZadania(array $zadania): int</code></pre>



<p class="wp-block-paragraph">Funkcja zwraca <strong>największe <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> na liście + 1</strong>. Dla pustej listy zwraca <code class="prettyprint lang-sql" style="font-size: 0.85em;">1</code>.</p>



<p class="wp-block-paragraph">Zanim zaczniesz, odpowiedz: dlaczego <strong>nie</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">count($zadania) + 1</code>? Wyobraź sobie, że Kama ręcznie usunęła z pliku zadanie o <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> 3. Na liście zostały <code class="prettyprint lang-sql" style="font-size: 0.85em;">1, 2, 4, 5</code> — ile wynosi <code class="prettyprint lang-sql" style="font-size: 0.85em;">count() + 1</code> i co by się stało?</p>



<p class="wp-block-paragraph"><strong>Jak sprawdzić:</strong> w <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php</code>:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">echo nastepneIdZadania(&#91;]) . "&lt;br&gt;";                                        // 1
echo nastepneIdZadania(&#91;&#91;'id' =&gt; 1], &#91;'id' =&gt; 2], &#91;'id' =&gt; 3]]) . "&lt;br&gt;";   // 4
echo nastepneIdZadania(&#91;&#91;'id' =&gt; 3], &#91;'id' =&gt; 10], &#91;'id' =&gt; 7]]) . "&lt;br&gt;";  // 11</code></pre>



<p class="wp-block-paragraph">(Funkcja potrzebuje tylko klucza <code class="prettyprint lang-sql" style="font-size: 0.85em;">'id'</code>, więc do testu wystarczą takie „okrojone” zadania.)</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>HINT</strong></summary>
<p class="wp-block-paragraph">To klasyczne szukanie maksimum pętlą <code class="prettyprint lang-sql" style="font-size: 0.85em;">foreach</code> — pamiętasz podobny wzorzec z sumowaniem XP w <code class="prettyprint lang-sql" style="font-size: 0.85em;">sumujXp()</code> (Lekcja 9). Zmienna pomocnicza startuje od <code class="prettyprint lang-sql" style="font-size: 0.85em;">0</code>, bo <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> są dodatnie.</p>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>ROZWIĄZANIE</strong></summary>
<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">function nastepneIdZadania(array $zadania): int
{
    $najwiekszeId = 0;

    foreach ($zadania as $zadanie) {
        if ($zadanie&#91;'id'] &gt; $najwiekszeId) {
            $najwiekszeId = $zadanie&#91;'id'];
        }
    }

    return $najwiekszeId + 1;
}</code></pre>



<p class="wp-block-paragraph">Odpowiedź: dla <code class="prettyprint lang-sql" style="font-size: 0.85em;">1, 2, 4, 5</code> <code class="prettyprint lang-sql" style="font-size: 0.85em;">count() + 1</code> daje <code class="prettyprint lang-sql" style="font-size: 0.85em;">5</code> — a zadanie o <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> 5 już istnieje. Dwa zadania z tym samym <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> to przepis na kłopoty, gdy zaczniemy wskazywać zadania po <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> (np. „oznacz zadanie 5 jako wykonane” w zadaniu końcowym A). (Istnieje też gotowa funkcja <code class="prettyprint lang-sql" style="font-size: 0.85em;">max()</code> — ale pętla pokazuje, co się naprawdę dzieje, i dla pustej listy działa bez dodatkowego <code class="prettyprint lang-sql" style="font-size: 0.85em;">if</code>.)</p>
</details>



<h2 class="wp-block-heading">Krok 3 — SAMODZIELNIE: strona <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code></h2>



<p class="wp-block-paragraph"><strong>Nowy plik projektu: <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code></strong> — w katalogu głównym <code class="prettyprint lang-sql" style="font-size: 0.85em;">taskquest/</code>, obok <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code>. Buduj go w trzech częściach i sprawdzaj po każdej.</p>



<h3 class="wp-block-heading">3a — szkielet i formularz</h3>



<p class="wp-block-paragraph">Struktura pliku jest taka sama jak <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> (dane i logika na górze, prezentacja na dole):</p>



<ol class="wp-block-list">
<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">require_once</code>: <code class="prettyprint lang-sql" style="font-size: 0.85em;">config/config.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_tekstowe.php</code> (będzie potrzebna <code class="prettyprint lang-sql" style="font-size: 0.85em;">wyczyscTytul()</code>), <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_zadan.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_plikow.php</code>,</li>



<li>wartości początkowe: <code class="prettyprint lang-sql" style="font-size: 0.85em;">$tytulZadania</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">$opisZadania</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">$terminZadania</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">$xpZadania</code> (puste teksty), <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bledy</code> (pusta tablica), <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bladZapisu</code> (<code class="prettyprint lang-sql" style="font-size: 0.85em;">null</code> — tu trafi komunikat wyjątku, jak <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bladZadan</code> w <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code>),</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">$nazwaStrony = 'Dodaj zadanie — TaskQuest';</code> i dołączenie <code class="prettyprint lang-sql" style="font-size: 0.85em;">header.php</code>,</li>



<li>nagłówek <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;h2&gt;Nowe zadanie&lt;/h2&gt;</code>, link powrotny do <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code>,</li>



<li>formularz: metoda POST, <code class="prettyprint lang-sql" style="font-size: 0.85em;">action="dodaj-zadanie.php"</code>, pola <code class="prettyprint lang-sql" style="font-size: 0.85em;">tytul</code> (text, <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">maxlength="100"</code>), <code class="prettyprint lang-sql" style="font-size: 0.85em;">opis</code> (textarea, <code class="prettyprint lang-sql" style="font-size: 0.85em;">maxlength="300"</code>), <code class="prettyprint lang-sql" style="font-size: 0.85em;">termin</code> (date, <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code>), <code class="prettyprint lang-sql" style="font-size: 0.85em;">xp</code> (number, <code class="prettyprint lang-sql" style="font-size: 0.85em;">min="1"</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">max="100"</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code>), przycisk „Dodaj zadanie”. Każde pole z <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;label for="..."&gt;</code> i wartością z odpowiedniej zmiennej,</li>



<li>dołączenie <code class="prettyprint lang-sql" style="font-size: 0.85em;">footer.php</code>.</li>
</ol>



<p class="wp-block-paragraph"><strong>Sprawdź:</strong> otwórz <code class="prettyprint lang-sql" style="font-size: 0.85em;">http://localhost/taskquest/dodaj-zadanie.php</code>. Widać nagłówek TaskQuest, formularz i stopkę. Brak ostrzeżeń PHP. Wysłanie formularza na razie nic nie robi (strona po prostu się przeładuje).</p>



<h3 class="wp-block-heading">3b — odbiór i sprawdzenie danych</h3>



<p class="wp-block-paragraph">Między wartościami początkowymi a prezentacją dodaj blok <code class="prettyprint lang-sql" style="font-size: 0.85em;">if ($_SERVER['REQUEST_METHOD'] === 'POST') { ... }</code>, który:</p>



<ol class="wp-block-list">
<li>odczytuje cztery pola przez <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code> i od razu je czyści:
<ul class="wp-block-list">
<li>tytuł — istniejącą funkcją <code class="prettyprint lang-sql" style="font-size: 0.85em;">wyczyscTytul()</code> (Lekcja 8: przycina i zamienia wielokrotne białe znaki na jedną spację),</li>



<li>opis — <code class="prettyprint lang-sql" style="font-size: 0.85em;">preg_replace('/\s+/', ' ', ...)</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">trim()</code>: <code class="prettyprint lang-sql" style="font-size: 0.85em;">textarea</code> pozwala wpisać kilka linii, a my chcemy mieć opis w <strong>jednej</strong> linii pliku CSV (pamiętasz ograniczenie numerów linii z Lekcji 12?),</li>



<li>termin i XP — <code class="prettyprint lang-sql" style="font-size: 0.85em;">trim()</code>,</li>
</ul>
</li>



<li>wywołuje <code class="prettyprint lang-sql" style="font-size: 0.85em;">walidujNoweZadanie()</code> i zapisuje wynik w <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bledy</code>.</li>
</ol>



<p class="wp-block-paragraph">W prezentacji, nad formularzem: gdy <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bledy</code> nie jest puste — ramka <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;div class="blad"&gt;</code> z tekstem „Popraw formularz:” i listą <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;ul&gt;</code> komunikatów.</p>



<p class="wp-block-paragraph"><strong>Sprawdź</strong> (na czas sprawdzania dopisz do znacznika <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;form</code> atrybut <code class="prettyprint lang-sql" style="font-size: 0.85em;">novalidate</code>, żeby przeglądarka nie blokowała wysłania — <strong>potem go usuń</strong>): pusty formularz → trzy komunikaty; tytuł <code class="prettyprint lang-sql" style="font-size: 0.85em;">Nauka</code>, termin pusty, XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">500</code> → dwa komunikaty, a w polu tytułu nadal jest <code class="prettyprint lang-sql" style="font-size: 0.85em;">Nauka</code>.</p>



<h3 class="wp-block-heading">3c — zapis do pliku</h3>



<p class="wp-block-paragraph">Gdy <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bledy</code> jest puste, zadanie trzeba dopisać do pliku. <code class="prettyprint lang-sql" style="font-size: 0.85em;">zapiszZadania()</code> nadpisuje <strong>cały plik</strong> (tryb <code class="prettyprint lang-sql" style="font-size: 0.85em;">w</code>), więc nie da się „dopisać jednej linii” — trzeba:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">wczytajZadania()  →  $zadania&#91;] = nowe zadanie  →  zapiszZadania()</code></pre>



<p class="wp-block-paragraph">Obie funkcje plikowe mogą rzucić wyjątek, więc cała ta trójka idzie do <strong>jednego</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code>, a w <code class="prettyprint lang-sql" style="font-size: 0.85em;">catch</code> zapamiętujemy komunikat w <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bladZapisu</code>. Nowe zadanie musi mieć <strong>dokładnie tę samą strukturę kluczy</strong> co pozostałe: <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> (z <code class="prettyprint lang-sql" style="font-size: 0.85em;">nastepneIdZadania()</code>), <code class="prettyprint lang-sql" style="font-size: 0.85em;">tytul</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">opis</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">termin</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">wykonane</code> (<code class="prettyprint lang-sql" style="font-size: 0.85em;">false</code> — nowe zadanie nie jest wykonane), <code class="prettyprint lang-sql" style="font-size: 0.85em;">xp</code> (jako <code class="prettyprint lang-sql" style="font-size: 0.85em;">int</code>!).</p>



<p class="wp-block-paragraph">Po udanym zapisie (na razie) ustaw zmienną <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadanieDodane = true;</code> i wyświetl nad formularzem <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;p class="sukces"&gt;Zadanie zostało dodane.&lt;/p&gt;</code>. Gdy <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bladZapisu !== null</code>, wyświetl <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;p class="blad"&gt;Nie udało się dodać zadania: ...&lt;/p&gt;</code>.</p>



<p class="wp-block-paragraph">Zastanów się, zanim napiszesz kod: co by się stało, gdyby <code class="prettyprint lang-sql" style="font-size: 0.85em;">wczytajZadania()</code> była <strong>poza</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code>, a przy awarii odczytu kod przyjąłby <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadania = []</code> i mimo to wywołał <code class="prettyprint lang-sql" style="font-size: 0.85em;">zapiszZadania()</code>? Pomyśl o pliku, w którym jeden wiersz jest uszkodzony.</p>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>HINT</strong></summary>
<p class="wp-block-paragraph">Wszystko, co ma się wykonać <strong>tylko po udanym odczycie</strong>, musi stać w <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code> <strong>pod</strong> wywołaniem <code class="prettyprint lang-sql" style="font-size: 0.85em;">wczytajZadania()</code>. Jeśli odczyt rzuci wyjątek, PHP przeskoczy do <code class="prettyprint lang-sql" style="font-size: 0.85em;">catch</code> i zapis się w ogóle nie wykona. Pamiętaj o <code class="prettyprint lang-sql" style="font-size: 0.85em;">(int)</code> przy XP — z formularza przychodzi tekst, a <code class="prettyprint lang-sql" style="font-size: 0.85em;">sumujXp()</code> i porównania w aplikacji oczekują liczby.</p>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>POMOC</strong></summary>
<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">if ($_SERVER&#91;'REQUEST_METHOD'] === 'POST') {
    $tytulZadania = wyczyscTytul($_POST&#91;'tytul'] ?? '');
    $opisZadania = trim(preg_replace('/\s+/', ' ', $_POST&#91;'opis'] ?? ''));
    // ... termin i xp ...

    $bledy = walidujNoweZadanie(/* ... */);

    if (count($bledy) === 0) {
        try {
            $zadania = wczytajZadania(PLIK_ZADAN);

            $zadania&#91;] = &#91;
                'id' =&gt; /* ... */,
                // ... pozostałe klucze ...
            ];

            zapiszZadania(PLIK_ZADAN, $zadania);
            // ... zadanie dodane ...
        } catch (Exception $wyjatek) {
            // ... komunikat do $bladZapisu ...
        }
    }
}</code></pre>



<p class="wp-block-paragraph">Formularz (fragment):</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">        &lt;form method="post" action="dodaj-zadanie.php"&gt;
            &lt;label for="tytul"&gt;Tytuł *&lt;/label&gt;
            &lt;input type="text" id="tytul" name="tytul" maxlength="100" required value="&lt;?= $tytulZadania; ?&gt;"&gt;

            &lt;label for="opis"&gt;Opis&lt;/label&gt;
            &lt;textarea id="opis" name="opis" rows="3" maxlength="300"&gt;&lt;?= $opisZadania; ?&gt;&lt;/textarea&gt;

            &lt;!-- termin, xp, przycisk --&gt;
        &lt;/form&gt;</code></pre>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>ROZWIĄZANIE</strong></summary>
<h4 class="wp-block-heading">(wersja przejściowa — w Kroku 4 zmienimy jedną rzecz)</h4>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php

// --- pliki aplikacji ---
require_once __DIR__ . '/config/config.php';
require_once __DIR__ . '/includes/funkcje_tekstowe.php';
require_once __DIR__ . '/includes/funkcje_zadan.php';
require_once __DIR__ . '/includes/funkcje_plikow.php';


// --- dane nowego zadania (na start puste — formularz jeszcze nie został wysłany) ---
$tytulZadania = '';
$opisZadania = '';
$terminZadania = '';
$xpZadania = '';

$bledy = &#91;];
$bladZapisu = null;
$zadanieDodane = false;   // tylko w wersji przejściowej


// --- obsługa wysłanego formularza (POST) ---
if ($_SERVER&#91;'REQUEST_METHOD'] === 'POST') {
    $tytulZadania = wyczyscTytul($_POST&#91;'tytul'] ?? '');
    $opisZadania = trim(preg_replace('/\s+/', ' ', $_POST&#91;'opis'] ?? ''));
    $terminZadania = trim($_POST&#91;'termin'] ?? '');
    $xpZadania = trim($_POST&#91;'xp'] ?? '');

    $bledy = walidujNoweZadanie($tytulZadania, $opisZadania, $terminZadania, $xpZadania);

    if (count($bledy) === 0) {
        try {
            $zadania = wczytajZadania(PLIK_ZADAN);

            $zadania&#91;] = &#91;
                'id' =&gt; nastepneIdZadania($zadania),
                'tytul' =&gt; $tytulZadania,
                'opis' =&gt; $opisZadania,
                'termin' =&gt; $terminZadania,
                'wykonane' =&gt; false,
                'xp' =&gt; (int) $xpZadania
            ];

            zapiszZadania(PLIK_ZADAN, $zadania);
            $zadanieDodane = true;   // tylko w wersji przejściowej
        } catch (Exception $wyjatek) {
            $bladZapisu = $wyjatek-&gt;getMessage();
        }
    }
}


// --- prezentacja ---
$nazwaStrony = 'Dodaj zadanie — TaskQuest';
require_once __DIR__ . '/includes/header.php';
?&gt;
        &lt;h2&gt;Nowe zadanie&lt;/h2&gt;
        &lt;p&gt;&lt;a href="index.php"&gt;← Wróć do listy zadań&lt;/a&gt;&lt;/p&gt;

        &lt;?php if ($zadanieDodane) { ?&gt;
        &lt;p class="sukces"&gt;Zadanie zostało dodane.&lt;/p&gt;
        &lt;?php } ?&gt;

        &lt;?php if (count($bledy) &gt; 0) { ?&gt;
        &lt;div class="blad"&gt;
            &lt;p&gt;Popraw formularz:&lt;/p&gt;
            &lt;ul&gt;
                &lt;?php foreach ($bledy as $blad) { ?&gt;
                &lt;li&gt;&lt;?= $blad; ?&gt;&lt;/li&gt;
                &lt;?php } ?&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
        &lt;?php } ?&gt;

        &lt;?php if ($bladZapisu !== null) { ?&gt;
        &lt;p class="blad"&gt;Nie udało się dodać zadania: &lt;?= $bladZapisu; ?&gt;&lt;/p&gt;
        &lt;?php } ?&gt;

        &lt;form method="post" action="dodaj-zadanie.php"&gt;
            &lt;label for="tytul"&gt;Tytuł *&lt;/label&gt;
            &lt;input type="text" id="tytul" name="tytul" maxlength="100" required value="&lt;?= $tytulZadania; ?&gt;"&gt;

            &lt;label for="opis"&gt;Opis&lt;/label&gt;
            &lt;textarea id="opis" name="opis" rows="3" maxlength="300"&gt;&lt;?= $opisZadania; ?&gt;&lt;/textarea&gt;

            &lt;label for="termin"&gt;Termin *&lt;/label&gt;
            &lt;input type="date" id="termin" name="termin" required value="&lt;?= $terminZadania; ?&gt;"&gt;

            &lt;label for="xp"&gt;Nagroda XP (1–100) *&lt;/label&gt;
            &lt;input type="number" id="xp" name="xp" min="1" max="100" required value="&lt;?= $xpZadania; ?&gt;"&gt;

            &lt;button type="submit"&gt;Dodaj zadanie&lt;/button&gt;
        &lt;/form&gt;
&lt;?php
require_once __DIR__ . '/includes/footer.php';</code></pre>
</details>



<p class="wp-block-paragraph">Klasa <code class="prettyprint lang-sql" style="font-size: 0.85em;">.sukces</code> pojawi się w <code class="prettyprint lang-sql" style="font-size: 0.85em;">header.php</code> w Kroku 4 — do tego czasu komunikat będzie po prostu zwykłym akapitem.</p>



<p class="wp-block-paragraph">Odpowiedź na pytanie: przy uszkodzonym wierszu <code class="prettyprint lang-sql" style="font-size: 0.85em;">wczytajZadania()</code> rzuca wyjątek. Gdyby kod mimo to zapisał listę złożoną tylko z nowego zadania, <code class="prettyprint lang-sql" style="font-size: 0.85em;">zapiszZadania()</code> (tryb <code class="prettyprint lang-sql" style="font-size: 0.85em;">w</code>!) <strong>skasowałaby wszystkie dotychczasowe zadania</strong> — zamiast jednego uszkodzonego wiersza Kama straciłaby cały plik. Dlatego: <strong>nie udało się wczytać → nie zapisujemy niczego.</strong></p>



<p class="wp-block-paragraph"><strong>Sprawdź:</strong> dodaj zadanie <code class="prettyprint lang-sql" style="font-size: 0.85em;">Powtórka z formularzy</code>, termin za tydzień, <code class="prettyprint lang-sql" style="font-size: 0.85em;">30</code> XP. Otwórz <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code> w edytorze — na końcu jest nowy wiersz z <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> 6 i <code class="prettyprint lang-sql" style="font-size: 0.85em;">wykonane</code> = <code class="prettyprint lang-sql" style="font-size: 0.85em;">0</code>. Otwórz <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> — zadanie jest na liście, liczniki pokazują 6 zadań.</p>



<h2 class="wp-block-heading">Krok 4 — SAMODZIELNIE: odświeżenie nie może dodać zadania drugi raz</h2>



<p class="wp-block-paragraph">Wróć na <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code>, dodaj jeszcze jedno zadanie, a gdy zobaczysz „Zadanie zostało dodane.”, naciśnij <strong>F5</strong> i potwierdź ponowne wysłanie. Otwórz <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code>: to samo zadanie jest <strong>dwa razy</strong>, z różnymi <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code>. Przeglądarka po prostu powtórzyła ostatnie żądanie — a ostatnie żądanie to był POST z danymi.</p>



<p class="wp-block-paragraph">Rozwiązanie to wzorzec <strong>POST → przekierowanie → GET</strong> (ang. <em>Post/Redirect/Get</em>, w skrócie PRG):</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">1. przeglądarka: POST dodaj-zadanie.php (dane)
2. PHP: zapisuje zadanie i odpowiada "idź pod adres index.php?dodano=1" (przekierowanie)
3. przeglądarka: GET index.php?dodano=1   ← to jest teraz "ostatnie żądanie"
4. F5 powtarza tylko krok 3 — czyli zwykłe wyświetlenie strony</code></pre>



<p class="wp-block-paragraph">Przekierowanie robi się tak:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">header('Location: index.php?dodano=1');
exit;</code></pre>



<ul class="wp-block-list">
<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">header()</code> wysyła przeglądarce <strong>nagłówek</strong> odpowiedzi — informację „techniczną”, która idzie przed treścią strony. Nagłówek <code class="prettyprint lang-sql" style="font-size: 0.85em;">Location</code> mówi: „tu nic nie ma, idź pod ten adres”.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">exit</code> natychmiast kończy skrypt. Bez niego PHP wykonałby resztę pliku (wygenerowałby cały formularz), którego i tak nikt nie zobaczy.</li>



<li>Nagłówki muszą być wysłane <strong>przed</strong> jakąkolwiek treścią strony. U nas logika stoi nad <code class="prettyprint lang-sql" style="font-size: 0.85em;">require_once header.php</code>, więc to jest spełnione. Gdyby wcześniej coś zostało wypisane, PHP może zgłosić <code class="prettyprint lang-sql" style="font-size: 0.85em;">Warning: Cannot modify header information - headers already sent</code> i przekierowanie nie zadziała. (W XAMPP-ie włączone jest buforowanie wyjścia, więc przy małej ilości tekstu ostrzeżenie może się nie pojawić — ale nie polegaj na tym.)</li>
</ul>



<p class="wp-block-paragraph">Skąd <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> ma wiedzieć, że ma pokazać komunikat? Z adresu — dokładnie tak, jak w 3.2 PHP dostał <code class="prettyprint lang-sql" style="font-size: 0.85em;">?imie=Kama</code>. Parametr <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodano=1</code> odczytamy przez <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code>.</p>



<p class="wp-block-paragraph"><strong>Zadanie</strong> — zmień cztery pliki:</p>



<ol class="wp-block-list">
<li><strong><code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code></strong>: w miejscu <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadanieDodane = true;</code>(to usuń) wstaw przekierowanie na <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?dodano=1</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">exit</code>. Usuń z tego pliku wszystko, co dotyczyło <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadanieDodane</code> (wartość początkową i akapit „Zadanie zostało dodane.”) — komunikat przenosi się na stronę główną.</li>



<li><strong><code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code></strong>:
<ul class="wp-block-list">
<li>w bloku wywołań funkcji, po obliczeniu streaka, dodaj nowy blok <code class="prettyprint lang-sql" style="font-size: 0.85em;">// --- parametry z adresu strony (GET) ---</code> i w nim zmienną <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadanieDodane</code> — <code class="prettyprint lang-sql" style="font-size: 0.85em;">true</code> tylko wtedy, gdy w adresie jest <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodano=1</code>,</li>



<li>pod <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;h2&gt;Twoje zadania&lt;/h2&gt;</code> dodaj link <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;p&gt;&lt;a href="dodaj-zadanie.php"&gt;+ Dodaj zadanie&lt;/a&gt;&lt;/p&gt;</code> oraz — gdy <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadanieDodane</code> — <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;p class="sukces"&gt;Zadanie zostało dodane.&lt;/p&gt;</code>. Oba elementy mają być <strong>przed</strong> konstrukcją <code class="prettyprint lang-sql" style="font-size: 0.85em;">if ($bladZadan !== null)</code>, żeby link działał także wtedy, gdy wczytanie zadań się nie udało.</li>
</ul>
</li>



<li><strong><code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/header.php</code></strong>: dodaj regułę <code class="prettyprint lang-sql" style="font-size: 0.85em;">.sukces</code> (zielona odmiana <code class="prettyprint lang-sql" style="font-size: 0.85em;">.blad</code>) i trzy proste reguły dla formularza (etykieta w osobnej linii, pola na całą szerokość, odstęp przycisku).</li>



<li><strong><code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/footer.php</code></strong>: <code class="prettyprint lang-sql" style="font-size: 0.85em;">TaskQuest 1.3</code>.</li>
</ol>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>HINT</strong></summary>
<p class="wp-block-paragraph"><code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET['dodano']</code> przy zwykłym wejściu na stronę nie istnieje — użyj <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code> z pustym tekstem, a wynik porównaj przez <code class="prettyprint lang-sql" style="font-size: 0.85em;">===</code> z tekstem <code class="prettyprint lang-sql" style="font-size: 0.85em;">'1'</code> (pamiętasz? z adresu przychodzą teksty). Wynik porównania to od razu <code class="prettyprint lang-sql" style="font-size: 0.85em;">true</code> albo <code class="prettyprint lang-sql" style="font-size: 0.85em;">false</code>.</p>
</details>



<h3 class="wp-block-heading">ROZWIĄZANIE</h3>



<p class="wp-block-paragraph"><strong>a)</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code> — fragment w <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code>:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">            zapiszZadania(PLIK_ZADAN, $zadania);

            header('Location: index.php?dodano=1');
            exit;</code></pre>



<p class="wp-block-paragraph"><strong>b)</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> — nowy blok po <code class="prettyprint lang-sql" style="font-size: 0.85em;">$streak = aktualizujStreak(...);</code>:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">// --- parametry z adresu strony (GET) ---
$zadanieDodane = ($_GET&#91;'dodano'] ?? '') === '1';</code></pre>



<p class="wp-block-paragraph"><strong>c)</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> — prezentacja:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">        &lt;h2&gt;Twoje zadania&lt;/h2&gt;

        &lt;p&gt;&lt;a href="dodaj-zadanie.php"&gt;+ Dodaj zadanie&lt;/a&gt;&lt;/p&gt;

        &lt;?php if ($zadanieDodane) { ?&gt;
        &lt;p class="sukces"&gt;Zadanie zostało dodane.&lt;/p&gt;
        &lt;?php } ?&gt;

        &lt;?php if ($bladZadan !== null) { ?&gt;</code></pre>



<p class="wp-block-paragraph"><strong>d)</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/header.php</code> — w <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;style&gt;</code>, pod <code class="prettyprint lang-sql" style="font-size: 0.85em;">.blad</code>:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">.sukces { border: 1px solid #080; background: #eaf7ea; color: #060; border-radius: 8px; padding: 8px 16px; }
form label { display: block; margin-top: 12px; }
form input, form textarea { width: 100%; padding: 6px; box-sizing: border-box; }
form button { margin-top: 16px; padding: 8px 16px; }</code></pre>



<p class="wp-block-paragraph"><strong>e)</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/footer.php</code>: <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;p&gt;TaskQuest 1.3&lt;/p&gt;</code>.</p>



<p class="wp-block-paragraph"><strong>Sprawdź:</strong> dodaj zadanie. Przeglądarka sama przechodzi na <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?dodano=1</code>, widać zieloną ramkę i nowe zadanie. Naciśnij F5 — <strong>brak</strong> pytania o ponowne wysłanie, w pliku nie przybyło zadań. W F12 → Sieć zobaczysz dwa żądania: <code class="prettyprint lang-sql" style="font-size: 0.85em;">POST dodaj-zadanie.php</code> ze statusem <strong>302</strong> (przekierowanie) i <code class="prettyprint lang-sql" style="font-size: 0.85em;">GET index.php?dodano=1</code> ze statusem <strong>200</strong>.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>Uczciwie o ograniczeniu:</strong> ramka „Zadanie zostało dodane.” pojawia się po każdym odświeżeniu <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?dodano=1</code>, a nawet po ręcznym wpisaniu takiego adresu — mimo że nic nie dodano. Adres nie jest dobrym miejscem na jednorazowe komunikaty. Porządnie rozwiążemy to w Lekcji 14 (sesje).</p>
</blockquote>



<h1 class="wp-block-heading">6. Kolejne zadanie — widoki listy przez GET</h1>



<p class="wp-block-paragraph">Formularz dodawania <strong>zmienia dane</strong>, więc używa POST. Teraz coś, co <strong>tylko wybiera, co pokazać</strong> — czyli zadanie dla GET.</p>



<p class="wp-block-paragraph"><strong>SAMODZIELNIE — plik projektu: <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code>.</strong> Nad listą zadań dodaj trzy linki: <code class="prettyprint lang-sql" style="font-size: 0.85em;">wszystkie</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">aktywne</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">wykonane</code>, prowadzące do <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?filtr=wszystkie</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?filtr=aktywne</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?filtr=wykonane</code>. Lista ma pokazywać odpowiednio wszystkie zadania, tylko niewykonane albo tylko wykonane — <strong>zawsze posortowane wg terminu</strong>. Pod linkami wypisz <code class="prettyprint lang-sql" style="font-size: 0.85em;">Widok: &lt;nazwa widoku&gt;</code>.</p>



<p class="wp-block-paragraph">Wymagania:</p>



<ol class="wp-block-list">
<li>W bloku <code class="prettyprint lang-sql" style="font-size: 0.85em;">// --- parametry z adresu strony (GET) ---</code> odczytaj parametr <code class="prettyprint lang-sql" style="font-size: 0.85em;">filtr</code> do zmiennej <code class="prettyprint lang-sql" style="font-size: 0.85em;">$filtr</code>. Brak parametru = <code class="prettyprint lang-sql" style="font-size: 0.85em;">wszystkie</code>.</li>



<li><strong>Każda inna wartość</strong> niż <code class="prettyprint lang-sql" style="font-size: 0.85em;">aktywne</code> czy <code class="prettyprint lang-sql" style="font-size: 0.85em;">wykonane</code> (np. <code class="prettyprint lang-sql" style="font-size: 0.85em;">?filtr=abc</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">?filtr=AKTYWNE</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">?filtr=</code>) ma dawać widok <code class="prettyprint lang-sql" style="font-size: 0.85em;">wszystkie</code>. Parametr z adresu to dane od użytkownika — nie ufamy mu.</li>



<li>Do wyboru listy użyj <code class="prettyprint lang-sql" style="font-size: 0.85em;">match</code> (znasz go z <code class="prettyprint lang-sql" style="font-size: 0.85em;">pobierzTytul()</code>) i zapisz wynik w nowej zmiennej <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadaniaDoWyswietlenia</code>. Brakuje posortowanej listy wykonanych — dodaj <code class="prettyprint lang-sql" style="font-size: 0.85em;">$wykonaneWgTerminu</code> obok <code class="prettyprint lang-sql" style="font-size: 0.85em;">$aktywneWgTerminu</code>.</li>



<li>Pętla <code class="prettyprint lang-sql" style="font-size: 0.85em;">foreach</code> wyświetlająca listę ma przechodzić po <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadaniaDoWyswietlenia</code> zamiast po <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadaniaWgTerminu</code>.</li>



<li>Gdy w pliku są zadania, ale wybrany widok jest pusty (np. żadne nie jest wykonane) — wypisz <code class="prettyprint lang-sql" style="font-size: 0.85em;">Brak zadań w tym widoku.</code></li>



<li><strong>Bez zmian</strong> zostają: liczniki, XP do zdobycia, komunikat o awansie i „najpilniejsze zadanie” — one zawsze dotyczą całej listy, niezależnie od widoku.</li>
</ol>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>HINT</strong></summary>
<p class="wp-block-paragraph">Najpierw „zawęź” <code class="prettyprint lang-sql" style="font-size: 0.85em;">$filtr</code> do trzech dozwolonych wartości (jeden <code class="prettyprint lang-sql" style="font-size: 0.85em;">if</code> z <code class="prettyprint lang-sql" style="font-size: 0.85em;">&amp;&amp;</code> — bardzo podobny do sprawdzenia pola <code class="prettyprint lang-sql" style="font-size: 0.85em;">wykonane</code> w <code class="prettyprint lang-sql" style="font-size: 0.85em;">sprawdzWierszZadania()</code>), a dopiero potem użyj <code class="prettyprint lang-sql" style="font-size: 0.85em;">match</code>. Dzięki temu <code class="prettyprint lang-sql" style="font-size: 0.85em;">match</code> zawsze trafi na któryś ze swoich przypadków.</p>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>POMOC</strong></summary>
<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">// --- parametry z adresu strony (GET) ---
$filtr = $_GET&#91;'filtr'] ?? 'wszystkie';

if (/* ani aktywne, ani wykonane */) {
    $filtr = 'wszystkie';
}

$zadaniaDoWyswietlenia = match ($filtr) {
    'wszystkie' =&gt; /* ... */,
    'aktywne' =&gt; /* ... */,
    'wykonane' =&gt; /* ... */
};

$zadanieDodane = ($_GET&#91;'dodano'] ?? '') === '1';</code></pre>
</details>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>ROZWIĄZANIE</strong></summary>
<p class="wp-block-paragraph"><strong>a)</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> — lista zadań, pod <code class="prettyprint lang-sql" style="font-size: 0.85em;">$aktywneWgTerminu = ...</code>:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">$wykonaneWgTerminu = sortujPoTerminie($wykonaneZadania);</code></pre>



<p class="wp-block-paragraph"><strong>b)</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> — cały blok parametrów GET:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">// --- parametry z adresu strony (GET) ---
$filtr = $_GET&#91;'filtr'] ?? 'wszystkie';

if ($filtr !== 'aktywne' &amp;&amp; $filtr !== 'wykonane') {
    $filtr = 'wszystkie';
}

$zadaniaDoWyswietlenia = match ($filtr) {
    'wszystkie' =&gt; $zadaniaWgTerminu,
    'aktywne' =&gt; $aktywneWgTerminu,
    'wykonane' =&gt; $wykonaneWgTerminu
};

$zadanieDodane = ($_GET&#91;'dodano'] ?? '') === '1';</code></pre>



<p class="wp-block-paragraph"><strong>c)</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> — w prezentacji zamień początek listy (dotąd: <code class="prettyprint lang-sql" style="font-size: 0.85em;">echo "&lt;h3&gt;Lista zadań (wg terminu)&lt;/h3&gt;";</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">foreach ($zadaniaWgTerminu as $zadanie) {</code>) na:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">        echo "&lt;h3&gt;Lista zadań (wg terminu)&lt;/h3&gt;";
        echo "&lt;p&gt;Pokaż: &lt;a href=\"index.php?filtr=wszystkie\"&gt;wszystkie&lt;/a&gt; | "
            . "&lt;a href=\"index.php?filtr=aktywne\"&gt;aktywne&lt;/a&gt; | "
            . "&lt;a href=\"index.php?filtr=wykonane\"&gt;wykonane&lt;/a&gt;&lt;/p&gt;";
        echo "&lt;p&gt;Widok: &lt;strong&gt;$filtr&lt;/strong&gt;&lt;/p&gt;";

        if ($liczbaWszystkich &gt; 0 &amp;&amp; count($zadaniaDoWyswietlenia) === 0) {
            echo "&lt;p&gt;Brak zadań w tym widoku.&lt;/p&gt;";
        }

        foreach ($zadaniaDoWyswietlenia as $zadanie) {</code></pre>



<p class="wp-block-paragraph">Reszta pętli bez zmian.</p>



<p class="wp-block-paragraph">Zwróć uwagę: <code class="prettyprint lang-sql" style="font-size: 0.85em;">$filtr</code> trafia na stronę (<code class="prettyprint lang-sql" style="font-size: 0.85em;">Widok: ...</code>), a mimo to jest bezpieczny — po sprawdzeniu może mieć <strong>tylko jedną z trzech znanych nam wartości</strong>, niezależnie od tego, co ktoś wpisze w adres. Takie „zawężanie do listy dozwolonych wartości” to jedna z najskuteczniejszych technik obrony przed złymi danymi.</p>



<p class="wp-block-paragraph">Ostatnia drobna zmiana: <strong><code class="prettyprint lang-sql" style="font-size: 0.85em;">diagnostyka.php</code></strong> — dopisz <code class="prettyprint lang-sql" style="font-size: 0.85em;">'dodaj-zadanie.php'</code> do tablicy <code class="prettyprint lang-sql" style="font-size: 0.85em;">$wymaganePliki</code>. Strona powinna pokazać <strong>12 × <code class="prettyprint lang-sql" style="font-size: 0.85em;">[OK]</code></strong>. (Lista plików <code class="prettyprint lang-sql" style="font-size: 0.85em;">.php</code> w <code class="prettyprint lang-sql" style="font-size: 0.85em;">config/</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/</code> się nie zmienia — nowy plik leży w katalogu głównym.)</p>
</details>



<h1 class="wp-block-heading">7. Testy</h1>



<h2 class="wp-block-heading">Test 1 — <code class="prettyprint lang-sql" style="font-size: 0.85em;">walidujNoweZadanie()</code> na granicach</h2>



<p class="wp-block-paragraph"><strong>BRUDNOPIS.</strong> <strong>Przewidź</strong> wynik każdego przypadku, zanim uruchomisz kod:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php

require_once __DIR__ . '/includes/funkcje_zadan.php';

$przypadki = &#91;
    &#91;'Nauka PHP', '', '2026-10-02', '20'],
    &#91;'', '', '2026-10-02', '20'],
    &#91;str_repeat('a', 101), '', '2026-10-02', '20'],
    &#91;str_repeat('ą', 100), '', '2026-10-02', '20'],
    &#91;'Nauka PHP', str_repeat('x', 301), '2026-10-02', '20'],
    &#91;'Nauka PHP', '', '', '20'],
    &#91;'Nauka PHP', '', '02.10.2026', '20'],
    &#91;'Nauka PHP', '', '2026-02-29', '20'],
    &#91;'Nauka PHP', '', '2028-02-29', '20'],
    &#91;'Nauka PHP', '', '2026-13-01', '20'],
    &#91;'Nauka PHP', '', '2026-10-02', '0'],
    &#91;'Nauka PHP', '', '2026-10-02', '1'],
    &#91;'Nauka PHP', '', '2026-10-02', '100'],
    &#91;'Nauka PHP', '', '2026-10-02', '101'],
    &#91;'Nauka PHP', '', '2026-10-02', 'dużo'],
    &#91;'Nauka PHP', '', '2026-10-02', '12abc'],
    &#91;'Nauka PHP', '', '2026-10-02', '-5'],
    &#91;'Nauka PHP', '', '2026-10-02', '7.5'],
    &#91;'', '', '', ''],
];

foreach ($przypadki as $numer =&gt; $dane) {
    $bledy = walidujNoweZadanie($dane&#91;0], $dane&#91;1], $dane&#91;2], $dane&#91;3]);
    echo ($numer + 1) . ". " . (count($bledy) === 0 ? "OK" : implode(' | ', $bledy)) . "&lt;br&gt;";
}</code></pre>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Nr</th><th>Dane (co jest nietypowe)</th><th>Oczekiwany wynik</th></tr></thead><tbody><tr><td>1</td><td>wszystko poprawne</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">OK</code></td></tr><tr><td>2</td><td>pusty tytuł</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Tytuł jest wymagany.</code></td></tr><tr><td>3</td><td>tytuł: 101 znaków</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Tytuł może mieć najwyżej 100 znaków.</code></td></tr><tr><td>4</td><td>tytuł: 100 × <code class="prettyprint lang-sql" style="font-size: 0.85em;">ą</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">OK</code> (100 znaków, mimo 200 bajtów — dlatego <code class="prettyprint lang-sql" style="font-size: 0.85em;">mb_strlen()</code>)</td></tr><tr><td>5</td><td>opis: 301 znaków</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Opis może mieć najwyżej 300 znaków.</code></td></tr><tr><td>6</td><td>pusty termin</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Termin musi być datą w formacie RRRR-MM-DD.</code></td></tr><tr><td>7</td><td>termin <code class="prettyprint lang-sql" style="font-size: 0.85em;">02.10.2026</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Termin musi być datą w formacie RRRR-MM-DD.</code></td></tr><tr><td>8</td><td>termin <code class="prettyprint lang-sql" style="font-size: 0.85em;">2026-02-29</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Taki dzień nie istnieje w kalendarzu.</code> (2026 nie jest przestępny)</td></tr><tr><td>9</td><td>termin <code class="prettyprint lang-sql" style="font-size: 0.85em;">2028-02-29</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">OK</code> (2028 jest przestępny)</td></tr><tr><td>10</td><td>termin <code class="prettyprint lang-sql" style="font-size: 0.85em;">2026-13-01</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Taki dzień nie istnieje w kalendarzu.</code></td></tr><tr><td>11</td><td>XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">0</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">XP musi być z zakresu 1–100.</code></td></tr><tr><td>12</td><td>XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">1</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">OK</code></td></tr><tr><td>13</td><td>XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">100</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">OK</code></td></tr><tr><td>14</td><td>XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">101</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">XP musi być z zakresu 1–100.</code></td></tr><tr><td>15</td><td>XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">dużo</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">XP musi być liczbą całkowitą.</code></td></tr><tr><td>16</td><td>XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">12abc</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">XP musi być liczbą całkowitą.</code></td></tr><tr><td>17</td><td>XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">-5</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">XP musi być liczbą całkowitą.</code> (minus to nie cyfra)</td></tr><tr><td>18</td><td>XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">7.5</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">XP musi być liczbą całkowitą.</code></td></tr><tr><td>19</td><td>wszystko puste</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Tytuł jest wymagany. \| Termin musi być datą w formacie RRRR-MM-DD. \| XP musi być liczbą całkowitą.</code></td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><code class="prettyprint lang-sql" style="font-size: 0.85em;">implode(' | ', $bledy)</code> skleja elementy tablicy w jeden tekst, wstawiając między nie <code class="prettyprint lang-sql" style="font-size: 0.85em;">' | '</code> — tylko na potrzeby testu.</p>



<h2 class="wp-block-heading">Test 2 — <code class="prettyprint lang-sql" style="font-size: 0.85em;">nastepneIdZadania()</code></h2>



<p class="wp-block-paragraph"><strong>BRUDNOPIS.</strong></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Lista <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code></th><th>Oczekiwany wynik</th></tr></thead><tbody><tr><td>(pusta lista)</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">1</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">1, 2, 3</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">4</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">3, 10, 7</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">11</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">1, 2, 4, 5</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">6</code> (a nie <code class="prettyprint lang-sql" style="font-size: 0.85em;">5</code>!)</td></tr><tr><td>prawdziwe zadania: <code class="prettyprint lang-sql" style="font-size: 0.85em;">nastepneIdZadania(wczytajZadania(PLIK_ZADAN))</code> na oryginalnym pliku</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">6</code></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">(Do ostatniego przypadku dołącz w brudnopisie także <code class="prettyprint lang-sql" style="font-size: 0.85em;">config/config.php</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_plikow.php</code>.)</p>



<h2 class="wp-block-heading">Test 3 — dodawanie przez stronę</h2>



<p class="wp-block-paragraph"><strong>PLIK PROJEKTU <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code>.</strong> Na czas tego testu dopisz do znacznika <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;form</code> atrybut <code class="prettyprint lang-sql" style="font-size: 0.85em;">novalidate</code> (przeglądarka nie będzie blokować wysłania), a po teście <strong>usuń go</strong>. Przed testem przywróć oryginalny <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code> z kopii.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Co wpisujesz</th><th>Oczekiwany efekt</th></tr></thead><tbody><tr><td>nic (pusty formularz)</td><td>trzy komunikaty w czerwonej ramce, plik bez zmian</td></tr><tr><td>tytuł <code class="prettyprint lang-sql" style="font-size: 0.85em;">  Nauka   formularzy </code>, termin pusty, XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">30</code></td><td>jeden komunikat (termin); w polu tytułu jest już <code class="prettyprint lang-sql" style="font-size: 0.85em;">Nauka formularzy</code> (oczyszczony!), XP nadal <code class="prettyprint lang-sql" style="font-size: 0.85em;">30</code></td></tr><tr><td>tytuł <code class="prettyprint lang-sql" style="font-size: 0.85em;">Nauka formularzy</code>, poprawny termin, XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">30</code></td><td>przejście na <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?dodano=1</code>, zielona ramka, zadanie na liście; w pliku nowy wiersz z <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> <strong>6</strong></td></tr><tr><td>tytuł <code class="prettyprint lang-sql" style="font-size: 0.85em;">Zadanie; z "cudzysłowem"</code>, opis w <strong>dwóch liniach</strong>, poprawny termin, XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">5</code></td><td>zadanie dodane z <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> <strong>7</strong>; w pliku tytuł jest w cudzysłowie, a cudzysłowy w środku są podwojone (<code class="prettyprint lang-sql" style="font-size: 0.85em;">""</code>) — tak <code class="prettyprint lang-sql" style="font-size: 0.85em;">fputcsv()</code> chroni średniki i cudzysłowy; opis w jednej linii; strona główna nadal działa</td></tr><tr><td>F5 na <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?dodano=1</code></td><td>brak pytania o ponowne wysłanie, w pliku nadal 7 zadań</td></tr><tr><td>to samo zadanie jeszcze raz przez formularz</td><td>dodaje się jako <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> <strong>8</strong> — aplikacja nie sprawdza duplikatów (tego nie wymagaliśmy)</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">Test 4 — dodawanie, gdy plik ma problem</h2>



<p class="wp-block-paragraph"><strong>PLIK DANYCH PROJEKTU.</strong> Po każdym wierszu przywróć oryginalny <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code>.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Zmiana</th><th>Oczekiwany efekt po wysłaniu poprawnego formularza</th></tr></thead><tbody><tr><td>plik nazywa się <code class="prettyprint lang-sql" style="font-size: 0.85em;">zadania_x.csv</code></td><td>czerwona ramka: <code class="prettyprint lang-sql" style="font-size: 0.85em;">Nie udało się dodać zadania: Plik z zadaniami nie istnieje.</code>; formularz zachowuje wpisane dane; <strong>w <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/</code> nie powstał nowy plik</strong></td></tr><tr><td>w wierszu zadania <code class="prettyprint lang-sql" style="font-size: 0.85em;">3</code> pierwszy średnik zamieniony na spację</td><td>ramka: <code class="prettyprint lang-sql" style="font-size: 0.85em;">... Linia 4: oczekiwano 6 pól, a jest 5.</code>; <strong>plik jest dokładnie taki jak przed wysłaniem</strong> (nic nie skasowano, nie dopisano)</td></tr><tr><td>plik oznaczony jako tylko do odczytu (Windows: prawy przycisk → Właściwości → „Tylko do odczytu”)</td><td>ostrzeżenie PHP <code class="prettyprint lang-sql" style="font-size: 0.85em;">Warning: fopen(...): Failed to open stream: Permission denied</code> oraz ramka: <code class="prettyprint lang-sql" style="font-size: 0.85em;">Nie udało się dodać zadania: Nie udało się zapisać pliku z zadaniami.</code> (po teście odznacz „Tylko do odczytu”)</td></tr><tr><td>w pliku tylko nagłówek</td><td>zadanie dodane z <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> <strong>1</strong></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">W drugim wierszu tabeli widać, po co odczyt i zapis są w <strong>jednym</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code>.</p>



<h2 class="wp-block-heading">Test 5 — widoki listy (GET)</h2>



<p class="wp-block-paragraph"><strong>PLIK PROJEKTU <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code>.</strong> Na oryginalnym pliku z 5 zadaniami (2 wykonane, 3 aktywne):</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Adres</th><th>Oczekiwany efekt</th></tr></thead><tbody><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Widok: wszystkie</code>, 5 zadań</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?filtr=aktywne</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Widok: aktywne</code>, 3 zadania, wszystkie z <code class="prettyprint lang-sql" style="font-size: 0.85em;">[ ]</code>, posortowane wg terminu</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?filtr=wykonane</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Widok: wykonane</code>, 2 zadania z <code class="prettyprint lang-sql" style="font-size: 0.85em;">[X]</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?filtr=abc</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Widok: wszystkie</code>, 5 zadań, <strong>brak błędów</strong></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?filtr=AKTYWNE</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Widok: wszystkie</code> (wielkość liter ma znaczenie)</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?filtr=aktywne&amp;dodano=1</code></td><td>widok aktywnych i zielona ramka</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?filtr=wykonane</code>, gdy w pliku wszystkie zadania mają <code class="prettyprint lang-sql" style="font-size: 0.85em;">wykonane</code> = <code class="prettyprint lang-sql" style="font-size: 0.85em;">0</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">Brak zadań w tym widoku.</code></td></tr></tbody></table></figure>



<p class="wp-block-paragraph">W każdym widoku liczniki (<code class="prettyprint lang-sql" style="font-size: 0.85em;">Wszystkie: 5 | Wykonane: 2 | Aktywne: 3</code>) i „najpilniejsze zadanie” wyglądają <strong>tak samo</strong>.</p>



<h2 class="wp-block-heading">Test 6 — regresja</h2>



<p class="wp-block-paragraph">Uruchom obok siebie <code class="prettyprint lang-sql" style="font-size: 0.85em;">taskquest_lekcja12/index.php</code> i nowy <code class="prettyprint lang-sql" style="font-size: 0.85em;">taskquest/index.php</code> <strong>tego samego dnia</strong> (na oryginalnym pliku zadań). Różnice mogą być tylko cztery: link „+ Dodaj zadanie”, linia z linkami widoków, linia <code class="prettyprint lang-sql" style="font-size: 0.85em;">Widok: wszystkie</code> i wersja w stopce (<code class="prettyprint lang-sql" style="font-size: 0.85em;">1.2</code> → <code class="prettyprint lang-sql" style="font-size: 0.85em;">1.3</code>). Liczniki, pasek, komunikaty terminów i lista — identyczne. <code class="prettyprint lang-sql" style="font-size: 0.85em;">diagnostyka.php</code> pokazuje <strong>12 × <code class="prettyprint lang-sql" style="font-size: 0.85em;">[OK]</code></strong>. Test awarii z Lekcji 12 (zmiana nazwy pliku z zadaniami) nadal daje czerwoną ramkę na stronie głównej — a link „+ Dodaj zadanie” jest nad nią.</p>



<h2 class="wp-block-heading">Sprzątanie</h2>



<p class="wp-block-paragraph">Gdy testy przeszły:</p>



<ol class="wp-block-list">
<li>usuń <code class="prettyprint lang-sql" style="font-size: 0.85em;">brudnopis.php</code>,</li>



<li>usuń atrybut <code class="prettyprint lang-sql" style="font-size: 0.85em;">novalidate</code> z formularza w <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code>, jeśli został,</li>



<li>przywróć <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code> z kopii (5 oryginalnych zadań) — albo zostaw zadania, które naprawdę chcesz mieć w TaskQuest,</li>



<li>odśwież <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">diagnostyka.php</code>.</li>
</ol>



<h1 class="wp-block-heading">8. Obowiązkowe eksperymenty</h1>



<p class="wp-block-paragraph"><strong>Gdzie:</strong> wszystkie eksperymenty wykonuj w plikach projektu wskazanych przy każdym z nich. Po każdym <strong>przywróć kod</strong> do stanu z sekcji 5–6 i oryginalny <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code>.</p>



<h3 class="wp-block-heading">Eksperyment 1: przeglądarka to nie strażnik</h3>



<p class="wp-block-paragraph"><strong>Plik projektu <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code>.</strong> Bez <code class="prettyprint lang-sql" style="font-size: 0.85em;">novalidate</code> spróbuj wysłać pusty formularz — przeglądarka nie pozwala. Teraz otwórz F12 → Elementy, kliknij pole XP i usuń z niego atrybuty <code class="prettyprint lang-sql" style="font-size: 0.85em;">min</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">max</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code> (dwuklik na atrybucie → usuń tekst). Wpisz <code class="prettyprint lang-sql" style="font-size: 0.85em;">999</code> i wyślij. Kto zatrzymał złe dane? Co by się stało, gdyby w PHP nie było <code class="prettyprint lang-sql" style="font-size: 0.85em;">walidujNoweZadanie()</code>? Sprawdź też, co zrobi strona, gdy w F12 zmienisz <code class="prettyprint lang-sql" style="font-size: 0.85em;">type="number"</code> na <code class="prettyprint lang-sql" style="font-size: 0.85em;">type="text"</code> i wpiszesz <code class="prettyprint lang-sql" style="font-size: 0.85em;">dużo</code>.</p>



<h3 class="wp-block-heading">Eksperyment 2: formularz wysłany metodą GET</h3>



<p class="wp-block-paragraph"><strong>Plik projektu <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code>.</strong> Zmień <code class="prettyprint lang-sql" style="font-size: 0.85em;">method="post"</code> na <code class="prettyprint lang-sql" style="font-size: 0.85em;">method="get"</code> i dodaj poprawne zadanie. Co widać w pasku adresu? Czy zadanie zostało dodane? Dlaczego nie — który warunek w kodzie nie jest spełniony? Następnie zastanów się: gdyby kod sprawdzał <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code> zamiast <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST</code>, to czym groziłoby wysłanie komuś linku z takim adresem albo zapisanie go w zakładkach?</p>



<h3 class="wp-block-heading">Eksperyment 3: <code class="prettyprint lang-sql" style="font-size: 0.85em;">match</code> bez listy dozwolonych wartości</h3>



<p class="wp-block-paragraph"><strong>Plik projektu <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code>.</strong> Zakomentuj <code class="prettyprint lang-sql" style="font-size: 0.85em;">if</code>, który zamienia nieznany filtr na <code class="prettyprint lang-sql" style="font-size: 0.85em;">wszystkie</code>, i otwórz <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?filtr=abc</code>. Przeczytaj pierwszą linię komunikatu: <code class="prettyprint lang-sql" style="font-size: 0.85em;">Fatal error: Uncaught UnhandledMatchError ...</code>. Czy to <code class="prettyprint lang-sql" style="font-size: 0.85em;">Exception</code>, czy <code class="prettyprint lang-sql" style="font-size: 0.85em;">Error</code> (Lekcja 12, sekcja 3.8)? Dlaczego nie ratuje nas żaden <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code>? Dopisz do <code class="prettyprint lang-sql" style="font-size: 0.85em;">match</code> przypadek <code class="prettyprint lang-sql" style="font-size: 0.85em;">default =&gt; $zadaniaWgTerminu</code> i sprawdź jeszcze raz — działa, ale co teraz wypisze linia <code class="prettyprint lang-sql" style="font-size: 0.85em;">Widok: ...</code> dla adresu <code class="prettyprint lang-sql" style="font-size: 0.85em;">?filtr=abc</code>? Które rozwiązanie jest lepsze i dlaczego?</p>



<h3 class="wp-block-heading">Eksperyment 4: <code class="prettyprint lang-sql" style="font-size: 0.85em;">(int)</code> zamiast regexa</h3>



<p class="wp-block-paragraph"><strong>Plik projektu <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_zadan.php</code>.</strong> W <code class="prettyprint lang-sql" style="font-size: 0.85em;">walidujNoweZadanie()</code> zamień sprawdzenie XP na samo:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">    if ((int) $xpZadania &lt; 1 || (int) $xpZadania &gt; 100) {
        $bledy&#91;] = "XP musi być z zakresu 1–100.";
    }</code></pre>



<p class="wp-block-paragraph">Uruchom Test 1. Które przypadki zmieniły wynik? Co zostanie zapisane w pliku, jeśli ktoś wyśle <code class="prettyprint lang-sql" style="font-size: 0.85em;">12abc</code>? Jaki komunikat dostanie ktoś, kto wpisał <code class="prettyprint lang-sql" style="font-size: 0.85em;">dużo</code> — i czy mówi mu prawdę?</p>



<h3 class="wp-block-heading">Eksperyment 5: tekst użytkownika trafia na stronę (zapowiedź Lekcji 22)</h3>



<p class="wp-block-paragraph"><strong>Plik projektu <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code> (z <code class="prettyprint lang-sql" style="font-size: 0.85em;">novalidate</code>).</strong></p>



<p class="wp-block-paragraph">a) Wpisz tytuł <code class="prettyprint lang-sql" style="font-size: 0.85em;">Zadanie "PHP"</code>, termin zostaw pusty i wyślij. Formularz wraca z błędem — co jest teraz w polu tytułu? Otwórz F12 → Elementy i spójrz na atrybut <code class="prettyprint lang-sql" style="font-size: 0.85em;">value</code>. Dlaczego tekst urwał się na cudzysłowie?</p>



<p class="wp-block-paragraph">b) Bez <code class="prettyprint lang-sql" style="font-size: 0.85em;">novalidate</code>: dodaj poprawne zadanie o tytule <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;b&gt;Bardzo&lt;/b&gt; ważne</code>. Otwórz stronę główną. Jak wygląda tytuł na liście? Przeglądarka potraktowała tekst od użytkownika <strong>jak kod HTML</strong>. Pogrubienie jest niewinne — ale w to samo miejsce ktoś mógłby wpisać znaczniki, które zmieniają wygląd całej strony albo uruchamiają skrypt. To się nazywa <strong>wstrzykiwanie HTML</strong> (a w wersji ze skryptem — XSS) i naprawimy to funkcją <code class="prettyprint lang-sql" style="font-size: 0.85em;">htmlspecialchars()</code> w Lekcji 22.</p>



<p class="wp-block-paragraph">Po eksperymencie <strong>usuń to zadanie z pliku</strong> (przywróć kopię).</p>



<h3 class="wp-block-heading">Eksperyment 6 (dodatkowy): po co <code class="prettyprint lang-sql" style="font-size: 0.85em;">exit</code>?</h3>



<p class="wp-block-paragraph"><strong>Plik projektu <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code>.</strong> Usuń <code class="prettyprint lang-sql" style="font-size: 0.85em;">exit;</code> spod <code class="prettyprint lang-sql" style="font-size: 0.85em;">header(...)</code>, a pod <code class="prettyprint lang-sql" style="font-size: 0.85em;">header(...)</code> dopisz tymczasowo <code class="prettyprint lang-sql" style="font-size: 0.85em;">file_put_contents(__DIR__ . '/po_przekierowaniu.txt', 'wykonano');</code>. Dodaj zadanie. Przekierowanie działa — a czy plik <code class="prettyprint lang-sql" style="font-size: 0.85em;">po_przekierowaniu.txt</code> powstał? Co to mówi o tym, co robi <code class="prettyprint lang-sql" style="font-size: 0.85em;">header()</code>, a czego nie robi? Przywróć <code class="prettyprint lang-sql" style="font-size: 0.85em;">exit;</code> i usuń dopisaną linię oraz plik.</p>



<h1 class="wp-block-heading">9. ROZWIĄZANIE</h1>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>ROZWIĄZANIE</strong></summary>
<p class="wp-block-paragraph">Pełny kod TaskQuest po Lekcji 13. Nowe lub zmienione: <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code> (nowy), <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_zadan.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/header.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/footer.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">diagnostyka.php</code>. <strong>Bez zmian względem Lekcji 12:</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">config/config.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_grywalizacji.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_tekstowe.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_terminow.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_diagnostyczne.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_plikow.php</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code> (poza zadaniami dodanymi przez formularz).</p>



<h2 class="wp-block-heading"><code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/funkcje_zadan.php</code></h2>



<p class="wp-block-paragraph">Cztery dotychczasowe funkcje (<code class="prettyprint lang-sql" style="font-size: 0.85em;">pobierzWykonaneZadania()</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">pobierzAktywneZadania()</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">sumujXp()</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">sortujPoTerminie()</code>) zostają <strong>bez zmian</strong>. Na końcu pliku dochodzą:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">function nastepneIdZadania(array $zadania): int
{
    $najwiekszeId = 0;

    foreach ($zadania as $zadanie) {
        if ($zadanie&#91;'id'] &gt; $najwiekszeId) {
            $najwiekszeId = $zadanie&#91;'id'];
        }
    }

    return $najwiekszeId + 1;
}


function walidujNoweZadanie(string $tytulZadania, string $opisZadania, string $terminZadania, string $xpZadania): array
{
    $bledy = &#91;];

    if ($tytulZadania === '') {
        $bledy&#91;] = "Tytuł jest wymagany.";
    } elseif (mb_strlen($tytulZadania) &gt; 100) {
        $bledy&#91;] = "Tytuł może mieć najwyżej 100 znaków.";
    }

    if (mb_strlen($opisZadania) &gt; 300) {
        $bledy&#91;] = "Opis może mieć najwyżej 300 znaków.";
    }

    if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $terminZadania)) {
        $bledy&#91;] = "Termin musi być datą w formacie RRRR-MM-DD.";
    } else {
        $czesciDaty = explode('-', $terminZadania);

        if (!checkdate((int) $czesciDaty&#91;1], (int) $czesciDaty&#91;2], (int) $czesciDaty&#91;0])) {
            $bledy&#91;] = "Taki dzień nie istnieje w kalendarzu.";
        }
    }

    if (!preg_match('/^\d+$/', $xpZadania)) {
        $bledy&#91;] = "XP musi być liczbą całkowitą.";
    } elseif ((int) $xpZadania &lt; 1 || (int) $xpZadania &gt; 100) {
        $bledy&#91;] = "XP musi być z zakresu 1–100.";
    }

    return $bledy;
}</code></pre>



<h2 class="wp-block-heading"><code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code></h2>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php

// --- pliki aplikacji ---
require_once __DIR__ . '/config/config.php';
require_once __DIR__ . '/includes/funkcje_tekstowe.php';
require_once __DIR__ . '/includes/funkcje_zadan.php';
require_once __DIR__ . '/includes/funkcje_plikow.php';


// --- dane nowego zadania (na start puste — formularz jeszcze nie został wysłany) ---
$tytulZadania = '';
$opisZadania = '';
$terminZadania = '';
$xpZadania = '';

$bledy = &#91;];
$bladZapisu = null;


// --- obsługa wysłanego formularza (POST) ---
if ($_SERVER&#91;'REQUEST_METHOD'] === 'POST') {
    $tytulZadania = wyczyscTytul($_POST&#91;'tytul'] ?? '');
    $opisZadania = trim(preg_replace('/\s+/', ' ', $_POST&#91;'opis'] ?? ''));
    $terminZadania = trim($_POST&#91;'termin'] ?? '');
    $xpZadania = trim($_POST&#91;'xp'] ?? '');

    $bledy = walidujNoweZadanie($tytulZadania, $opisZadania, $terminZadania, $xpZadania);

    if (count($bledy) === 0) {
        try {
            $zadania = wczytajZadania(PLIK_ZADAN);

            $zadania&#91;] = &#91;
                'id' =&gt; nastepneIdZadania($zadania),
                'tytul' =&gt; $tytulZadania,
                'opis' =&gt; $opisZadania,
                'termin' =&gt; $terminZadania,
                'wykonane' =&gt; false,
                'xp' =&gt; (int) $xpZadania
            ];

            zapiszZadania(PLIK_ZADAN, $zadania);

            header('Location: index.php?dodano=1');
            exit;
        } catch (Exception $wyjatek) {
            $bladZapisu = $wyjatek-&gt;getMessage();
        }
    }
}


// --- prezentacja ---
$nazwaStrony = 'Dodaj zadanie — TaskQuest';
require_once __DIR__ . '/includes/header.php';
?&gt;
        &lt;h2&gt;Nowe zadanie&lt;/h2&gt;
        &lt;p&gt;&lt;a href="index.php"&gt;← Wróć do listy zadań&lt;/a&gt;&lt;/p&gt;

        &lt;?php if (count($bledy) &gt; 0) { ?&gt;
        &lt;div class="blad"&gt;
            &lt;p&gt;Popraw formularz:&lt;/p&gt;
            &lt;ul&gt;
                &lt;?php foreach ($bledy as $blad) { ?&gt;
                &lt;li&gt;&lt;?= $blad; ?&gt;&lt;/li&gt;
                &lt;?php } ?&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
        &lt;?php } ?&gt;

        &lt;?php if ($bladZapisu !== null) { ?&gt;
        &lt;p class="blad"&gt;Nie udało się dodać zadania: &lt;?= $bladZapisu; ?&gt;&lt;/p&gt;
        &lt;?php } ?&gt;

        &lt;form method="post" action="dodaj-zadanie.php"&gt;
            &lt;label for="tytul"&gt;Tytuł *&lt;/label&gt;
            &lt;input type="text" id="tytul" name="tytul" maxlength="100" required value="&lt;?= $tytulZadania; ?&gt;"&gt;

            &lt;label for="opis"&gt;Opis&lt;/label&gt;
            &lt;textarea id="opis" name="opis" rows="3" maxlength="300"&gt;&lt;?= $opisZadania; ?&gt;&lt;/textarea&gt;

            &lt;label for="termin"&gt;Termin *&lt;/label&gt;
            &lt;input type="date" id="termin" name="termin" required value="&lt;?= $terminZadania; ?&gt;"&gt;

            &lt;label for="xp"&gt;Nagroda XP (1–100) *&lt;/label&gt;
            &lt;input type="number" id="xp" name="xp" min="1" max="100" required value="&lt;?= $xpZadania; ?&gt;"&gt;

            &lt;button type="submit"&gt;Dodaj zadanie&lt;/button&gt;
        &lt;/form&gt;
&lt;?php
require_once __DIR__ . '/includes/footer.php';</code></pre>



<h2 class="wp-block-heading"><code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code></h2>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php

// --- pliki aplikacji ---
require_once __DIR__ . '/config/config.php';
require_once __DIR__ . '/includes/funkcje_grywalizacji.php';
require_once __DIR__ . '/includes/funkcje_tekstowe.php';
require_once __DIR__ . '/includes/funkcje_terminow.php';
require_once __DIR__ . '/includes/funkcje_zadan.php';
require_once __DIR__ . '/includes/funkcje_plikow.php';


// --- dane użytkownika ---
$imieUzytkownika = "Kama";
$login = "Kama_2026";


// --- dane zadań (z pliku CSV) ---
$zadania = &#91;];
$bladZadan = null;

try {
    $zadania = wczytajZadania(PLIK_ZADAN);
} catch (Exception $wyjatek) {
    $bladZadan = $wyjatek-&gt;getMessage();
}


// --- dane grywalizacji (symulowane) ---
$xp = 99;
$streak = 5;
$dataOstatniejAktywnosci = "2026-09-14";


// --- wywołania funkcji: grywalizacja ---
$poziom = obliczPoziom($xp);
$tytul = pobierzTytul($poziom);
$xpWAktualnymPoziomie = obliczXpWAktualnymPoziomie($xp, $poziom);
$procentPostepu = obliczProcentPostepu($xpWAktualnymPoziomie);
$brakujeXp = ileBrakujeXp($xp, $poziom);

//pasek postępu
$liczbaAktywnychSegmentow = (int) ($procentPostepu / 10);
$progNastepnegoPoziomu = $poziom * XP_NA_POZIOM;


// --- wywołania funkcji: lista zadań ---
$wykonaneZadania = pobierzWykonaneZadania($zadania);
$aktywneZadania = pobierzAktywneZadania($zadania);

$liczbaWszystkich = count($zadania);
$liczbaWykonanych = count($wykonaneZadania);
$liczbaAktywnych = count($aktywneZadania);

$xpDoZdobycia = sumujXp($aktywneZadania);

$zadaniaWgTerminu = sortujPoTerminie($zadania);
$aktywneWgTerminu = sortujPoTerminie($aktywneZadania);
$wykonaneWgTerminu = sortujPoTerminie($wykonaneZadania);

// uproszczenie (symulacja): aktywność = wykonane jakiekolwiek zadanie z listy
$zadanieWykonane = $liczbaWykonanych &gt; 0;

$streak = aktualizujStreak($streak, $dataOstatniejAktywnosci, $zadanieWykonane);


// --- parametry z adresu strony (GET) ---
$filtr = $_GET&#91;'filtr'] ?? 'wszystkie';

if ($filtr !== 'aktywne' &amp;&amp; $filtr !== 'wykonane') {
    $filtr = 'wszystkie';
}

$zadaniaDoWyswietlenia = match ($filtr) {
    'wszystkie' =&gt; $zadaniaWgTerminu,
    'aktywne' =&gt; $aktywneWgTerminu,
    'wykonane' =&gt; $wykonaneWgTerminu
};

$zadanieDodane = ($_GET&#91;'dodano'] ?? '') === '1';


// --- prezentacja ---
$nazwaStrony = 'TaskQuest';
require_once __DIR__ . '/includes/header.php';
?&gt;
        &lt;p&gt;Zdobywaj punkty za wykonane zadania!&lt;/p&gt;

        &lt;?php
        if (walidujLogin($login)) {
            echo "&lt;p&gt;Login poprawny: $login&lt;/p&gt;";
        } else {
            echo "&lt;p&gt;Login jest niepoprawny.&lt;/p&gt;";
        }
        ?&gt;

        &lt;h2&gt;Witaj, &lt;?= $imieUzytkownika; ?&gt;!&lt;/h2&gt;

        &lt;p&gt;Poziom: &lt;strong&gt;&lt;?= $poziom; ?&gt;&lt;/strong&gt;&lt;/p&gt;
        &lt;p&gt;Tytuł: &lt;strong&gt;&lt;?= $tytul; ?&gt;&lt;/strong&gt;&lt;/p&gt;
        &lt;p&gt;Łączne XP: &lt;strong&gt;&lt;?= $xp; ?&gt;&lt;/strong&gt;&lt;/p&gt;

        &lt;h3&gt;
            &lt;?= "Masz $xpWAktualnymPoziomie XP z " . XP_NA_POZIOM . " XP potrzebnych do poziomu " . ($poziom + 1) . " (" . round($procentPostepu, 2) . "%)"; ?&gt;
        &lt;/h3&gt;

        &lt;div class="pasek-xp"&gt;
            &lt;?php
            for ($segment = 1; $segment &lt;= 10; $segment++) {
                if ($segment &lt;= $liczbaAktywnychSegmentow) {
                    echo "█";
                } else {
                    echo "░";
                }
            }
            ?&gt;
        &lt;/div&gt;

        &lt;p&gt;Kolejny próg: &lt;?= $progNastepnegoPoziomu; ?&gt; XP&lt;/p&gt;
        &lt;p&gt;Brakuje XP: &lt;strong&gt;&lt;?= $brakujeXp; ?&gt;&lt;/strong&gt;&lt;/p&gt;
        &lt;p&gt;Streak: &lt;strong&gt;&lt;?= $streak ?&gt;&lt;/strong&gt;&lt;/p&gt;

        &lt;h2&gt;Twoje zadania&lt;/h2&gt;

        &lt;p&gt;&lt;a href="dodaj-zadanie.php"&gt;+ Dodaj zadanie&lt;/a&gt;&lt;/p&gt;

        &lt;?php if ($zadanieDodane) { ?&gt;
        &lt;p class="sukces"&gt;Zadanie zostało dodane.&lt;/p&gt;
        &lt;?php } ?&gt;

        &lt;?php if ($bladZadan !== null) { ?&gt;
        &lt;p class="blad"&gt;Nie udało się wczytać zadań: &lt;?= $bladZadan; ?&gt;&lt;/p&gt;
        &lt;?php } else { ?&gt;

        &lt;p&gt;
            Wszystkie: &lt;strong&gt;&lt;?= $liczbaWszystkich; ?&gt;&lt;/strong&gt; |
            Wykonane: &lt;strong&gt;&lt;?= $liczbaWykonanych; ?&gt;&lt;/strong&gt; |
            Aktywne: &lt;strong&gt;&lt;?= $liczbaAktywnych; ?&gt;&lt;/strong&gt;
        &lt;/p&gt;
        &lt;p&gt;XP do zdobycia z aktywnych zadań: &lt;strong&gt;&lt;?= $xpDoZdobycia; ?&gt;&lt;/strong&gt;&lt;/p&gt;

        &lt;?php
        if ($liczbaWszystkich === 0) {
            echo "&lt;p&gt;Brak zadań.&lt;/p&gt;";
        } elseif ($liczbaAktywnych === 0) {
            echo "&lt;p&gt;Wszystkie zadania wykonane — świetna robota!&lt;/p&gt;";
        } else {
            if ($xpDoZdobycia &gt;= $brakujeXp) {
                echo "&lt;p class=\"awans\"&gt;Wykonując aktywne zadania, awansujesz na poziom " . ($poziom + 1) . "!&lt;/p&gt;";
            } else {
                echo "&lt;p&gt;Aktywne zadania nie wystarczą jeszcze na awans — brakuje " . ($brakujeXp - $xpDoZdobycia) . " XP.&lt;/p&gt;";
            }

            $najpilniejsze = $aktywneWgTerminu&#91;0];
            echo "&lt;p&gt;Najpilniejsze zadanie: &lt;strong&gt;" . wyczyscTytul($najpilniejsze&#91;'tytul']) . "&lt;/strong&gt; — "
                . komunikatTerminu($najpilniejsze&#91;'termin']) . "&lt;/p&gt;";
        }

        echo "&lt;h3&gt;Lista zadań (wg terminu)&lt;/h3&gt;";
        echo "&lt;p&gt;Pokaż: &lt;a href=\"index.php?filtr=wszystkie\"&gt;wszystkie&lt;/a&gt; | "
            . "&lt;a href=\"index.php?filtr=aktywne\"&gt;aktywne&lt;/a&gt; | "
            . "&lt;a href=\"index.php?filtr=wykonane\"&gt;wykonane&lt;/a&gt;&lt;/p&gt;";
        echo "&lt;p&gt;Widok: &lt;strong&gt;$filtr&lt;/strong&gt;&lt;/p&gt;";

        if ($liczbaWszystkich &gt; 0 &amp;&amp; count($zadaniaDoWyswietlenia) === 0) {
            echo "&lt;p&gt;Brak zadań w tym widoku.&lt;/p&gt;";
        }

        foreach ($zadaniaDoWyswietlenia as $zadanie) {
            $tytulCzysty = wyczyscTytul($zadanie&#91;'tytul']);
            $znacznik = $zadanie&#91;'wykonane'] ? "&#91;X]" : "&#91; ]";
            $klasa = $zadanie&#91;'wykonane'] ? "zadanie wykonane" : "zadanie";

            echo "&lt;div class=\"$klasa\"&gt;";
            echo "&lt;p&gt;&lt;strong&gt;$znacznik $tytulCzysty&lt;/strong&gt; — {$zadanie&#91;'xp']} XP&lt;/p&gt;";
            echo "&lt;p&gt;" . skrocOpis($zadanie&#91;'opis'], 50) . "&lt;/p&gt;";
            echo "&lt;p&gt;Termin: {$zadanie&#91;'termin']}";

            if (!$zadanie&#91;'wykonane']) {
                echo " — " . komunikatTerminu($zadanie&#91;'termin']);
            }

            echo "&lt;/p&gt;";

            if (stripos($tytulCzysty, 'PHP') !== false) {
                echo "&lt;p&gt;To zadanie dotyczy PHP.&lt;/p&gt;";
            }

            echo "&lt;/div&gt;";
        }
        ?&gt;
        &lt;?php } ?&gt;
&lt;?php
require_once __DIR__ . '/includes/footer.php';</code></pre>



<h2 class="wp-block-heading"><code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/header.php</code></h2>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;!DOCTYPE html&gt;
&lt;html lang="pl"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;&lt;?= $nazwaStrony; ?&gt;&lt;/title&gt;
    &lt;style&gt;
        body { font-family: Arial, sans-serif; max-width: 700px; margin: 40px auto; padding: 20px; }
        .panel { border: 1px solid #ccc; padding: 24px; border-radius: 12px; }
        .pasek-xp { display: flex; gap: 4px; margin: 10px 0; }
        .segment { width: 32px; height: 18px; background: #ddd; border-radius: 3px; }
        .segment.aktywny { background: #555; }
        .awans { font-weight: bold; }
        .zadanie { border: 1px solid #ddd; border-radius: 8px; padding: 4px 16px; margin: 10px 0; }
        .zadanie.wykonane { background: #f2f2f2; color: #666; }
        .blad { border: 1px solid #c00; background: #fdecec; color: #900; border-radius: 8px; padding: 8px 16px; }
        .sukces { border: 1px solid #080; background: #eaf7ea; color: #060; border-radius: 8px; padding: 8px 16px; }
        form label { display: block; margin-top: 12px; }
        form input, form textarea { width: 100%; padding: 6px; box-sizing: border-box; }
        form button { margin-top: 16px; padding: 8px 16px; }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div class="panel"&gt;
        &lt;h1&gt;TaskQuest&lt;/h1&gt;</code></pre>



<h2 class="wp-block-heading"><code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/footer.php</code></h2>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">        &lt;p&gt;TaskQuest 1.3&lt;/p&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>



<h2 class="wp-block-heading"><code class="prettyprint lang-sql" style="font-size: 0.85em;">diagnostyka.php</code></h2>



<p class="wp-block-paragraph">Jedyna zmiana: w tablicy <code class="prettyprint lang-sql" style="font-size: 0.85em;">$wymaganePliki</code> dochodzi pozycja <code class="prettyprint lang-sql" style="font-size: 0.85em;">'dodaj-zadanie.php'</code> (razem <strong>12</strong> ścieżek). Reszta pliku bez zmian względem Lekcji 11.</p>
</details>



<h1 class="wp-block-heading">10. Co zmieniliśmy w TaskQuest?</h1>



<h3 class="wp-block-heading">Zostaje</h3>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">$imieUzytkownika, $login
$xp, $poziom, $tytul, $xpWAktualnymPoziomie, $procentPostepu, $brakujeXp,
$liczbaAktywnychSegmentow, $progNastepnegoPoziomu
$streak, $dataOstatniejAktywnosci, $zadanieWykonane
$zadania, $bladZadan, $wykonaneZadania, $aktywneZadania, $liczbaWszystkich, $liczbaWykonanych,
$liczbaAktywnych, $zadaniaWgTerminu, $aktywneWgTerminu, $xpDoZdobycia
$nazwaStrony
define('XP_NA_POZIOM', 100), define('PLIK_ZADAN', ...)

wszystkie funkcje z Lekcji 1–12 — kod bez zmian
struktura tablicy $zadania (klucze: id, tytul, opis, termin, wykonane, xp)
format data/zadania.csv
obsługa awarii wczytania zadań w index.php (try/catch, ramka .blad)</code></pre>



<h3 class="wp-block-heading">Dochodzi</h3>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">// index.php
$wykonaneWgTerminu        // sortujPoTerminie($wykonaneZadania)
$filtr                    // widok listy z $_GET&#91;'filtr']: 'wszystkie' | 'aktywne' | 'wykonane' (inne wartości → 'wszystkie')
$zadaniaDoWyswietlenia    // lista wybrana przez match ($filtr)
$zadanieDodane            // bool: czy w adresie jest dodano=1

// dodaj-zadanie.php (nowy plik)
$tytulZadania, $opisZadania, $terminZadania, $xpZadania   // dane nowego zadania z formularza (teksty, oczyszczone)
$bledy                    // tablica komunikatów z walidujNoweZadanie()
$bladZapisu               // null albo komunikat wyjątku z wczytajZadania()/zapiszZadania()

// includes/funkcje_zadan.php
nastepneIdZadania()       // największe id + 1 (dla pustej listy 1)
walidujNoweZadanie()      // zwraca tablicę komunikatów o błędach (pusta = dane poprawne)

// includes/header.php
.sukces, form label, form input, form textarea, form button</code></pre>



<p class="wp-block-paragraph">Nowe narzędzia PHP i HTML: <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;form method action&gt;</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="text|date|number"&gt;</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;textarea&gt;</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;label for&gt;</code>, atrybuty <code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">min</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">max</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">maxlength</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">novalidate</code>; <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_SERVER['REQUEST_METHOD']</code>, operator <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">checkdate()</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">header('Location: ...')</code> + <code class="prettyprint lang-sql" style="font-size: 0.85em;">exit</code>, wzorzec POST → przekierowanie → GET, <code class="prettyprint lang-sql" style="font-size: 0.85em;">match</code> na liście dozwolonych wartości.</p>



<h3 class="wp-block-heading">Zmieniamy</h3>



<ul class="wp-block-list">
<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code>: link „+ Dodaj zadanie” i komunikat o dodaniu pod <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;h2&gt;Twoje zadania&lt;/h2&gt;</code>; nowy blok „parametry z adresu strony (GET)”; pętla listy przechodzi po <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadaniaDoWyswietlenia</code> zamiast po <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadaniaWgTerminu</code>; nad listą linki widoków, linia <code class="prettyprint lang-sql" style="font-size: 0.85em;">Widok: ...</code> i komunikat o pustym widoku,</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/header.php</code>: nowe reguły CSS,</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">includes/footer.php</code>: <code class="prettyprint lang-sql" style="font-size: 0.85em;">TaskQuest 1.3</code>,</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">diagnostyka.php</code>: 12 wymaganych plików,</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">zapiszZadania()</code> — <strong>kod bez zmian</strong>, ale pierwszy raz jest wywoływana ze strony aplikacji (w <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code> / <code class="prettyprint lang-sql" style="font-size: 0.85em;">catch</code>, razem z <code class="prettyprint lang-sql" style="font-size: 0.85em;">wczytajZadania()</code>).</li>
</ul>



<h3 class="wp-block-heading">Usuwamy</h3>



<p class="wp-block-paragraph">Nic. Żadna zmienna ani funkcja z Lekcji 12 nie została usunięta. <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadaniaWgTerminu</code> nadal istnieje — jest jedną z list, spośród których wybiera <code class="prettyprint lang-sql" style="font-size: 0.85em;">match</code>.</p>



<h3 class="wp-block-heading">Co nowego potrafi TaskQuest po tej lekcji?</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Po Lekcji 13 TaskQuest przyjmuje zadania od użytkownika. Na stronie <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code> jest formularz, którego dane PHP sprawdza sam (nie ufając przeglądarce), pokazuje wszystkie błędy naraz i nie każe wpisywać wszystkiego od nowa. Poprawne zadanie dostaje unikalne <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> i jest dopisywane do <code class="prettyprint lang-sql" style="font-size: 0.85em;">data/zadania.csv</code> — ale tylko wtedy, gdy plik udało się poprawnie wczytać, więc uszkodzony plik nie zostanie nadpisany. Po zapisie aplikacja przekierowuje na stronę główną, dzięki czemu odświeżenie nie dodaje zadania drugi raz. Lista zadań ma trzy widoki (wszystkie, aktywne, wykonane) wybierane przez adres strony.</p>
</blockquote>



<h1 class="wp-block-heading">Do samodzielnego sprawdzenia</h1>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&#91; ] Nadal rozwijam ten sam projekt TaskQuest (nie zaczynam nowego).
&#91; ] Potrafię wyjaśnić, czym różni się żądanie GET od POST i gdzie jadą dane w każdym z nich.
&#91; ] Wiem, że atrybut name pola staje się kluczem w $_GET / $_POST, a wartości zawsze są tekstami.
&#91; ] Potrafię odróżnić pierwsze wejście od wysłania formularza ($_SERVER&#91;'REQUEST_METHOD']).
&#91; ] Używam ??, żeby nie sięgać po nieistniejące klucze.
&#91; ] Wiem, dlaczego required/min/max nie zastępują sprawdzania w PHP.
&#91; ] Wiem, dlaczego błędy w formularzu to tablica komunikatów, a awaria pliku — wyjątek.
&#91; ] Mam walidujNoweZadanie() i nastepneIdZadania() w funkcje_zadan.php; Test 1 i Test 2 się zgadzają.
&#91; ] dodaj-zadanie.php zachowuje wpisane dane po błędzie i pokazuje wszystkie błędy naraz.
&#91; ] Odczyt i zapis są w jednym try — uszkodzony plik nie zostaje nadpisany (Test 4).
&#91; ] Po dodaniu zadania następuje przekierowanie; F5 nie dodaje zadania drugi raz.
&#91; ] index.php ma trzy widoki listy; nieznany filtr daje widok "wszystkie" bez błędów (Test 5).
&#91; ] diagnostyka.php pokazuje 12 razy &#91;OK]; strona główna jak po Lekcji 12 (Test 6).
&#91; ] Wiem, jaki problem pokazał Eksperyment 5 i w której lekcji go naprawimy.
&#91; ] Usunęłam/usunąłem brudnopis.php i novalidate; plik zadań ma zawartość, którą chcę zachować.</code></pre>



<h1 class="wp-block-heading">Pytania kontrolne</h1>



<ol class="wp-block-list">
<li><p>Wpisujesz w formularzu (metoda GET) <code class="prettyprint lang-sql" style="font-size: 0.85em;">Kama</code> w pole z <code class="prettyprint lang-sql" style="font-size: 0.85em;">name="imie"</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">id="pole1"</code>. Jak wygląda adres po wysłaniu i jaki klucz pojawi się w <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code>? Co się zmieni, gdy usuniesz z pola atrybut <code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code>?</p></li>



<li><p>Przewidź wynik każdej linii, gdy strona została otwarta zwykłym wejściem (bez formularza i bez niczego w adresie):<code class="prettyprint lang-sql" style="font-size: 0.85em;">echo $_SERVER['REQUEST_METHOD'];<br />echo $_POST['tytul'] ?? 'brak';<br />echo ($_GET['dodano'] ?? '') === '1' ? 'tak' : 'nie';<br />var_dump($_POST);<br /></code>Pole <code class="prettyprint lang-sql" style="font-size: 0.85em;">xp</code> ma <code class="prettyprint lang-sql" style="font-size: 0.85em;">type="number"</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">min="1"</code>. Podaj dwa sposoby, w jakie użytkownik może mimo to wysłać <code class="prettyprint lang-sql" style="font-size: 0.85em;">xp=-50</code>. Która część TaskQuest go zatrzyma?</p></li>



<li><p>Dlaczego sprawdzamy XP regexem <code class="prettyprint lang-sql" style="font-size: 0.85em;">/^\d+$/</code> <strong>przed</strong> porównaniem z zakresem? Co zapisałby TaskQuest dla <code class="prettyprint lang-sql" style="font-size: 0.85em;">12abc</code>, a jaki komunikat zobaczyłby użytkownik dla <code class="prettyprint lang-sql" style="font-size: 0.85em;">dużo</code>, gdyby regexa nie było?</p></li>



<li><p>Dlaczego <code class="prettyprint lang-sql" style="font-size: 0.85em;">walidujNoweZadanie()</code> zwraca tablicę komunikatów, a nie rzuca wyjątku przy pierwszym błędzie — skoro w <code class="prettyprint lang-sql" style="font-size: 0.85em;">sprawdzWierszZadania()</code> wyjątek był dobrym pomysłem?</p></li>



<li><p>W <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php</code> wywołania <code class="prettyprint lang-sql" style="font-size: 0.85em;">wczytajZadania()</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">zapiszZadania()</code> są w <strong>jednym</strong> bloku <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code>. Opisz krok po kroku, co stałoby się z plikiem zawierającym jeden uszkodzony wiersz, gdyby przy awarii odczytu kod przyjął pustą listę i mimo to zapisał nowe zadanie.</p></li>



<li><p>Dlaczego <code class="prettyprint lang-sql" style="font-size: 0.85em;">nastepneIdZadania()</code> szuka największego <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code>, zamiast liczyć <code class="prettyprint lang-sql" style="font-size: 0.85em;">count($zadania) + 1</code>? Podaj przykład listy, dla której te dwa sposoby dają różny wynik.</p></li>



<li><p>Co się dzieje po kliknięciu „Dodaj zadanie” — wymień po kolei żądania, które widać w F12 → Sieć, z ich metodami i statusami. Dlaczego po tym F5 nie dodaje zadania ponownie?</p></li>



<li><p>Dlaczego filtr listy zadań przekazujemy przez GET, a dodawanie zadania przez POST? Co mogłoby się stać, gdyby dodawanie działało przez link w stylu <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodaj-zadanie.php?tytul=...&amp;xp=...</code>?</p></li>



<li><p>W <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> wartość <code class="prettyprint lang-sql" style="font-size: 0.85em;">$filtr</code> pochodzi od użytkownika, a mimo to wypisujemy ją na stronie bez obaw. Dlaczego jest bezpieczna? Czy to samo można powiedzieć o tytule zadania dodanym przez formularz (Eksperyment 5)?</p></li>



<li><p>Komunikat „Zadanie zostało dodane.” można wywołać, wpisując ręcznie <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?dodano=1</code>. Dlaczego? Jakiego mechanizmu brakuje, żeby komunikat był naprawdę jednorazowy?</p></li>
</ol>



<h1 class="wp-block-heading">Zadanie końcowe — rozbuduj TaskQuest samodzielnie</h1>



<p class="wp-block-paragraph">Wybierz <strong>jedno</strong> (albo oba):</p>



<p class="wp-block-paragraph"><strong>A) „Zrobione!” — oznaczanie zadania jako wykonanego.</strong> Przy każdym <strong>niewykonanym</strong> zadaniu na liście w <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> dodaj mały formularz z jednym przyciskiem „Zrobione” (metoda POST, <code class="prettyprint lang-sql" style="font-size: 0.85em;">action="oznacz-wykonane.php"</code>) i ukrytym polem <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="hidden" name="id" value="..."&gt;</code> z <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> zadania. Utwórz stronę <code class="prettyprint lang-sql" style="font-size: 0.85em;">oznacz-wykonane.php</code>, która:</p>



<ol class="wp-block-list">
<li>przyjmuje <strong>tylko</strong> żądania POST (dla GET od razu przekierowuje na <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code>),</li>



<li>sprawdza, czy <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> składa się z samych cyfr,</li>



<li>w <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code>: wczytuje zadania, znajduje zadanie o tym <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> (pętla <code class="prettyprint lang-sql" style="font-size: 0.85em;">foreach</code> z kluczem: <code class="prettyprint lang-sql" style="font-size: 0.85em;">foreach ($zadania as $klucz =&gt; $zadanie)</code>), ustawia mu <code class="prettyprint lang-sql" style="font-size: 0.85em;">'wykonane' =&gt; true</code> przez <code class="prettyprint lang-sql" style="font-size: 0.85em;">$zadania[$klucz]['wykonane'] = true;</code>, zapisuje plik i przekierowuje na <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?wykonano=1</code>,</li>



<li>gdy zadania o takim <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> nie ma albo wystąpi wyjątek — pokazuje czytelny komunikat i link powrotny.</li>
</ol>



<p class="wp-block-paragraph">Na stronie głównej dodaj komunikat „Zadanie oznaczone jako wykonane.” (analogicznie do <code class="prettyprint lang-sql" style="font-size: 0.85em;">dodano=1</code>). Pytania do przemyślenia: dlaczego przycisk to formularz POST, a nie link <code class="prettyprint lang-sql" style="font-size: 0.85em;">oznacz-wykonane.php?id=3</code>? Czy ukryte pole jest naprawdę ukryte przed użytkownikiem (sprawdź w F12)? Co się stanie, gdy ktoś wyśle <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> nieistniejącego zadania? <strong>Nie zmieniaj <code class="prettyprint lang-sql" style="font-size: 0.85em;">$xp</code> użytkownika</strong> — XP jest nadal symulowane, a przyznawanie nagrody wróci razem z bazą danych.</p>



<p class="wp-block-paragraph"><strong>B) Wyszukiwarka.</strong> Nad listą zadań w <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php</code> dodaj formularz wyszukiwania (metoda <strong>GET</strong>, pole <code class="prettyprint lang-sql" style="font-size: 0.85em;">name="szukaj"</code>). Lista ma pokazywać tylko te zadania z bieżącego widoku, których oczyszczony tytuł zawiera wpisane słowo — bez względu na wielkość liter (<code class="prettyprint lang-sql" style="font-size: 0.85em;">stripos()</code> znasz z Lekcji 8). Wymagania: puste pole = brak filtrowania; po wyszukaniu słowo zostaje w polu; wyszukiwanie ma działać <strong>razem</strong> z widokiem (np. aktywne + „PHP”) — podpowiedź: ukryte pole <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="hidden" name="filtr" value="..."&gt;</code> w formularzu wyszukiwania. Dlaczego tu GET jest lepszy niż POST? Sprawdź: czy adres z wynikami wyszukiwania można zapisać w zakładkach? Uwaga: wpisane słowo wraca na stronę (w polu i ewentualnie w komunikacie) — pamiętaj o Eksperymencie 5.</p>



<p class="wp-block-paragraph">Nie potrzebujesz niczego, czego nie poznałaś/poznałeś do tej pory.</p>



<h1 class="wp-block-heading">Most do Lekcji 14</h1>



<p class="wp-block-paragraph">TaskQuest przyjmuje już dane od użytkownika. Ale każda strona nadal działa „w próżni”: skrypt kończy się po wysłaniu odpowiedzi i niczego nie pamięta. Stąd nasze obejścia:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">"Zadanie zostało dodane."   →  przekazane w adresie (?dodano=1) — widać je po każdym odświeżeniu i każdy może je "podrobić"
$imieUzytkownika, $login     →  wpisane na sztywno w index.php — TaskQuest nie wie, KTO z niego korzysta
wybrany widok listy          →  znika, gdy wejdziesz na stronę bez ?filtr=...</code></pre>



<p class="wp-block-paragraph">Potrzebujemy sposobu, żeby aplikacja <strong>pamiętała coś między kolejnymi żądaniami</strong> tego samego użytkownika — jego login, jednorazowy komunikat, ulubiony widok. To temat Lekcji 14: cookies i sesje.</p>
</details>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 class="wp-block-heading">PODSUMOWANIE LEKCJI</h1>



<h1 id="dodatek" class="wp-block-heading">W skrócie</h1>



<p class="wp-block-paragraph"><strong>Jak to w ogóle działa</strong></p>



<ul class="wp-block-list">
<li>Każde otwarcie strony to <strong>żądanie</strong> → PHP startuje <strong>od zera</strong> → odsyła <strong>odpowiedź</strong> → skrypt się kończy i wszystkie zmienne znikają. Dlatego dane trzeba przesyłać przy każdym żądaniu (w adresie albo w treści żądania).</li>



<li><strong>GET</strong> — dane w adresie, po <code class="prettyprint lang-sql" style="font-size: 0.85em;">?</code>, w parach <code class="prettyprint lang-sql" style="font-size: 0.85em;">klucz=wartość</code> rozdzielonych <code class="prettyprint lang-sql" style="font-size: 0.85em;">&amp;</code>. Trafiają do <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code>.</li>



<li><strong>POST</strong> — dane w treści żądania, adres się nie zmienia. Trafiają do <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST</code>.</li>



<li>Zasada na cały kurs: <strong>GET — gdy tylko czytamy. POST — gdy coś zmieniamy.</strong></li>
</ul>



<p class="wp-block-paragraph"><strong>Co wędruje z formularza do PHP</strong></p>



<ul class="wp-block-list">
<li>Kluczem w <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code> / <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST</code> jest <strong>atrybut <code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code></strong>, nie <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code>. Pole bez <code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code> nie zostanie wysłane w ogóle.</li>



<li><strong>Wszystko, co przychodzi, jest tekstem</strong> — także z <code class="prettyprint lang-sql" style="font-size: 0.85em;">type="number"</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">type="date"</code> (data zawsze w formacie <code class="prettyprint lang-sql" style="font-size: 0.85em;">RRRR-MM-DD</code>, niezależnie od tego, jak przeglądarka ją wyświetla).</li>



<li>Pole <strong>niezaznaczone</strong> (<code class="prettyprint lang-sql" style="font-size: 0.85em;">checkbox</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">radio</code>) <strong>nie wysyła nic</strong> — klucza po prostu nie ma w tablicy. Dlatego sprawdzamy <code class="prettyprint lang-sql" style="font-size: 0.85em;">isset()</code> lub <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code>, zamiast porównywać wartość.</li>



<li>Żeby dotarł <strong>więcej niż jeden</strong> wybór, nazwa musi mieć nawiasy: <code class="prettyprint lang-sql" style="font-size: 0.85em;">name="food[]"</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;select name="hobby[]" multiple&gt;</code>. W PHP powstaje wtedy tablica.</li>



<li>Przycisk trafia do <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST</code> tylko wtedy, gdy ma <code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code> (<code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;button type="submit" name="przycisk"&gt;</code> → <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['przycisk']</code>).</li>
</ul>



<p class="wp-block-paragraph"><strong>Odbiór danych w PHP</strong></p>



<ul class="wp-block-list">
<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">$_SERVER['REQUEST_METHOD']</code> odróżnia pierwsze wejście na stronę (<code class="prettyprint lang-sql" style="font-size: 0.85em;">'GET'</code>) od wysłania formularza (<code class="prettyprint lang-sql" style="font-size: 0.85em;">'POST'</code>). Cała obsługa formularza siedzi w <code class="prettyprint lang-sql" style="font-size: 0.85em;">if ($_SERVER['REQUEST_METHOD'] === 'POST') { ... }</code>.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code> chroni przed <code class="prettyprint lang-sql" style="font-size: 0.85em;">Warning: Undefined array key</code>: <code class="prettyprint lang-sql" style="font-size: 0.85em;">$tytul = $_POST['tytul'] ?? '';</code>.</li>



<li><strong>Uwaga na pułapkę:</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code> reaguje tylko na <em>brak klucza</em> albo <code class="prettyprint lang-sql" style="font-size: 0.85em;">null</code>. Pusty tekst z formularza <strong>istnieje</strong>, więc <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['xp'] ?? 10</code> dla pustego pola da <code class="prettyprint lang-sql" style="font-size: 0.85em;">''</code>, a nie <code class="prettyprint lang-sql" style="font-size: 0.85em;">10</code>.</li>
</ul>



<p class="wp-block-paragraph"><strong>Sprawdzanie danych</strong></p>



<ul class="wp-block-list">
<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">min</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">max</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">maxlength</code> to wygoda dla użytkownika, <strong>nie ochrona</strong>: można dopisać <code class="prettyprint lang-sql" style="font-size: 0.85em;">novalidate</code>, usunąć atrybut w F12 albo wysłać dane bez przeglądarki. <strong>PHP zawsze sprawdza dane sam.</strong></li>



<li>Błąd użytkownika (pusty tytuł, XP = 500) → <strong>tablica komunikatów</strong> <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bledy</code>, pokazana w całości. Awaria (nie ma pliku, nie da się zapisać) → <strong>wyjątek</strong> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">try</code> / <code class="prettyprint lang-sql" style="font-size: 0.85em;">catch</code>.</li>



<li>Rzutowanie <code class="prettyprint lang-sql" style="font-size: 0.85em;">(int)</code> nie jest walidacją: <code class="prettyprint lang-sql" style="font-size: 0.85em;">(int) "12abc"</code> daje <code class="prettyprint lang-sql" style="font-size: 0.85em;">12</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">(int) "dużo"</code> daje <code class="prettyprint lang-sql" style="font-size: 0.85em;">0</code>. Najpierw sprawdzamy format (np. <code class="prettyprint lang-sql" style="font-size: 0.85em;">preg_match('/^\d+$/', $xp)</code>), dopiero potem zakres.</li>
</ul>



<p class="wp-block-paragraph"><strong>Po zapisie</strong></p>



<ul class="wp-block-list">
<li>Odświeżenie strony powtarza ostatnie żądanie. Jeśli był to POST — zadanie dopisze się drugi raz. Ratunek to <strong>POST → przekierowanie → GET</strong>: <code class="prettyprint lang-sql" style="font-size: 0.85em;">header('Location: index.php?dodano=1'); exit;</code>.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">header()</code> musi zostać wywołane <strong>przed</strong> wypisaniem czegokolwiek na stronę, a <code class="prettyprint lang-sql" style="font-size: 0.85em;">exit</code> jest konieczne — <code class="prettyprint lang-sql" style="font-size: 0.85em;">header()</code> sam nie przerywa skryptu.</li>



<li>Komunikat przekazany w adresie (<code class="prettyprint lang-sql" style="font-size: 0.85em;">?dodano=1</code>) każdy może wpisać ręcznie. To obejście do czasu poznania sesji (Lekcja 14).</li>



<li>POST <strong>nie jest tajny</strong> — dane widać w F12 → Sieć → Ładunek. Szyfruje HTTPS, nie metoda.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Co trzeba umieć na INF.03?</h2>



<ul class="wp-block-list">
<li><strong>napisać formularz „wysyłający dane metodą bezpieczną do tego samego pliku”</strong> — w języku arkuszy CKE „metoda bezpieczna” to <strong>POST</strong> (dane nie pokazują się w adresie). Uwaga: to nie to samo co „szyfrowana” — o tym była mowa w lekcji;</li>



<li><strong>wysłać formularz do tego samego pliku</strong> — czyli pominąć atrybut <code class="prettyprint lang-sql" style="font-size: 0.85em;">action</code> albo wpisać w nim nazwę bieżącego pliku;</li>



<li><strong>zbudować pola z etykietami połączonymi z polem</strong> (<code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;label for="..."&gt;</code> + <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> w polu), w tym <strong>pole edycyjne typu numerycznego</strong> (<code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="number"&gt;</code>) i <strong>listę rozwijaną</strong> (<code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;select&gt;</code>), której opcje są generowane skryptem PHP z danych z bazy;</li>



<li><strong>napisać odnośnik przesyłający parametr metodą GET do tej samej strony</strong> (<code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?idKontynent=6</code>) i odczytać ten parametr w PHP;</li>



<li><strong>przyjąć wartość domyślną, gdy parametru brak</strong> — arkusz wprost tego wymaga („a w przypadku braku – wartością 6”). Arkusz podaje w tabeli funkcji <code class="prettyprint lang-sql" style="font-size: 0.85em;">isset($zmienna)</code>; w naszym kursie krótszym zapisem jest <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code>;</li>



<li><strong>uruchomić fragment skryptu tylko wtedy, gdy formularz został wysłany</strong> („działa tylko w przypadku, gdy z formularza zostały przesłane dane”) — <code class="prettyprint lang-sql" style="font-size: 0.85em;">isset($_POST['...'])</code> lub <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_SERVER['REQUEST_METHOD'] === 'POST'</code>;</li>



<li><strong>przenieść dane z formularza do zapytania SQL</strong> (to już Lekcje 17–19, ale formularz z tej lekcji jest tam punktem wyjścia).</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Minimum, które powinno wejść w pamięć</h2>



<p class="wp-block-paragraph">Jeżeli masz zapamiętać tylko kilka rzeczy z tej lekcji, zapamiętaj te:</p>



<ol class="wp-block-list">
<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code> w polu formularza = klucz w <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code> / <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST</code>. Bez <code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code> pole nie istnieje dla PHP. <code class="prettyprint lang-sql" style="font-size: 0.85em;">id</code> służy tylko etykiecie i CSS.</li>



<li>Z formularza przychodzą <strong>wyłącznie teksty</strong>. Liczbę trzeba sprawdzić samodzielnie, zanim się jej użyje.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">if ($_SERVER['REQUEST_METHOD'] === 'POST')</code> — tylko w środku tego <code class="prettyprint lang-sql" style="font-size: 0.85em;">if</code> sięgamy po dane formularza.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['klucz'] ?? ''</code> zamiast gołego <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['klucz']</code>. Puste pole to nie brak klucza.</li>



<li>Niezaznaczonego checkboxa i radia <strong>nie ma</strong> w tablicy. Wiele wartości = <code class="prettyprint lang-sql" style="font-size: 0.85em;">name="cos[]"</code>.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code> / <code class="prettyprint lang-sql" style="font-size: 0.85em;">min</code> / <code class="prettyprint lang-sql" style="font-size: 0.85em;">max</code> da się obejść — <strong>PHP sprawdza wszystko jeszcze raz</strong>.</li>



<li>Błąd użytkownika → tablica <code class="prettyprint lang-sql" style="font-size: 0.85em;">$bledy</code> i formularz z zachowanymi danymi. Awaria → wyjątek.</li>



<li>GET do czytania (filtry, wyszukiwanie, linki), POST do zmieniania danych (dodawanie, logowanie).</li>



<li>Po udanym zapisie: <code class="prettyprint lang-sql" style="font-size: 0.85em;">header('Location: ...'); exit;</code> — inaczej F5 zapisze drugi raz.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">header()</code> musi być przed jakimkolwiek <code class="prettyprint lang-sql" style="font-size: 0.85em;">echo</code> i przed HTML-em.</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Ściągawka – najważniejsze polecenia i składnia</h2>



<p class="wp-block-paragraph"><strong>Formularz</strong></p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;form method="post" action="dodaj-zadanie.php"&gt;   &lt;!-- brak action = ten sam plik --&gt;
    &lt;label for="tytul"&gt;Tytuł&lt;/label&gt;
    &lt;input type="text" id="tytul" name="tytul" value="&lt;?= $tytulZadania ?&gt;" maxlength="100" required&gt;

    &lt;textarea name="opis" rows="3"&gt;&lt;?= $opisZadania ?&gt;&lt;/textarea&gt;   &lt;!-- textarea NIE ma value --&gt;

    &lt;input type="date"   name="termin" value="&lt;?= $terminZadania ?&gt;"&gt;
    &lt;input type="number" name="xp" min="1" max="100" value="&lt;?= $xpZadania ?&gt;"&gt;

    &lt;input type="checkbox" name="zgoda" value="tak"&gt;            &lt;!-- pojedynczy --&gt;
    &lt;input type="checkbox" name="food&#91;]" value="pizza"&gt;         &lt;!-- wiele --&gt;
    &lt;input type="radio"    name="plec" value="k"&gt;
    &lt;select name="klasa"&gt;&lt;option value="1"&gt;1&lt;/option&gt;&lt;/select&gt;
    &lt;select name="hobby&#91;]" multiple size="4"&gt; ... &lt;/select&gt;
    &lt;input type="hidden" name="filtr" value="aktywne"&gt;

    &lt;button type="submit" name="przycisk"&gt;Wyślij&lt;/button&gt;
&lt;/form&gt;</code></pre>



<p class="wp-block-paragraph"><strong>Odbiór w PHP</strong></p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET&#91;'klucz']              // dane z adresu: strona.php?klucz=wartość
$_POST&#91;'klucz']             // dane z formularza method="post"
$_SERVER&#91;'REQUEST_METHOD']  // 'GET' albo 'POST'
$_FILES&#91;'plik']             // przesłany plik (formularz z enctype="multipart/form-data")

$tytul = $_POST&#91;'tytul'] ?? '';          // brak klucza → wartość domyślna
isset($_POST&#91;'przycisk'])                // czy klucz istnieje
is_array($_POST&#91;'food'] ?? null)         // czy przyszła tablica wartości
trim($tytul) === ''                      // puste pole (same spacje też)</code></pre>



<p class="wp-block-paragraph"><strong>Typowe wzorce</strong></p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">// 1. obsługa formularza tylko po wysłaniu
if ($_SERVER&#91;'REQUEST_METHOD'] === 'POST') {
    $tytul = trim($_POST&#91;'tytul'] ?? '');
    $xp    = trim($_POST&#91;'xp'] ?? '');

    $bledy = &#91;];
    if ($tytul === '')                 { $bledy&#91;] = "Tytuł jest wymagany."; }
    if (!preg_match('/^\d+$/', $xp))   { $bledy&#91;] = "XP musi być liczbą całkowitą."; }
    elseif ((int) $xp &lt; 1 || (int) $xp &gt; 100) { $bledy&#91;] = "XP musi być z zakresu 1–100."; }

    if (count($bledy) === 0) {
        // zapis + przekierowanie
    }
}

// 2. POST → przekierowanie → GET (F5 nie powtórzy zapisu)
header('Location: index.php?dodano=1');
exit;

// 3. parametr z adresu z wartością domyślną i listą dozwolonych wartości
$filtr = $_GET&#91;'filtr'] ?? 'wszystkie';
$lista = match ($filtr) {
    'aktywne'  =&gt; $aktywneZadania,
    'wykonane' =&gt; $wykonaneZadania,
    default    =&gt; $wszystkieZadania,
};

// 4. data poprawna kalendarzowo
checkdate(10, 2, 2026);   // miesiąc, dzień, rok → true/false</code></pre>



<p class="wp-block-paragraph"><strong>Pułapki</strong></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Zapis</th><th>Co się stanie</th></tr></thead><tbody><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="text" id="xp"&gt;</code></td><td>brak <code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code> → <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['xp']</code> nie istnieje</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['xp'] ?? 10</code> przy pustym polu</td><td>wynik to <code class="prettyprint lang-sql" style="font-size: 0.85em;">''</code>, nie <code class="prettyprint lang-sql" style="font-size: 0.85em;">10</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">(int) $_POST['xp']</code> bez sprawdzenia</td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">"12abc"</code> → <code class="prettyprint lang-sql" style="font-size: 0.85em;">12</code>, <code class="prettyprint lang-sql" style="font-size: 0.85em;">"dużo"</code> → <code class="prettyprint lang-sql" style="font-size: 0.85em;">0</code></td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;textarea value="..."&gt;</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">textarea</code> nie ma <code class="prettyprint lang-sql" style="font-size: 0.85em;">value</code> — tekst wpisz między znacznikami</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">echo "OK"; header('Location: ...');</code></td><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">headers already sent</code>, przekierowanie nie zadziała</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">header('Location: ...')</code> bez <code class="prettyprint lang-sql" style="font-size: 0.85em;">exit</code></td><td>reszta skryptu nadal się wykona</td></tr><tr><td><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;a href="usun.php?id=3"&gt;</code></td><td>zmiana danych przez GET — zrobi to odświeżenie lub robot</td></tr></tbody></table></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Quiz – sprawdź się</h2>



<p class="wp-block-paragraph"><strong>1.</strong> Użytkownik wszedł pod adres <code class="prettyprint lang-sql" style="font-size: 0.85em;">index.php?filtr=aktywne&amp;strona=2</code>. Co dokładnie znajdzie się w <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_GET</code> i jakiego typu są tam wartości?</p>



<p class="wp-block-paragraph"><strong>2.</strong> Pole wygląda tak: <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="text" id="tytul"&gt;</code>. Formularz wysłano metodą POST. Co zwróci <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['tytul'] ?? '(brak)'</code> i dlaczego?</p>



<p class="wp-block-paragraph"><strong>3.</strong> Formularz wysłano metodą POST z <strong>pustym</strong> polem <code class="prettyprint lang-sql" style="font-size: 0.85em;">xp</code>. Co wypisze kod?</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">$xp = $_POST&#91;'xp'] ?? 10;
var_dump($xp);</code></pre>



<p class="wp-block-paragraph"><strong>4.</strong> W formularzu jest <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="checkbox" name="zgoda" value="tak"&gt;</code>. Napisz warunek, który sprawdzi, czy użytkownik zaznaczył to pole. Dlaczego <code class="prettyprint lang-sql" style="font-size: 0.85em;">if ($_POST['zgoda'] === 'tak')</code> to zły pomysł?</p>



<p class="wp-block-paragraph"><strong>5.</strong> Uczeń chce odebrać wszystkie zaznaczone gatunki, ale w <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['gatunek']</code> jest zawsze tylko jedna wartość. Co trzeba zmienić?</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="checkbox" name="gatunek" value="rock"&gt;
&lt;input type="checkbox" name="gatunek" value="pop"&gt;
&lt;input type="checkbox" name="gatunek" value="jazz"&gt;</code></pre>



<p class="wp-block-paragraph"><strong>6.</strong> Znajdź błąd:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;textarea name="opis" rows="3" value="&lt;?= $opisZadania ?&gt;"&gt;&lt;/textarea&gt;</code></pre>



<p class="wp-block-paragraph"><strong>7.</strong> Co zapisze ten kod dla wpisanego XP <code class="prettyprint lang-sql" style="font-size: 0.85em;">12abc</code>, a co dla <code class="prettyprint lang-sql" style="font-size: 0.85em;">dużo</code>? Jak to naprawić?</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">$xp = (int) ($_POST&#91;'xp'] ?? 0);
if ($xp &gt;= 1 &amp;&amp; $xp &lt;= 100) {
    $bledy = &#91;];
} else {
    $bledy&#91;] = "XP musi być z zakresu 1–100.";
}</code></pre>



<p class="wp-block-paragraph"><strong>8.</strong> Uzupełnij kod tak, aby wykonał się <strong>tylko</strong> po wysłaniu formularza:</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">if ($_SERVER&#91;'____________'] === '______') {
    // obsługa formularza
}</code></pre>



<p class="wp-block-paragraph"><strong>9.</strong> Dla każdej operacji wybierz metodę (GET / POST) i uzasadnij jednym zdaniem: a) wyszukiwanie zadań po słowie, b) oznaczenie zadania jako wykonanego, c) logowanie, d) przełączenie widoku listy na „aktywne”.</p>



<p class="wp-block-paragraph"><strong>10.</strong> Dlaczego ten kod nie przekieruje użytkownika?</p>



<pre class="wp-block-code prettyprint lang-sql has-small-font-size" style="font-size: 0.85em;"><code class="prettyprint lang-sql" style="font-size: 0.85em;">require_once 'includes/header.php';
zapiszZadania(PLIK_ZADAN, $zadania);
header('Location: index.php?dodano=1');
exit;</code></pre>



<p class="wp-block-paragraph"><strong>11.</strong> Po dodaniu zadania użytkownik naciska F5 i zadanie pojawia się w pliku drugi raz. Który mechanizm tego nie zastosowano i jak dokładnie wygląda jego kod?</p>



<p class="wp-block-paragraph"><strong>12.</strong> Formularz ma pole <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;input type="number" name="xp" min="1" max="100" required&gt;</code>. Podaj dwa sposoby, w jakie użytkownik może mimo to wysłać <code class="prettyprint lang-sql" style="font-size: 0.85em;">xp=-50</code>. Co go zatrzyma?</p>



<p class="wp-block-paragraph"><strong>13. Zadanie praktyczne.</strong> Napisz w jednym pliku <code class="prettyprint lang-sql" style="font-size: 0.85em;">szukaj.php</code>: formularz wysyłany metodą GET z polem <code class="prettyprint lang-sql" style="font-size: 0.85em;">szukaj</code> i przyciskiem, a nad nim kod PHP, który — jeśli w adresie jest parametr <code class="prettyprint lang-sql" style="font-size: 0.85em;">szukaj</code> i nie jest pusty — wypisze <code class="prettyprint lang-sql" style="font-size: 0.85em;">Szukasz: &lt;fraza&gt;</code>, a w przeciwnym razie <code class="prettyprint lang-sql" style="font-size: 0.85em;">Wpisz szukane słowo.</code>. Po wyszukaniu wpisane słowo ma zostać w polu.</p>



<p class="wp-block-paragraph"><strong>14.</strong> W treści zadania egzaminacyjnego jest: „Formularz wysyłający dane metodą bezpieczną do tego samego pliku”. Jak wygląda znacznik <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;form&gt;</code> spełniający ten wymóg?</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<details class="wp-block-details is-layout-flow wp-block-details-is-layout-flow"><summary><strong>Odpowiedzi</strong></summary>
<ol class="wp-block-list">
<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">['filtr' =&gt; 'aktywne', 'strona' =&gt; '2']</code> — tablica asocjacyjna, w której obie wartości są <strong>tekstami</strong> (również <code class="prettyprint lang-sql" style="font-size: 0.85em;">'2'</code>).</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">(brak)</code>. Pole bez atrybutu <code class="prettyprint lang-sql" style="font-size: 0.85em;">name</code> w ogóle nie zostaje wysłane, więc klucza <code class="prettyprint lang-sql" style="font-size: 0.85em;">tytul</code> w <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST</code> nie ma; <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code> podstawia wartość domyślną.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">string(0) ""</code>. Klucz <code class="prettyprint lang-sql" style="font-size: 0.85em;">xp</code> istnieje (pole zostało wysłane, tylko puste), więc <code class="prettyprint lang-sql" style="font-size: 0.85em;">??</code> się nie uruchamia. Pustkę trzeba sprawdzić osobno, np. <code class="prettyprint lang-sql" style="font-size: 0.85em;">trim($_POST['xp'] ?? '') === ''</code>.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">if (isset($_POST['zgoda']))</code> (lub <code class="prettyprint lang-sql" style="font-size: 0.85em;">if (($_POST['zgoda'] ?? '') === 'tak')</code>). Niezaznaczony checkbox <strong>nie wysyła nic</strong>, więc odwołanie do <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['zgoda']</code> da <code class="prettyprint lang-sql" style="font-size: 0.85em;">Warning: Undefined array key</code>.</li>



<li>Nazwa musi być tablicowa: <code class="prettyprint lang-sql" style="font-size: 0.85em;">name="gatunek[]"</code> w każdym polu. Wtedy w <code class="prettyprint lang-sql" style="font-size: 0.85em;">$_POST['gatunek']</code> będzie tablica zaznaczonych wartości (a przy braku zaznaczeń — klucza nie będzie wcale).</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;textarea&gt;</code> nie ma atrybutu <code class="prettyprint lang-sql" style="font-size: 0.85em;">value</code>. Wartość wpisuje się między znacznikami: <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;textarea name="opis" rows="3"&gt;&lt;?= $opisZadania ?&gt;&lt;/textarea&gt;</code>.</li>



<li>Dla <code class="prettyprint lang-sql" style="font-size: 0.85em;">12abc</code> <code class="prettyprint lang-sql" style="font-size: 0.85em;">(int)</code> da <code class="prettyprint lang-sql" style="font-size: 0.85em;">12</code> — kod uzna dane za poprawne i zapisze 12 XP, choć użytkownik wpisał śmieci. Dla <code class="prettyprint lang-sql" style="font-size: 0.85em;">dużo</code> da <code class="prettyprint lang-sql" style="font-size: 0.85em;">0</code> — użytkownik zobaczy komunikat o zakresie 1–100, mimo że nie wpisał żadnej liczby. Naprawa: najpierw sprawdzić format regexem <code class="prettyprint lang-sql" style="font-size: 0.85em;">preg_match('/^\d+$/', $xp)</code>, dopiero potem zakres.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">$_SERVER['REQUEST_METHOD'] === 'POST'</code>.</li>



<li>a) GET — tylko czytanie, adres z wynikami da się zapisać w zakładkach i wysłać; b) POST — zmienia dane w pliku; c) POST — hasło w adresie trafiłoby do historii przeglądarki i logów serwera; d) GET — to wybór, co pokazać, niczego nie zmienia.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">require_once 'includes/header.php'</code> wypisało już HTML, a nagłówki muszą pójść <strong>przed</strong> treścią. PHP zgłosi <code class="prettyprint lang-sql" style="font-size: 0.85em;">Warning: Cannot modify header information – headers already sent</code>. Przekierowanie musi znaleźć się przed dołączeniem nagłówka strony.</li>



<li>Wzorca POST → przekierowanie → GET. Po udanym zapisie: <code class="prettyprint lang-sql" style="font-size: 0.85em;">header('Location: index.php?dodano=1'); exit;</code> Ostatnim żądaniem staje się wtedy zwykłe GET, więc F5 tylko wyświetla stronę.</li>



<li>Np. dopisać do <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;form&gt;</code> atrybut <code class="prettyprint lang-sql" style="font-size: 0.85em;">novalidate</code>, usunąć <code class="prettyprint lang-sql" style="font-size: 0.85em;">min</code> i <code class="prettyprint lang-sql" style="font-size: 0.85em;">required</code> w F12 → Elementy, albo wysłać żądanie bez przeglądarki (np. <code class="prettyprint lang-sql" style="font-size: 0.85em;">curl</code>). Zatrzyma go dopiero sprawdzenie w PHP (<code class="prettyprint lang-sql" style="font-size: 0.85em;">walidujNoweZadanie()</code>).</li>



<li>Przykładowe rozwiązanie: <code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;?php $szukane = trim($_GET['szukaj'] ?? ''); if ($szukane !== '') { echo "&lt;p&gt;Szukasz: $szukane&lt;/p&gt;"; } else { echo "&lt;p&gt;Wpisz szukane słowo.&lt;/p&gt;"; } ?&gt; &lt;form method="get" action="szukaj.php"&gt; &lt;input type="text" name="szukaj" value="&lt;?= $szukane ?&gt;"&gt; &lt;button type="submit"&gt;Szukaj&lt;/button&gt; &lt;/form&gt;</code> Formularz wysyłany metodą GET, więc fraza zostaje w adresie (<code class="prettyprint lang-sql" style="font-size: 0.85em;">szukaj.php?szukaj=PHP</code>) i wraca do pola przez <code class="prettyprint lang-sql" style="font-size: 0.85em;">value</code>.</li>



<li><code class="prettyprint lang-sql" style="font-size: 0.85em;">&lt;form method="post"&gt;</code> — „metoda bezpieczna” to POST, a brak atrybutu <code class="prettyprint lang-sql" style="font-size: 0.85em;">action</code> oznacza wysłanie do tego samego pliku (można też napisać <code class="prettyprint lang-sql" style="font-size: 0.85em;">action=""</code> albo wprost nazwę pliku, np. <code class="prettyprint lang-sql" style="font-size: 0.85em;">action="index.php"</code>).</li>
</ol>
</details>



<h2 class="wp-block-heading">ZADANIE NA OCENĘ</h2>



<div class="wp-block-file"><span style="font-size: 120%">PHP – Zapisy na turniej gier</span><a href="https://kamakaczmarek.net/wp-content/uploads/13_1_Zapisy_na_turniej_gier.docx" class="wp-block-file__button wp-element-button" download="" aria-describedby="wp-block-file--media-38dd5b68-c5dc-4a03-b93a-d23f9d016fc0">Pobierz</a></div>
<p>Artykuł <a href="https://kamakaczmarek.net/php-odbieranie-danych-z-przegladarki-formularze-html-metody-get-i-post-wysylanie-danych-do-przegladarki/">13. PHP &#8211; odbieranie danych z przeglądarki &#8211; formularze html, metody GET i POST. Wysyłanie danych do przeglądarki.</a> pochodzi z serwisu <a href="https://kamakaczmarek.net">Kama Kaczmarek</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
