Structured Data WordPress Plugin

Structured Data WordPress Plugin

Major search engines use structured data to show rich and meaningful search results. For example, if you have an article published on your website, you can tell search engines exactly what the title, content or image is. This keeps the search engines from trying to guess what information on the page is important to the article. Schema.org was founded by Google, Microsoft, Yahoo, and Yandex in a collaborate effort to standardize the vocabulary used for structured data. In the past, structured data was typically added to the html. However, current implementations use JSON for Linking Data (JSON-LD). I wrote a structured data plugin for WordPress that I use on this site. It includes organizational, article and navigation schemas. While this won’t necessarily give you any better search ranking, it will help search engines show the information you want it to.

The results of the plugin for the whole site look like this.

Organization

<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Organization",
  "url": "https://www.beanyogurt.com",
  "logo": "https://www.beanyogurt.com/wp-content/uploads/2018/07/logo.png"
}
</script>

Search

<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "WebSite",
  "name": "BeanYogurt",
  "alternateName": "BeanYogurt",
  "url": "https://www.beanyogurt.com",
  "potentialAction": {
    "@type": "SearchAction",
    "target": "https://www.beanyogurt.com/?s={s}",
    "query-input": "required name=s"
  }
}
</script>

Here are the results for this page specifically.

Article

<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Article",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://www.beanyogurt.com/software-development/wordpress/structured-data-wordpress-plugin"
  },
  "headline": "Structured Data WordPress Plugin",
  "image": {
    "@type": "ImageObject",
    "url": "https://www.beanyogurt.com/wp-content/uploads/2018/07/structured-data.png",
    "width": 446,
    "height": 324
  },
  "datePublished": "2018-07-23T10:24:49+00:00",
  "dateModified": "2018-07-23T10:24:49+00:00",
  "author": {
    "@type": "Person",
    "name": "Chris Pounds"
  },
  "publisher": {
    "@type": "Organization",
    "name": "BeanYogurt",
    "logo": {
      "@type": "ImageObject",
      "url": "https://www.beanyogurt.com/wp-content/uploads/2018/07/logo.png",
      "width": 186,
      "height": 48
    }
  },
  "description": "Major search engines use structured data to show rich and meaningful search results. For example, if you have an article published on your website, you can tell search engines exactly what the title, content or image is. This keeps the search engines from trying to guess what information on the page is important to the [&hellip;]"
}
</script>

Breadcrumbs

{
  "@context": "http://schema.org",
  "@type": "Breadcrumblist",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "item": {
        "@id": "https://www.beanyogurt.com",
        "name": "BeanYogurt"
      }
    },
    {
      "@type": "ListItem",
      "position": 2,
      "item": {
        "@id": "https://www.beanyogurt.com/software-development/",
        "name": "Software Development"
      }
    },
    {
      "@type": "ListItem",
      "position": 3,
      "item": {
        "@id": "https://www.beanyogurt.com/software-development/wordpress/",
        "name": "WordPress"
      }
    },
    {
      "@type": "ListItem",
      "position": 4,
      "item": {
        "@id": "https://www.beanyogurt.com/software-development/wordpress/structured-data-wordpress-plugin",
        "name": "Structured Data WordPress Plugin"
      }
    }
  ]
}

Here is the main plugin code. Use it as you like.

<?php
/*
Plugin Name: Structured Data
Description: Add structured data to web page head in json+ld format.
Version: 1.0
Author: Chris Pounds
*/
class StructuredData {

    private $plugin_prefix = 'bysd_';
    private $plugin_name = 'Structured Data';

    public function __construct() {

        register_uninstall_hook(__FILE__, 'plugin_uninstall');

        add_action('admin_enqueue_scripts', array($this, 'admin_scripts'));

        add_action('admin_menu', array($this, 'settings_menu'));
        add_action('admin_init', array($this, 'setup_sections'));
        add_action('admin_init', array($this, 'setup_fields'));

        add_action('wp_head', array($this, 'generate_schemas'));
    }

    public static function plugin_uninstall() {
        delete_option($this->plugin_prefix . 'organization_schema');
        delete_option($this->plugin_prefix . 'organization_logo');
        delete_option($this->plugin_prefix . 'sitesearch_schema');
        delete_option($this->plugin_prefix . 'breadcrumb_schema');
        delete_option($this->plugin_prefix . 'post_schema');
        delete_option($this->plugin_prefix . 'page_schema');
        delete_option($this->plugin_prefix . 'publisher_name');
        delete_option($this->plugin_prefix . 'publisher_logo');
    }

