Custom importer for WP Recipe Maker

I frequently work with food bloggers to improve the design, speed, and functionality of their websites. This often includes migrating to WP Recipe Maker, the industry-leading recipe card plugin.

WP Recipe Maker already includes importers for many popular recipe plugins. You can skip to the end to see how to import your recipes if you are using BigOven, Cookbook, Cooked, Create, EasyRecipe, FoodiePress, Meal Planner Pro, Purr Design, Recipe Card, Simmer, Simple Recipe Pro, Tasty, WordPress.com, WP Ultimate Recipe, Yummly, or ZipList.

Sometimes during our discovery phase we’ll find the previous developer built their own custom recipe card directly in the theme. In the process of redesigning the site we’ll need build a custom importer and migrate all the recipes to WP Recipe Maker.

Switching to WP Recipe Maker lets us take advantage of all the modern features it provides (schema for SEO, advanced templating, user ratings…), and stay up-to-date with future changes required for SEO.

Here’s a walkthrough of how we import the recipes:

  1. Review the recipe data structure
  2. Build the custom importer
  3. Import recipes

Review the recipe data structure

The first step is to figure out how the current plugin is storing the recipe data. Most custom built recipe cards will store it as post meta, but it could be stored in a separate post type or custom database tables.

A quick tip for inspecting post meta is to drop this code in functions.php in my local development environment:

/**
 * Display all post meta
 *
 */
add_action( 'wp_footer', function() {
	if( is_single() )
		ea_pp( get_post_meta( get_the_ID() ) );
});

/**
 * Pretty Printing
 *
 */
function ea_pp( $obj, $label = '' ) {
	$data = json_encode( print_r( $obj,true ) );
	?>
	<style type="text/css">
		#bsdLogger {
		position: absolute;
		top: 30px;
		right: 0px;
		border-left: 4px solid #bbb;
		padding: 6px;
		background: white;
		color: #444;
		z-index: 999;
		font-size: 1.25em;
		width: 400px;
		height: 800px;
		overflow: scroll;
		}
	</style>
	<script type="text/javascript">
		var doStuff = function(){
			var obj = <?php echo $data; ?>;
			var logger = document.getElementById('bsdLogger');
			if (!logger) {
				logger = document.createElement('div');
				logger.id = 'bsdLogger';
				document.body.appendChild(logger);
			}
			////console.log(obj);
			var pre = document.createElement('pre');
			var h2 = document.createElement('h2');
			pre.innerHTML = obj;
			h2.innerHTML = '<?php echo addslashes($label); ?>';
			logger.appendChild(h2);
			logger.appendChild(pre);
		};
		window.addEventListener ("DOMContentLoaded", doStuff, false);
	</script>
	<?php
}

When viewing a single post, there will be a column added to the right side of the screen that lists all of its metadata:

It’s also a good idea to dig into the theme/plugin to see how it’s accessing and displaying the recipe data. Simple fields like Prep Time and Cook Time are usually straightforward, but more complicated fields like Ingredients and Directions may require more work to assemble.

The site I’m working on right now is storing ingredients and instructions as serialized arrays.

$ingredients_data = maybe_unserialize( get_post_meta( $id, '_recipe_ingredients', true ) );

Write down all the data you’ll need to access and how it is stored. I’ll usually create a simple spreadsheet with Field Type (“ingredients”), Field Meta Key (_recipe_incredients), and Notes (“stored as serialized array”).

Build the custom importer

Now that you know how the current data is stored, you can get to work writing the importer.

You can find all of WP Recipe Maker’s current importers and their example importer in /plugins/wp-recipe-maker/includes/import/.

To create your own custom importer, create a file in your theme or core functionality plugin named class-wprm-import-{something}.php, where {something} is your unique importer name. I’ll usually use the client’s name for this, so in the custom importer I’m building for The Mom 100 it’s called class-wprm-import-themom100.php.

Then use the wprm_importer_directories filter to tell WP Recipe Maker the directory in which your importer can be found. It will search that directory for files using the above naming structure, so you can include a directory that has other files in it without issues.

I placed my custom importer in the /inc/ directory of my core functionality plugin, then added the following code:

// Plugin directory
define( 'EA_DIR' , plugin_dir_path( __FILE__ ) );

