Licznik pobrań pliku

Niejeden programista chciałby móc zliczyć pobrania pliku, który udostępnia na swojej stronie internetowej. A skoro przed chwilą miałem przyjemność taką funkcjonalność wdrażać, postanowiłem podzielić się informacją.

AJAX

Sposoby przyszły mi do głowy dwa. Pierwszy z nich to kawałek JavaScriptu, który możemy podpiąć pod wszystkie linki do plików do pobrania. Wystarczy do interesujących nas linków podczepić zdarzenie onclick, które za pomocą żądania AJAX przekaże skryptowi liczącemu kliknięcia adres URL pliku (lub inną informację, dzięki której plik zidentyfikujemy). Wywołany skrypt może zapisać w bazie danych, czy od biedy nawet w pliku, informację o kliknięciu.

Przykładowo mogłoby to wyglądać tak (na potrzeby przykładu użyję tu funkcji jQuery.ajax()):

<a rel="download" href="plik.zip">Pobierz plik</a>
$(document).ready(function() {
    $("[rel=download]").on({
        click: function() {
            $.ajax({
                data: { url: $(this).attr("href") },
                url: "/download.php"
            });
            var href = $(this).attr("href");
            setTimeout(function() {
                document.location = href;
            }, 100);
        }
    });
});

Powyższy kod podepnie zdarzenie do każdego linka z atrybutem rel="download". Po kliknięciu takiego linka, zanim pobierzemy plik, JavaScript wyśle żądanie AJAX do pliku /download.php, przekazując mu parametr GET url=plik.zip.

W skrypcie download.php możemy z tą informacją zrobić, co nam się podoba, np. zapisać do bazy danych:

$db = new PDO(
    "mysql:dbname=mojabaza;host=127.0.0.1",
    "uzytkownik",
    "haslo"
);
$db
    ->prepare(
        "UPDATE pliki
        SET pobrania = pobrania + 1
        WHERE url = :url"
    )->execute(
        array("url" => $_GET["url"])
    );

Wadą rozwiązania jest, rzecz jasna, fakt korzystania z JavaScriptu. Jeśli ktoś go wyłączy, automatycznie przestaną być zliczane pobrania.

Nagłówek

Drugi sposób, o którym pomyślałem (i który wdrożyłem), to wykorzystanie skryptu PHP do przesłania żądanego pliku nagłówkiem, przy jednoczesnym zapisaniu informacji w bazie danych.

Link do pobranego pliku, w moim przypadku, był odpowiednio spreparowany. Przechwytywał go router, a aplikacja wywoływała kontroler zajmujący się przesyłaniem plików nagłówkami. Jeśli nie wiesz, jak to działa, zerknij na mój post dotyczący routingów.

Kontroler otrzymywał w URI żądania nazwę pliku do pobrania, jednak plik w rzeczywistości nie istniał pod wskazywaną lokalizacją. Zamiast tego, był trzymany poza zasięgiem użytkownika, w katalogu leżącym poza DOCUMENT_ROOT. Kontroler zapisywał informację o pobraniu pliku do bazy danych (przyjmijmy, że korzystał z przykładowego kodu przytoczonego powyżej), a następnie otwierał prawdziwy plik i wysyłał go użytkownikowi. Kod wyglądał w maksymalnym uproszczeniu tak:

$file = FILE_PATH . "/{$_SERVER["REQUEST_URI"]}";
$filename = array_pop(explode("/", $file));
header("Content-type: application/force-download");
header("Content-Transfer-Encoding: Binary");
header("Content-length: ".filesize($file));
header("Content-disposition: attachment; filename=\"{$filename}\"");
readfile($file);

Co tu się dzieje? Najpierw pobieramy prawdziwą ścieżkę, potem wykrajamy z niej nazwę pliku. Następnie wysyłamy do przeglądarki kilka nagłówków, zmuszając ją do pobierania pliku. Sam plik wysyłamy funkcją readfile().

Warto tu wspomnieć, że sposób, o ile nie pomija użytkowników z wyłączoną obsługą JavaScriptu, o tyle wprowadza inny problem: zareaguje również na boty i skrypty indeksujące stronę. Nie będę wnikał tu w szczegóły, jak zabezpieczyć się przed nadmiernym naliczaniem pobrań, pozwolę sobie tylko polecić zapoznanie się z możliwościami biblioteki PhpBrowscap i ewentualnie rzucenie okiem na ten krótki artykuł (gdzie, tak na marginesie, opisana jest metoda bardzo zbliżona do mojej, tyle, że zamiast stosowania routera, nazwa pliku przekazywana jest jako parametr GET do skryptu wysyłającego plik nagłówkiem).


Napisz komentarz


Szukaj wpisów


Chmura tagów