Categories
Software Development

Creating a Photo Album Taxonomy and Bulk Importing Images

I finally tackled a project I’ve had on my plate for quite a long time. Once upon a time, my wife blogged our family shenanigans. They are great memories and I love them. She (and I) spent a significant amount of time putting in some effort to upload and caption photos as well. Re-reading the stories of our young family and preserving those memories are priceless.

However, the software I used originally, fell out of use and eventually was no longer supported. It limped along for many years and our site fell into that abandoned ghost town territory common to older blogs. (It appears the software was dormant for 7 years and in 2019 someone started working it again – 🤔. But now I want to only have “one” solution…)

To preserve those posts I had a couple goals.

  1. Update the site to be as standard based as possible.
  2. Migrating a site to another system should be easy(-ish).
  3. Keep the posts the same as possible.

Originally, I thought about migrating to a new site. I decided to stay with WordPress, but the sticking part was migrating the photo albums. In order to support my goals, I made the decision to use the WordPress built-in media library and taxonomies.

Creating the taxonomy itself was pretty easy:

// register new taxonomy which applies to attachments
function at_add_album_taxonomy() {
	$labels = array(
		'name'              => __( 'Albums', 'album_taxonomy' ),
		'singular_name'     => __( 'Album', 'album_taxonomy' ),
		'search_items'      => __( 'Search Albums', 'album_taxonomy' ),
		'all_items'         => __( 'All Albums', 'album_taxonomy' ),
		'parent_item'       => __( 'Parent Album', 'album_taxonomy' ),
		'parent_item_colon' => __( 'Parent Album:', 'album_taxonomy' ),
		'edit_item'         => __( 'Edit Album', 'album_taxonomy' ),
		'update_item'       => __( 'Update Album', 'album_taxonomy' ),
		'add_new_item'      => __( 'Add New Album', 'album_taxonomy' ),
		'new_item_name'     => __( 'New Album Name', 'album_taxonomy' ),
		'menu_name'         => __( 'Albums', 'album_taxonomy' ),
	);
	$args = array(
		'public' => true,
		'labels' => $labels,
		'hierarchical' => true,
		'rewrite' => [
			'slug' => 'albums',
			'hierarchical' => true,
		],
		'show_admin_column' => 'true',
		'show_in_rest' => true,
		'update_count_callback' => '_update_generic_term_count',
	);
	register_taxonomy( 'albums', 'attachment', $args );
}
add_action( 'init', 'at_add_album_taxonomy', 0 );Code language: PHP (php)

Note: the biggest takeaways from this section were the rewrite and update_count_callback args. The rewrite hierarchies supports the archives page (which effectively is the album page) displaying a multi-tier URL for sub-albums.

The update_count_callback change makes the counter work the way you think it should (each album counts any image tagged appropriately).

Adding the images and a Bulk Import:

After exporting the images into a folder and backing up the database, I imported the database into a sqlite file. I don’t remember the steps I took to do that, but it must not have been super difficult 😆.

I originally created a python script running WP-CLI commands to import the images (with appropriate metadata). After playing around I realized the round-trip nature (send the command, execute, return) made it prohibitive for importing 6K images.

AI actually made quick work of converting that script into a WP-CLI command I would upload and run directly. I’m not generally enamored with AI for actual production software (yet) – but it was perfect for this one-off use.

Because of the specific nature of my import I’m not sure how useful the script would be to anyone else. In broad strokes, it takes a folder and sqlite db as input, iterates through each file, pulls metadata from file/db, imports file, and sets the image metadata and taxonomy.

When I was running locally it blazed through my folders in a number of seconds, but remotely it took some time. While it was running I noticed it slowing down so I added a throttle (sleep after running 200 images).

Other WordPress changes:

Finally I have the images uploaded and now I needed to make them searchable for making the changes. (Side note: I plan on going into each post and fine-tuning the posts by hand because I want to remove the custom HTML originally used and replace with WordPress block structuring.)

In addition to the album taxonomy I preserved the original “path” and “id” of the image. To add these terms to the media library search I did the following:

1. Create a search specific query var:

// Need to hook into media library search to filter by album or g2id (metadata)
function at_search_meta( $query ) {
	$query['at_search_meta'] = true;
	return $query;
}
add_filter( 'ajax_query_attachments_args', 'at_search_meta' );Code language: PHP (php)

2. Adjust the search parameters:

The posts_search function adjusts the WHERE clause by removing the end parenthesis, adds the columns I want to search with, and replaces the parenthesis.

The posts_clauses function adds the meta table as a separate alias (used in posts_search) because WordPress does a different join with the metadata table.

add_filter( 'posts_search', function( $search, $query ) {
	if ( ! filter_var( $query->get('at_search_meta'), FILTER_VALIDATE_BOOLEAN )
	     || empty( $query->get('s') )
	) {
		return $search;
	}

	// Remove the last ')' and add our own search clauses...
	$search = preg_replace( '/\)\s*$/', '', $search );
	$search .= " OR (at_meta.meta_key = 'g2id' AND nef_meta.meta_value LIKE '%" . $query->get('s') . "%')";
	$search .= " OR (at_meta.meta_key = 'g2path' AND nef_meta.meta_value LIKE '%" . $query->get('s') . "%')";
	$search .= ') ';
	return $search;
}, 100, 2 );

add_filter( 'posts_clauses', function( $clauses, $query ) {
	if ( ! filter_var( $query->get('at_search_meta'), FILTER_VALIDATE_BOOLEAN )
	     || empty( $query->get('s') )
	) {
		return $clauses;
	}

	global $wpdb;
	// Check if we need to join the postmeta table
	$clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} AS at_meta ON at_meta.post_id = {$wpdb->posts}.ID";
	$clauses['distinct'] = 'DISTINCT';

	return $clauses;
}, 100, 2 );Code language: PHP (php)

✨Add metadata to image metadata UI:

Not necessarily something anyone else would need, this snippet adds specific metadata just to the UI as a helper.

// Attachment details (show metadata)
function at_attachment_details( $form_fields, $post ) {
	$form_fields['g2id'] = array(
		'label' => 'G2 ID',
		'input' => 'text',
		'value' => get_post_meta( $post->ID, 'g2id', true ),
	);
	$form_fields['g2path'] = array(
		'label' => 'G2 Path',
		'input' => 'text',
		'value' => get_post_meta( $post->ID, 'g2path', true ),
	);
	return $form_fields;
}
add_filter( 'attachment_fields_to_edit', 'at_attachment_details', 10, 2 );Code language: PHP (php)

Displaying the lovely albums:

This was the part I wanted to document the most since it was done primarily “by hand” using the built-in site editor.

I used the create-block-theme plugin to export the current theme into a modifiable version I can adjust. Then clicked the “Add template” button (currently at the top-right when viewing templates), and chose the option for an Albums page. (This creates a taxonomy-albums.html file).

In the editor for this template I selected the “Post template” block and changed it to grid.

Additionally, I had to add this snippet to get the images to display because they weren’t attached to existing posts and had an inherit post status. YMMV depending on your import.

// Adjust archive query to include attachments (with "inherit" post_status).
add_filter( 'pre_get_posts', function( $query ) {
	if ( ! is_admin() && $query->is_main_query() && $query->is_tax( 'albums' ) ) {
		$query->set( 'post_status', [ 'publish', 'inherit' ] );
		$query->set( 'posts_per_page', 100 );
	}
	return $query;
} );Code language: PHP (php)

And then…

Now I have to do the hard part. Maybe there will be another post…

One reply on “Creating a Photo Album Taxonomy and Bulk Importing Images”

Comments are closed.