Productafbeeldingen in bulk verwijderen (PHP-API)

Uit De Vliegende Brigade
(wijz) ← Oudere versie | Huidige versie (wijz) | Nieuwere versie → (wijz)
Naar navigatie springen Naar zoeken springen

Hoe kun je geautomatiseerd in bulk productafbeeldingen verwijderen van een site? Dus:

  1. Associatie tussen afbeeldingen en producten verwijderen
  2. Afbeeldingen verwijderen uit de media-bibliotheek
  3. Extra: Afbeeldingen verwijderen uit het bestandssysteem.

Dit vraagstuk ontstond rond 2018/2019 voor het eerst, toen ik begon met geautomatiseerd vullen van een site: Vanwege het vele experimenteren, zou het prettig zijn als dat vullen ook weer ongedaan zou kunnen worden gemaakt. In april 2021 werd deze vraag opnieuw actueel: Voor een bestaande webwinkel wil ik het aantal afbeeldingen verminderen, door een hoop producten dezelfde afbeelding te laten gebruiken (BrushTour, post ch_fr, redim-php). Dat lijkt het handigste te gaan, als ik eerst de bestaande afbeeldingen kan verwijderen.

Dit hoofdstuk is een inventaris van verschillende manieren om dit te realiseren:

SQL + wp_delete_attachment + beetje handwerk

Voorbeeld van een resulterend PHP-script. Deze was 30.466 regels lang. Executie kostte tijd (2021.05.05)

Dit is tot op heden (2021.05) de beste oplossing die ik heb om in bulk productafbeeldingen te verwijderen: Genereer in SQL een PHP-script, en voer vervolgens dat PHP-script uit. In een later stadium kan dit volledig geautomatiseerd worden. Dus dat je in PHP middels bv. een loop alle ID's van afbeeldingen ophaalt, en die vervolgens verwijdert. Er is daarnaast nog één complexiteit: Het veld waarin gallerij-afbeeldingen worden opgeslagen, is serialised: Het bevat meerdere ID's, gescheiden door komma's (geloof ik).

Voorbeeld: Creëer een PHP-script dat alle afbeeldingen verwijdert die horen bij producten met "cas-" in de naam:

################################################################################
# Select database
################################################################################
#
use knl_s1;


################################################################################
# Orientation
################################################################################
#
# select
# #	wp_posts.ID				as product_id,
# #    wp_posts.post_title		as product_title,
# #    wp_postmeta.*
# 	concat
#     (
# 		"wp_delete_attachment(", meta_value, ", True);"
# 	) as php_command
# from 
# 	wp_postmeta
# join 
# 	wp_posts 
# 	on 
# 	wp_postmeta.post_id = wp_posts.ID 
# where 
# 	wp_posts.post_type like 'product'
#     and
#     wp_posts.post_title like "% cas-%"
# 	and
# 	(
# 		wp_postmeta.meta_key like '_thumbnail_id'
# 		or
# 		wp_postmeta.meta_key like '_product_image_gallery'
# 	);


################################################################################
# Create PHP-script
################################################################################
#
# * The SQL code sometimes creates NULL lines. Those need to be removed in the
#   PHP file
# * Manually replace escape codes (like "\n" in the PHP file
#
select "<?php
require_once('/var/www/knl.s1/wp-load.php');
"
union select
   concat
   (
      "wp_delete_attachment(", meta_value, ", True);"
   ) as php_command
from 
   wp_postmeta
join 
   wp_posts 
   on 
   wp_postmeta.post_id = wp_posts.ID 
where 
   wp_posts.post_type like 'product'
   and
   wp_posts.post_title like "% cas-%"
   and
   (
      wp_postmeta.meta_key like '_thumbnail_id'
      or
      wp_postmeta.meta_key like '_product_image_gallery'
   );

wp_delete_attachment (PHP-API)

Het commando wp_delete_attachment is een belangrijk gereedschap om productafbeeldingen in bulk te verwijderen, maar het is niet het complete antwoord. Het voorbeeld eerder in dit hoofdstuk maakt gebruik van deze functie. Zie wp_delete_attachment (PHP-API) voor details.

Andere relevante API-calls?

dvb_delete_all_thumbnails

Rond 2019 heb ik dit PHP-script geschreven. Het maakt gebruik van $wpdb (db-layer) en is eigenlijk niet meer dan een veredeld database-script. Het is supersnel, maar niet nauwkeurig of robuust. Daarnaast is het vrij beperkt.

Hoe het werkt

  • Verzamel in een tijdelijke tabel de post-id's van alle afbeeldingen die als thumbnail gebruikt worden
  • Verwijder uit tabel wp_posts alle posts met deze id's
  • Verwijder alle entries uit wp_postmeta voor deze id's.

