My Blog
WordPress Media Library CSV Report με AJAX Export για Original Images
Σε αυτό το άρθρο δείχνω πώς μπορούμε να δημιουργήσουμε ένα custom WordPress admin εργαλείο το οποίο κάνει export σε CSV όλες τις εικόνες του Media Library, εστιάζοντας στα original image files και όχι στα generated sizes / παραλλαγές.
Ο στόχος είναι να έχουμε ένα πρακτικό report το οποίο μπορεί να χρησιμοποιηθεί για προσφορά, έλεγχο περιεχομένου ή οργανωμένη αντικατάσταση εικόνων σε υπάρχον website.
Τι περιλαμβάνει το report
Για κάθε εικόνα του media library, το export μπορεί να περιλαμβάνει:
- Το πλήρες URL της εικόνας
- Το όνομα αρχείου
- Το Alt Text
- Τη σελίδα / post / link όπου εμφανίζεται
- Την ημερομηνία που προστέθηκε στο σύστημα
Γιατί χρειάστηκε AJAX προσέγγιση
Σε μικρά site ίσως αρκεί ένα απλό PHP export σε ένα μόνο request. Σε πιο μεγάλα site όμως, όταν υπάρχουν πολλές εικόνες, posts και metadata, ένα τέτοιο request συχνά οδηγεί σε:
- Gateway Timeout
- 504 errors
- server limits από proxy / nginx / hosting layer
Για αυτό η σωστή προσέγγιση είναι το export να εκτελείται σε batches μέσω AJAX. Έτσι:
- δεν προσπαθεί να ολοκληρωθεί όλη η διαδικασία σε ένα request
- κρατάμε χαμηλότερο execution time ανά βήμα
- έχουμε progress indication
- στο τέλος παίρνουμε έτοιμο download link για το CSV
Πού εμφανίζεται το εργαλείο
Το snippet προσθέτει admin σελίδα στο:
Tools → Media Images CSV Report
Μέσα από εκεί ο διαχειριστής μπορεί να ξεκινήσει το export και να κατεβάσει το τελικό αρχείο CSV.
Πού αποθηκεύεται το CSV
Το CSV αποθηκεύεται στο uploads directory του WordPress, μέσα στον φάκελο:
/wp-content/uploads/wg-media-reports/
Αυτό σημαίνει ότι το αρχείο είναι εύκολα προσβάσιμο μετά το export μέσω URL, εφόσον το site επιτρέπει public access στα uploads.
Τι ελέγχει ο κώδικας
Η λογική που χρησιμοποιείται προσπαθεί να εντοπίσει τη χρήση κάθε εικόνας μέσω:
- featured image references
- post content
- πιθανές αναφορές σε post meta
Προφανώς, επειδή κάθε site μπορεί να έχει διαφορετικό builder, custom fields, plugins ή serialized δομές, σε ορισμένες περιπτώσεις μπορεί να χρειαστεί extra προσαρμογή. Παρ’ όλα αυτά, η παρακάτω βάση είναι πολύ καλή για real-world reporting.
Ο κώδικας
Ακολουθεί ο πλήρης κώδικας:
<?php
if (!defined('ABSPATH')) {
exit;
}
/**
* Admin page
*/
add_action('admin_menu', function () {
add_management_page(
'Media Images CSV Report',
'Media Images CSV Report',
'manage_options',
'wg-media-images-csv-report',
'wg_media_images_csv_report_admin_page_ajax'
);
});
function wg_media_images_csv_report_admin_page_ajax() {
if (!current_user_can('manage_options')) {
return;
}
$nonce = wp_create_nonce('wg_media_csv_ajax_nonce');
?>
<div class="wrap">
<h1>Media Images CSV Report</h1>
<p>Creates CSV in batches to avoid timeout.</p>
<p>
<button type="button" class="button button-primary" id="wg-start-media-report">
Start Export
</button>
</p>
<div id="wg-media-report-status" style="margin-top:15px;"></div>
<div style="width: 500px; max-width: 100%; background: #ddd; height: 24px; border-radius: 4px; overflow: hidden; margin-top: 15px;">
<div id="wg-media-report-bar" style="width:0%; height:100%; background:#2271b1; transition:width .3s ease;"></div>
</div>
<p id="wg-media-report-progress-text" style="margin-top:10px;"></p>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%3E%0A%09%09(function()%7B%0A%09%09%09const%20startBtn%20%3D%20document.getElementById('wg-start-media-report')%3B%0A%09%09%09const%20statusBox%20%3D%20document.getElementById('wg-media-report-status')%3B%0A%09%09%09const%20bar%20%3D%20document.getElementById('wg-media-report-bar')%3B%0A%09%09%09const%20progressText%20%3D%20document.getElementById('wg-media-report-progress-text')%3B%0A%0A%09%09%09let%20sessionId%20%3D%20null%3B%0A%09%09%09let%20total%20%3D%200%3B%0A%09%09%09let%20processed%20%3D%200%3B%0A%09%09%09let%20running%20%3D%20false%3B%0A%0A%09%09%09function%20setStatus(html)%20%7B%0A%09%09%09%09statusBox.innerHTML%20%3D%20html%3B%0A%09%09%09%7D%0A%0A%09%09%09function%20setProgress(done%2C%20totalItems)%20%7B%0A%09%09%09%09const%20percent%20%3D%20totalItems%20%3E%200%20%3F%20Math.round((done%20%2F%20totalItems)%20*%20100)%20%3A%200%3B%0A%09%09%09%09bar.style.width%20%3D%20percent%20%2B%20'%25'%3B%0A%09%09%09%09progressText.textContent%20%3D%20done%20%2B%20'%20%2F%20'%20%2B%20totalItems%20%2B%20'%20images%20processed%20('%20%2B%20percent%20%2B%20'%25)'%3B%0A%09%09%09%7D%0A%0A%09%09%09function%20post(action%2C%20data%20%3D%20%7B%7D)%20%7B%0A%09%09%09%09const%20formData%20%3D%20new%20FormData()%3B%0A%09%09%09%09formData.append('action'%2C%20action)%3B%0A%09%09%09%09formData.append('_ajax_nonce'%2C%20'%3C%3Fphp%20echo%20esc_js(%24nonce)%3B%20%3F%3E')%3B%0A%0A%09%09%09%09Object.keys(data).forEach(function(key)%7B%0A%09%09%09%09%09formData.append(key%2C%20data%5Bkey%5D)%3B%0A%09%09%09%09%7D)%3B%0A%0A%09%09%09%09return%20fetch(ajaxurl%2C%20%7B%0A%09%09%09%09%09method%3A%20'POST'%2C%0A%09%09%09%09%09credentials%3A%20'same-origin'%2C%0A%09%09%09%09%09body%3A%20formData%0A%09%09%09%09%7D).then(r%20%3D%3E%20r.json())%3B%0A%09%09%09%7D%0A%0A%09%09%09function%20runBatch()%20%7B%0A%09%09%09%09post('wg_media_csv_process_batch'%2C%20%7B%20session_id%3A%20sessionId%20%7D)%0A%09%09%09%09%09.then(function(response)%7B%0A%09%09%09%09%09%09if%20(!response%20%7C%7C%20!response.success)%20%7B%0A%09%09%09%09%09%09%09const%20msg%20%3D%20response%20%26%26%20response.data%20%26%26%20response.data.message%20%3F%20response.data.message%20%3A%20'Unknown%20AJAX%20error.'%3B%0A%09%09%09%09%09%09%09setStatus('%3Cdiv%20class%3D%22notice%20notice-error%22%3E%3Cwp-p%3E'%20%2B%20msg%20%2B%20'%3C%2Fwp-p%3E%3C%2Fdiv%3E')%3B%0A%09%09%09%09%09%09%09running%20%3D%20false%3B%0A%09%09%09%09%09%09%09startBtn.disabled%20%3D%20false%3B%0A%09%09%09%09%09%09%09return%3B%0A%09%09%09%09%09%09%7D%0A%0A%09%09%09%09%09%09const%20data%20%3D%20response.data%3B%0A%09%09%09%09%09%09processed%20%3D%20parseInt(data.processed%20%7C%7C%200%2C%2010)%3B%0A%09%09%09%09%09%09total%20%3D%20parseInt(data.total%20%7C%7C%200%2C%2010)%3B%0A%0A%09%09%09%09%09%09setProgress(processed%2C%20total)%3B%0A%0A%09%09%09%09%09%09if%20(data.done)%20%7B%0A%09%09%09%09%09%09%09let%20html%20%3D%20'%3Cdiv%20class%3D%22notice%20notice-success%22%3E%3Cwp-p%3EExport%20completed.%3C%2Fwp-p%3E%3C%2Fdiv%3E'%3B%0A%09%09%09%09%09%09%09if%20(data.download_url)%20%7B%0A%09%09%09%09%09%09%09%09html%20%2B%3D%20'%3Cwp-p%3E%3Ca%20class%3D%22button%20button-primary%22%20href%3D%22'%20%2B%20data.download_url%20%2B%20'%22%20target%3D%22_blank%22%3EDownload%20CSV%3C%2Fa%3E%3C%2Fwp-p%3E'%3B%0A%09%09%09%09%09%09%09%7D%0A%09%09%09%09%09%09%09setStatus(html)%3B%0A%09%09%09%09%09%09%09running%20%3D%20false%3B%0A%09%09%09%09%09%09%09startBtn.disabled%20%3D%20false%3B%0A%09%09%09%09%09%09%09return%3B%0A%09%09%09%09%09%09%7D%0A%0A%09%09%09%09%09%09runBatch()%3B%0A%09%09%09%09%09%7D)%0A%09%09%09%09%09.catch(function(error)%7B%0A%09%09%09%09%09%09console.error(error)%3B%0A%09%09%09%09%09%09setStatus('%3Cdiv%20class%3D%22notice%20notice-error%22%3E%3Cwp-p%3ERequest%20failed.%3C%2Fwp-p%3E%3C%2Fdiv%3E')%3B%0A%09%09%09%09%09%09running%20%3D%20false%3B%0A%09%09%09%09%09%09startBtn.disabled%20%3D%20false%3B%0A%09%09%09%09%09%7D)%3B%0A%09%09%09%7D%0A%0A%09%09%09startBtn.addEventListener('click'%2C%20function()%7B%0A%09%09%09%09if%20(running)%20return%3B%0A%0A%09%09%09%09running%20%3D%20true%3B%0A%09%09%09%09startBtn.disabled%20%3D%20true%3B%0A%09%09%09%09setStatus('%3Cdiv%20class%3D%22notice%20notice-info%22%3E%3Cwp-p%3EPreparing%20export...%3C%2Fwp-p%3E%3C%2Fdiv%3E')%3B%0A%09%09%09%09bar.style.width%20%3D%20'0%25'%3B%0A%09%09%09%09progressText.textContent%20%3D%20''%3B%0A%0A%09%09%09%09post('wg_media_csv_start')%0A%09%09%09%09%09.then(function(response)%7B%0A%09%09%09%09%09%09if%20(!response%20%7C%7C%20!response.success)%20%7B%0A%09%09%09%09%09%09%09const%20msg%20%3D%20response%20%26%26%20response.data%20%26%26%20response.data.message%20%3F%20response.data.message%20%3A%20'Could%20not%20start%20export.'%3B%0A%09%09%09%09%09%09%09setStatus('%3Cdiv%20class%3D%22notice%20notice-error%22%3E%3Cwp-p%3E'%20%2B%20msg%20%2B%20'%3C%2Fwp-p%3E%3C%2Fdiv%3E')%3B%0A%09%09%09%09%09%09%09running%20%3D%20false%3B%0A%09%09%09%09%09%09%09startBtn.disabled%20%3D%20false%3B%0A%09%09%09%09%09%09%09return%3B%0A%09%09%09%09%09%09%7D%0A%0A%09%09%09%09%09%09sessionId%20%3D%20response.data.session_id%3B%0A%09%09%09%09%09%09total%20%3D%20parseInt(response.data.total%20%7C%7C%200%2C%2010)%3B%0A%09%09%09%09%09%09processed%20%3D%200%3B%0A%09%09%09%09%09%09setProgress(0%2C%20total)%3B%0A%09%09%09%09%09%09setStatus('%3Cdiv%20class%3D%22notice%20notice-info%22%3E%3Cwp-p%3EExport%20started...%3C%2Fwp-p%3E%3C%2Fdiv%3E')%3B%0A%0A%09%09%09%09%09%09runBatch()%3B%0A%09%09%09%09%09%7D)%0A%09%09%09%09%09.catch(function(error)%7B%0A%09%09%09%09%09%09console.error(error)%3B%0A%09%09%09%09%09%09setStatus('%3Cdiv%20class%3D%22notice%20notice-error%22%3E%3Cwp-p%3EFailed%20to%20initialize%20export.%3C%2Fwp-p%3E%3C%2Fdiv%3E')%3B%0A%09%09%09%09%09%09running%20%3D%20false%3B%0A%09%09%09%09%09%09startBtn.disabled%20%3D%20false%3B%0A%09%09%09%09%09%7D)%3B%0A%09%09%09%7D)%3B%0A%09%09%7D)()%3B%0A%09%09%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object mce-object-script" width="20" height="20" alt="<script>" />
</div>
<?php
}
/**
* AJAX: start export
*/
add_action('wp_ajax_wg_media_csv_start', function () {
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Unauthorized'));
}
check_ajax_referer('wg_media_csv_ajax_nonce');
$attachments = get_posts(array(
'post_type' => 'attachment',
'post_mime_type' => 'image',
'post_status' => 'inherit',
'posts_per_page' => -1,
'orderby' => 'ID',
'order' => 'ASC',
'fields' => 'ids',
));
if (!is_array($attachments)) {
$attachments = array();
}
$upload = wp_upload_dir();
if (!empty($upload['error'])) {
wp_send_json_error(array('message' => $upload['error']));
}
$dir = trailingslashit($upload['basedir']) . 'wg-media-reports';
if (!file_exists($dir)) {
wp_mkdir_p($dir);
}
if (!is_dir($dir) || !is_writable($dir)) {
wp_send_json_error(array('message' => 'Upload directory is not writable.'));
}
$session_id = 'wg_media_csv_' . wp_generate_password(12, false, false);
$file_name = 'media-library-original-images-report-' . date('Y-m-d-H-i-s') . '-' . $session_id . '.csv';
$file_path = trailingslashit($dir) . $file_name;
$file_url = trailingslashit($upload['baseurl']) . 'wg-media-reports/' . $file_name;
$fp = fopen($file_path, 'w');
if (!$fp) {
wp_send_json_error(array('message' => 'Could not create CSV file.'));
}
// UTF-8 BOM
fprintf($fp, chr(0xEF) . chr(0xBB) . chr(0xBF));
fputcsv($fp, array(
'Image URL',
'Filename',
'Alt Text',
'Appears In Title',
'Appears In URL',
'Appears In Type',
'Date Added',
));
fclose($fp);
set_transient('wg_media_csv_session_' . $session_id, array(
'attachment_ids' => $attachments,
'total' => count($attachments),
'offset' => 0,
'file_path' => $file_path,
'file_url' => $file_url,
'started_at' => time(),
), 6 * HOUR_IN_SECONDS);
wp_send_json_success(array(
'session_id' => $session_id,
'total' => count($attachments),
));
});
/**
* AJAX: process batch
*/
add_action('wp_ajax_wg_media_csv_process_batch', function () {
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => 'Unauthorized'));
}
check_ajax_referer('wg_media_csv_ajax_nonce');
$session_id = isset($_POST['session_id']) ? sanitize_text_field(wp_unslash($_POST['session_id'])) : '';
if (!$session_id) {
wp_send_json_error(array('message' => 'Missing session ID.'));
}
$data = get_transient('wg_media_csv_session_' . $session_id);
if (!$data || empty($data['attachment_ids']) || empty($data['file_path'])) {
wp_send_json_error(array('message' => 'Export session expired or not found.'));
}
$attachment_ids = $data['attachment_ids'];
$total = (int) $data['total'];
$offset = (int) $data['offset'];
$file_path = $data['file_path'];
$file_url = $data['file_url'];
$batch_size = 10;
$batch_ids = array_slice($attachment_ids, $offset, $batch_size);
if (!file_exists($file_path)) {
wp_send_json_error(array('message' => 'CSV file not found.'));
}
$fp = fopen($file_path, 'a');
if (!$fp) {
wp_send_json_error(array('message' => 'Could not open CSV file for writing.'));
}
foreach ($batch_ids as $attachment_id) {
$original_url = wg_media_report_get_original_image_url($attachment_id);
if (!$original_url) {
$offset++;
continue;
}
$filename_only = wg_media_report_get_attachment_filename($attachment_id);
$alt_text = wg_media_report_get_attachment_alt($attachment_id);
$date_added = wg_media_report_get_attachment_date($attachment_id);
$usages = wg_media_report_find_image_usages_for_single_attachment($attachment_id, $original_url);
if (empty($usages)) {
fputcsv($fp, array(
$original_url,
$filename_only,
$alt_text,
'',
'',
'',
$date_added,
));
} else {
foreach ($usages as $usage) {
fputcsv($fp, array(
$original_url,
$filename_only,
$alt_text,
$usage['title'],
$usage['url'],
$usage['type'],
$date_added,
));
}
}
$offset++;
}
fclose($fp);
$data['offset'] = $offset;
set_transient('wg_media_csv_session_' . $session_id, $data, 6 * HOUR_IN_SECONDS);
$done = ($offset >= $total);
if ($done) {
delete_transient('wg_media_csv_session_' . $session_id);
}
wp_send_json_success(array(
'processed' => $offset,
'total' => $total,
'done' => $done,
'download_url' => $done ? $file_url : '',
));
});
/**
* Helpers
*/
function wg_media_report_get_original_image_url($attachment_id) {
$url = wp_get_original_image_url($attachment_id);
if (!$url) {
$url = wp_get_attachment_url($attachment_id);
}
return $url ? $url : '';
}
function wg_media_report_get_attachment_alt($attachment_id) {
$alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
return is_string($alt) ? $alt : '';
}
function wg_media_report_get_attachment_filename($attachment_id) {
$file = get_attached_file($attachment_id);
if ($file) {
return wp_basename($file);
}
$url = wg_media_report_get_original_image_url($attachment_id);
return $url ? wp_basename(parse_url($url, PHP_URL_PATH)) : '';
}
function wg_media_report_get_attachment_date($attachment_id) {
$post = get_post($attachment_id);
if (!$post) {
return '';
}
return mysql2date('Y-m-d H:i:s', $post->post_date, false);
}
function wg_media_report_normalize_url_for_search($url) {
$url = trim((string) $url);
if ($url === '') {
return '';
}
$url = html_entity_decode($url, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$parts = wp_parse_url($url);
if (!$parts || empty($parts['scheme']) || empty($parts['host'])) {
return $url;
}
$normalized = $parts['scheme'] . '://' . $parts['host'];
$normalized .= isset($parts['port']) ? ':' . $parts['port'] : '';
$normalized .= isset($parts['path']) ? $parts['path'] : '';
return $normalized;
}
function wg_media_report_get_url_variations($url) {
$variations = array();
$normalized = wg_media_report_normalize_url_for_search($url);
if ($normalized !== '') {
$variations[] = $normalized;
$parsed = wp_parse_url($normalized);
if (!empty($parsed['path'])) {
$variations[] = $parsed['path'];
$variations[] = ltrim($parsed['path'], '/');
}
}
return array_values(array_unique(array_filter($variations)));
}
function wg_media_report_post_content_has_image($post_content, $attachment_id, $url_variations) {
if (empty($post_content)) {
return false;
}
$content = (string) $post_content;
if (strpos($content, 'wp-image-' . $attachment_id) !== false) {
return true;
}
if (preg_match('/"id"\s*:\s*' . preg_quote((string) $attachment_id, '/') . '\b/', $content)) {
return true;
}
foreach ($url_variations as $needle) {
if ($needle !== '' && strpos($content, $needle) !== false) {
return true;
}
}
return false;
}
function wg_media_report_postmeta_has_image($meta_value, $attachment_id, $url_variations) {
if ($meta_value === '' || $meta_value === null) {
return false;
}
if (is_array($meta_value) || is_object($meta_value)) {
$meta_value = maybe_serialize($meta_value);
}
$meta_value = (string) $meta_value;
if ($meta_value === (string) $attachment_id) {
return true;
}
if (preg_match('/(^|[^0-9])' . preg_quote((string) $attachment_id, '/') . '([^0-9]|$)/', $meta_value)) {
return true;
}
foreach ($url_variations as $needle) {
if ($needle !== '' && strpos($meta_value, $needle) !== false) {
return true;
}
}
return false;
}
function wg_media_report_find_image_usages_for_single_attachment($attachment_id, $original_url) {
global $wpdb;
$usages = array();
$url_variations = wg_media_report_get_url_variations($original_url);
$post_ids = array();
$thumb_ids = $wpdb->get_col($wpdb->prepare(
"SELECT post_id
FROM {$wpdb->postmeta}
WHERE meta_key = '_thumbnail_id'
AND meta_value = %s",
(string) $attachment_id
));
if (!empty($thumb_ids)) {
foreach ($thumb_ids as $pid) {
$post_ids[(int) $pid] = 'featured_image';
}
}
$content_conditions = array();
$content_params = array();
$content_conditions[] = "post_content LIKE %s";
$content_params[] = '%' . $wpdb->esc_like('wp-image-' . $attachment_id) . '%';
$content_conditions[] = "post_content LIKE %s";
$content_params[] = '%' . $wpdb->esc_like('"id":' . $attachment_id) . '%';
$content_conditions[] = "post_content LIKE %s";
$content_params[] = '%' . $wpdb->esc_like('"id": ' . $attachment_id) . '%';
foreach ($url_variations as $variation) {
$content_conditions[] = "post_content LIKE %s";
$content_params[] = '%' . $wpdb->esc_like($variation) . '%';
}
$sql = "SELECT ID, post_type, post_title
FROM {$wpdb->posts}
WHERE post_status IN ('publish','private','draft','future','pending')
AND (" . implode(' OR ', $content_conditions) . ")";
$prepared = $wpdb->prepare($sql, $content_params);
$content_posts = $wpdb->get_results($prepared);
if (!empty($content_posts)) {
foreach ($content_posts as $post) {
$post_ids[(int) $post->ID] = 'post_content';
}
}
$meta_conditions = array();
$meta_params = array();
$meta_conditions[] = "meta_value = %s";
$meta_params[] = (string) $attachment_id;
$meta_conditions[] = "meta_value LIKE %s";
$meta_params[] = '%' . $wpdb->esc_like((string) $attachment_id) . '%';
foreach ($url_variations as $variation) {
$meta_conditions[] = "meta_value LIKE %s";
$meta_params[] = '%' . $wpdb->esc_like($variation) . '%';
}
$meta_sql = "SELECT DISTINCT post_id
FROM {$wpdb->postmeta}
WHERE meta_key NOT IN ('_edit_lock','_edit_last')
AND (" . implode(' OR ', $meta_conditions) . ")";
$meta_prepared = $wpdb->prepare($meta_sql, $meta_params);
$meta_post_ids = $wpdb->get_col($meta_prepared);
if (!empty($meta_post_ids)) {
foreach ($meta_post_ids as $pid) {
if (!isset($post_ids[(int) $pid])) {
$post_ids[(int) $pid] = 'postmeta';
}
}
}
if (empty($post_ids)) {
return array();
}
$posts = get_posts(array(
'post_type' => 'any',
'post_status' => array('publish', 'private', 'draft', 'future', 'pending'),
'posts_per_page' => -1,
'post__in' => array_keys($post_ids),
));
foreach ($posts as $post) {
$match_type = $post_ids[(int) $post->ID];
$permalink = get_permalink($post->ID);
if (!$permalink) {
$permalink = admin_url('post.php?post=' . $post->ID . '&action=edit');
}
$usages[] = array(
'title' => get_the_title($post->ID),
'url' => $permalink,
'type' => $post->post_type . ' (' . $match_type . ')',
);
}
return $usages;
}
Σημειώσεις απόδοσης
Αν σε κάποιο server το export παραμένει βαρύ, το πρώτο σημείο που αξίζει να μειωθεί είναι το batch size:
$batch_size = 10;
Σε πιο αυστηρό hosting περιβάλλον μπορεί να γίνει:
$batch_size = 3;
Αυτό αυξάνει τον συνολικό αριθμό AJAX requests, αλλά μειώνει το βάρος κάθε request και βοηθάει σημαντικά σε sites με πολλά attachments ή αρκετά metadata.
Χρήσιμες παρατηρήσεις
- Το εργαλείο στοχεύει στα original images και όχι στα resized variants.
- Το τελικό CSV μπορεί να χρησιμοποιηθεί για content audit ή για estimate / quotation process.
- Αν ένα site χρησιμοποιεί heavily custom builders ή περίπλοκα serialized structures, ίσως χρειαστεί custom adaptation.
- Η AJAX λογική είναι συνήθως πολύ πιο ασφαλής επιλογή από ένα μονοκόμματο export request.
Συμπέρασμα
Αυτό το approach είναι ιδιαίτερα χρήσιμο όταν θέλουμε να παραδώσουμε οργανωμένο report για εικόνες ενός WordPress site χωρίς να βασιστούμε σε βαρύ custom script που τρέχει σε ένα μόνο request. Με batching, progress indication και CSV output στο uploads directory, η διαδικασία γίνεται πολύ πιο πρακτική και πιο φιλική προς το server.
Credits: Nicolas Lagios & ChatGPT
This post is also available in:
English
