Terence Eden’s Blog<p><strong>Adding Semantic Reviews / Rich Snippets to your WordPress Site</strong></p><p><a href="https://shkspr.mobi/blog/2020/07/adding-semantic-reviews-rich-snippets-to-your-wordpress-site/" rel="nofollow noopener" translate="no" target="_blank"><span class="invisible">https://</span><span class="ellipsis">shkspr.mobi/blog/2020/07/addin</span><span class="invisible">g-semantic-reviews-rich-snippets-to-your-wordpress-site/</span></a></p><p></p><p>This is a real "scratch my own itch" post. I want to add <a href="https://schema.org/Review" rel="nofollow noopener" target="_blank">Schema.org semantic metadata</a> to the book reviews I write on my blog. This will enable "rich snippets" in search engines.</p><p>There are <em>loads</em> of WordPress plugins which do this. But where's the fun in that?! So here's how I quickly built it into my <a href="https://gitlab.com/edent/blog-theme" rel="nofollow noopener" target="_blank">open source blog theme</a>.</p><p><strong>Screen options</strong></p><p>First, let's add some <a href="https://make.wordpress.org/support/user-manual/getting-to-know-wordpress/screen-options/" rel="nofollow noopener" target="_blank">screen options</a> to the WordPress editor screen.</p><p>This is what it will look like when done:</p><p></p><p>This is how to add a <a href="https://developer.wordpress.org/plugins/metadata/custom-meta-boxes/#adding-meta-boxes" rel="nofollow noopener" target="_blank">custom metabox</a> to the editor screen:</p><pre><code>// Place this in functions.php// Display the boxfunction edent_add_review_custom_box(){ $screens = ['post']; foreach ($screens as $screen) { add_meta_box( 'edent_review_box_id', // Unique ID 'Book Review Metadata', // Box title 'edent_review_box_html',// Content callback, must be of type callable $screen // Post type ); }}add_action('add_meta_boxes', 'edent_add_review_custom_box');</code></pre><p>The contents of the box are bog standard HTML</p><pre><code>// Place this in functions.php// HTML for the boxfunction edent_review_box_html($post){ $review_data = get_post_meta(get_the_ID(), "_edent_book_review_meta_key", true); echo "<table>"; $checked = ""; if ($review_data["review"] == "true") { $checked = "checked"; } echo "<tr><td><label for='edent_book_review'>Embed Book Review:</label></td><td><input type=checkbox id=edent_book_review name=edent_book_review[review] value=true {$checked}></tr>"; echo "<tr><td><label for='edent_rating'>Rating:</label></td><td><input type=range id=edent_rating name=edent_book_review[rating] min=0 max=5 step=0.5 value='". esc_html($review_data["rating"]) ."'></tr>"; echo "<tr><td><label for=edent_isbn >ISBN:</label></td><td><input name=edent_book_review[isbn] id=edent_isbn type=text value='" . esc_html($review_data["isbn"]) . "' autocomplete=off></tr>"; echo "</table>";}</code></pre><p>Done! We now have a box for metadata. That data will be <code>POST</code>ed every time the blogpost is saved. But where do the data go?</p><p><strong>Saving data</strong></p><p>This function is added every time the blogpost is saved. If the checkbox has been ticked, the metadata are saved to the database. If the checkbox is unticked, the metadata are deleted.</p><pre><code>// Place this in functions.php// Save the boxfunction edent_review_save_postdata($post_id){ if (array_key_exists('edent_book_review', $_POST)) { if ($_POST['edent_book_review']["review"] == "true") { update_post_meta( $post_id, '_edent_book_review_meta_key', $_POST['edent_book_review'] ); } else { delete_post_meta( $post_id, '_edent_book_review_meta_key' ); } }}add_action('save_post', 'edent_review_save_postdata');</code></pre><p>Nice! But how do we get the data back out again?</p><p><strong>Retrieving the data</strong></p><p>We can use the <a href="https://developer.wordpress.org/reference/functions/get_post_meta/" rel="nofollow noopener" target="_blank"><code>get_post_meta()</code> function</a> to get all the metadata associated with a blog entry. We can then turn it into a Schema.org structured metadata entry.</p><pre><code>function edent_book_review_display($post_id){ // https://developer.wordpress.org/reference/functions/the_meta/ $review_data = get_post_meta($post_id, "_edent_book_review_meta_key", true); if ($review_data["review"] == "true") { $blog_author_data = get_the_author_meta(); $schema_review = array ( '@context' => 'https://schema.org', '@type' => 'Review', 'author' => array ( '@type' => 'Person', 'name' => get_the_author_meta("user_firstname") . " " . get_the_author_meta("user_lastname"), 'sameAs' => array ( 0 => get_the_author_meta("user_url"), ), ), 'url' => get_permalink(), 'datePublished' => get_the_date('c'), 'publisher' => array ( '@type' => 'Organization', 'name' => get_bloginfo("name"), 'sameAs' => get_bloginfo("url"), ), 'description' => mb_substr(get_the_excerpt(), 0, 198), 'inLanguage' => get_bloginfo("language"), 'itemReviewed' => array ( '@type' => 'Book', 'name' => $review_data["title"], 'isbn' => $review_data["isbn"], 'sameAs' => $review_data["book_url"], 'author' => array ( '@type' => 'Person', 'name' => $review_data["author"], 'sameAs' => $review_data["author_url"], ), 'datePublished' => $review_data["book_date"], ), 'reviewRating' => array ( '@type' => 'Rating', 'worstRating' => 0, 'bestRating' => 5, 'ratingValue' => $review_data["rating"], ), 'thumbnailUrl' => get_the_post_thumbnail_url(), ); echo '<script type="application/ld+json">' . json_encode($schema_review) . '</script>'; echo "<div class='edent-review' style='clear:both;'>"; if (isset($review_data["rating"])) { echo "<span class='edent-rating-stars' style='font-size:2em;color:yellow;background-color:#13131380;'>"; $full = floor($review_data["rating"]); $half = 0; if ($review_data["rating"] - $full == 0.5) { $half = 1; } $empty = 5 - $half - $full; for ($i=0; $i < $full ; $i++) { echo "★"; } if ($half == 1) { echo "⯪"; } for ($i=0; $i < $empty ; $i++) { echo "☆"; } echo "</span>"; } echo "<ul>"; if ($review_data["amazon_url"] != "") { echo "<li><a href='{$review_data["amazon_url"]}'>Buy it on Amazon</a></li>"; } if ($review_data["author_url"] != "") { echo "<li><a href='{$review_data["author_url"]}'>Author's homepage</a></li>"; } if ($review_data["book_url"] != "") { echo "<li><a href='{$review_data["book_url"]}'>Publisher's details</a></li>"; } echo "</ul>"; } echo "</div>";}</code></pre><p>In <code>index.php</code>, after <code>the_content();</code> add:</p><pre><code>edent_book_review_display(get_the_ID());</code></pre><p>Then, on the website, it will look something like this:</p><p></p><p>Note the use of the <a href="http://www.righto.com/2016/10/inspired-by-hn-comment-four-half-star.html" rel="nofollow noopener" target="_blank">Unicode Half Star</a> for the ratings.</p><p>The source code of the site shows the output of the JSON LD:</p><p>When run through a <a href="https://search.google.com/structured-data/testing-tool/u/0/" rel="nofollow noopener" target="_blank">Structured Data Testing Tool</a>, it shows as a valid review:</p><p></p><p>And this means, when search engines access your blog, they will display rich snippets based on the semantic metadata.</p><p></p><p>You can <a href="https://shkspr.mobi/blog/2020/06/review-the-house-of-shattered-wings-aliette-de-bodard/" rel="nofollow noopener" target="_blank">see the final blog post</a> to see how it works.</p><p><strong>ToDo</strong></p><p>My code is horrible and hasn't been tested, validated, or sanitised. It's only for my own blog, and I'm unlikely to hack myself, but that needs fixing.</p><p>I want to add review metadata for movies, games, and gadgets. That will either require multiple boxes, or a clever way to only show the necessary fields.</p><p></p><p><a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/metadata/" target="_blank">#metadata</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/php/" target="_blank">#php</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/schema-org/" target="_blank">#schemaOrg</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/wordpress/" target="_blank">#WordPress</a></p>