/**
 * Include custom WPRM importer
 * @link https://www.billerickson.net/custom-importer-for-wp-recipe-maker/
 */
function be_custom_wprm_importer( $directories ) {
	$directories[] = EA_DIR . '/inc/';
	return $directories;
}
add_filter( 'wprm_importer_directories', 'be_custom_wprm_importer' );

Copy the contents of the class-wprm-import-example.php file into your new file, then rename the class from WPRM_Import_Example to WPRM_Import_{Something}, changing {Something} to match what you used in the file name.

Update the get_uid() method to use your slug (ex: themom100), and the get_name() method to use your name (ex: The Mom 100).

Get Recipes

The next step is to update the get_recipe_count() and get_recipes() methods used to find the recipes on the site.

On this project, recipes are every post in the recipe post type. If your recipes are mixed in with posts, you would write a query that searches for posts containing a meta key only used by recipes.

It’s also important to eliminate recipes you have already imported. After I finish importing a recipe, I’m going to run update_post_meta( $post_id, '_recipe_imported', 1 ); to indicate this one has been imported. In my query below, I’m looking for recipes that do not have that key because we only want to list recipes that haven’t been imported yet.

For get_recipe_count() you want to find the total recipe count, so set posts_per_page arbitrarily high and only query for IDs.

	/**
	 * Get the total number of recipes to import.
	 *
	 * @since    1.20.0
	 */
	public function get_recipe_count() {

		$loop = new WP_Query( array(
			'fields' => 'ids',
			'post_type' => 'recipe',
			'posts_per_page' => 999,
			'meta_query' => array(
				array(
					'key' => '_recipe_imported',
					'value' => 1,
					'compare' => 'NOT EXISTS',
				)
			)
		) );

		return $loop->found_posts;

	}

For the get_recipes() method, we’re getting a paginated list of recipes to import. You want to keep the list to a reasonable size (ex: 20) so it doesn’t time out if someone tries importing all the recipes in the list at once. You’ll also want to use the $page parameter so that clicking the pagination links actually changes the posts shown.

Use this loop to build the $recipes array that includes the post ID, post title, and edit post link.

	/**
	 * Get a list of recipes that are available to import.
	 *
	 * @since    1.20.0
	 * @param	 int $page Page of recipes to get.
	 */
	public function get_recipes( $page = 0 ) {
		// Return an array of recipes to be imported with name and edit URL.
		// If not the same number of recipes as in "get_recipe_count" are returned pagination will be used.

		$loop = new WP_Query( array(
			'post_type' => 'recipe',
			'posts_per_page' => 20,
			'paged' => $page,
			'meta_query' => array(
				array(
					'key' => '_recipe_imported',
					'value' => 1,
					'compare' => 'NOT EXISTS',
				)
			)
		) );

		$recipes = array();
		foreach( $loop->posts as $post ) {
			$recipes[ $post->ID ] = array(
				'name' => $post->post_title,
				'url'  => get_edit_post_link( $post->ID )
			);
		}
		return $recipes;
	}

Get Recipe

The get_recipe() method is where all the hard work happens. We’re going to take the recipe data we mapped out earlier to build the $recipe array used by WP Recipe Maker to generate its recipe.

The first parameter is $id which is the Post ID. Use this to access any necessary post data. Don’t assume the post data is already set up. Instead of using get_the_title(), use get_the_title( $id ).

The example file will already contain all the necessary fields for the recipe. Go through and replace them with how your current recipe data is stored. Here’s how the simple fields looked in my importer:

$recipe['name'] = get_the_title( $id );
$recipe['summary'] = get_post_meta( $id, '_recipe_subtitle', true );
$recipe['author_name'] = get_the_author( $id );
$recipe['image_id'] = get_post_thumbnail_id( $id );
$recipe['servings'] = get_post_meta( $id, '_r_yield', true );
$recipe['servings_unit'] = 'Servings';
$recipe['prep_time'] = get_post_meta( $id, '_r_prep_time', true );
$recipe['cook_time'] = get_post_meta( $id, '_r_grill_time', true );
$recipe['total_time'] = $recipe['prep_time'] + $recipe['cook_time'];

