Как можно принудительно скачать файл в панели администратора WordPress?
Я хотел бы добавить кнопку "Нажмите для скачивания" в один из моих плагинов WordPress, и я не уверен, какой хук использовать. На данный момент, подключение к 'admin_init' с этим кодом, похоже, работает:
// Установка типа содержимого как скачиваемый файл
header("Content-type: application/x-msdownload");
// Установка заголовка для скачивания с именем файла data.csv
header("Content-Disposition: attachment; filename=data.csv");
// Отключение кэширования
header("Pragma: no-cache");
// Установка времени истечения
header("Expires: 0");
// Вывод данных
echo 'данные';
// Завершение выполнения скрипта
exit();
Это вроде бы работает, но я хотел бы узнать, есть ли какие-то лучшие практики для этого.
Спасибо, Dave

Если я правильно вас понял, вы хотите иметь URL примерно следующего вида, ответом на который в браузере будет сгенерированное вами содержимое, то есть ваш файл .CSV
, без какого-либо сгенерированного контента от WordPress?
http://example.com/download/data.csv
Я думаю, вам нужен хук 'template_redirect'
. Вы можете найти 'template_redirect'
в файле /wp-includes/template-loader.php
, с которым должны быть знакомы все разработчики WordPress; он короткий и простой, а также обрабатывает каждый запрос неадминистративной страницы, поэтому обязательно изучите его.
Просто добавьте следующее в файл functions.php
вашей темы или в другой файл, который вы подключаете через include
в functions.php
:
add_action('template_redirect','yoursite_template_redirect');
function yoursite_template_redirect() {
if ($_SERVER['REQUEST_URI']=='/downloads/data.csv') {
header("Content-type: application/x-msdownload",true,200);
header("Content-Disposition: attachment; filename=data.csv");
header("Pragma: no-cache");
header("Expires: 0");
echo 'data';
exit();
}
}
Обратите внимание на проверку URL '/downloads/data.csv'
через $_SERVER['REQUEST_URI']
. Также обратите внимание на добавленные параметры ,true,200
в вызов header()
при установке Content-type
; это связано с тем, что WordPress уже установил код состояния 404
"Не найдено", так как не распознал URL. Однако это не проблема, так как параметр true
указывает header()
заменить 404
, установленный WordPress, на HTTP-код 200
"OK".
Вот как это выглядит в FireFox (Примечание: на скриншоте нет виртуальной директории /downloads/
, потому что после создания и аннотирования скриншота показалось хорошей идеей добавить виртуальную директорию '/downloads/'
):
(источник: mikeschinkel.com)
ОБНОВЛЕНИЕ
Если вы хотите, чтобы загрузка происходила по URL с префиксом /wp-admin/
, чтобы визуально показать пользователю, что она защищена входом в систему, вы также можете это реализовать; описание одного из способов приведено ниже.
На этот раз я инкапсулировал код в класс под названием DownloadCSV
и создал пользовательскую "возможность" 'download_csv'
для роли 'administrator'
(подробнее о ролях и возможностях можно прочитать здесь). Вы также можете использовать стандартную возможность 'export'
, если хотите, просто замените 'download_csv'
на 'export'
и удалите вызов register_activation_hook()
и функцию activate()
. Кстати, необходимость в хуке активации — одна из причин, по которой я перенес этот код в плагин вместо того, чтобы оставлять его в файле functions.php
темы.*
Я также добавил пункт меню "Загрузить CSV" в раздел "Инструменты" с помощью add_submenu_page()
и связал его с возможностью 'download_csv'
.
Наконец, я выбрал хук 'plugins_loaded'
, так как это самый ранний подходящий хук. Вы можете использовать 'admin_init'
, но этот хук вызывается гораздо позже (1130-й вызов хука против 3-го), так зачем заставлять WordPress выполнять лишнюю работу? (Я использовал свой плагин Instrument Hooks, чтобы определить, какой хук использовать.)
В хуке я проверяю, начинается ли URL с /wp-admin/tools.php
, анализируя переменную $pagenow
, убеждаюсь, что current_user_can('download_csv')
, и если все в порядке, проверяю $_GET['download']
на наличие значения data.csv
; если да, запускается практически тот же код, что и раньше. Я также убрал параметры ,true,200
из вызова header()
в предыдущем примере, так как здесь WordPress знает, что URL корректный, и еще не установил статус 404. Вот ваш код:
<?php
/*
Plugin Name: Download CSV
Author: Mike Schinkel
Author URI: http://mikeschinkel.com
*/
if (!class_exists('DownloadCSV')) {
class DownloadCSV {
static function on_load() {
add_action('plugins_loaded',array(__CLASS__,'plugins_loaded'));
add_action('admin_menu',array(__CLASS__,'admin_menu'));
register_activation_hook(__FILE__,array(__CLASS__,'activate'));
}
static function activate() {
$role = get_role('administrator');
$role->add_cap('download_csv');
}
static function admin_menu() {
add_submenu_page('tools.php', // Родительское меню
'Загрузить CSV', // Заголовок страницы
'Загрузить CSV', // Название пункта меню
'download_csv', // Возможность
'tools.php?download=data.csv');// URL относительно /wp-admin/
}
static function plugins_loaded() {
global $pagenow;
if ($pagenow=='tools.php' &&
current_user_can('download_csv') &&
isset($_GET['download']) &&
$_GET['download']=='data.csv') {
header("Content-type: application/x-msdownload");
header("Content-Disposition: attachment; filename=data.csv");
header("Pragma: no-cache");
header("Expires: 0");
echo 'data';
exit();
}
}
}
DownloadCSV::on_load();
}
И вот скриншот активированного плагина:
(источник: mikeschinkel.com)
Наконец, вот скриншот запуска загрузки:
(источник: mikeschinkel.com)

