Loading 0
Share

My Blog

Scroll Down
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="&lt;script&gt;" />
	</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

Leave a Reply

Η ηλ. διεύθυνση σας δεν δημοσιεύεται. Τα υποχρεωτικά πεδία σημειώνονται με *

01.

Here you can see all the services I provide

Registration and management of domain names (website address such as www.nicolaslagios.com)

Also management of dns records (e.g. connecting the domain to a specific server, fixing email spam problems, etc.)

Also ssl renewals etc

Installation and management of web & mail server in ubuntu vps with virtualmin, plesk, cpanel

Also studying and fixing server problems.

Necessary condition, the target server meets the conditions

At the moment for new wordpress websites you can choose from ready-made themes and we change the content (no custom changes). You can buy with a fixed price by clicking here!

My team and I undertake any data bridging implementation for Wordpress, Prestashop, Opencart, Joomla platforms.

We can connect data from any source, as long as the structure is stable and there is proper documentation and briefing.

We undertake the creation, regulation and enrichment of pages for social networks: Facebook, Linkedin, Instagram (profile), Twitter (profile), Tiktok (profile).

We also undertake the first boost of your pages for quick results in followers.

We undertake the repair and maintenance of your existing wordpress website.

For more information about the services, you can read the following and return here to schedule a meeting with me: https://maxservices.gr/en/internet-services/website-services-blank/additional-website-services/