Добавление функционала экспорта в CSV в мой плагин

28 авг. 2012 г., 01:39:59
Просмотры: 15.1K
Голосов: 2

Мне нужно добавить функционал "экспорт в .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;

    } 

Что я делаю неправильно?

0
Все ответы на вопрос 3
1

Начало того, что вы делаете неправильно, можно объяснить этой цитатой:

атрибут 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 и намеренно выполняет это действие.

28 авг. 2012 г. 01:59:39
Комментарии

Отлично. Я знал, что делаю что-то не так, но ваши объяснения помогли мне разобраться. Спасибо!!

pixeline pixeline
28 авг. 2012 г. 02:31:08
1

Привожу полностью обновленный код.

В обновлении вашего плагина

<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();
    }
  }
}
2 мая 2017 г. 16:46:51
Комментарии

Особая благодарность за часть с add_action("admin_init", "download_csv"); :)

Jasom Dotnet Jasom Dotnet
1 июн. 2017 г. 10:18:16
0

У меня возникли проблемы с вышеупомянутыми методами - 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-заголовки попадают в файл, если использовать код через шорткод или прямой вызов на фронтенде. При использовании в админке / бэкенде все работает идеально.

2 апр. 2019 г. 23:42:07