Добавление функционала экспорта в CSV в мой плагин
Мне нужно добавить функционал "экспорт в .csv" в мой плагин. Проблема в том, что я получаю предупреждение "header already sent", потому что к моменту вызова моей функции экспорта WordPress уже начал рендеринг.
Я организовал это через небольшую форму на экране настроек, где атрибут action указывает на файл (download.csv.php) в папке моего плагина, который не должен отображаться, а только вызывать диалог загрузки файла с csv-дампом.
Вот мой код.
На экране настроек моего плагина:
<form method="post" id="download_form" action="<?php echo plugins_url( 'download.csv.php' , __FILE__ ); ?>">
<input type="hidden" name="download" value="<?php echo get_home_path(); ?>" />
<input type="submit" name="download_csv" class="button-primary" value="<?php _e('Скачать лог (.csv)', $this->localizationDomain); ?>" />
</form>
И файл download.csv.php:
$core = $_POST['download'].'wp-load.php';
if(isset($_POST['download']) && is_file($core)){
require_once( $core );
global $wpdb;
$rows = $wpdb->get_results('SELECT * FROM `'.$this->dbName.'`;',ARRAY_A);
if($rows){
require_once('classes/csvmaker.class.php');
$CSV = new CSVMaker();
$CSVHeader = array();
$CSVHeader['id'] = "ID";
$CSVHeader['when'] = "КОГДА";
$CSVHeader['who'] = "КТО";
$CSVHeader['description'] = "ОПИСАНИЕ";
$CSVHeader['type'] = "ТИП";
foreach($rows as $r){
$CSV->addEntry($r);
}
$file_name = plugin_dir_path(__FILE__).'data.csv';
file_put_contents($file_name,$CSV->buildDoc);
ob_start();
header("Expires: Mon, 1 Apr 1974 05:00:00 GMT");
header("Last-Modified: " . gmdate("D,d M YH:i:s") . " GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
header("Content-type: application/CSV");
header("Content-Disposition: attachment; filename=$file_name.csv");
echo $CSV->buildDoc;
return ob_get_clean();
exit;
}
Что я делаю неправильно?

Начало того, что вы делаете неправильно, можно объяснить этой цитатой:
атрибут action указывает на файл (download.csv.php) в папке моего плагина, который не должен отображаться, а только вызывать диалог загрузки файла для CSV-дампа.
и этим кодом:
$core = $_POST['download'].'wp-load.php';
if(isset($_POST['download']) && is_file($core)){
require_once( $core );
По сути, вы a) выходите за пределы среды WordPress и напрямую обращаетесь к части вашего плагина и b) затем пытаетесь загрузить среду WordPress изнутри этого файла (и делаете это очень небезопасным способом, стоит добавить).
Вместо этого лучше оставаться внутри среды WordPress с самого начала и переопределить вывод так, как вам нужно.
Лучшим подходом будет оставаться в админке, подключиться к хуку admin_init и определить момент, когда вам нужно получить CSV-вывод, и вернуть его вместо обычного содержимого.
Итак, для вашей формы сделайте что-то вроде этого:
<form method="post" id="download_form" action="">
<input type="submit" name="download_csv" class="button-primary" value="<?php _e('Скачать лог (.csv)', $this->localizationDomain); ?>" />
</form>
Обратите внимание, что здесь не используется атрибут action. Это означает, что форма отправляется обратно на ту же страницу админки без изменений. Теперь вы можете обнаружить это в любой функции, подключенной к хуку admin_init, примерно так:
global $plugin_page;
if ( isset($_POST['download_csv']) && $plugin_page == 'whatever' ) {
echo "HELLO"; die;
}
Глобальная переменная $plugin_page будет содержать значение page=whatever, которое есть в обычном экране настроек вашего плагина. Вместо вывода "HELLO", как в этом примере, вызовите функцию в вашем плагине для генерации и правильного вывода CSV с нужными заголовками и завершите выполнение с помощью die. Или что-то в этом роде.
Обратите внимание, что это упрощенный подход и может быть небезопасным. Возможно, вам также стоит добавить проверку nonce и прав пользователя, чтобы убедиться, что пользователь имеет право скачивать этот CSV и намеренно выполняет это действие.

Привожу полностью обновленный код.
В обновлении вашего плагина
<form method="post" id="download_form" action="">
<input type="submit" name="download_csv" class="button-primary" value="<?php _e('Скачать лог (.csv)', $this->localizationDomain); ?>" />
</form>
Теперь в вашем function.php
add_action("admin_init", "download_csv");
function download_csv() {
if (isset($_POST['download_csv'])) {
global $wpdb;
$sql = "SELECT * FROM {$wpdb->prefix}table_name";
$rows = $wpdb->get_results($sql, 'ARRAY_A');
if ($rows) {
$csv_fields = array();
$csv_fields[] = "first_column";
$csv_fields[] = 'second_column';
$output_filename = 'file_name' .'.csv';
$output_handle = @fopen('php://output', 'w');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Description: File Transfer');
header('Content-type: text/csv');
header('Content-Disposition: attachment; filename=' .
$output_filename);
header('Expires: 0');
header('Pragma: public');
$first = true;
// Преобразуем результаты в формат csv
foreach ($rows as $row) {
// Добавляем заголовки таблицы
if ($first) {
$titles = array();
foreach ($row as $key => $val) {
$titles[] = $key;
}
fputcsv($output_handle, $titles);
$first = false;
}
$leadArray = (array) $row; // Преобразуем объект в массив
// Добавляем строку в файл
fputcsv($output_handle, $leadArray);
}
//echo '<a href="'.$output_handle.'">test</a>';
// Закрываем поток вывода файла
fclose($output_handle);
die();
}
}
}

У меня возникли проблемы с вышеупомянутыми методами - add_action. Я не хотел, чтобы плагин выполнял эту функциональность автоматически. Вместо этого мне нужно было административное решение.
1. Обнаружил, что нужно очистить кеш, иначе вывод HTML попадает в CSV-файл.
2. Необходимо завершить процесс с помощью "exit".
Вот мое решение. Измените таблицы и поля в соответствии с вашими потребностями:
function Export()
{
global $wpdb;
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="export.csv"');
// очищаем буфер вывода
ob_end_clean();
$fp = fopen('php://output', 'w');
$header_row = array(
0 => 'метка поля 1',
1 => 'метка поля 2',
2 => 'метка поля 3',
);
fputcsv($fp, $header_row);
$Table_Name = $wpdb->prefix.'YourTable';
$sql_query = $wpdb->prepare("SELECT * FROM $Table_Name", 1) ;
$rows = $wpdb->get_results($sql_query, ARRAY_A);
if(!empty($rows))
{
foreach($rows as $Record)
{
$OutputRecord = array($Record['field1'],$Record['field2'],$Record['field3']);
fputcsv($fp, $OutputRecord);
}
}
fclose( $fp );
exit;
}
ОБНОВЛЕНИЕ: После того как я написал этот код, обнаружилось, что HTML-заголовки попадают в файл, если использовать код через шорткод или прямой вызов на фронтенде. При использовании в админке / бэкенде все работает идеально.