Beperkingen

  • Afbeeldingen worden verwijderd als ze als product-thumbnail worden gebruikt. Ongeacht of afbeeldingen daarnaast ook op andere manieren worden gebruikt
  • Gallery-afbeeldingen worden niet verwijderd
  • De afbeeldingen worden niet verwijderd uit het bestandssyteem.

Snelheid

De routine is snel, want de eigenlijke functionaliteit is afgebeeld in SQL en niet in PHP. In april 2021 toegepast op een webshop met zo'n 15.000 producten. Het script was binnen een minuut klaar.

Voorbeeld

Associaties met producten zijn verwijderd - itt. foutmeldingen dat afbeeldingen niet gevonden kunnen worden
Associaties met producten zijn écht verwijderd - Maar dat geldt niet voor gallerij-afbeeldingen
Media-bibliotheek, oude situatie: 33.117 afbeeldingen
Media-bibliotheek, nieuwe situatie: Ongeveer de helft is verwijderd. De gallerij-afbeeldingen zijn behouden

SQL

Dit zijn de vier SQL-statements in deze functie. Met toegevoegde indentatie & commentaar:

sql_01

drop table if exists 
   wp_image_tmp;

sql_02

# Collect the post_IDs from images that are used for products and 
# have property "_thumbnail_id"
######################################################################
#
create temporary table 
   wp_image_tmp 
select 
   wp_postmeta.meta_value as image_id 
from 
   wp_postmeta
join 
   wp_posts 
   on 
   wp_postmeta.post_id = wp_posts.ID 
where 
   wp_posts.post_type like 'product' 
   and 
   wp_postmeta.meta_key like '_thumbnail_id';

sql_03

# Delete the posts with these post-ids
######################################################################
#
delete 
   wp_posts 
from 
   wp_posts 
join 
   wp_image_tmp 
   on 
   wp_posts.ID = wp_image_tmp.image_id;

sql_04

# Delete all wp_postmeta entries for these post-ids
######################################################################
#
delete 
   wp_postmeta 
from 
   wp_postmeta 
join 
   wp_image_tmp 
   on 
   wp_postmeta.post_id = wp_image_tmp.image_id;

Script

#######################################################################################
# dvb_delete_all_thumbnails
#######################################################################################
#
# Removes images that are associated with products as thumbnails
#
# * Only thumbnails - No gallery-related images
# * Only thumbnails - If an image is used for mutiple purposes (e.g., site logo), it
#   will be removed
# * Images won't be removed from the upload-directory - Keep it simple
# * When deleting both images and products, delete images first! Once they're not
#   thumbnails anymore, images won't be deleted anymore
# * This script should be called from another PHP-script, where the PHP-API from the
#   relevant site has already been initiated. That's why this function 
#   dvb_delete_all_thumbnails doesn't take arguments
# * It's quite impractical that this script has to be called from another script:
#   It actually means that from a command line, I call that other script, that calls
#   this script - Not intuitive at all.
#
function dvb_delete_all_thumbnails()
{
	# Access
	######################################
	#
	# require_once($pad . "wp-load.php");
	#
	global $wpdb;


	# Assemble queries
	######################################
	#
	$sql_01 = "drop table if exists ".$wpdb->prefix."image_tmp;";
	$sql_02 = "create temporary table ".$wpdb->prefix."image_tmp select ".$wpdb->prefix."postmeta.meta_value as image_id from ".$wpdb->prefix."postmeta
	join ".$wpdb->prefix."posts on ".$wpdb->prefix."postmeta.post_id = ".$wpdb->prefix."posts.ID where ".$wpdb->prefix."posts.post_type like 'product' and ".$wpdb->prefix."postmeta.meta_key like '_thumbnail_id';";
	$sql_03 = "delete ".$wpdb->prefix."posts from ".$wpdb->prefix."posts join ".$wpdb->prefix."image_tmp on ".$wpdb->prefix."posts.ID = ".$wpdb->prefix."image_tmp.image_id;";
	$sql_04 = "delete ".$wpdb->prefix."postmeta from ".$wpdb->prefix."postmeta join ".$wpdb->prefix."image_tmp on ".$wpdb->prefix."postmeta.post_id = ".$wpdb->prefix."image_tmp.image_id;";


	# Ready to go?
	######################################
	#
	// echo "\nsql_01: ".$sql_01;
	// echo "\nsql_02: ".$sql_02;
	// echo "\nsql_03: ".$sql_03;
	// echo "\nsql_04: ".$sql_04."\n";


	# Go!
	######################################
	#
	try
	{
	    $wpdb->query($sql_01);
	    $wpdb->query($sql_02);
	    $wpdb->query($sql_03);
	    $wpdb->query($sql_04);
	    
	}
	catch(Exception $e){}
}