Ingredients and Instructions use arrays of groups, each group containing an optional name (ex: “For the Sauce”) and an array of ingredients/instructions. For each ingredient you can specify array( 'raw' => $ingredient ); and WP Recipe Maker will handle processing that raw data (ex: “1 tsp salt”) into its components (“1” count, “tsp” unit, and “salt” ingredient).

Here’s what my ingredients and instructions looked like:

$ingredients_data = maybe_unserialize( get_post_meta( $id, '_recipe_ingredients', true ) );
if( !empty( $ingredients_data ) ) {
	foreach( $ingredients_data as $data_group ) {
		$group = array();
		if( !empty( $data_group['title'] ) )
			$group['name'] = wp_strip_all_tags( $data_group['title'] );
		for( $i = 0; $i < count( $data_group['nums'] ); $i++ ) {
			$ingredient = '';
			if( !empty( $data_group['nums'][ $i ] ) )
				$ingredient .= $data_group['nums'][ $i ];
			if( !empty( $data_group['types'][ $i ] ) && 'no type' !== $data_group['types'][ $i ] )
				$ingredient .= ' ' . $data_group['types'][ $i ];
			if( !empty( $data_group['names'][ $i ] ) )
				$ingredient .= ' ' . $data_group['names'][ $i ];
			if( !empty( $ingredient ) )
				$group['ingredients'][] = array( 'raw' => $ingredient );
		}
		if( !empty( $group ) )
			$recipe['ingredients'][] = $group;
	}
}

$instructions_data = maybe_unserialize( get_post_meta( $id, '_recipe_steps_data', true ) );

$instructions = array();
foreach( $instructions_data as $item ) {
	$instructions[] = array(
		'text' => $item['content'],
	);
}

// Instructions have to follow this array structure consisting of groups first.
$recipe['instructions'] = array(
	array(
		'name' => '', // Group names can be empty.
		'instructions' => $instructions
	)
);

Replace Recipe

The replace_recipe() method lets you run additional code after the recipe has been imported. If your previous recipe plugin used a shortcode, use this to find/replace it with the WP Recipe Maker shortcode.

In my case the previous theme hardcoded the recipe below the post content, so I’m appending the WP Recipe Maker shortcode to the end of the post content.

I also use this method to mark the post as imported, using my custom field _recipe_imported, and to set the parent post for the recipe, using the custom field wprm_parent_post_id.

	/**
	 * Replace the original recipe with the newly imported WPRM one.
	 *
	 * @since    1.20.0
	 * @param	 mixed $id ID of the recipe we want replace.
	 * @param	 mixed $wprm_id ID of the WPRM recipe to replace with.
	 * @param	 array $post_data POST data passed along when submitting the form.
	 */
	public function replace_recipe( $id, $wprm_id, $post_data ) {
		// The recipe with ID $id has been imported and we now have a WPRM recipe with ID $wprm_id (can be the same ID).
		// $post_data will contain any input fields set in the "get_settings_html" function.
		// Use this function to do anything after the import, like replacing shortcodes.

		// Mark as migrated so it isn't re-imported
		update_post_meta( $id, '_recipe_imported', 1 );

		// Set parent post that contains recipe
		update_post_meta( $wprm_id, 'wprm_parent_post_id', $id );

		// Add the WPRM shortcode
		$post = get_post( $id );
		$content = $post->post_content . ' [wprm-recipe id="' . $wprm_id . '"]';
		wp_update_post( array( 'ID' => $id, 'post_content' => $content ) );

	}

That’s it! The importer is built and ready for testing. You’ll want to import a few recipes to ensure everything is working as expected. If you run into any issues, I recommend looking through the existing importers for guidance / ideas.

If your current recipe plugin stores the entire ingredient list as an HTML bulleted list, check out the parse_recipe_component_list() method in class-wprm-import-purr.php for an example of parsing the HTML into individual ingredients.

Importing recipes

Once you have a WP Recipe Maker importer for your current recipe plugin, the process of importing recipes is very straightforward.

In the WordPress backend go to WP Recipe Maker > Import Recipes. You should see a section with your recipe plugin’s name and the number of recipes it found. Click “Explore Import Options”

On the next screen you’ll see a list of recipes you can import. Select all the ones you’d like to import, then click “Import Selected Recipes”.

I recommend you import one recipe at a time, then view the post to make sure all the recipe data made it over.

Bill Erickson

Bill Erickson is a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Leave A Reply