Make your WordPress site irresistible. Natural SEO attraction with: - robots.txt management - sitemap.xml generation - LLMs.txt support - Google integration (Analytics, Search Console, Tag Manager) - Schema.org structured data - Open Graph / Twitter Card meta tags - AMP support - Visual elements gallery - Built-in backup/restore module Includes build.sh and .distignore for WordPress-installable release ZIPs.
44 KiB
Schema.org ImageObject Structured Data - Comprehensive Analysis & Implementation Guide
Executive Summary
This document provides comprehensive technical specifications and implementation guidance for Schema.org ImageObject structured data within WordPress SEO environments. The analysis covers core properties, best practices, WordPress integration patterns, and practical code examples for implementing a robust image SEO system.
1. Core ImageObject Properties
1.1 Required Properties
{
"@context": "https://schema.org",
"@type": "ImageObject",
"contentUrl": "https://example.com/image.jpg"
}
contentUrl (URL): The actual URL of the image file. This is the only required property for a valid ImageObject.
1.2 Essential Recommended Properties
{
"@context": "https://schema.org",
"@type": "ImageObject",
"contentUrl": "https://example.com/image.jpg",
"name": "Descriptive image title",
"description": "Detailed description of the image content",
"width": 1200,
"height": 800,
"encodingFormat": "image/jpeg",
"uploadDate": "2024-01-15T10:30:00+00:00",
"author": {
"@type": "Person",
"name": "Photographer Name"
},
"copyrightHolder": {
"@type": "Organization",
"name": "Copyright Owner"
}
}
1.3 Complete Property Specifications
Basic Image Properties
- contentUrl (URL): Direct link to the image file
- name (Text): Human-readable title/caption
- description (Text): Detailed description of image content
- alternateName (Text): Alternative name for the image
- caption (Text): Caption text displayed with the image
- text (Text): Text content embedded in or extracted from the image
Technical Properties
- width (Integer): Image width in pixels
- height (Integer): Image height in pixels
- encodingFormat (Text): MIME type (e.g., "image/jpeg", "image/png")
- fileFormat (Text): File format (e.g., "JPEG", "PNG", "WebP")
- contentSize (Text): File size (e.g., "2.5 MB")
- bitrate (Text): Bitrate for animated images
Publishing & Dates
- uploadDate (DateTime): When image was uploaded
- datePublished (DateTime): When image was first published
- dateModified (DateTime): When image was last modified
- dateCreated (DateTime): When image was originally created/taken
People & Organizations
- author (Person/Organization): Image creator/photographer
- creator (Person/Organization): Same as author
- copyrightHolder (Person/Organization): Copyright owner
- photographer (Person): Photographer (specific role)
- contributor (Person/Organization): Additional contributors
Copyright & Licensing
- license (URL/CreativeWork): License under which image is published
- copyrightYear (Number): Year of copyright
- copyrightNotice (Text): Copyright notice text
- usageInfo (URL/CreativeWork): Usage information/terms
- acquireLicensePage (URL): Where to acquire license
Location & Context
- contentLocation (Place): Where image content was captured
- locationCreated (Place): Where image was created
- about (Thing): Subject matter of the image
- depicts (Thing): Things depicted in the image
- keywords (Text): Comma-separated keywords
Technical Metadata
- exifData (PropertyValue[]): EXIF metadata as structured data
- representativeOfPage (Boolean): Whether image represents the page
- thumbnail (ImageObject): Thumbnail version of the image
- embeddedTextCaption (Text): Text caption embedded in image file
Accessibility
- accessibilityFeature (Text): Accessibility features present
- accessibilityHazard (Text): Potential accessibility hazards
- accessibilityAPI (Text): Accessibility APIs supported
- accessibilityControl (Text): Accessibility controls available
2. Best Practices for Image SEO
2.1 Search Engine Optimization
Google Image Search Optimization
- Use descriptive, keyword-rich names and descriptions
- Include relevant technical metadata (dimensions, format)
- Provide context through about/depicts properties
- Use structured captions and alt text
Core Web Vitals Impact
- Include width/height for CLS prevention
- Use appropriate encodingFormat for performance
- Implement responsive image markup with structured data
2.2 Content Quality Guidelines
Descriptive Metadata
{
"name": "Red brick Victorian house with white trim and garden",
"description": "A well-maintained Victorian-era house featuring red brick construction, white decorative trim, bay windows, and a front garden with seasonal flowers",
"keywords": "Victorian house, red brick, architecture, residential, garden, historic home",
"about": {
"@type": "ArchitecturalStructure",
"name": "Victorian House",
"architecturalStyle": "Victorian"
}
}
Technical Accuracy
- Always include accurate width/height dimensions
- Use correct MIME types for encodingFormat
- Provide realistic file sizes in contentSize
- Include upload/creation dates when available
2.3 Copyright & Legal Compliance
License Information
{
"license": "https://creativecommons.org/licenses/by/4.0/",
"copyrightHolder": {
"@type": "Person",
"name": "John Photographer",
"sameAs": "https://johnsportfolio.com"
},
"copyrightYear": 2024,
"usageInfo": "https://example.com/image-usage-terms",
"acquireLicensePage": "https://example.com/license-purchase"
}
3. WordPress Implementation Patterns
3.1 Hook Integration Strategy
/**
* WordPress hooks for ImageObject implementation
*/
class TigerStyleSEO_ImageObject {
public function __construct() {
// Frontend hooks
add_action('wp_head', array($this, 'inject_image_structured_data'), 5);
// Admin hooks for image metadata
add_filter('attachment_fields_to_edit', array($this, 'add_image_schema_fields'), 10, 2);
add_filter('attachment_fields_to_save', array($this, 'save_image_schema_fields'), 10, 2);
// AJAX for dynamic image detection
add_action('wp_ajax_detect_page_images', array($this, 'ajax_detect_page_images'));
add_action('wp_ajax_nopriv_detect_page_images', array($this, 'ajax_detect_page_images'));
}
}
3.2 Media Library Integration
/**
* Add Schema.org metadata fields to WordPress media library
*/
public function add_image_schema_fields($form_fields, $post) {
// Only for images
if (!wp_attachment_is_image($post->ID)) {
return $form_fields;
}
$schema_fields = array(
'schema_name' => array(
'label' => __('Schema Name', 'tigerstyle-heat'),
'input' => 'text',
'value' => get_post_meta($post->ID, '_schema_name', true),
'helps' => __('Descriptive name for search engines', 'tigerstyle-heat')
),
'schema_description' => array(
'label' => __('Schema Description', 'tigerstyle-heat'),
'input' => 'textarea',
'value' => get_post_meta($post->ID, '_schema_description', true),
'helps' => __('Detailed description of image content', 'tigerstyle-heat')
),
'schema_keywords' => array(
'label' => __('Schema Keywords', 'tigerstyle-heat'),
'input' => 'text',
'value' => get_post_meta($post->ID, '_schema_keywords', true),
'helps' => __('Comma-separated keywords', 'tigerstyle-heat')
),
'schema_copyright_holder' => array(
'label' => __('Copyright Holder', 'tigerstyle-heat'),
'input' => 'text',
'value' => get_post_meta($post->ID, '_schema_copyright_holder', true)
),
'schema_license_url' => array(
'label' => __('License URL', 'tigerstyle-heat'),
'input' => 'text',
'value' => get_post_meta($post->ID, '_schema_license_url', true),
'helps' => __('URL to license information', 'tigerstyle-heat')
),
'schema_representative_of_page' => array(
'label' => __('Representative of Page', 'tigerstyle-heat'),
'input' => 'html',
'html' => sprintf(
'<input type="checkbox" name="attachments[%d][schema_representative_of_page]" value="1" %s>',
$post->ID,
checked(get_post_meta($post->ID, '_schema_representative_of_page', true), '1', false)
)
)
);
return array_merge($form_fields, $schema_fields);
}
3.3 EXIF Data Integration
/**
* Extract and structure EXIF data for Schema.org
*/
public function get_exif_structured_data($attachment_id) {
$exif_data = wp_get_attachment_metadata($attachment_id);
if (empty($exif_data['image_meta'])) {
return array();
}
$image_meta = $exif_data['image_meta'];
$structured_exif = array();
// Camera information
if (!empty($image_meta['camera'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'camera',
'value' => $image_meta['camera']
);
}
// Focal length
if (!empty($image_meta['focal_length'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'focalLength',
'value' => $image_meta['focal_length'] . 'mm'
);
}
// Aperture
if (!empty($image_meta['aperture'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'aperture',
'value' => 'f/' . $image_meta['aperture']
);
}
// ISO
if (!empty($image_meta['iso'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'iso',
'value' => $image_meta['iso']
);
}
// Shutter speed
if (!empty($image_meta['shutter_speed'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'shutterSpeed',
'value' => $image_meta['shutter_speed'] . 's'
);
}
// Creation timestamp
if (!empty($image_meta['created_timestamp'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'dateTimeOriginal',
'value' => date('c', $image_meta['created_timestamp'])
);
}
// GPS coordinates if available
if (!empty($image_meta['latitude']) && !empty($image_meta['longitude'])) {
$structured_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'gpsCoordinates',
'value' => array(
'@type' => 'GeoCoordinates',
'latitude' => $image_meta['latitude'],
'longitude' => $image_meta['longitude']
)
);
}
return $structured_exif;
}
3.4 Automatic Image Detection
/**
* Detect and analyze images in post content
*/
public function detect_content_images($post_id) {
$post = get_post($post_id);
if (!$post) {
return array();
}
$images = array();
$content = $post->post_content;
// Find all img tags
preg_match_all('/<img[^>]+>/i', $content, $img_tags);
foreach ($img_tags[0] as $img_tag) {
$image_data = $this->parse_img_tag($img_tag);
if ($image_data) {
$images[] = $image_data;
}
}
// Find WordPress gallery shortcodes
preg_match_all('/\[gallery[^\]]*\]/', $content, $gallery_shortcodes);
foreach ($gallery_shortcodes[0] as $shortcode) {
$gallery_images = $this->parse_gallery_shortcode($shortcode);
$images = array_merge($images, $gallery_images);
}
// Find featured image
$featured_image_id = get_post_thumbnail_id($post_id);
if ($featured_image_id) {
$featured_data = $this->get_attachment_image_data($featured_image_id);
if ($featured_data) {
$featured_data['representativeOfPage'] = true;
array_unshift($images, $featured_data);
}
}
return $images;
}
private function parse_img_tag($img_tag) {
// Extract src, alt, width, height, class from img tag
$attributes = array();
if (preg_match('/src=["\']([^"\']+)["\']/', $img_tag, $matches)) {
$attributes['src'] = $matches[1];
}
if (preg_match('/alt=["\']([^"\']*)["\']/', $img_tag, $matches)) {
$attributes['alt'] = $matches[1];
}
if (preg_match('/width=["\']?(\d+)["\']?/', $img_tag, $matches)) {
$attributes['width'] = intval($matches[1]);
}
if (preg_match('/height=["\']?(\d+)["\']?/', $img_tag, $matches)) {
$attributes['height'] = intval($matches[1]);
}
if (preg_match('/class=["\']([^"\']+)["\']/', $img_tag, $matches)) {
$attributes['class'] = $matches[1];
}
if (empty($attributes['src'])) {
return null;
}
// Try to find WordPress attachment ID
$attachment_id = attachment_url_to_postid($attributes['src']);
if ($attachment_id) {
return $this->get_attachment_image_data($attachment_id, $attributes);
} else {
return $this->get_external_image_data($attributes);
}
}
4. Media Library & EXIF Integration
4.1 Extended EXIF Processing
/**
* Comprehensive EXIF data extraction and formatting
*/
public function process_image_exif($attachment_id) {
$file_path = get_attached_file($attachment_id);
if (!$file_path || !function_exists('exif_read_data')) {
return array();
}
$exif = @exif_read_data($file_path);
if (!$exif) {
return array();
}
$processed_exif = array();
// Basic camera settings
$exif_mappings = array(
'Make' => 'cameraMake',
'Model' => 'cameraModel',
'FocalLength' => 'focalLength',
'FNumber' => 'aperture',
'ISOSpeedRatings' => 'iso',
'ExposureTime' => 'shutterSpeed',
'Flash' => 'flash',
'WhiteBalance' => 'whiteBalance',
'ExposureProgram' => 'exposureProgram',
'MeteringMode' => 'meteringMode'
);
foreach ($exif_mappings as $exif_key => $schema_key) {
if (isset($exif[$exif_key])) {
$processed_exif[] = array(
'@type' => 'PropertyValue',
'name' => $schema_key,
'value' => $this->format_exif_value($exif_key, $exif[$exif_key])
);
}
}
// GPS coordinates
if (isset($exif['GPSLatitude']) && isset($exif['GPSLongitude'])) {
$lat = $this->gps_to_decimal($exif['GPSLatitude'], $exif['GPSLatitudeRef']);
$lng = $this->gps_to_decimal($exif['GPSLongitude'], $exif['GPSLongitudeRef']);
if ($lat && $lng) {
$processed_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'geoLocation',
'value' => array(
'@type' => 'GeoCoordinates',
'latitude' => $lat,
'longitude' => $lng
)
);
}
}
// Date taken
if (isset($exif['DateTimeOriginal'])) {
$processed_exif[] = array(
'@type' => 'PropertyValue',
'name' => 'dateTimeOriginal',
'value' => date('c', strtotime($exif['DateTimeOriginal']))
);
}
return $processed_exif;
}
private function format_exif_value($key, $value) {
switch ($key) {
case 'FocalLength':
if (strpos($value, '/') !== false) {
list($num, $den) = explode('/', $value);
return round($num / $den, 1) . 'mm';
}
return $value . 'mm';
case 'FNumber':
if (strpos($value, '/') !== false) {
list($num, $den) = explode('/', $value);
return 'f/' . round($num / $den, 1);
}
return 'f/' . $value;
case 'ExposureTime':
if (strpos($value, '/') !== false) {
list($num, $den) = explode('/', $value);
if ($num == 1) {
return '1/' . $den . 's';
} else {
return round($num / $den, 2) . 's';
}
}
return $value . 's';
default:
return $value;
}
}
private function gps_to_decimal($coordinate, $hemisphere) {
if (!is_array($coordinate) || count($coordinate) != 3) {
return false;
}
$degrees = $this->fraction_to_decimal($coordinate[0]);
$minutes = $this->fraction_to_decimal($coordinate[1]);
$seconds = $this->fraction_to_decimal($coordinate[2]);
$decimal = $degrees + ($minutes / 60) + ($seconds / 3600);
if ($hemisphere == 'S' || $hemisphere == 'W') {
$decimal = -$decimal;
}
return $decimal;
}
private function fraction_to_decimal($fraction) {
if (strpos($fraction, '/') !== false) {
list($num, $den) = explode('/', $fraction);
return $num / $den;
}
return floatval($fraction);
}
4.2 Media Library Enhancement
/**
* Enhanced media library fields for comprehensive image metadata
*/
public function add_comprehensive_image_fields($form_fields, $post) {
if (!wp_attachment_is_image($post->ID)) {
return $form_fields;
}
// Get existing metadata
$schema_meta = get_post_meta($post->ID, '_schema_imageobject', true);
if (!is_array($schema_meta)) {
$schema_meta = array();
}
// Basic information section
$form_fields['schema_section_basic'] = array(
'tr' => '<tr><td colspan="2"><h3>Schema.org ImageObject Metadata</h3></td></tr>'
);
$form_fields['schema_name'] = array(
'label' => __('Image Name', 'tigerstyle-heat'),
'input' => 'text',
'value' => $schema_meta['name'] ?? '',
'helps' => __('Descriptive name for the image', 'tigerstyle-heat')
);
$form_fields['schema_description'] = array(
'label' => __('Description', 'tigerstyle-heat'),
'input' => 'textarea',
'value' => $schema_meta['description'] ?? '',
'helps' => __('Detailed description of what the image shows', 'tigerstyle-heat')
);
$form_fields['schema_keywords'] = array(
'label' => __('Keywords', 'tigerstyle-heat'),
'input' => 'text',
'value' => $schema_meta['keywords'] ?? '',
'helps' => __('Comma-separated keywords describing the image', 'tigerstyle-heat')
);
// Copyright section
$form_fields['schema_section_copyright'] = array(
'tr' => '<tr><td colspan="2"><h4>Copyright & Licensing</h4></td></tr>'
);
$form_fields['schema_copyright_holder'] = array(
'label' => __('Copyright Holder', 'tigerstyle-heat'),
'input' => 'text',
'value' => $schema_meta['copyrightHolder'] ?? '',
'helps' => __('Name of the person or organization holding copyright', 'tigerstyle-heat')
);
$form_fields['schema_license_url'] = array(
'label' => __('License URL', 'tigerstyle-heat'),
'input' => 'text',
'value' => $schema_meta['license'] ?? '',
'helps' => __('URL to the license under which this image is published', 'tigerstyle-heat')
);
$form_fields['schema_copyright_notice'] = array(
'label' => __('Copyright Notice', 'tigerstyle-heat'),
'input' => 'textarea',
'value' => $schema_meta['copyrightNotice'] ?? '',
'helps' => __('Copyright notice text', 'tigerstyle-heat')
);
// Technical section
$form_fields['schema_section_technical'] = array(
'tr' => '<tr><td colspan="2"><h4>Technical Properties</h4></td></tr>'
);
$form_fields['schema_representative_of_page'] = array(
'label' => __('Representative of Page', 'tigerstyle-heat'),
'input' => 'html',
'html' => sprintf(
'<input type="checkbox" name="attachments[%d][schema_representative_of_page]" value="1" %s> <span class="description">Check if this image represents the main content of the page</span>',
$post->ID,
checked($schema_meta['representativeOfPage'] ?? false, true, false)
)
);
// Location section
$form_fields['schema_section_location'] = array(
'tr' => '<tr><td colspan="2"><h4>Location Information</h4></td></tr>'
);
$form_fields['schema_content_location'] = array(
'label' => __('Content Location', 'tigerstyle-heat'),
'input' => 'text',
'value' => $schema_meta['contentLocation'] ?? '',
'helps' => __('Where the image content was captured (e.g., "New York, NY")', 'tigerstyle-heat')
);
return $form_fields;
}
5. Copyright & Licensing Schema
5.1 Creative Commons Integration
/**
* Creative Commons license detection and structured data
*/
public function get_creative_commons_license($license_url) {
$cc_licenses = array(
'https://creativecommons.org/licenses/by/4.0/' => array(
'name' => 'Creative Commons Attribution 4.0 International',
'alternateName' => 'CC BY 4.0',
'description' => 'This license allows reusers to distribute, remix, adapt, and build upon the material in any medium or format, so long as attribution is given to the creator.',
'permissions' => array('commercial use', 'modification', 'distribution'),
'requirements' => array('attribution'),
'prohibitions' => array()
),
'https://creativecommons.org/licenses/by-sa/4.0/' => array(
'name' => 'Creative Commons Attribution-ShareAlike 4.0 International',
'alternateName' => 'CC BY-SA 4.0',
'description' => 'This license allows reusers to distribute, remix, adapt, and build upon the material in any medium or format, so long as attribution is given to the creator. The license allows for commercial use. If you remix, adapt, or build upon the material, you must license the modified material under identical terms.',
'permissions' => array('commercial use', 'modification', 'distribution'),
'requirements' => array('attribution', 'share-alike'),
'prohibitions' => array()
),
'https://creativecommons.org/licenses/by-nc/4.0/' => array(
'name' => 'Creative Commons Attribution-NonCommercial 4.0 International',
'alternateName' => 'CC BY-NC 4.0',
'description' => 'This license allows reusers to distribute, remix, adapt, and build upon the material in any medium or format for noncommercial purposes only, and only so long as attribution is given to the creator.',
'permissions' => array('modification', 'distribution'),
'requirements' => array('attribution'),
'prohibitions' => array('commercial use')
)
);
if (isset($cc_licenses[$license_url])) {
$license_data = $cc_licenses[$license_url];
return array(
'@type' => 'CreativeWork',
'name' => $license_data['name'],
'alternateName' => $license_data['alternateName'],
'description' => $license_data['description'],
'url' => $license_url,
'usageInfo' => $license_url
);
}
return array(
'@type' => 'CreativeWork',
'url' => $license_url
);
}
5.2 Rights Management
/**
* Comprehensive rights and licensing metadata
*/
public function build_rights_metadata($attachment_id) {
$rights_data = array();
// Get stored metadata
$schema_meta = get_post_meta($attachment_id, '_schema_imageobject', true);
// Copyright holder
$copyright_holder = $schema_meta['copyrightHolder'] ?? '';
if (!empty($copyright_holder)) {
$rights_data['copyrightHolder'] = array(
'@type' => 'Person', // or Organization
'name' => $copyright_holder
);
}
// Copyright year
$copyright_year = $schema_meta['copyrightYear'] ?? '';
if (!empty($copyright_year)) {
$rights_data['copyrightYear'] = intval($copyright_year);
}
// License
$license_url = $schema_meta['license'] ?? '';
if (!empty($license_url)) {
if (strpos($license_url, 'creativecommons.org') !== false) {
$rights_data['license'] = $this->get_creative_commons_license($license_url);
} else {
$rights_data['license'] = $license_url;
}
}
// Usage info
$usage_info = $schema_meta['usageInfo'] ?? '';
if (!empty($usage_info)) {
$rights_data['usageInfo'] = $usage_info;
}
// Acquire license page
$acquire_license = $schema_meta['acquireLicensePage'] ?? '';
if (!empty($acquire_license)) {
$rights_data['acquireLicensePage'] = $acquire_license;
}
// Copyright notice
$copyright_notice = $schema_meta['copyrightNotice'] ?? '';
if (!empty($copyright_notice)) {
$rights_data['copyrightNotice'] = $copyright_notice;
}
return $rights_data;
}
6. Caption, Alt Text & Accessibility
6.1 Accessibility Integration
/**
* Enhanced accessibility features for image structured data
*/
public function build_accessibility_metadata($attachment_id, $context = array()) {
$accessibility_data = array();
// Get WordPress alt text
$alt_text = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
if (!empty($alt_text)) {
$accessibility_data['alternateName'] = $alt_text;
}
// Get caption from WordPress
$post = get_post($attachment_id);
if ($post && !empty($post->post_excerpt)) {
$accessibility_data['caption'] = wp_strip_all_tags($post->post_excerpt);
}
// Check for accessibility features
$accessibility_features = array();
// High contrast detection
if ($this->has_high_contrast($attachment_id)) {
$accessibility_features[] = 'highContrast';
}
// Text in image detection
if ($this->contains_text($attachment_id)) {
$accessibility_features[] = 'textualContent';
// Add OCR-extracted text if available
$extracted_text = $this->extract_text_from_image($attachment_id);
if (!empty($extracted_text)) {
$accessibility_data['text'] = $extracted_text;
}
}
// Decorative image detection
if ($this->is_decorative_image($attachment_id, $context)) {
$accessibility_features[] = 'decorative';
}
if (!empty($accessibility_features)) {
$accessibility_data['accessibilityFeature'] = $accessibility_features;
}
// Potential accessibility hazards
$accessibility_hazards = array();
if ($this->has_flashing_content($attachment_id)) {
$accessibility_hazards[] = 'flashingHazard';
}
if ($this->has_motion_simulation($attachment_id)) {
$accessibility_hazards[] = 'motionSimulationHazard';
}
if (!empty($accessibility_hazards)) {
$accessibility_data['accessibilityHazard'] = $accessibility_hazards;
}
return $accessibility_data;
}
private function has_high_contrast($attachment_id) {
// Implement image analysis for high contrast
// This would require image processing libraries
return false;
}
private function contains_text($attachment_id) {
// Check if image likely contains text (logos, signs, documents)
$file_path = get_attached_file($attachment_id);
$filename = basename($file_path);
// Simple heuristic based on filename patterns
$text_indicators = array('logo', 'sign', 'text', 'document', 'screenshot', 'infographic');
foreach ($text_indicators as $indicator) {
if (stripos($filename, $indicator) !== false) {
return true;
}
}
return false;
}
private function extract_text_from_image($attachment_id) {
// Placeholder for OCR integration
// Could integrate with Google Vision API, Tesseract, or similar
return '';
}
private function is_decorative_image($attachment_id, $context) {
// Determine if image is decorative based on context
$post = get_post($attachment_id);
// Check alt text patterns
$alt_text = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
if (empty($alt_text) || in_array(strtolower($alt_text), array('', 'decoration', 'decorative'))) {
return true;
}
// Check CSS classes if provided in context
if (isset($context['css_classes'])) {
$decorative_classes = array('decoration', 'ornament', 'separator', 'divider');
foreach ($decorative_classes as $class) {
if (strpos($context['css_classes'], $class) !== false) {
return true;
}
}
}
return false;
}
6.2 Multi-language Caption Support
/**
* Multi-language caption and description support
*/
public function get_multilingual_captions($attachment_id) {
$captions = array();
// Get default language caption
$default_caption = get_post_field('post_excerpt', $attachment_id);
if (!empty($default_caption)) {
$captions[get_locale()] = wp_strip_all_tags($default_caption);
}
// WPML integration
if (function_exists('icl_get_languages')) {
$languages = icl_get_languages('skip_missing=0');
foreach ($languages as $language) {
$translated_id = apply_filters('wpml_object_id', $attachment_id, 'attachment', false, $language['language_code']);
if ($translated_id && $translated_id !== $attachment_id) {
$translated_caption = get_post_field('post_excerpt', $translated_id);
if (!empty($translated_caption)) {
$captions[$language['language_code']] = wp_strip_all_tags($translated_caption);
}
}
}
}
// Polylang integration
if (function_exists('pll_get_post_translations')) {
$translations = pll_get_post_translations($attachment_id);
foreach ($translations as $lang_code => $translated_id) {
if ($translated_id !== $attachment_id) {
$translated_caption = get_post_field('post_excerpt', $translated_id);
if (!empty($translated_caption)) {
$captions[$lang_code] = wp_strip_all_tags($translated_caption);
}
}
}
}
return $captions;
}
7. Complete Implementation Example
7.1 Main ImageObject Class
<?php
/**
* Comprehensive ImageObject structured data implementation
*/
class TigerStyleSEO_ImageObject {
private static $instance = null;
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init();
}
private function init() {
// Frontend hooks
add_action('wp_head', array($this, 'inject_image_structured_data'), 5);
// Admin hooks
add_filter('attachment_fields_to_edit', array($this, 'add_image_schema_fields'), 10, 2);
add_filter('attachment_fields_to_save', array($this, 'save_image_schema_fields'), 10, 2);
// Enhancement hooks
add_action('add_attachment', array($this, 'process_new_attachment'));
add_action('edit_attachment', array($this, 'update_attachment_schema'));
}
/**
* Main method to inject ImageObject structured data
*/
public function inject_image_structured_data() {
if (!TigerStyleSEO_Utils::get_option('imageobject_enabled', true)) {
return;
}
$images = $this->get_page_images();
if (empty($images)) {
return;
}
$structured_data = array();
foreach ($images as $image_data) {
$schema = $this->build_image_schema($image_data);
if (!empty($schema)) {
$structured_data[] = $schema;
}
}
if (!empty($structured_data)) {
echo "\n<!-- TigerStyle Heat ImageObject Structured Data -->\n";
foreach ($structured_data as $schema) {
echo '<script type="application/ld+json">';
echo wp_json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
echo '</script>' . "\n";
}
echo "<!-- /TigerStyle Heat ImageObject Structured Data -->\n";
}
}
/**
* Get all relevant images for the current page
*/
private function get_page_images() {
$images = array();
if (is_single() || is_page()) {
global $post;
// Featured image (most important)
$featured_id = get_post_thumbnail_id($post->ID);
if ($featured_id) {
$featured_data = $this->get_attachment_data($featured_id);
if ($featured_data) {
$featured_data['representativeOfPage'] = true;
$featured_data['priority'] = 1;
$images[] = $featured_data;
}
}
// Content images
$content_images = $this->extract_content_images($post->post_content);
foreach ($content_images as $image) {
$image['priority'] = 2;
$images[] = $image;
}
// Gallery images
$gallery_images = $this->extract_gallery_images($post->post_content);
foreach ($gallery_images as $image) {
$image['priority'] = 3;
$images[] = $image;
}
}
// Sort by priority
usort($images, function($a, $b) {
return ($a['priority'] ?? 99) - ($b['priority'] ?? 99);
});
// Limit to prevent excessive structured data
$max_images = TigerStyleSEO_Utils::get_option('imageobject_max_per_page', 10);
return array_slice($images, 0, $max_images);
}
/**
* Build complete ImageObject schema
*/
private function build_image_schema($image_data) {
if (empty($image_data['contentUrl'])) {
return null;
}
$schema = array(
'@context' => 'https://schema.org',
'@type' => 'ImageObject',
'contentUrl' => $image_data['contentUrl']
);
// Basic properties
if (!empty($image_data['name'])) {
$schema['name'] = $image_data['name'];
}
if (!empty($image_data['description'])) {
$schema['description'] = $image_data['description'];
}
if (!empty($image_data['alternateName'])) {
$schema['alternateName'] = $image_data['alternateName'];
}
if (!empty($image_data['caption'])) {
$schema['caption'] = $image_data['caption'];
}
// Technical properties
if (!empty($image_data['width'])) {
$schema['width'] = $image_data['width'];
}
if (!empty($image_data['height'])) {
$schema['height'] = $image_data['height'];
}
if (!empty($image_data['encodingFormat'])) {
$schema['encodingFormat'] = $image_data['encodingFormat'];
}
if (!empty($image_data['contentSize'])) {
$schema['contentSize'] = $image_data['contentSize'];
}
// Dates
if (!empty($image_data['uploadDate'])) {
$schema['uploadDate'] = $image_data['uploadDate'];
}
if (!empty($image_data['datePublished'])) {
$schema['datePublished'] = $image_data['datePublished'];
}
if (!empty($image_data['dateModified'])) {
$schema['dateModified'] = $image_data['dateModified'];
}
// People
if (!empty($image_data['author'])) {
$schema['author'] = $image_data['author'];
}
if (!empty($image_data['photographer'])) {
$schema['photographer'] = $image_data['photographer'];
}
// Copyright
if (!empty($image_data['copyrightHolder'])) {
$schema['copyrightHolder'] = $image_data['copyrightHolder'];
}
if (!empty($image_data['license'])) {
$schema['license'] = $image_data['license'];
}
if (!empty($image_data['copyrightYear'])) {
$schema['copyrightYear'] = $image_data['copyrightYear'];
}
if (!empty($image_data['copyrightNotice'])) {
$schema['copyrightNotice'] = $image_data['copyrightNotice'];
}
// Location
if (!empty($image_data['contentLocation'])) {
$schema['contentLocation'] = $image_data['contentLocation'];
}
// Keywords
if (!empty($image_data['keywords'])) {
$schema['keywords'] = $image_data['keywords'];
}
// Representation
if (!empty($image_data['representativeOfPage'])) {
$schema['representativeOfPage'] = true;
}
// EXIF data
if (!empty($image_data['exifData'])) {
$schema['exifData'] = $image_data['exifData'];
}
// Accessibility
if (!empty($image_data['accessibilityFeature'])) {
$schema['accessibilityFeature'] = $image_data['accessibilityFeature'];
}
if (!empty($image_data['accessibilityHazard'])) {
$schema['accessibilityHazard'] = $image_data['accessibilityHazard'];
}
return $schema;
}
/**
* Get comprehensive attachment data
*/
private function get_attachment_data($attachment_id) {
if (!wp_attachment_is_image($attachment_id)) {
return null;
}
$attachment = get_post($attachment_id);
if (!$attachment) {
return null;
}
$image_url = wp_get_attachment_url($attachment_id);
if (!$image_url) {
return null;
}
$metadata = wp_get_attachment_metadata($attachment_id);
$schema_meta = get_post_meta($attachment_id, '_schema_imageobject', true);
if (!is_array($schema_meta)) {
$schema_meta = array();
}
$data = array(
'contentUrl' => $image_url,
'name' => $schema_meta['name'] ?? $attachment->post_title,
'description' => $schema_meta['description'] ?? $attachment->post_content,
'caption' => $attachment->post_excerpt,
'alternateName' => get_post_meta($attachment_id, '_wp_attachment_image_alt', true),
'uploadDate' => date('c', strtotime($attachment->post_date)),
'dateModified' => date('c', strtotime($attachment->post_modified))
);
// Technical metadata
if (!empty($metadata['width'])) {
$data['width'] = $metadata['width'];
}
if (!empty($metadata['height'])) {
$data['height'] = $metadata['height'];
}
$file_path = get_attached_file($attachment_id);
if ($file_path) {
$mime_type = get_post_mime_type($attachment_id);
if ($mime_type) {
$data['encodingFormat'] = $mime_type;
}
$file_size = filesize($file_path);
if ($file_size) {
$data['contentSize'] = size_format($file_size);
}
}
// Schema-specific metadata
foreach ($schema_meta as $key => $value) {
if (!empty($value)) {
$data[$key] = $value;
}
}
// EXIF data
$exif_data = $this->process_image_exif($attachment_id);
if (!empty($exif_data)) {
$data['exifData'] = $exif_data;
}
// Accessibility data
$accessibility_data = $this->build_accessibility_metadata($attachment_id);
$data = array_merge($data, $accessibility_data);
return $data;
}
}
// Initialize the ImageObject module
TigerStyleSEO_ImageObject::instance();
8. Testing & Validation
8.1 Google Rich Results Testing
/**
* Testing utilities for ImageObject structured data
*/
class TigerStyleSEO_ImageObject_Testing {
/**
* Validate ImageObject schema against Google guidelines
*/
public function validate_image_schema($schema) {
$errors = array();
$warnings = array();
// Required properties
if (empty($schema['contentUrl'])) {
$errors[] = 'contentUrl is required for ImageObject';
}
// Recommended properties
if (empty($schema['name'])) {
$warnings[] = 'name property is recommended for better SEO';
}
if (empty($schema['description'])) {
$warnings[] = 'description property is recommended for accessibility';
}
// URL validation
if (!empty($schema['contentUrl']) && !filter_var($schema['contentUrl'], FILTER_VALIDATE_URL)) {
$errors[] = 'contentUrl must be a valid URL';
}
// Dimension validation
if (!empty($schema['width']) && (!is_numeric($schema['width']) || $schema['width'] <= 0)) {
$errors[] = 'width must be a positive number';
}
if (!empty($schema['height']) && (!is_numeric($schema['height']) || $schema['height'] <= 0)) {
$errors[] = 'height must be a positive number';
}
// Date validation
$date_fields = array('uploadDate', 'datePublished', 'dateModified');
foreach ($date_fields as $field) {
if (!empty($schema[$field]) && !$this->is_valid_iso_date($schema[$field])) {
$errors[] = $field . ' must be a valid ISO 8601 date';
}
}
return array(
'errors' => $errors,
'warnings' => $warnings,
'valid' => empty($errors)
);
}
private function is_valid_iso_date($date) {
return (bool) preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})$/', $date);
}
/**
* Test structured data against Google's Rich Results API
*/
public function test_with_google_api($url) {
$api_url = 'https://searchconsole.googleapis.com/v1/urlTestingTools/richResults:run';
$data = array(
'url' => $url
);
$response = wp_remote_post($api_url, array(
'headers' => array(
'Content-Type' => 'application/json'
),
'body' => wp_json_encode($data)
));
if (is_wp_error($response)) {
return array('error' => $response->get_error_message());
}
$body = wp_remote_retrieve_body($response);
return json_decode($body, true);
}
}
Conclusion
This comprehensive analysis provides a complete foundation for implementing Schema.org ImageObject structured data in WordPress. The implementation covers:
- Complete property specifications with all relevant Schema.org properties
- WordPress-specific integration patterns for seamless CMS integration
- EXIF data processing for technical metadata extraction
- Copyright and licensing support including Creative Commons integration
- Accessibility enhancements for inclusive web experiences
- Testing and validation utilities for quality assurance
The modular approach allows for gradual implementation while maintaining code quality and performance standards. Each component can be enhanced further based on specific requirements and use cases.