Здравствуйте, уважаемые подписчики и посетители блога 4remind.ru. В этой заметке я предлагаю вашему вниманию PHP-скрипт, служащий для подсчета количества скачиваний файлов с сайта с использованием базы данных MySQL и применением файла Apache .htaccess. Счетчик может быть полезным для тех, кто выкладывает на своих сайтах файлы для скачивания и хочет вести учет по количеству скачиваний либо конкретного файла, либо всех доступных для скачивания файлов. Например, это могут быть файлы фотографий, плагинов, утилит, приложений, аудио, видео, экранных заставок.
Для ведения статистики кроме PHP-скрипта нам понадобится еще таблица в базе данных MySQL. Но кроме того, нам еще понадобится модуль Apache mod_rewrite
, который должен быть активным. Модуль mod_rewrite доступен на большинстве Apache-серверов, а если нет, то придется его подключить самостоятельно.
Итак, переступим к делу.
Допустим, что наш PHP-скрипт счетчика скачиваний файлов будет называться downloads.php
. Тогда для начала нам нужно будет в файле .htaccess
добавить правило редиректа, как показано в следующем примере:
#Правило редиректа для скрипта счетчика скачиваний файла RewriteEngine on RewriteRule ^(.*).(zip|pdf|rar)$ /downloads.php?file=$1.$2 [R,L]
После добавления этого правила имя каждого файла с расширением .zip, .pdf, .rar будет передаваться в виде GET-параметра для вызова PHP-скрипта. Например, если файл называется bonus.zip, то с учетом установленного нами правила редиректа мы получим на выходе ссылку /downloads.php?file=bonus.zip
. Кроме указанных в примере расширений можно добавлять другие, разделяя их символом вертикальной черты «|».
Для того, чтобы повысить безопасность и чтобы ссылка редиректа не показывалась, в .htaccess нужно использовать только параметр [L] и к ссылке добавить параметр-ключ, например «key=secretkey23709676»:
#Правило редиректа для скрипта счетчика скачиваний файла RewriteEngine on RewriteRule ^(.*).(zip|pdf|rar)$ /downloads.php?file=$1.$2&key=secretkey23709676 [L]
Стоит учесть, что по приведенному выше примеру файл PHP-скрипта downloads.php должен будет находиться в корневой директории сайта. Если же Вы захотите его расположить в другом месте, то в файле .htaccess нужно будет произвести соответствующие изменения. То же самое касается и файлов для скачивания. Если они находятся не в корневой директории, то путь к ним нужно будет указать либо в файле .htaccess
, либо в самом скрипте downloads.php
.
Теперь настала пора создать в базе данных таблицу для учета скачиваний файлов. Открываем phpMyAdmin
, выбираем нужную нам базу данных и выполняем в ней следующий SQL-запрос:
CREATE TABLE `downloads` ( `filename` varchar(255) NOT NULL, `dcount` int(11) NOT NULL, PRIMARY KEY (`filename`) )
Теперь необходимая нам таблица в базе данных создана и пора на сайт закачать файл downloads.php
. Вот код PHP-скрипта этого файла:
0) { $qry = "UPDATE downloads SET dcount = dcount + 1 WHERE filename = '" . $filename . "'"; } else { $qry = "INSERT INTO downloads (filename, dcount) VALUES ('" . $filename . "', 1)"; } $statresult = mysql_query($qry); // теперь отдаем файл по частям $fsize = filesize($pathtofile); $pathtofile_parts = pathinfo($pathtofile); header("Content-type: application/octet-stream"); header('Content-Disposition: filename="'.$pathtofile_parts["basename"].'"'); header("Content-length: $fsize"); header("Cache-control: private"); // используйте это для открытия файла напрямую while(!feof($fdwn)) { $download_buffer = fread($fdwn, 2048); // буфер для скачивания файла частями echo $download_buffer; } } fclose ($fdwn); exit; ?>
Только не забудьте в первых строках скрипта заменить значения localhost
, username
, password
и dbname
на ваши для доступа к вашей базе данных. Да, и еще, Вы наверно заметили, что для повышения безопасности выше рекомендовалось в .htaccess добавлять к запросу параметр «key=secretkey23709676» (значение Вы сами свое конечно придумаете). Поэтому и в скрипте учтена проверка этого параметра-ключа в первых строках.
В примерах показаны коды только для базового ознакомления с алгоритмом, но Вы можете модифицировать их под свои конкретные нужды.
Для тех, кто уже хорошо знаком с командами запросов MySQL, один из посетителей блога по имени Олег предложил сократить количество запросов к базе данных до одного, вот так:
$qry = "INSERT INTO downloads (filename, dcount) VALUES ('" . $filename . "', 1) ON DUPLICATE KEY UPDATE dcount= (dcount+1)";
В этом запросе ON DUPLICATE KEY
означает: при дублировании ключевого поля (в нашем случае ключевое поле `filename`, так как при создании таблицы указано: PRIMARY KEY = `filename`) увеличить на 1 числовое (int) поле `dcount`.
Удачи Вам в использовании счетчиков скачиваний файлов на языке PHP и использования .htaccess и MySQL!
Примечание
В некоторых случаях счетчик скачивания файлов может увеличиваться не на единицу, а на две (или больше, но это уж очень редкий случай), и связано это как правило с использованием мульти-поточных качалок типа Download Master, FlashGet и тому подобных.
Способы решения подобной проблемы Вы можете просмотреть в комментариях к этой статье. Ну а если у Вас есть свои решения, то поделитесь пожалуйста ими, добавив код в комментарии. Многие будут за это Вам благодарны.
Полезный скрипт! Попробую прикрутить к своему форуму. Правда вот не совсем понял отличий в параметрах .htaccess в строке RewriteRule [R,L] и [L]. В чем будет разница и насколько это важно?
Xsaika, параметр, а точнее флаг [R] означает, что URL будет перенаправлен и будет отображаться в браузере, т.е. будут видны и GET-параметры в строке запроса, где будет виден и приведенный в примере секретный ключ &key=secretkey23709676. Это конечно недопустимо, в плане безопасности. Поэтому, чтобы скрыть от глаз пользователя перенаправление, рекомендую убрать из набора [R,L] флаг R и оставить только [L].
Флаг [L] предотвращает переопределение нашего правила следующими строками с правилами в файле .htaccess, если конечно такие там есть. Его в принципе тоже можно не добавлять, если у вас больше нет правил с директивой RewriteRule.
Спасибо за скрипт. Попробовал его внедрить (с небольшими изменениями). Если можно несколько вопросов
1. Команда $mainpath = $_SERVER['DOCUMENT_ROOT'].’/’; — возвращает неправильный путь. Пришлось прописать вручную
2. SQL-запрос почему-то вписывает в базу 2 строки, я так понимаю выполняется 2 раза (по времени разница 2-3 сек) не знаю как отследить где он еще раз вызывается…
3. У меня по умолчанию закачку перехватывает программа-качалка. Можно ли как-то отследить закачал пользователь файл или отменил?
Андрей, постараюсь ответить по порядку:
1. затрудняюсь сказать что у вас не так, возможно все зависит от того, где файл находится;
2. это происходит именно потому, что дважды вызывается скрипт, т.к. у вас происходит перехват закачки программой-качалкой (см. ответ в п.3);
3. попробуйте использовать функцию «connection_aborted()» в пределах цикла «while», а процесс записи счетчика в БД перенесите в место после цикла «while»:
В этом случае (по идее) скрипт не будет увеличивать счетчик и добавлять в базу 2 строки, если программа-качалка перехватила загрузку.
Возможно еще нужно будет уменьшить размер буфера в строке «$download_buffer = fread($fdwn, 2048);», если файлы у вас размером меньше 2048 байт.
Да, и еще можно подправить одну строчку формирования заголовка в скрипте на:
Большое спасибо за столь скорый и основательный ответ. Проверил, действительно при загрузке средствами браузера — добавляется одна строчка. Качалкой — 2. Тег «Attachment» привел к тому, что по крайней мере из Оперы не происходит перехват на качалку (это сомнительное достижение, все таки многие предпочитают качать именно качалкой и могут забивать адрес вручную). А при скачке через даунлоад-мастер все равно пишет 2 строки (код «if (connection_aborted()) — не помог»). Запись в базу я ставил изначально после цикла while, изменение размера буфера тоже безрезультатно (тренировался на файле в 1 МБ)…
Попробовал сделать так:
Итог: все равно в базе 2 записи после качалки — скрипт исполняется 2 раза. Почему? Как Предотвратить?
Немного разобрался, но не знаю насколько красиво. Проблема с качалкой в том, что любая качалка пытается качать в несколько потоков (думаю на больших файлах можно и 5 записей получать), а скрипт исполняется на каждый такой запрос от качалки. Сначала пробовал блокировать одновременную скачку с одного IP, но так явно не красиво. В результате остановился на варианте отслеживания запроса от клиента на закачку не с нулевой позиции.
В итоге империческим путем удалось установить, что кроме более правильного подсчета теперь наш скрипт поддерживает докачку.
Замеченные проколы:
- если на маленьком файле (1МБ) при закачке через браузер попробовать отменить закачку — счетчик сработает — видимо браузер успевает откешировать.
- если качая через даунлоад-мастер остановить закачку до начала собственно закачки, а потом продолжить ее — счетчик будет инкрементирован 2 раза. Если остановить на середине — то только один раз — не помогает ни анализ if connection_aborted ни попытка подсчета частей.
Владимиру спасибо за скрипт. Не знаю насколько ценно мое дополнение, но можно хотя бы указать в статье об обнаруженных проблемах
Андрей,
спасибо, что поделились своими соображениями и скриптом!
Я думаю ваше решение найдет применение (мне например оно интересно).
Наиболее подходящего и универсального решения я все же пока так и не нашел, но от себя могу предложить вот примерно такую схему работы:
1. при первом обращении к скрипту сам скрипт добавляет в БД «индикатор», например значение = 1 и время, включая секунды;
2. как только закачка завершилась, то счетчик скачиваний увеличиваем на единичку, а если соединение оборвалось, то в БД «индикатор» сбрасывается в значение = 0, но счетчик закачек не увеличиваем;
3. при каждом обращении к скрипту, сам скрипт проверяет значение и время у «индикатора» в БД, и если время например не превысило 5 секунд или значение = 1, то процесс скачивания скрипт не запускает (т.е. счетчик скачиваний в БД не меняет значение);
Однако у предложенного мной метода тоже есть изъяны — например счетчик не будет меняться, если файл одновременно закачивался с разных компов в течении установленного интервала времени. Хотя и для этого можно добавить проверку например User-Agent, IP или еще что-то…
Андрей писал: «но можно хотя бы указать в статье об обнаруженных проблемах«.
-> примечание о возможных проблемах уже добавлено в конце статьи.
А как сделать, чтоб можно было передать в ссылке на файл дополнительные GET параметры?, чтоб их .htaccess передал скрипту. например ссылка на скачивание выглядела бы так: site.ru/downloads/superfile.zip?user=memberuser , а ешо лучше просто ключ без параметра: site.ru/downloads/superfile.zip?memberuser
чую что в htaccess но он для меня тёмный less :)
Вроде сам допёр, вот такая строчка вышла:
RewriteRule ^(.*).(zip|pdf|rar)&(.*)$ /downloads.php?file=$1.$2&$3&key=secretkey123123 [L]
Наверно для безопасности ешо регулярное выражение какоенить вставить надо в скобки вместо .* В чем я так-же разбираюсь как в htaccess :)
Конечно можно и регулярку вставить вместо * , но это зависит от ваших требований к ссылке, запросу который обрабатывается с помощью RewriteRule.
Например чтобы имена файлов содержали ТОЛЬКО цифры и латинские буквы, то вместо первой звездочки с точкой в скобках добавьте вот это:
([a-zA-Z0-9]+)
Вместо:
одним запросом:
нечего базу данных по 3 раза дергать из-за 1 значения
Отлично, Олег! Спасибо за код! В примерах в статье было специально по частям все сделано, и именно для того, чтобы новичкам было легче понять как, что и когда происходит.
тогда напишите пользователям, что ON DUPLICATE KEY означает: при дублировании ключевого поля (в нашем случае ключевое поле `filename`, так как при создании таблицы указано: «PRIMARY KEY (`filename`) ) увеличить на 1 числовое(int) поле `dcount`»
Спасибо, Олег. Я добавил в конце статьи эту информацию со ссылкой на Ваш комментарий.
Ещё, с htacces-ом я не совсем понял, зачем secrtkey. Если боязнь, что переход на страницу download.php не с Вашего сайта, а, скажем, с локальной страницы (счётчик-то всё равно увеличится) поможет $_SERVER['HTTP_REFERER']:
если его хост не соответствует хосту вашего сайта, ip внести в чёрный список и досвидос:
header(‘location:anywhere’);
Ну, это уже немного другая тема, то есть про черные списки и т.д. На сайтах с высокой активностью посетителей можно по ошибке или случайности надобавлять в черный список целые сети… Может позже напишу об этом.
Ещё, извиняюсь за упорство, как узнать реальное количество закачек? Если чел перешел на страницу downloads, не факт, что он дождался загрузки(я сам частенько после начала скачки, особенно, когда сперва не понял, что это- ссылка на загрузку, обрываю закачку).
Уведомление по завершении загрузки, имхо, важнее (это Вам по силам?)
Для этого думается мне можно немного изменить концовку скрипта. Там в цикле «while( !feof($fdwn) )» есть проверка «if (connection_aborted())», так вот запрос в БД нужно перенести вниз, чтобы счетчик в БД НЕ увеличивался, если был обрыв или отмена закачки.
А насколько важно пользователя еще предупреждать, что закачка завершилась? Ведь большинство качают не из консоли, а браузерными качалками и прочими, которые показывают закачался ли фал или нет. Даже «wget» показывает процесс закачки в консоли.
Хотя, в принципе, если уж кому захочется, то можно и показать сообщение, и опять же, конечный статус закачки можно получать в том же «while( !feof($fdwn) )».
Еще будет лучше задействовать переменные, которые будут отражать текущий статус, чтобы не дергать лишний раз функции.
Отличный скрипт! не поможете доработать? как в базе данных сделать так чтобы было видно дату и время последней загрузки, и по возможности как сделать чтобы данные из базы mysql отображались на какой нибудь HTML странице? Буду очень благодарен если поможете !!!
Александр, для этого нужно просто добавить еще одно поле в таблицу БД, например поле `date`:
В скрипте подправить/добавить заполнение поля `date` (с помощью функции MySQL NOW()) в таблице `downloads`:
Для вывода на страницу сайта всех данных можно применить вот такой скрипт:
В этом скрипте подразумевается, что он находится в файле PHP и Вы уже подключились к нужной БД заранее.
Владимир спасибо за помощь, с датой и выводом данных разобрался, одно только но при добавлении поля `date` (с помощью функции MySQL NOW()) в таблице `downloads`, перестает работать счетчик закачек, показывает постоянно единицу:
Александр, в моем комментарии (смотри выше) была ошибка, поэтому там нужно заменить строки:
на одну:
Тот комментарий я уже тоже подправил (см. выше).
Спасибо! Отличный сайт и статья! Владимиру огромное спасибо за помощь в доработке скрипта (вывод данных и сортировку)!
А зачем это:
когда и так переадресация на скрипт будет только если выполнится условие которое в файле .htaccess и там указаны расширения при обращении к которым будет происходить редирект на скрипт, как начало для переделки скриптик пойдёт, но для многих новичков будет многое приводить в умственный тупик. То же самое и ключ, так и не понял зачем он тем более в файле .htaccess когда и так понятно что если редирект в этом файле не сработает то и весь скрипт не отдаст файл и не увеличит запись в базе.
Если можно объясните 2 этих момента.
Андрей, эта часть кода конечно не обязательна для использования и ее можно удалить из скрипта, однако она может понадобиться в том случае, если Вы например захотите временно или на постоянно ограничить список расширений файлов, обрабатываемых этим скриптом, но по какой-то причине у Вас отсутствует доступ к .htaccess. Ключ тоже не обязателен, а нужен он для того, чтобы скрипт счетчика скачивания не работал по прямыми ссылкам к файлам без знания ключа (по ссылкам, в которых расширение файла может являться не концом строки запроса).
Не допер как изменить filename.
Хочу чтобы субстрингилось расширение и не было полного пути.
То есть чтобы оставалось только название файла. Ибо геморно каждый раз из базы эксплоудить слеши и пробелы
Всю ночь я провоевал с этим чудом.
В общем какого то черта не воспринимает файлы какие угодно кроме exe.
Какого то черта не вытаскивает значения с varchar.
В связи с чем применен md5.
на запись
на вывод
салат,
собственно не так и сложно выделить имя файла с расширением из полного пути:
А что там у Вас с файлами «exe»? Напишите подробнее, а то как-то не совсем понятно «какого то черта не воспринимает файлы какие угодно кроме exe».
Я решил поеврейничать:) Может позже опишу у себя на сайте.
Потому что очень много времени убил на это все дело.
Скажу только, что я отказался от htaccess и теперь явой определяю мемтайп и передаю аяксом все данные в «download.php» через класс.
В результате чего ограничил количество соединений до одного.
Т.е. на странице сразу же получаем и вывод из базы и записываем если это нужно.
+ админку написал для слежения и редактирования данных.
За наводку на правильные мысли все равно спасибо)
Думал что найду что то готовое, но нифига готовых скриптов нету. Пришлось вот самому писать.
З.Ы.
Странно у вас комменты работают при первом субмите ничего не отправляется и форма очищается. Хорошо что скопировал сообщение..
Салат, если выложите у себя на сайте ваше решение, то бросьте ссылочку, интересно будет взглянуть.
Кстати, поосторожнее с Ajax, старайтесь аккуратнее принимать входящие запросы, другими словами обрабатывайте их, эскейпите и так далее, чтобы избежать проломов SQL-инжекциями.
З.Ы.
Проверил, вроде нормально работают комментарии. А каким браузером Вы пользуетесь?
Здравствуйте
Сделал все как сказано. Правда убрал $mainpath = $_SERVER['DOCUMENT_ROOT'].’/’; на localhost работает как часы швейцарские, выкладываю на сервер пишет «Страница на _http://portal-holod.ru/controllers/Dixell_XR_60.zip временно недоступна или перемещена на новый веб-адрес.» Пробовал возвращать в код $mainpath, результата не дало. Пролазил интернет не чего вразумительного одни предположения. Помогите разобраться.
Начал делать, но столкнулся с проблемой.
Сам файл htaccess у меня выглядит так:
php_value error_reporting E_NONE
php_value display_errors 1
php_value display_startup_errors 1
DirectoryIndex /core/CreatePage.php
RewriteEngine On
RewriteRule ^(.*).(zip|pdf|rar)$ /core/download.php?file=$1.$2 [R,L]
RewriteCond %{REQUEST_URI} admin.*$
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^admin/?.* /admin/index.php [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ./* /core/CreatePage.php [L]
Когда я пытаюсь скачать файл так: _http://домен/core/download.php?file=file/123.rar то счетчик увеличивается
При попытке скачать файл _http://домен/file/123.rar то счетчик не увеличивается.
В обоих случаях закачка идет успешно. Что я делаю не так?
Валентин, попробуйте убрать ключ внешнего редиректа «R» из списка ключей [R,L], т.е. сделайте вот такое перенаправление ссылок на файлы:
К сожалению не помогло
Тогда попробуйте перенести скрипт download.php в корень сайта и измените редирект:
Еще, как вариант, можно добавить например логирование в скрипт download.php, чтобы точно знать, какие параметры ему передаются.
Возможно скрипт ошибочно обрабатывает путь к файлу после редиректа, например на вход скрипту попадает путь /file/123.rar, а скрипт возможно ожидает такой file/123.rar, т.е. возможно в скрипте учитывается или наоборот не учитывается слэш в начале пути или что-то подобное.
Неа. Скрипт не при чем. Сейчас проверил, не происходит переадресация. Перенос скрипта в корень тоже не помог.
Валентин, странно как-то и затрудняюсь тогда еще что-то посоветовать, разве что попробуйте к примеру переместить строку редиректа в конец файла .htaccess или временно убрать из него другие редиректы и для теста проверить например такое:
Где файл test.html будет обычной HTML-страничкой с любым контентом, расположенным в корне сайта. В таком случае, при правильно сработавшем редиректе, клик по ссылке на файлы zip, pdf, rar должен перенаправлять на страницу /test.html
Вообщем получается так что если файл существует то он сразу же кидает на него, а если файл не существует то тогда срабатывает редирект на страницу загрузки. Как приоритет выставить можно?)
Возможно у тебя сервер на Apache + Nginx и хостер настроил статическую отдачу некоторых форматов файлов, следовательно апач их не обрабатывает.
Да так и было. Обратился к хостинг провайдеру, он мне перенастроил как надо