Functions: fputcsv, fputs, fopen, fclose
Result:

Плагин для выкачки в CSV формате всех CPT Experience со всеми acf полями.
Code:
<?php
/*
Plugin Name: Experience CSV Export/Import
Description: Allows admin to export and import 'experience' CPT with ACF fields via CSV.
Version: 1.0
Author: **
*/
add_action('admin_menu', function () {
add_submenu_page(
'tools.php',
'Export/Import Experiences',
'Experiences CSV',
'manage_options',
'experience-csv',
'render_experience_csv_page'
);
});
add_action('admin_post_export_experiences_csv', function () {
if (!current_user_can('manage_options') || !check_admin_referer('export_experiences_csv')) {
wp_die('Access denied or invalid nonce.');
}
$acf_fields = get_acf_field_keys_for_experience();
export_experiences_csv($acf_fields);
});
function render_experience_csv_page()
{
if (!current_user_can('manage_options')) {
wp_die('Access denied');
}
$acf_fields = get_acf_field_keys_for_experience();
echo '<div class="wrap"><h1>Export/Import Experiences</h1>';
// Export
echo '<h2>Export CSV</h2>';
echo '<form method="post" action="' . admin_url('admin-post.php') . '">';
echo '<input type="hidden" name="action" value="export_experiences_csv">';
echo wp_nonce_field('export_experiences_csv', '_wpnonce', true, false);
submit_button('Download CSV');
echo '</form>';
// Import
echo '<h2>Import CSV</h2>';
echo '<form method="post" enctype="multipart/form-data">';
echo '<input type="file" name="csv_file" accept=".csv" required>';
submit_button('Upload CSV');
echo '</form>';
echo '</div>';
// Handle Import
if (!empty($_FILES['csv_file']['tmp_name'])) {
import_experiences_csv($_FILES['csv_file']['tmp_name'], $acf_fields);
}
}
function export_experiences_csv($acf_fields)
{
header('Content-Type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment; filename="experiences.csv"');
$output = fopen('php://output', 'w');
// Добавим BOM для Excel
fputs($output, chr(239) . chr(187) . chr(191));
fputcsv($output, array_merge(['ID', 'Title'], $acf_fields));
$experiences = get_posts([
'post_type' => 'experience',
'posts_per_page' => -1,
'post_status' => 'publish'
]);
foreach ($experiences as $post) {
$row = [$post->ID, $post->post_title];
foreach ($acf_fields as $field) {
$value = get_field($field, $post->ID);
// Если массив (например, галерея, repeater) — сериализуем в JSON
if (is_array($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
}
// Добавим в строку
$row[] = $value;
}
fputcsv($output, $row);
}
fclose($output);
exit;
}
function import_experiences_csv($file, $acf_fields)
{
$handle = fopen($file, 'r');
$header = fgetcsv($handle);
while ($row = fgetcsv($handle)) {
$data = array_combine($header, $row);
$post_id = (int) $data['ID'];
if (get_post_type($post_id) === 'experience') {
wp_update_post([
'ID' => $post_id,
'post_title' => sanitize_text_field($data['Title'])
]);
foreach ($acf_fields as $field) {
if (isset($data[$field])) {
update_field($field, $data[$field], $post_id);
}
}
}
}
fclose($handle);
echo '<div class="notice notice-success"><p>Import completed successfully.</p></div>';
}
function get_acf_field_keys_for_experience() {
$acf_fields = [];
$groups = acf_get_field_groups(['post_type' => 'experience']);
foreach ($groups as $group) {
$fields = acf_get_fields($group['key']);
if (!$fields) continue;
foreach ($fields as $field) {
if (empty($field['name']) || $field['type'] === 'message') {
continue;
}
if (in_array($field['type'], ['group', 'repeater'])) {
if (!empty($field['sub_fields'])) {
foreach ($field['sub_fields'] as $sub_field) {
if (!empty($sub_field['name'])) {
$acf_fields[] = $field['name'] . '_' . $sub_field['name'];
}
}
}
} elseif ($field['type'] === 'clone') {
foreach ($field['clone'] as $cloned_group_key) {
$cloned_fields = acf_get_fields($cloned_group_key);
if (!$cloned_fields) continue;
foreach ($cloned_fields as $cloned_field) {
if (!empty($cloned_field['name'])) {
$acf_fields[] = $cloned_field['name'];
}
}
}
} else {
$acf_fields[] = $field['name'];
}
}
}
return array_unique($acf_fields);
}
Общая цель
Создать CSV-файл с данными из WordPress и отдать его браузеру для скачивания.
1. 📥 Получение запроса от пользователя
Когда админ нажимает кнопку “Download CSV”, в WordPress отправляется POST-запрос на:
/wp-admin/admin-post.php?action=export_experiences_csv
WordPress вызывает соответствующий обработчик:
add_action('admin_post_export_experiences_csv', function () {
export_experiences_csv($acf_fields);
});
Дальше запускается функция export_experiences_csv(), которая и создаёт файл.

Вот наш action указан:

admin-post.php — это встроенный обработчик WordPress, который позволяет выполнять кастомные действия (например, экспорт, импорт, генерацию файла) через HTTP-запрос.
Как это работает.
Ты создаёшь форму:
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>">
<input type="hidden" name="action" value="export_experiences_csv">
🔹 Когда пользователь нажимает кнопку — отправляется POST-запрос на URL:
/wp-admin/admin-post.php?action=export_experiences_csv
WordPress ловит параметр action
- WordPress по умолчанию ищет
$_POST['action']или$_GET['action'] - И вызывает специальный хук:
do_action("admin_post_{$action}");
В нашем случае:
do_action("admin_post_export_experiences_csv");
📤 Установка заголовков (header)
header('Content-Type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment; filename="experiences.csv"');
Что делают эти заголовки?
Content-Type: text/csv; charset=UTF-8
🔹 Сообщает браузеру, что это CSV-файл в кодировке UTF-8.Content-Disposition: attachment; filename="..."
🔹 Указывает браузеру не отображать файл в окне, а запустить загрузку, предлагая сохранить файл под указанным именем (experiences.csv)

📄 Открытие потока в браузер (fopen)
$output = fopen('php://output', 'w');
Что это значит?
'php://output'— специальный поток в PHP, который выводит данные напрямую в браузер.'w'— режим “запись”.
👉 С этого момента всё, что мы запишем в $output, будет отправлено клиенту.
✅ BOM для Excel (fputs)
Что это делает?
- Записывает BOM (Byte Order Mark) — специальные байты
EF BB BFв начале файла. - Это помогает Excel правильно распознать кодировку UTF-8, особенно для кириллицы и юникода.
5. 🧱 Запись данных (fputcsv)
Сначала — заголовки колонок:
fputcsv($output, ['ID', 'Title', 'Field1', 'Field2']);
fputcsvавтоматически:- Экранирует значения.
- Разделяет их запятыми.
- Добавляет
\nв конце.
Потом — строки данных:
foreach ($posts as $post) {
$row = [$post->ID, $post->post_title, ...];
fputcsv($output, $row);
}
Это превращает массив PHP в строку CSV, отправляемую в браузер.
🔚 Закрытие потока и завершение (fclose, exit)
fclose($output);
exit;
fclose($output)— закрывает потокphp://output, хотя это не обязательно, но хорошая практика.exit— завершает выполнение скрипта, чтобы WordPress не добавил лишний HTML после CSV.
✅ Результат
📎 Пользователь получает чистый CSV-файл, который:
- скачивается автоматически,
- открывается в Excel без ошибок кодировки,
- содержит правильно структурированные данные.

Основные функции и их роль:
| Функция | Назначение |
|---|---|
header() | Устанавливает заголовки HTTP, чтобы браузер знал, как обрабатывать файл |
fopen() | Открывает поток вывода в браузер |
fputs() | Записывает BOM (спецсимволы для UTF-8) |
fputcsv() | Преобразует массив в строку CSV и пишет в поток |
fclose() | Закрывает поток вывода |
exit | Завершает скрипт, чтобы браузер не получил лишнего контента |