Майк, спасибо за помощь. Единственный нюанс с этой функцией в том, что я хочу, чтобы файл скачивался из бэкенда. Похоже, что template_redirect не работает в бэкенде, и если мне не следует использовать admin_init, интересно, что использовать вместо него. admin_init вроде бы работает для меня сейчас, возможно, я оставлю его, по крайней мере, на короткий срок. Это второстепенная функция, которой будут пользоваться только несколько человек.

@Dave Morris - Можете уточнить, что вы подразумеваете под "бэкендом"? Вы имеете в виду на сервере? Если да, то 'template_redirect'
определенно выполняется на сервере. Если нет, я совсем запутался; не могли бы вы прояснить вопрос? Заранее спасибо.

@Dave: Если под "бэкендом" вы имеете в виду админку, это все равно будет работать. URL для скачивания начинается с /downloads/data.csv
, что является несуществующим файлом, поэтому WordPress "фронтенд" обработает этот запрос и в итоге достигнет template-redirect
. Вы просто создаете ссылку в админке, которая ведет на этот фронтенд URL. (Стоит отметить, что таким образом вы не получаете защиту авторизации в админке бесплатно — любой, кто знает URL, сможет скачать файл, но возможно, есть простой способ это исправить?)

@Jan Fabry - А, теперь я понял. Под "бэкендом" он имел в виду админку, верно? Он может использовать функцию current_user_can()
с приведённым выше кодом или выбрать другой подход. После этого комментария я обновлю свой ответ.

Да, извините, я не получаю email-уведомления с этого сайта, поэтому задержка с ответом. Я действительно имел в виду админку WordPress, когда говорил "бэкенд". Прошу прощения. Попробую использовать template_redirect и посмотрю, что получится. Спасибо! ~Dave

@Dave Morris - template_redirect
больше подходит для внешнего доступа. Попробуй второй пример, который я привёл.

Это отлично. Вы избавили меня от множества проблем. Я просто хочу уточнить для других, кому может не понадобиться страница подменю, что всё, что нужно сделать — это подключить метод класса DownloadCSV
admin_menu
к хуку действия с вызовом add_action('admin_menu',array(__CLASS__,'admin_menu'));
и добавить ваш код для принудительной загрузки в метод.

еще один полезный плагин для экспорта в CSV. может кому-то пригодиться
<?php
class CSVExport
{
/**
* Конструктор
*/
public function __construct()
{
if(isset($_GET['download_report']))
{
$csv = $this->generate_csv();
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private", false);
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"report.csv\";" );
header("Content-Transfer-Encoding: binary");
echo $csv;
exit;
}
// Добавляем дополнительные пункты меню для администраторов
add_action('admin_menu', array($this, 'admin_menu'));
// Создаем конечные точки
add_filter('query_vars', array($this, 'query_vars'));
add_action('parse_request', array($this, 'parse_request'));
}
/**
* Добавляем дополнительные пункты меню для администраторов
*/
public function admin_menu()
{
add_menu_page('Скачать отчет', 'Скачать отчет', 'manage_options', 'download_report', array($this, 'download_report'));
}
/**
* Разрешаем использование пользовательских переменных запроса
*/
public function query_vars($query_vars)
{
$query_vars[] = 'download_report';
return $query_vars;
}
/**
* Обрабатываем запрос
*/
public function parse_request(&$wp)
{
if(array_key_exists('download_report', $wp->query_vars))
{
$this->download_report();
exit;
}
}
/**
* Скачивание отчета
*/
public function download_report()
{
echo '<div class="wrap">';
echo '<div id="icon-tools" class="icon32">
</div>';
echo '<h2>Скачать отчет</h2>';
//$url = site_url();
echo '<p>Экспорт пользователей';
}
/**
* Конвертация данных в CSV
*/
public function generate_csv()
{
$csv_output = '';
$table = 'users';
$result = mysql_query("SHOW COLUMNS FROM ".$table."");
$i = 0;
if (mysql_num_rows($result) > 0) {
while ($row = mysql_fetch_assoc($result)) {
$csv_output = $csv_output . $row['Field'].",";
$i++;
}
}
$csv_output .= "\n";
$values = mysql_query("SELECT * FROM ".$table."");
while ($rowr = mysql_fetch_row($values)) {
for ($j=0;$j<$i;$j++) {
$csv_output .= $rowr[$j].",";
}
$csv_output .= "\n";
}
return $csv_output;
}
}
// Создаем экземпляр плагина
$csvExport = new CSVExport();

Хук admin_init или хук load-(page) кажется работают, WordPress в этом состоянии еще не установил заголовки. Я использую хук load-(page), потому что он срабатывает при загрузке страницы админ-меню. Вы можете загружать свой скрипт для конкретной страницы.
Вы можете проверить хук load-(page) в WordPress Codex
Если вы используете хук admin_init, убедитесь, что проверяете nonce с помощью check_admin_referer, иначе другие скрипты могут пройти условие и вывести ваш файл для скачивания.
