Kawałek Kodu

Lato w pełni, więc możemy się pokusić o połączenie przyjemnego z pożytecznym. Będziemy prawie robić psikusy, czyli rzucać wodorostami w upatrzoną najładniejszą dziewczynę (lub chłopaka) i uciekać gdzie pieprz, albo choćby trzcina rośnie. Co prawda bardziej przyjemne będzie to dla nas, a nie dla naszego celu, ale jak powiedziałem, będzie to prawie psikus. Tak naprawdę będziemy chcieli dwa analogicznie działania przenieść na płaszczyznę programowania.

Dziel i rządź...? Nieee... Rzucaj i uciekaj!

Dziś pokażę Ci jeden ze sposobów na wymuszenie pobrania pliku, czyli po prostu "wyrzucenie" go do przeglądarki wraz z przekierowaniem. Jasnym jest, że wykonanie tych dwóch operacji w PHP z wykorzystaniem funkcji header, nie jest możliwe. Albo możemy wykonać przekierowanie, albo wymusić download. Skoro wymusimy download, to nie zrobimy przekierowania. Jeśli jednak zrobimy przekierowanie, to możemy z pomocą pewnej sztuczki wykonać download. I będzie to odbywać się dokładnie w takiej kolejności - najpierw przekierowanie, potem download. Odwrotnie byłoby kiepawo.

No dobra, czyli przekierowanie wykonamy w znany sposób:

header('Location: /index.php');

Przekierowujemy na nasz główny plik. Oczywiście może to być inny plik, ale musimy w nim obsłużyć download. Możesz teraz pomyśleć, że przecież wystarczy dodać zmienne jako query string, które odbierzemy w index.php i będziemy wiedzieć, że musimy zrobić download. Niby tak, ale jeśli użytkownik odświeżyłby taką stronę, to znów wykonalibyśmy download, a przecież nie zawsze o to chodzi, bo nie chcemy generować w kółko tego samego pliku lub też sedno jest w tym, aby plik wygenerować jednokrotnie.

Trzeba w jakiś inny sposób przekazać informację do pliku index.php, że należy wysłać plik do przeglądarki. Naszym wyczekiwanym, nadmorskim zachodem Słońca będzie sesja. To w niej zapiszemy sobie informację o tym, że mamy wyrzucić plik. Jednak po przekierowaniu nie możemy jednocześnie wygenerować kodu HTML naszej strony i wykonać download, bo przecież otrzymamy nielubianą informację headers already sent. I teraz naszym oczom ukazuje się drugi, niemniej piękny zachód Słońca, mianowicie metoda meta refresh udająca prawdziwe przekierowanie HTTP.

<meta http-equiv="refresh" content="0; url=jakis_URL">

Pewnie najczęściej ten tag używany jest po prostu do przekierowania na inną, w tym inną wizualnie, podstronę. Ale przecież nie jest powiedziane, że można go użyć tylko do tego. Możemy przekierować na dowolny adres, również taki, w którym poinformujemy nasz skrypt, że ma wyrzucić plik do przeglądarki.
Tak więc po przekierowaniu wygenerujemy kod nowej podstrony, tam w zależności od istnienia zmiennej w sesji dodamy metatag. W metatagu będzie adres z parametrem, który przekieruje przeglądarkę na ten sam skrypt, a ten wygeneruje tylko i wyłącznie plik, bez kodu HTML.

Jak działa przekierowanie z downloadem? Tak. (Po wpisaniu wartości w pole otrzymasz plik z tąże zwartością).

Na mecie.

Działa to tak:

  • jeśli nie ma żadnych danych z $_POST, ani $_GET, to generujemy nasz formularz (czyli stronę startową),
  • jeśli mamy dane z $_POST, to:
    • pobieramy je,
    • generujemy losową nazwę pliku,
    • zapisujemy do pliku dane,
    • nazwę pliku zapisujemy do sesji,
    • wykonujemy przekierowanie i kończymy,
  • jeśli mamy dane w sesji, to generujemy w stronie dodatkowo metatag refresh z URL na nasz skrypt i parametrem informującym, że trzeba pobrać dane, niech to będzie parametr getfile,
  • jeśli mamy w $_GET parametr getfile, to:
    • wyrzucamy plik do przeglądarki,
    • usuwamy plik,
    • usuwamy nazwę pliku z sesji (w ten sposób odświeżając stronę nie będzie generowany metatag refresh, a więc i download),

Oczywiście stroną startową (przed przekierowaniem) nie musi być formularz. Najważniejsze jest to, aby przed redirect zapisać w sesji informację o pliku, po przekierowaniu wygenerować stronę z metatagiem refresh (jeśli jest informacja w sesji), i obsłużyć pobieranie pliku na podstawie URL z metatagu wraz z usuwaniem informacji z sesji. Samo generowanie pliku można wykonać w momencie zapisu informacji do sesji lub ad hoc - w trakcie żądania pobierania.

Prosty kod, który użyłem dla tego przykładu, wygląda następująco:

<?php
    session_start();

    if($_SERVER['REQUEST_METHOD'] === 'POST'){
        if(!empty($_POST['name'])){
            $filename = uniqid();
            file_put_contents($filename, $_POST['name']);
            $_SESSION['filename'] = $filename;
            header('Location: index.php');
            exit();
        }
    }   
    else{
        if(isset($_GET['getfile'])){
            header('Content-Type: text/plain');
            header("Content-Transfer-Encoding: Binary");
            header("Content-disposition: attachment; filename=plik.txt");
            readfile($_SESSION['filename']);
            unlink($_SESSION['filename']);
            unset($_SESSION['filename']);
            exit;
        }    
    }
?>
<!DOCTYPE html>
<html lang="pl">
<head>
<title></title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
<?php
    if(!empty($_SESSION['filename'])){
?>
<meta http-equiv="Refresh" content="0; url=index.php?getfile"/>
<?php
    }
?>
</head>
<body>

<?php
    if(isset($_SESSION['filename'])){
        echo "Pobieranie pliku rozpoczęło się!";
    }
    else{
?>
    <form method="post">
        <input type="text" name="name"/>
        <input type="submit" value="Pobierz"/>
    </form>
<?php
    }
?>
</body>
</html>

Tym zdaniem kończę nasze dzisiejsze psikusowanie. Proponuję przekierować się na plażę i ściągnąć...
Do przeczytania!

 

Przydatne linki:
O meta refresh w Wikipedii.