    public function admin_scripts($page) {
        wp_enqueue_media();
        wp_enqueue_script('bysd_admin_js', plugins_url('assets/js/admin.js', __FILE__), array(
            'jquery'
        ));
    }

    public function settings_menu() {
        add_submenu_page('options-general.php', $this->plugin_name, $this->plugin_name, 'manage_options', $this->plugin_prefix . 'options', array($this, 'settings_page'));
    }

    public function settings_page() {
        if (!current_user_can('manage_options')) {
            wp_die(__('You do not have sufficient permissions to access this page.'));
        }

        ?>
        <div class="wrap">
            <h2><?php echo $this->plugin_name; ?></h2>
            <form method="post" action="options.php">
                <?php
                settings_fields($this->plugin_prefix . 'options');
                do_settings_sections($this->plugin_prefix . 'options');
                submit_button();
                ?>
            </form>
        </div>
        <?php
    }

    public function setup_sections() {
        add_settings_section('organization_section', 'Organization', array($this, 'section_callback'), $this->plugin_prefix . 'options');
        add_settings_section('article_section', 'Articles', array($this, 'section_callback'), $this->plugin_prefix . 'options');
        add_settings_section('navigation_section', 'Navigation', array($this, 'section_callback'), $this->plugin_prefix . 'options');
    }

    public function section_callback() {

    }

    public function setup_fields() {

        $fields = array(
            array(
                'uid'       => $this->plugin_prefix . 'organization_schema',
                'label'     => 'Generate Organization Schema',
                'section'   => 'organization_section',
                'type'      => 'radio',
                'options'   => array(1 => 'True', 0 => 'False'),
                'default'   => 0
            ),
            array(
                'uid'       => $this->plugin_prefix . 'organization_logo',
                'label'     => 'Organization Logo',
                'section'   => 'organization_section',
                'type'      => 'image',
                'default'   => null
            ),
            array(
                'uid'       => $this->plugin_prefix . 'sitesearch_schema',
                'label'     => 'Generate Sitesearch Schema',
                'section'   => 'navigation_section',
                'type'      => 'radio',
                'options'   => array(1 => 'True', 0 => 'False'),
                'default'   => 0
            ),
            array(
                'uid'       => $this->plugin_prefix . 'breadcrumb_schema',
                'label'     => 'Generate Breadcrumb Schema',
                'section'   => 'navigation_section',
                'type'      => 'radio',
                'options'   => array(1 => 'True', 0 => 'False'),
                'default'   => 0
            ),
            array(
                'uid'       => $this->plugin_prefix . 'post_schema',
                'label'     => 'Generate Post Schema',
                'section'   => 'article_section',
                'type'      => 'radio',
                'options'   => array(1 => 'True', 0 => 'False'),
                'default'   => 0
            ),
            array(
                'uid'       => $this->plugin_prefix . 'page_schema',
                'label'     => 'Generate Page Schema',
                'section'   => 'article_section',
                'type'      => 'radio',
                'options'   => array(1 => 'True', 0 => 'False'),
                'default'   => 0
            ),
            array(
                'uid'       => $this->plugin_prefix . 'publisher_name',
                'label'     => 'Publisher Name',
                'section'   => 'article_section',
                'type'      => 'text',
                'default'   => get_bloginfo('name')
            ),
            array(
                'uid'       => $this->plugin_prefix . 'publisher_logo',
                'label'     => 'Publisher Logo',
                'section'   => 'article_section',
                'type'      => 'image',
                'default'   => esc_url(plugins_url('assets/images/default.png', __FILE__))
            )
        );

        foreach($fields as $field) {
            register_setting($this->plugin_prefix . 'options', $field['uid']);
            add_settings_field($field['uid'], $field['label'], array($this, 'field_callback'), $this->plugin_prefix . 'options', $field['section'], $field);
        }
    }

    public function field_callback($arguments) {
        $value = get_option($arguments['uid']);
        if (!$value) {
            $value = $arguments['default'];
        }
        
        switch($arguments['type']) {
            case 'text':
                printf('<input name="%1$s" id="%1$s" type="%2$s" placeholder="%3$s" value="%4$s" />', $arguments['uid'], $arguments['type'], $arguments['placeholder'], $value);
                break;
            case 'radio':
                if (!empty($arguments['options']) && is_array($arguments['options'])) {
                    $options_markup = '';
                    $iterator = 0;

                    foreach ($arguments['options'] as $key => $label) {
                        $iterator++;
                        $options_markup .= sprintf('<label for="%1$s_%6$s"><input id="%1$s_%6$s" name="%1$s" type="%2$s" value="%3$s" %4$s /> %5$s</label>&nbsp;&nbsp;&nbsp;&nbsp;', $arguments['uid'], $arguments['type'], $key, checked($value, $key, false), $label, $iterator);
                    }

                    printf('<fieldset>%s</fieldset>', $options_markup);
                }
                break;
            case 'image':
                if ($arguments['uid'] === $this->plugin_prefix . 'organization_logo') {
                    $preview_value = $value == null ? esc_url(plugins_url('assets/images/default.png', __FILE__)) : $value;
                    printf('<img id="bysd_organization_logo_preview" src="' . $preview_value . '" width="200" />');
                    printf('<div>');
                        printf('<button class="bysd-set-organization-image button button-primary">Set Image</button> ');
                        printf('<button class="bysd-set-organization-default-image button" value="' . esc_url(plugins_url('assets/images/default.png', __FILE__)) . '">Clear</button>');
                    printf('</div>');
                    printf('<input name="%1$s" id="%1$s" type="%2$s" placeholder="%3$s" value="%4$s" />', $arguments['uid'], 'hidden', $arguments['placeholder'], $value);
                }

                if ($arguments['uid'] === $this->plugin_prefix . 'publisher_logo') {
                    $preview_value = $value == null ? esc_url(plugins_url('assets/images/default.png', __FILE__)) : $value;
                    printf('<img id="bysd_publisher_logo_preview" src="' . $preview_value . '" width="200" />');
                    printf('<div>');
                        printf('<button class="bysd-set-publisher-image button button-primary">Set Image</button> ');
                        printf('<button class="bysd-set-publisher-default-image button" value="' . esc_url(plugins_url('assets/images/default.png', __FILE__)) . '">Clear</button>');
                    printf('</div>');
                    printf('<input name="%1$s" id="%1$s" type="%2$s" placeholder="%3$s" value="%4$s" />', $arguments['uid'], 'hidden', $arguments['placeholder'], $value);
                }
                break;
        }
    }

    public function generate_schemas() {
        $this->generate_organization_json();
        $this->generate_sitesearch_json();
        $this->generate_breadcrumb_json();
        $this->generate_article_json();
    }

    public function generate_organization_json() {
        if (get_option($this->plugin_prefix . 'organization_schema', false)) {
            if (is_front_page() && is_home()) {

                if (get_option($this->plugin_prefix . 'organization_logo') !== '') {
                    $logo = wp_get_attachment_image_src($this->get_attachment_id_by_url(get_option($this->plugin_prefix . 'organization_logo')), 'full');
                } else {
                    $logo = null;
                }
                
                $output = '<script type="application/ld+json">';
                $output .= '{';
                    $output .= '"@context": "http://schema.org",';
                    $output .= '"@type": "Organization",';
                    $output .= '"url": "' . home_url() . '"';
                    
                    if ($logo) {
                        $output .= ',"logo": "' . $logo[0] . '"';
                    }
                    
                $output .= '}';
                $output .= '</script>';
                
                echo $output;
            }
        }
    }

    public function generate_sitesearch_json() {
        if (get_option($this->plugin_prefix . 'sitesearch_schema', false)) {
            if (is_front_page() && is_home()) {
                $output = '<script type="application/ld+json">';
                $output .= '{';
                    $output .= '"@context": "http://schema.org",';
                    $output .= '"@type": "WebSite",';
                    $output .= '"name": "' . get_bloginfo('name') . '",';
                    $output .= '"alternateName": "' .get_bloginfo('name') . '",';
                    $output .= '"url": "' . home_url() . '",';
                    $output .= '"potentialAction": {';
                        $output .= '"@type": "SearchAction",';
                        $output .= '"target": "' . home_url() . '/?s={s}",';
                        $output .= '"query-input": "required name=s"';
                    $output .= '}';
                $output .= '}';
                $output .= '</script>';
    
                echo $output;
            }
        }
    }

    public function generate_breadcrumb_json() {
        if (get_option($this->plugin_prefix . 'breadcrumb_schema', false)) {
            if (is_single() || is_category() || is_tag()) {
                $category = $this->bysd_get_category();
    
                $output_head = '<script type="application/ld+json">';
    
                $output_head .= '{';
                    $output_head .= '"@context": "http://schema.org",';
                    $output_head .= '"@type": "Breadcrumblist",';
                    $output_head .= '"itemListElement": [';
    
                    $output_head .= '{';
                        $output_head .= '"@type": "ListItem",';
                        $output_head .= '"position": 1,';
                        $output_head .= '"item": {';
                            $output_head .= '"@id": "' . home_url() . '",';
                            $output_head .= '"name": "' . get_bloginfo('name') . '"';
                        $output_head .= '}';
                    $output_head .= '},';
    
                $i = 2;
                $this->get_category_parent_json_display($category, $output, $i);
    
                $output = $output_head . $output;
    
                    $output .= '{';
                        $output .= '"@type": "ListItem",';
                        $output .= '"position": ' . $i . ',';
                        $output .= '"item": {';
                            $output .= '"@id": "' . esc_url(get_category_link($category->term_id)) . '",';
                            $output .= '"name": "' . esc_html($category->name) . '"';
                        $output .= '}';
                    $output .= '}';
    
                    $i++;
    
                    if (is_single()) {
                        global $post;
                        setup_postdata($post);
    
                        $output .= ',{';
                            $output .= '"@type": "ListItem",';
                            $output .= '"position": ' . $i . ',';
                            $output .= '"item": {';
                                $output .= '"@id": "' . esc_url(get_permalink($post->ID)) . '",';
                                $output .= '"name": "' . esc_html(get_the_title($post->ID)) . '"';
                            $output .= '}';
                        $output .= '}';
                    }
    
                    $output .= ']';
                $output .= '}';
    
                $output .= '</script>';
    
                echo $output;
            }
        }
    }

    public function generate_article_json() {
        if (get_option($this->plugin_prefix . 'post_schema', false) ||
            get_option($this->plugin_prefix . 'page_schema', false)) {
            if (is_single() || is_page()) {
                global $post;
                setup_postdata($post);
                
                if (get_option($this->plugin_prefix . 'publisher_logo') !== '') {
                    $logo = wp_get_attachment_image_src($this->get_attachment_id_by_url(get_option($this->plugin_prefix . 'publisher_logo')), 'full');
                } else {
                    $logo = null;
                }

                $excerpt = get_the_excerpt();
                $image = wp_get_attachment_image_src(get_post_thumbnail_id($post->ID), 'full');

                $output = '<script type="application/ld+json">';
                $output .= '{';
                    $output .= '"@context": "http://schema.org",';
                    $output .= '"@type": "Article",';
                    $output .= '"mainEntityOfPage": {';
                        $output .= '"@type": "WebPage",';
                        $output .= '"@id": "' . get_the_permalink() . '"';
                        $output .= '},';

                    $output .= '"headline": "' . get_the_title() . '",';

                    if ($image) {
                        $output .= '"image": {';
                            $output .= '"@type": "ImageObject",';
                            $output .= '"url": "' . $image[0] . '",';
                            $output .= '"width": ' . $image[1] . ',';
                            $output .= '"height": ' . $image[2];
                        $output .= '},';
                    }

                    $output .= '"datePublished": "' . get_the_date('c') . '",';
                    $output .= '"dateModified": "' .get_the_modified_date('c') . '",';

                    $output .= '"author": {';
                        $output .= '"@type": "Person",';
                        $output .= '"name": "' . get_the_author() . '"';
                    $output .= '},';

                    $output .= '"publisher": {';
                        $output .= '"@type": "Organization",';
                        $output .= '"name": "' . get_bloginfo('name') . '"';

                        if ($logo) {
                            $output .= ', ';
                            $output .= '"logo": {';
                                $output .= '"@type": "ImageObject",';
                                $output .= '"url": "' . $logo[0] . '",';
                                $output .= '"width": ' . $logo[1] . ',';
                                $output .= '"height": ' . $logo[2];
                            $output .= '}';
                        }

                    $output .= '}';

                    if ($excerpt) {
                        $output .= ',"description": "' .esc_attr($excerpt) . '"';
                    }

                $output .= '}';

                $output .= '</script>';

                echo $output;
            }
        }
    }

