Last updated 1 April 2020.

The WordPress plugin “WP All Import” allows site admins to bulk upload data in CSV (or other) formats. Its Pro version is compatible with WooCommerce and ACF, making it a popular choice for Woo shop owners to bulk import and update product data.

WP All Import comes with a multitude of settings which give great flexibility, but with great flexibility comes great complexity. And it’s not always clearly labelled what consequences these settings have. This blog post looks at one of the potentially disastrous events: permanent deletion of files from the media library. The following examples are taken from WP All Import Pro with the WooCommerce add-on.

How to stop WPAI deleting media library files

Images are set by default not be deleted, but any WP site using other attachments such as audio galleries, PDFs, videos, ZIP files etc needs to take care. WP All Import handles image & non-image files differently. To avoid any file from media library being permanently deleted, choose the following settings in Step 3 of the import process:

“Download images hosted elsewhere” or “Use images currently uploaded in wp-content/uploads/wpallimport/files/”
> Tick ” Keep images currently in Media Library”

“Use images currently in Media Library”
> with this setting images are kept in library by default

“Other Product Options” : “Download & Import Attachments”
> Tick “Search for existing attachments to prevent duplicates in media library”

Plugin Code explained

If you are developer and interested in the code, please read on.

Non-image files

During the import process the plugin
1.checks if either “Update all data” is selected or “Choose which data to update” with “Attachments” ticked.
2. If either applies, the code looks for non-image attachments to the post (Audio, PDFs etc)
3. If Step 3 : “Other Product Options” > “Search for existing attachments to prevent duplicates in media library” is not ticked then attached non-image files are permanently deleted.
4. If ticked, files are detached but remain in Media Library

The default setting for attachment deletion is true (is_search_existing_attach = 0).

if ($this->options['update_all_data'] == 'yes' or ($this->options['update_all_data'] == 'no' and $this->options['is_update_attachments'])) {
    $logger and call_user_func($logger, sprintf(__('Deleting attachments for `%s`', 'wp_all_import_plugin'), $articleData['post_title']));
    wp_delete_attachments($articleData['ID'], ! $this->options['is_search_existing_attach'], 'files');
}

IMO this is the is the most confusing setting. The wording of “Search for existing attachments to prevent duplicates in media library” does not indicate in any way that it’s linked to file deletion. And to make it even more obscure, the setting is hidden away at the bottom of “Other” options where casual users will never stray.

Image Files

During the import process the plugin
1.checks if either “Update all data” is selected or “Choose which data to update” with “Images > Update all images” ticked.
2. If either applies, the code looks for image attachments to the post (JPGs, PNGS etc)
3. It then checks if images are of type “pmxi_gallery_image”, ie images imported via the default image field and ACF image fields.
4. If they are and there are more than 1 image bundle, no action is taken
5. If the images are not and if Step 3 : “Images” > “Keep images currently in Media Library” is not ticked then attached image files are permanently deleted.
4. If ticked, files are detached but remain in Media Library

The default setting for permanent deletion is false (do_not_remove_images = true).

if ($this->options['update_all_data'] == 'yes' or ($this->options['update_all_data'] == 'no' and $this->options['is_update_images'] and $this->options['update_images_logic'] == "full_update")) {
    $logger and call_user_func($logger, sprintf(__('Deleting images for `%s`', 'wp_all_import_plugin'), $articleData['post_title']));
    if (!empty($images_bundle)) {
        foreach ($images_bundle as $slug => $bundle_data) {
            $option_slug = ($slug == 'pmxi_gallery_image') ? '' : $slug;    
            if (count($images_bundle) > 1 && $slug == 'pmxi_gallery_image') {
                continue;                                            
            }
            $do_not_remove_images = ($this->options[$option_slug . 'download_images'] == 'gallery' or $this->options[$option_slug . 'do_not_remove_images']) ? FALSE : TRUE;                                          
            $missing_images = wp_delete_attachments($articleData['ID'], $do_not_remove_images, 'images');
        }
    }
}

wp_delete_attachments()

The function wp_delete_attachments() takes three arguments: Post ID, true / false for deletion and the type of files to delete. Whilst the format of “wp_” would imply a core WP function in this case we’re dealing with a custom plugin function. The function:
1. loops through all attachments
2. If “unlink = true” then it permanently deletes the file and all its data (except parent)
3. If “unlink = false” then it detaches the file from its parent without deletion

function wp_delete_attachments($parent_id, $unlink = true, $type = 'images') {	
	if ( $type == 'images' and has_post_thumbnail($parent_id) ) delete_post_thumbnail($parent_id);
	$ids = array();
	$attachments = get_posts(array('post_parent' => $parent_id, 'post_type' => 'attachment', 'numberposts' => -1, 'post_status' => null));
    foreach ($attachments as $attach) {
        if ( ($type == 'files' && ! wp_attachment_is_image( $attach->ID )) || ($type == 'images' && wp_attachment_is_image( $attach->ID ))) {
            if ($unlink) {
                if (!empty($attach->ID)) {
                    $file = get_attached_file($attach->ID);
                    if (@file_exists($file)) {
                        wp_delete_attachment($attach->ID, TRUE);
                    }
                }
            }
            else {
                $ids[] = $attach->ID;
            }
        }
    }
    global $wpdb;
	if ( ! empty( $ids ) ) {
		$ids_string = implode( ',', $ids );
		// unattach
		$result = $wpdb->query( "UPDATE $wpdb->posts SET post_parent = 0 WHERE post_type = 'attachment' AND ID IN ( $ids_string )" );
		foreach ( $ids as $att_id ) {
			clean_attachment_cache( $att_id );
		}
	}	return $ids;
}

In my view, permanent deletion of files should come with its own setting and a warning in bold red font attached. Other plugins that allow irreversible deletions, for example the LiteSpeed Image Optimisation plugin make it very clear on the plugin page what effect the setting will have and that it cannot be reversed. But with WP All Import, no such warning exists and worst case, the first a shop owner will find out about it is when files are missing from the site.

Update by WP All Import team

The WP All Import team have written a reply advising how the accidental deletion of files can be prevented. Either in the custom / child theme functions.php or as a standalone plugin the following code can be used:

function wp_all_import_before_xml_import( $import_id ) {
    $import = new PMXI_Import_Record();
	$import->getById( $import_id );
	
	if ( ! $import->isEmpty() ) {
		$options = $import->options;
		$options['do_not_remove_images'] = 1;
		$options['is_search_existing_attach'] = 1;
		$import->set( array(
			'options' => $options
		) )->save();
	}
}

add_action('pmxi_before_xml_import', 'wp_all_import_before_xml_import', 10, 1);

For further info on this WP All Import hook see https://github.com/soflyy/wp-all-import-action-reference/blob/master/all-import/pmxi_before_xml_import.php

Leave a Reply

Your email address will not be published. Required fields are marked *