We recently launched our photographic print store on 75CentralPhotography.com and I wanted to take a few minutes to summarize how we did it.
If you’re unaware, 75CentralPhotography is my photography site where I’ve been posting photos for almost 16 years. For the first 15 years and change, I posted a new photo everyday; however, in late 2022, I decided to take a bit of a break and slowed my posting from daily to three times a week. However, I soon realized how much I missed creating and sharing photos that I’ve upped that frequency to every weekday.
For years, I’ve generated a nice side income from licensing photos and, to a lesser-extent, selling prints. For a long time, a third-party service handled the print orders, automatically creating the order, invoicing the customer, printing the photo and shipping it. However, I was never really pleased with this arrangement. The service took a huge cut of the revenue, the integration with the site was “wonky” and I was never 100% confident in the quality of the end-product.
For a while, I’d been thinking about fulfilling the orders myself, or at least using a trusted printer’s drop-ship/white label service to fulfill them while I handled the eCommerce side of things on the site. Since 75CentralPhotography.com runs on the WordPress platform, it seemed natural to use the well-regarded and mature WooCommerce environment to handle the “shop”.
However, there was one hitch: out-of-the-box WooCommerce requires you to manually create products. Since I wanted to be able to sell prints of almost every photo on the site, this was a non-starter for me—I wasn’t going to be able to manually create over 5,600 products. Even the bulk upload feature wouldn’t really work, since I’d still need to create the variations and product photos manually.
After noodling on the problem a bit, I thought of a solution: what if every photo had a “Buy” link that would either direct the user to the related product if it existed or automatically create the product if it didn’t exist?
I studied the WooCommerce API and, to my relief, programmatically creating products was a possibility. Unfortunately, there was little documentation or example code to be found, despite my “Google-Fu”. But that’s never stopped me before. As a professional software engineering manager, I’ve had plenty of experience in my career building things with little documentation to guide me.
Since, as mentioned, there’s little documentation available, I thought I’d share my code here in the event that it helps others trying to programmatically create WooCommerce products.
A couple of notes before we get started:
- I’m not a PHP developer, so don’t be too harsh on my code/methodology…there’s definitely more-elegant ways to write this code.
- I’ll present the complete code first, then an annotated version aftewards.
Below is the code. This file sits at the root of 75CentralPhotography.com and accepts the post ID of the current post as a querystring parameter (i.e. https://www.75centralphotography.com/printredirect.php?photoID=[POST_ID])
<?php
/**
* Woocommerce Product Creation from Post
* By Matt Harvey (matt@75central.com)
* 75CentralPhotography.com
* robotsprocket.com
*
*/
//Below forces page to not cache
header('Expires: Sun, 01 Jan 2014 00:00:00 GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', FALSE);
header('Pragma: no-cache');
require('./wp-load.php'); //load wordpress environment
/*Create our variation attributes*/
$PRICE_8X12 = 25;
$PRICE_12X18 = 45;
$PRICE_16X24 = 80;
$PRICE_20X30 = 125;
$PRICE_24X36 = 200;
$PRICE_12X36 = 75;
$PRICE_16X48 = 125;
$PRICE_20X60 = 200;
$PRICE_30X90 = 275;
$PRICE_8X8 = 20;
$PRICE_12X12 = 30;
$PRICE_16X16 = 60;
$PRICE_24X24 = 100;
$PRICE_30X30 = 150;
$PRICE_36X36 = 225;
$SIZE_8x12 = '8x12';
$SIZE_12x18 = '12x18';
$SIZE_16x24 = '16x24';
$SIZE_20x30 = '20x30';
$SIZE_24x36 = '24x36';
$SIZE_12x36 = '12x36';
$SIZE_16x48 = '16x48';
$SIZE_20x60 = '20x60';
$SIZE_30x90 = '30x90';
$SIZE_8x8 = '8x8';
$SIZE_12x12 = '12x12';
$SIZE_16x16 = '16x16';
$SIZE_24x24 = '24x24';
$SIZE_30x30 = '30x30';
$SIZE_36x36 = '36x36';
$FINISH_LUSTRE = 'lustre';
$FINISH_GLOSSY = 'glossy';
$FINISH_MATTE = 'matte';
$isPano = false; //flag to determine if we are creating a panorama print
$isSquare = false; //flag to determine if we are creating a square print
$pId = htmlspecialchars($_GET["photoID"]); //get the post id from the url
$post_id = $pId; //TODO Refactor to use consistent variable names
$imageID = get_post_thumbnail_id($post_id); //get the featured image of the post
$fullsize_path = get_attached_file( $imageID ); //get the path to the featured image
list($width, $height, $type, $attr) = getimagesize($fullsize_path);
if($width == $height){ //check if the image is square
$isSquare = true;
}
if(is_numeric($pId)==false){ //check if the post id is numeric. If it isn't, we don't need to create the product
echo '<script>window.location.href = "http://www.75centralphotography.com/problem/";</script>';
exit;
}
if(checkIfProductExists($post_id)){ //check if product already exists. If it does, we don't need to create it again.
//show loading animation
echo '<link rel="stylesheet" href="create.css">';
echo '<div class="loader-container">';
echo '<div class="spinner"></div>';
echo '</div>';
echo '<script>window.location.href = "http://www.75centralphotography.com/product/?p='.getProductIdfromPhoto($post_id).'";</script>'; //redirect to the product page
exit; //we exit because we don't need to create the product again
}
else{ //if the product doesn't exist, we create it
//show loading animation
echo 'Reticulating splines...';
echo '<link rel="stylesheet" href="create.css">';
echo '<div class="loader-container">';
echo '<div class="spinner"></div>';
echo '</div>';
}
$meta = get_post_meta($post_id); //get the post meta data
$content_post = get_post($pId); //get the post content
$content = $content_post->post_content;
$content = apply_filters('the_content', $content);
$content = str_replace(']]>', ']]>', $content);
$photoName = get_the_title($post_id); //get the post title
$desc = strip_tags($content); //strip the html tags from the post content to get the description of the product
$posttags =get_the_tags($post_id); //get the tags of the post
$productTags = array(); //create an array to hold the tags of the product
$productCategory = array(); //create an array to hold the category of the product
if(has_tag( 'panoramas', $post_id )){ //check if the post has the tag 'panoramas'. We treat panos differently because the print sizzes are different
$isPano = true;
}
if ($posttags) {
foreach($posttags as $tag) {
//we create a product tag for each tag on the post
$productTag = getOrCreateTag($tag->name);
array_push($productTags, $productTag);
}
foreach($posttags as $tag) {
//we map a set of post tags to a product category
if($tag->name == 'Landscapes' || $tag->name == 'Seascapes' || $tag->name =='Trees' || $tag->name == 'Flowers' || $tag->name == 'Beaches' ){
array_push($productCategory, '3545');
break;
}
else if($tag->name == 'Abstract' ){
array_push($productCategory, '3546');
break;
}
else if($tag->name == 'Architecture' || $tag->name == 'Cityscapes' ){
array_push($productCategory, '3547');
break;
}
else if($tag->name == 'Bridges' || $tag->name == 'Roads'|| $tag->name == 'Dams' ){
array_push($productCategory, '3548');
break;
}
else if($tag->name == 'Animals' || $tag->name == 'Birds'|| $tag->name == 'Dogs' ){
array_push($productCategory, '3549');
break;
}
else if($tag->name == 'Cars' ){
array_push($productCategory, '3550');
break;
}
}
}
if($productCategory == null){
array_push($productCategory, '3551'); //if the post doesn't have any of the tags above, we assign it to the 'Other Photos' category
}
$product = new WC_Product_Variable(); //create a new variable product
$product->set_name( $photoName ); //set the name of the product to the photo title
$product->set_image_id( $imageID ); //set the featured image of the product to the featured image of the post
$product->set_description( $desc ); //set the description of the product to the post content
$product->set_tag_ids( $productTags ); //set the tags of the product to the tags of the post
$product->set_category_ids( $productCategory ); //set the category of the product to the category of the post
$new_product_attributes = array(); //create an array to hold the attributes of the product
$attribute = new WC_Product_Attribute(); //create a new attribute
//create our photo finish attribute
$attribute->set_id( wc_attribute_taxonomy_id_by_name( 'pa_finish' ) );
$attribute->set_name( 'pa_finish' );
$attribute->set_options( array( $FINISH_GLOSSY, $FINISH_LUSTRE, $FINISH_MATTE ) );
$attribute->set_position( 0 );
$attribute->set_visible( true );
$attribute->set_variation( true );
$new_product_attributes[] = array_push ($new_product_attributes, $attribute);
$attribute = new WC_Product_Attribute();
//create our photo size attribute
$attribute->set_id( wc_attribute_taxonomy_id_by_name( 'pa_size' ) );
$attribute->set_name( 'pa_size' );
if($isPano){
$attribute->set_options( array($SIZE_12x36, $SIZE_16x48, $SIZE_20x60, $SIZE_30x90) ); //if the post is a pano, we set the sizes to the pano sizes
}
else if ($isSquare){
$attribute->set_options( array($SIZE_8x8, $SIZE_12x12, $SIZE_16x16, $SIZE_20x20, $SIZE_24x24, $SIZE_30x30, $SIZE_36x36) ); //if the post is a square, we set the sizes to the square sizes
}
else{
$attribute->set_options( array($SIZE_8x12, $SIZE_12x18, $SIZE_16x24, $SIZE_20x30, $SIZE_24x36) ); //if the post is not a pano, we set the sizes to the regular sizes
}
$attribute->set_position( 1 );
$attribute->set_visible( true );
$attribute->set_variation( true );
$new_product_attributes[] = array_push ($new_product_attributes, $attribute);
$product->set_attributes( $new_product_attributes ); //set the attributes of the product to the attributes we created
$product->save(); //save the product
$product_id = $product->get_id();
if($isPano==false && $isSquare == false ){ //if the photo is not a pano, we create the variations for the regular sizes
//TODO the below code is very repetitive. We should refactor it to be more DRY
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id);
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE, 'pa_size' =>$SIZE_8x12) );
$variation->set_regular_price( $PRICE_8X12 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_8x12 ) );
$variation->set_regular_price( $PRICE_8X12 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_8x12 ) );
$variation->set_regular_price( $PRICE_8X12 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' =>$SIZE_12x18) );
$variation->set_regular_price( $PRICE_12X18 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_12x18 ) );
$variation->set_regular_price( $PRICE_12X18 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_12x18 ) );
$variation->set_regular_price( $PRICE_12X18 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_16x24 ) );
$variation->set_regular_price( $PRICE_16X24 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_16x24 ) );
$variation->set_regular_price( $PRICE_16X24 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_16x24 ) );
$variation->set_regular_price( $PRICE_16X24 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_16x24 ) );
$variation->set_regular_price( $PRICE_16X24 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_20x30 ) );
$variation->set_regular_price( $PRICE_20X30 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_20x30 ) );
$variation->set_regular_price( $PRICE_20X30 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_20x30 ) );
$variation->set_regular_price( $PRICE_20X30 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_24x36 ) );
$variation->set_regular_price( $PRICE_24X36 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_24x36 ) );
$variation->set_regular_price( $PRICE_24X36 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_24x36 ) );
$variation->set_regular_price( $PRICE_24X36 );
$variation->save();
}
else if($isPano == true && $isSquare == false){
//handle pano photos
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_12x36 ) );
$variation->set_regular_price( $PRICE_12X36 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_12x36 ) );
$variation->set_regular_price( $PRICE_12X36 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_12x36 ) );
$variation->set_regular_price( $PRICE_12X36 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_16x48 ) );
$variation->set_regular_price( $PRICE_16X48 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_16x48 ) );
$variation->set_regular_price( $PRICE_16X48 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_16x48 ) );
$variation->set_regular_price( $PRICE_16X48 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_20x60 ) );
$variation->set_regular_price( $PRICE_20X60 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_20x60 ) );
$variation->set_regular_price( $PRICE_20X60 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_20x60 ) );
$variation->set_regular_price( $PRICE_20X60 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_30x90 ) );
$variation->set_regular_price( $PRICE_30X90 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_30x90 ) );
$variation->set_regular_price( $PRICE_30X90 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_30x90 ) );
$variation->set_regular_price( $PRICE_30X90 );
$variation->save();
}
else if($isPano == false && $isSquare == true){
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_8x8 ) );
$variation->set_regular_price( $PRICE_8X8 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_8x8 ) );
$variation->set_regular_price( $PRICE_8X8 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_8x8 ) );
$variation->set_regular_price( $PRICE_8X8 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_12x12 ) );
$variation->set_regular_price( $PRICE_12X12 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_12x12 ) );
$variation->set_regular_price( $PRICE_12X12 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_12x12 ) );
$variation->set_regular_price( $PRICE_12X12 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_16x16 ) );
$variation->set_regular_price( $PRICE_16X16 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_16x16 ) );
$variation->set_regular_price( $PRICE_16X16 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_16x16 ) );
$variation->set_regular_price( $PRICE_16X16 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_24x24 ) );
$variation->set_regular_price( $PRICE_24X24 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_24x24 ) );
$variation->set_regular_price( $PRICE_24X24 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_24x24 ) );
$variation->set_regular_price( $PRICE_24X24 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_30x30 ) );
$variation->set_regular_price( $PRICE_30X30 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_30x30 ) );
$variation->set_regular_price( $PRICE_30X30 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_30x30 ) );
$variation->set_regular_price( $PRICE_30X30 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_LUSTRE , 'pa_size' => $SIZE_36x36 ) );
$variation->set_regular_price( $PRICE_36X36 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_MATTE , 'pa_size' => $SIZE_36x36 ) );
$variation->set_regular_price( $PRICE_36X36 );
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id( $product_id );
$variation->set_attributes( array( 'pa_finish' => $FINISH_GLOSSY , 'pa_size' => $SIZE_36x36 ) );
$variation->set_regular_price( $PRICE_36X36 );
$variation->save();
}
else {
echo '<script>window.location.href = "http://www.75centralphotography.com/problem/";</script>'; //there's a problem, so we send them to a page that tells them to contact us
}
add_post_meta( $product_id, 'pId', $pId ); //set the photo ID as a meta value to the product
addproductIDtoPhoto($pId, $product_id); //do the inverse and add the product ID to the photo
echo '<script>window.location.href = "http://www.75centralphotography.com/product/?p='.$product->get_id().'";</script>'; //redirect to the newly-created product page
function getOrCreateTag($tag_name) { //get or create a tag
$term = term_exists($tag_name, 'product_tag');
if ($term !== 0 && $term !== null) {
return $term['term_id']; //return the tag ID
} else {
$term = wp_insert_term($tag_name, 'product_tag'); //create the tag if it doesn't exist
return $term['term_id']; //return the tag ID
}
}
function addproductIDtoPhoto($pId, $product_id){
if ( !metadata_exists( 'post', $pId, 'product_id' ) ) {
add_post_meta( $pId, 'product_id', $product_id );
}
}
function checkIfProductExists($pId){
$existing_pms = get_post_meta($pId, 'product_id', true);
if($existing_pms != ''){
return true;
}else{
return false;
}
}
function getProductIdfromPhoto($pId){
$existing_pms = get_post_meta($pId, 'product_id', true);
return $existing_pms;
}
Annotated Code
First, we set some header values to ensure that the page doesn’t cache and include wp-load.php so that the code can connect to our database.
header('Expires: Sun, 01 Jan 2014 00:00:00 GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', FALSE);
header('Pragma: no-cache');
require('./wp-load.php');
Next, we declare constants for each of our product variations (size, price and finish), we well as initialize flags for tracking if a photo is panoramic or square, as these get special handling. Since most of the photos on the site are in the traditional 2:3 aspect ratio, these are only used occasionally. We also get the photoID querystring parameter, which is really just the WordPress post_id.
$PRICE_8X12 = 25;
$PRICE_12X18 = 45;
$PRICE_16X24 = 80;
$PRICE_20X30 = 125;
$PRICE_24X36 = 200;
$PRICE_12X36 = 75;
$PRICE_16X48 = 125;
$PRICE_20X60 = 200;
$PRICE_30X90 = 275;
$PRICE_8X8 = 20;
$PRICE_12X12 = 30;
$PRICE_16X16 = 60;
$PRICE_24X24 = 100;
$PRICE_30X30 = 150;
$PRICE_36X36 = 225;
$SIZE_8x12 = '8x12';
$SIZE_12x18 = '12x18';
$SIZE_16x24 = '16x24';
$SIZE_20x30 = '20x30';
$SIZE_24x36 = '24x36';
$SIZE_12x36 = '12x36';
$SIZE_16x48 = '16x48';
$SIZE_20x60 = '20x60';
$SIZE_30x90 = '30x90';
$SIZE_8x8 = '8x8';
$SIZE_12x12 = '12x12';
$SIZE_16x16 = '16x16';
$SIZE_24x24 = '24x24';
$SIZE_30x30 = '30x30';
$SIZE_36x36 = '36x36';
$FINISH_LUSTRE = 'lustre';
$FINISH_GLOSSY = 'glossy';
$FINISH_MATTE = 'matte';
$isPano = false; //flag to determine if we are creating a panorama print
$isSquare = false; //flag to determine if we are creating a square print
$pId = htmlspecialchars($_GET["photoID"]); //get the post id from the url
Next, you can see some of my messiness as I create a second, confusing variable to hold the photoID, though to be fair, I do have a TODO: to clean it up at some point. We also call a function to get the featured image of the photo post. This will be used later to create the product photo. We also get the dimensions of the featured image to determine it’s aspect ratio.
$post_id = $pId; //TODO Refactor to use consistent variable names
$imageID = get_post_thumbnail_id($post_id); //get the featured image of the post
$fullsize_path = get_attached_file( $imageID ); //get the path to the featured image
list($width, $height, $type, $attr) = getimagesize($fullsize_path);
The get_attached_file() function is part of WordPress and is documented here. GetImageSize() is a native PHP function and is documented here.
Next, we see if the image’s width and height are equal and, if so, we know it’s square and set the isSquare flag to true:
if ($width == $height) { //check if the image is square
$isSquare = true;
}
Next, we verify that the photoID is numeric as all WordPress post_ids should be. A future improvement would be to verify that the post_Id is valid. If it’s not numeric, we redirect to a special “problem” page inviting the user to let us know there was an issue:
if (is_numeric($pId) == false) { //check if the post id is numeric. If it isn't, we don't need to create the product
echo '<script>window.location.href = "http://www.75centralphotography.com/problem/";</script>';
exit;
}
Next, we call a function to check if the product with that post_id already exists. If it does, we redirect to the product’s page; if it doesn’t, we show a loading animation and begin the process of creating the product programmatically:
if (checkIfProductExists($post_id)) { //check if product already exists. If it does, we don't need to create it again.
//show loading animation
echo '<link rel="stylesheet" href="create.css">';
echo '<div class="loader-container">';
echo '<div class="spinner"></div>';
echo '</div>';
echo '<script>window.location.href = "http://www.75centralphotography.com/product/?p=' . getProductIdfromPhoto($post_id) . '";</script>'; //redirect to the product page
exit; //we exit because we don't need to create the product again
} else { //if the product doesn't exist, we create it
//show loading animation
echo 'Reticulating splines...';
echo '<link rel="stylesheet" href="create.css">';
echo '<div class="loader-container">';
echo '<div class="spinner"></div>';
echo '</div>';
}
The checkIfProductExists() function just queries WordPress to see if a custom metadata field with the product_Id has already been set on the photo’s post:
function checkIfProductExists($pId)
{
$existing_pms = get_post_meta($pId, 'product_id', true);
if ($existing_pms != '') {
return true;
} else {
return false;
}
}
Since the product doesn’t exist, we can start building it in our code.
First, we get the originating post’s metadata and content. We strip out the HTML of the content as we’ll use that as our product description. We get the title of the post to use as a our product’s title. And we get the tags of the post as we’ll use those as our product’s tags. We also go ahead and declare a couple of arrays to hold the product’s tags and categories for when we actually create it.
$meta = get_post_meta($post_id);
$content_post = get_post($pId); //get the post content
$content = $content_post->post_content;
$content = apply_filters('the_content', $content);
$content = str_replace(']]>', ']]>', $content);
$photoName = get_the_title($post_id); //get the post title
$desc = strip_tags($content); //strip the html tags from the post content to get the description of the product
$posttags = get_the_tags($post_id);
$productTags = array(); //create an array to hold the tags of the product
$productCategory = array(); //create an array to hold the category of the product
Next, we cheat a bit to determine if the photo’s aspect ratio is panoramic since we know that every panoramic photo on the site is tagged as such:
if (has_tag('panoramas', $post_id)) { //check if the post has the tag 'panoramas'. We treat panos differently because the print sizes are different
$isPano = true;
}
Next, if the photo post has tags, we create product tags for each tag. We also examine the tags and categorize the product based on the tags. The code here is a bit sloppy in that we’ve hardcoded the category IDs, but since there are only a few and I don’t anticipate these changing, I’ll let it slide (I’d mark this as a fail in a code review at my job). If we can’t determine the category, we default to a catch-all category (“Other Photos”).
if ($posttags) {
foreach ($posttags as $tag) {
//we create a product tag for each tag on the post
$productTag = getOrCreateTag($tag->name);
array_push($productTags, $productTag);
}
foreach ($posttags as $tag) {
//we map a set of post tags to a product category
if ($tag->name == 'Landscapes' || $tag->name == 'Seascapes' || $tag->name == 'Trees' || $tag->name == 'Flowers' || $tag->name == 'Beaches') {
array_push($productCategory, '3545');
break;
} else if ($tag->name == 'Abstract') {
array_push($productCategory, '3546');
break;
} else if ($tag->name == 'Architecture' || $tag->name == 'Cityscapes') {
array_push($productCategory, '3547');
break;
} else if ($tag->name == 'Bridges' || $tag->name == 'Roads' || $tag->name == 'Dams') {
array_push($productCategory, '3548');
break;
} else if ($tag->name == 'Animals' || $tag->name == 'Birds' || $tag->name == 'Dogs') {
array_push($productCategory, '3549');
break;
} else if ($tag->name == 'Cars') {
array_push($productCategory, '3550');
break;
}
}
}
if ($productCategory == null) {
array_push($productCategory, '3551'); //if the post doesn't have any of the tags above, we assign it to the 'Other Photos' category
}
The getOrCreateTag() function checks if a tag already exists in the product tag taxonomy and, if it does, returns the tag’s ID. If it doesn’t already exist, it creates the tag and returns the ID:
function getOrCreateTag($tag_name)
{ //get or create a tag
$term = term_exists($tag_name, 'product_tag');
if ($term !== 0 && $term !== null) {
return $term['term_id']; //return the tag ID
} else {
$term = wp_insert_term($tag_name, 'product_tag'); //create the tag if it doesn't exist
return $term['term_id']; //return the tag ID
}
}
FInally, we get to the meaty part of the code: creating the product.
First, we initialize our product, then set its name, product image, description, tags and category:
$product = new WC_Product_Variable(); //create a new variable product
$product->set_name($photoName); //set the name of the product to the photo title
$product->set_image_id($imageID); //set the featured image of the product to the featured image of the post
$product->set_description($desc); //set the description of the product to the post content
$product->set_tag_ids($productTags); //set the tags of the product to the tags of the post
$product->set_category_ids($productCategory);
Next, since the photo finish variations are available on all of the prints we’re going to sell, we set it first before handling and of the various size variations:
$new_product_attributes = array(); //create an array to hold the attributes of the product
$attribute = new WC_Product_Attribute(); //create a new attribute
//create our photo finish attribute
$attribute->set_id(wc_attribute_taxonomy_id_by_name('pa_finish'));
$attribute->set_name('pa_finish');
$attribute->set_options(array($FINISH_GLOSSY, $FINISH_LUSTRE, $FINISH_MATTE));
$attribute->set_position(0);
$attribute->set_visible(true);
$attribute->set_variation(true);
Next, we set the attributes for the various size variations before saving the product:
$new_product_attributes[] = array_push($new_product_attributes, $attribute);
$attribute = new WC_Product_Attribute();
//create our photo size attribute
$attribute->set_id(wc_attribute_taxonomy_id_by_name('pa_size'));
$attribute->set_name('pa_size');
if ($isPano) {
$attribute->set_options(array($SIZE_12x36, $SIZE_16x48, $SIZE_20x60, $SIZE_30x90)); //if the post is a pano, we set the sizes to the pano sizes
} else if ($isSquare) {
$attribute->set_options(array($SIZE_8x8, $SIZE_12x12, $SIZE_16x16, $SIZE_20x20, $SIZE_24x24, $SIZE_30x30, $SIZE_36x36)); //if the post is a square, we set the sizes to the square sizes
} else {
$attribute->set_options(array($SIZE_8x12, $SIZE_12x18, $SIZE_16x24, $SIZE_20x30, $SIZE_24x36)); //if the post is not a pano, we set the sizes to the regular sizes
}
$attribute->set_position(1);
$attribute->set_visible(true);
$attribute->set_variation(true);
$new_product_attributes[] = array_push($new_product_attributes, $attribute);
$product->set_attributes($new_product_attributes); //set the attributes of the product to the attributes we created
$product->save(); //save the product
Once the product is created, we have to actually create the variations of the product. We start by getting the product ID of the product we just created. Then, since we’re offering different sizes based on the photo’s aspect ration (2:3/4×6, square and pano), we have a long, ugly block of code of a bunch of if…then statements to create the various combinations of size/pricing. This code could probably be refactored to reduce its complexity/repetitiveness. Finally, if there is a problem and our photo doesn’t, for some reason, fit any of the sizes, we redirect the user to an error page.
$product_id = $product->get_id();
if ($isPano == false && $isSquare == false) { //if the photo is not a pano, we create the variations for the regular sizes
//TODO the below code is very repetitive. We should refactor it to be more DRY
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_8x12));
$variation->set_regular_price($PRICE_8X12);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_8x12));
$variation->set_regular_price($PRICE_8X12);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_8x12));
$variation->set_regular_price($PRICE_8X12);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_12x18));
$variation->set_regular_price($PRICE_12X18);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_12x18));
$variation->set_regular_price($PRICE_12X18);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_12x18));
$variation->set_regular_price($PRICE_12X18);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_16x24));
$variation->set_regular_price($PRICE_16X24);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_16x24));
$variation->set_regular_price($PRICE_16X24);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_16x24));
$variation->set_regular_price($PRICE_16X24);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_16x24));
$variation->set_regular_price($PRICE_16X24);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_20x30));
$variation->set_regular_price($PRICE_20X30);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_20x30));
$variation->set_regular_price($PRICE_20X30);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_20x30));
$variation->set_regular_price($PRICE_20X30);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_24x36));
$variation->set_regular_price($PRICE_24X36);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_24x36));
$variation->set_regular_price($PRICE_24X36);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_24x36));
$variation->set_regular_price($PRICE_24X36);
$variation->save();
} else if ($isPano == true && $isSquare == false) {
//handle pano photos
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_12x36));
$variation->set_regular_price($PRICE_12X36);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_12x36));
$variation->set_regular_price($PRICE_12X36);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_12x36));
$variation->set_regular_price($PRICE_12X36);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_16x48));
$variation->set_regular_price($PRICE_16X48);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_16x48));
$variation->set_regular_price($PRICE_16X48);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_16x48));
$variation->set_regular_price($PRICE_16X48);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_20x60));
$variation->set_regular_price($PRICE_20X60);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_20x60));
$variation->set_regular_price($PRICE_20X60);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_20x60));
$variation->set_regular_price($PRICE_20X60);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_30x90));
$variation->set_regular_price($PRICE_30X90);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_30x90));
$variation->set_regular_price($PRICE_30X90);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_30x90));
$variation->set_regular_price($PRICE_30X90);
$variation->save();
} else if ($isPano == false && $isSquare == true) {
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_8x8));
$variation->set_regular_price($PRICE_8X8);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_8x8));
$variation->set_regular_price($PRICE_8X8);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_8x8));
$variation->set_regular_price($PRICE_8X8);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_12x12));
$variation->set_regular_price($PRICE_12X12);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_12x12));
$variation->set_regular_price($PRICE_12X12);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_12x12));
$variation->set_regular_price($PRICE_12X12);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_16x16));
$variation->set_regular_price($PRICE_16X16);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_16x16));
$variation->set_regular_price($PRICE_16X16);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_16x16));
$variation->set_regular_price($PRICE_16X16);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_24x24));
$variation->set_regular_price($PRICE_24X24);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_24x24));
$variation->set_regular_price($PRICE_24X24);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_24x24));
$variation->set_regular_price($PRICE_24X24);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_30x30));
$variation->set_regular_price($PRICE_30X30);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_30x30));
$variation->set_regular_price($PRICE_30X30);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_30x30));
$variation->set_regular_price($PRICE_30X30);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_LUSTRE, 'pa_size' => $SIZE_36x36));
$variation->set_regular_price($PRICE_36X36);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_MATTE, 'pa_size' => $SIZE_36x36));
$variation->set_regular_price($PRICE_36X36);
$variation->save();
$variation = new WC_Product_Variation();
$variation->set_parent_id($product_id);
$variation->set_attributes(array('pa_finish' => $FINISH_GLOSSY, 'pa_size' => $SIZE_36x36));
$variation->set_regular_price($PRICE_36X36);
$variation->save();
} else {
echo '<script>window.location.href = "http://www.75centralphotography.com/problem/";</script>'; //there's a problem, so we send them to a page that tells them to contact us
}
Finally, we add the originating photo’s post’s post_id to the new product’s custom metadata and we add the new product_id to the originating photo post’s custom metadata. Then we redirect the user to the newly-created product.
add_post_meta($product_id, 'pId', $pId); //set the photo ID as a meta value to the product
addproductIDtoPhoto($pId, $product_id); //do the inverse and add the product ID to the photo
echo '<script>window.location.href = "http://www.75centralphotography.com/product/?p=' . $product->get_id() . '";</script>'; //redirect to the newly-created product page
The add_post_meta() function is part of WordPress and is documented here. The addproductIDtoPhoto() function is a simple method that extends the add_post_meta() function:
function addproductIDtoPhoto($pId, $product_id)
{
if (!metadata_exists('post', $pId, 'product_id')) {
add_post_meta($pId, 'product_id', $product_id);
}
}
And now we have created a product programmatically from a WordPress post with variations based on size and finish: