How to Display Ecommerce Products Related to Each WordPress Post; Relevance on a Post Basis

Recently, we noticed that despite a spike in traffic to the articles section of our client’s site, product sales weren’t increasing. We decided to add a “Related Products” feed to the sidebar of each article/post in an effort to increase conversions. Unfortunately, our client’s E-commerce software offered no solutions when it came to integrating this kind of feed into a WordPress site.

What can be done when your E-commerce software offers no way to integrate your inventory and cart with the more SEO-friendly parts of your website? Using cURL with PHP’s native DOMDocument and DOMXPath classes, it’s possible to extract a dynamic feed of related products from an E-commerce site, and then insert the feed directly into the sidebar of a separate WordPress site.

The Prerequisites

Following are the minimum requirements necessary to get the feed up and running:

  1. Search results URL with keyword in its query string

    Since the feed will be generated based on a unique keyword per post, it’s necessary to have some kind of search functionality in place on the E-commerce site where a keyword can be plugged into the URL query string. If you have search functionality, do a search and copy the entire URL of your search results page. Keep the URL in a safe place–we’ll need it shortly.

  2. cURL (Client URL Library), DOMDocument class, & DOMXPath Class

    Each of these must be compiled in your installation of PHP, and are usually included by default. cURL is needed to retrieve the full HTML of our search results page, DOMDocument is needed to load the HTML into a nodeList for parsing, and DOMXPath allows us to extract only the nodes we need while leaving behind the rest

Extensions & Tools

The following is a list tools that we will include to extend PHP’s functionality and simplify the feed generation process:

  1. The DOMinnerHTML function (see code below)

    Allows us to push node content from a nodeList object into a plain text string (originally found in The PHP Manual)

  2. htmLawed (optional)

    An HTML purifier that may come in handy if your E-commerce software outputs invalid HTML–valid HTML is necessary to avoid having Warnings and Errors thrown while parsing. Download the latest version here. The Tidy extension for PHP can be used instead if available.

In our theme’s template directory, we’ll create a new directory called “related-products-feed” for our code and for any extensions related to our feed. In this directory, we’ll set up a new PHP file, “feed.php”, with the tools mentioned above:

include('htmLawed.php');

function DOMinnerHTML($element) {
    $innerHTML = "";
    $children = $element->childNodes;
    foreach ($children as $child) {
        $tmp_dom = new DOMDocument();
        $tmp_dom->appendChild($tmp_dom->importNode($child, true));
        $innerHTML.=trim($tmp_dom->saveHTML());
    }
    return $innerHTML;
}

For our client, I used custom fields in WordPress to define a keyword for each post. The original search results URL looked like this–http://search.optimum7clienturl.com/psearch/svc/search.php?uid=4&q=dynamic+keyword. The phrase in bold will be replaced by a WP custom field value (our keyword).

To retrieve the value of the custom field with key ‘keyword’, and to insert the retrieved keyword into a URL with a dynamic query string:

//retrieve keyword(s) and encode for insertion into query string
$keyword = urlencode( get_post_meta( $post->ID, 'keyword', true ) );

//set default keyword to 'camera' if no custom field value defined
if ( ! $keyword ) { $keyword = 'camera'; }

$url = "http://search.optimum7clienturl.com/psearch/svc/search.php?uid=4&aq=".$keyword;

Use cURL to access the dynamic URL and return the page’s full HTML:

// initialize a new session, return a cURL handle ($ch)
$ch = curl_init( $url );

// set options to return content as a string rather than output it directly
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);

// save retrieved HTML string as $content
$content = curl_exec($ch);

// close session and free all resources
curl_close($ch);

Refer to the PHP Manual for a full list of cURL options

At this point, if we were to echo ‘$content’, the result would be our search results page in its entirety.

To clean up invalid HTML, first set up configuration options for hmtLawed. Because our client’s search results returned HTML with duplicate ID’s, I configured htmLawed to remove duplicate IDs. Full configuration options can be found in the htmLawed Documentation. After setting configuration options, process the retrieved page’s HTML.

$config = array('unique_ids'=>1);

// clean up $content and save HTML as $processed (string)
$processed = htmLawed( $content, $config );

Now that our HTML is cleaned up and saved as a string, we will load the HTML into a new DOMDocument object and use DOMXpath to choose our individual product divs, leaving behind the HTML of the rest of the page.

Take a look at the HTML construct of your original search results page (using Firebug, or simply by viewing the page source) and find the outermost container (may it be a div, table row, etc.) for each individual product.

In the above screenshot of our client’s search results, you can see that the containing div of the first product found, along with the divs of each of the subsequent products, have the class grid_wrap_brief_box. We will use this class in our DOMXPath query to extract individual nodes.

// create a new DOMDocument object to load our HTML into
$dom = new domDocument;

// load our cleaned up HTML
$dom->loadHTML( $processed );

// create new DOMXPath object for navigating through our NodeList
$xpath = new DOMXpath( $dom );

// query divs of individual products only
$div = $xpath->query("//div[@class='grid_wrap_brief_box']");

Now that we have set up a query for the nodes we want to import into our feed, we can set up a loop to iterate over each matching node. We will limit the number of search results to 3 by setting variable $x to 0, increasing it by 1 before the completion of each iteration, and adding a condition that the loop only continue if said variable is less than 3.

$x = 0;

//start loop
foreach ( $div as $individual_product ) {

	if ( $x < 3 ) {
	        // save entire contents of node as plain text string
		$html = DOMinnerHTML( $individual_product );	

		$x++;
	} else break;

	echo $html;
}

The Finished Related Products Feed

In our theme's sidebar.php, we can simply include our code in its own container.

Related Products

Note: If you prefer to call the feed from a widget, you can download the inPHP Widget plugin and paste the code in, making sure the include paths are correct.

With a little styling, we now have a seamless, dynamic Related Products feed for each post/page.

The SEO power of our client's WordPress site can finally be used to drive traffic to his E-commerce site. Since visitors who land on a WordPress article will immediately see links to the products that they would be most likely to buy, we expect to see an increase in product sales. In fewer words, our new feed will facilitate the conversion of readers to customers.