    private function get_category_parent_json_display($category, &$output, &$i) {
        if ($category->parent > 0) {
            $category_parent = get_category($category->parent);
    
            if ($category_parent->parent > 0) {
                get_category_parent_json_display($category_parent, $output, $i);
            }
    
            $result .= '{';
                $result .= '"@type": "ListItem",';
                $result .= '"position": ' . $i . ',';
                $result .= '"item": {';
                    $result .= '"@id": "' . esc_url(get_category_link($category_parent->term_id)) . '",';
                    $result .= '"name": "' . esc_html($category_parent->name) . '"';
                $result .= '}';
            $result .= '},';
    
            $output .= $result;
    
            $i++;
        }
    }

    function get_attachment_id_by_url($url) {
        $parsed_url = explode(parse_url(WP_CONTENT_URL, PHP_URL_PATH), $url);
        $this_host = str_ireplace('www.', '', parse_url(home_url(), PHP_URL_HOST));
        $file_host = str_ireplace('www.', '', parse_url($url, PHP_URL_HOST));
    
        if (!isset($parsed_url[1]) || empty($parsed_url[1]) || ($this_host != $file_host)) {
            return;
        }
    
        global $wpdb;
        $attachment = $wpdb->get_col($wpdb->prepare("SELECT ID FROM {$wpdb->prefix}posts WHERE guid RLIKE %s;", $parsed_url[1]));
    
        return $attachment[0];
    }

    function bysd_get_category() {
        if (is_category() || is_tag()) {
            $obj_id = get_queried_object_id();
            return get_category(get_term($obj_id));
        } elseif (is_single()) {
            global $post;
            setup_postdata($post);
    
            $category_id = get_post_meta($post->ID, '_yoast_wpseo_primary_category', true);
            
            if ($category_id) {
                return get_category($category_id);
            } else {
                return get_the_category($post->ID)[0];
            }
        }
    }
}

new StructuredData();
?>

This JavaScript code is for the image uploads.

(function($, window, document) {
    $(function() {
        $('.bysd-set-organization-image').on('click', function(event) {
            event.preventDefault();
            var self = $(this);
            var file_frame = wp.media.frames.file_frame = wp.media({
                title: self.data('uploader_title'),
                button: {
                    text: self.data('uploader_button_text')
                },
                multiple: false
            })
            .on('select', function() {
                attachment = file_frame.state().get('selection').first().toJSON();
                $('#bysd_organization_logo_preview').attr('src', attachment.url);
                $('#bysd_organization_logo').val(attachment.url);
            })
            .open();
        });

        $(document).on('click', '.bysd-set-organization-default-image', function(e) {
            e.preventDefault();
            var defValue = $(this).val();
            $('#bysd_organization_logo_preview').attr('src', defValue);
            $('#bysd_organization_logo').val('');
        });

        $('.bysd-set-publisher-image').on('click', function(event) {
            event.preventDefault();
            var self = $(this);
            var file_frame = wp.media.frames.file_frame = wp.media({
                title: self.data('uploader_title'),
                button: {
                    text: self.data('uploader_button_text')
                },
                multiple: false
            })
            .on('select', function() {
                attachment = file_frame.state().get('selection').first().toJSON();
                $('#bysd_publisher_logo_preview').attr('src', attachment.url);
                $('#bysd_publisher_logo').val(attachment.url);
            })
            .open();
        });

        $(document).on('click', '.bysd-set-publisher-default-image', function(e) {
            e.preventDefault();
            var defValue = $(this).val();
            $('#bysd_publisher_logo_preview').attr('src', defValue);
            $('#bysd_publisher_logo').val('');
        });
    });
}(window.jQuery, window, document));
Categories: CategoriesSoftware Development WordPress

About Chris

Chris Pounds is a software engineer, entrepreneur, and tech junky. He holds a Bachelor of Science in Information Technology from Western Governors University.

Leave a comment

Your email address will not be published. Required fields are marked *