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.
516 lines
16 KiB
PHP
516 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* Visual Elements Gallery Module for TigerStyle Heat
|
|
* Implements Google's Visual Elements Gallery optimization with carousel support
|
|
*/
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class TigerStyleSEO_VisualElementsGallery {
|
|
|
|
/**
|
|
* Single instance
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Get instance
|
|
*/
|
|
public static function instance() {
|
|
if (is_null(self::$instance)) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct() {
|
|
$this->init();
|
|
}
|
|
|
|
/**
|
|
* Initialize the module
|
|
*/
|
|
private function init() {
|
|
// Frontend hooks
|
|
add_action('wp_head', array($this, 'inject_gallery_structured_data'), 3);
|
|
|
|
// Admin hooks
|
|
if (is_admin()) {
|
|
add_action('admin_post_update_visual_gallery', array($this, 'handle_form_submission'));
|
|
}
|
|
|
|
// Gallery enhancement hooks
|
|
add_filter('gallery_style', array($this, 'enhance_gallery_markup'), 10, 1);
|
|
add_filter('post_gallery', array($this, 'enhance_gallery_shortcode'), 10, 3);
|
|
}
|
|
|
|
/**
|
|
* Inject Visual Elements Gallery structured data
|
|
*/
|
|
public function inject_gallery_structured_data() {
|
|
if (!TigerStyleSEO_Utils::get_option('visual_gallery_enabled', false)) {
|
|
return;
|
|
}
|
|
|
|
global $post;
|
|
if (!$post) {
|
|
return;
|
|
}
|
|
|
|
$gallery_data = $this->get_gallery_structured_data($post->ID);
|
|
|
|
if (!empty($gallery_data)) {
|
|
echo "\n<!-- TigerStyle Heat Visual Elements Gallery -->\n";
|
|
foreach ($gallery_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 Visual Elements Gallery -->\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get gallery structured data for a post
|
|
*/
|
|
private function get_gallery_structured_data($post_id) {
|
|
$schemas = array();
|
|
|
|
// Get post content
|
|
$post = get_post($post_id);
|
|
if (!$post) {
|
|
return $schemas;
|
|
}
|
|
|
|
// Detect galleries in content
|
|
$galleries = $this->detect_galleries($post->post_content);
|
|
|
|
foreach ($galleries as $gallery) {
|
|
$gallery_type = TigerStyleSEO_Utils::get_option('visual_gallery_type', 'ImageGallery');
|
|
|
|
if ($gallery_type === 'ItemList' && count($gallery['images']) >= 3) {
|
|
// Generate ItemList schema for carousel optimization
|
|
$schemas[] = $this->generate_itemlist_schema($gallery, $post);
|
|
} elseif ($gallery_type === 'ImageGallery') {
|
|
// Generate ImageGallery schema
|
|
$schemas[] = $this->generate_imagegallery_schema($gallery, $post);
|
|
}
|
|
}
|
|
|
|
return $schemas;
|
|
}
|
|
|
|
/**
|
|
* Detect galleries in post content
|
|
*/
|
|
private function detect_galleries($content) {
|
|
$galleries = array();
|
|
|
|
// Detect WordPress [gallery] shortcode
|
|
$pattern = '/\[gallery[^\]]*\]/';
|
|
preg_match_all($pattern, $content, $matches);
|
|
|
|
foreach ($matches[0] as $shortcode) {
|
|
$gallery = $this->parse_gallery_shortcode($shortcode);
|
|
if (!empty($gallery['images'])) {
|
|
$galleries[] = $gallery;
|
|
}
|
|
}
|
|
|
|
// Detect Gutenberg gallery blocks
|
|
$block_pattern = '/<!-- wp:gallery.*?-->.*?<!-- \/wp:gallery -->/s';
|
|
preg_match_all($block_pattern, $content, $block_matches);
|
|
|
|
foreach ($block_matches[0] as $block) {
|
|
$gallery = $this->parse_gallery_block($block);
|
|
if (!empty($gallery['images'])) {
|
|
$galleries[] = $gallery;
|
|
}
|
|
}
|
|
|
|
return $galleries;
|
|
}
|
|
|
|
/**
|
|
* Parse gallery shortcode
|
|
*/
|
|
private function parse_gallery_shortcode($shortcode) {
|
|
$atts = shortcode_parse_atts($shortcode);
|
|
$gallery = array(
|
|
'type' => 'shortcode',
|
|
'images' => array(),
|
|
'attributes' => $atts
|
|
);
|
|
|
|
if (isset($atts['ids'])) {
|
|
$ids = explode(',', $atts['ids']);
|
|
foreach ($ids as $id) {
|
|
$image_data = $this->get_image_structured_data(trim($id));
|
|
if ($image_data) {
|
|
$gallery['images'][] = $image_data;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $gallery;
|
|
}
|
|
|
|
/**
|
|
* Parse Gutenberg gallery block
|
|
*/
|
|
private function parse_gallery_block($block) {
|
|
$gallery = array(
|
|
'type' => 'gutenberg',
|
|
'images' => array(),
|
|
'attributes' => array()
|
|
);
|
|
|
|
// Extract image IDs from block content
|
|
preg_match_all('/wp-image-(\d+)/', $block, $matches);
|
|
|
|
if (!empty($matches[1])) {
|
|
foreach ($matches[1] as $id) {
|
|
$image_data = $this->get_image_structured_data($id);
|
|
if ($image_data) {
|
|
$gallery['images'][] = $image_data;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $gallery;
|
|
}
|
|
|
|
/**
|
|
* Get structured data for a single image or 3D model
|
|
*/
|
|
private function get_image_structured_data($attachment_id) {
|
|
$attachment = get_post($attachment_id);
|
|
if (!$attachment || $attachment->post_type !== 'attachment') {
|
|
return null;
|
|
}
|
|
|
|
$attachment_url = wp_get_attachment_url($attachment_id);
|
|
if (!$attachment_url) {
|
|
return null;
|
|
}
|
|
|
|
// Check if this is a glTF 3D model
|
|
$gltf_module = tigerstyle_heat()->get_module('gltf_metadata');
|
|
if ($gltf_module && $gltf_module->is_gltf_attachment($attachment_id)) {
|
|
return $gltf_module->get_gltf_structured_data($attachment_id);
|
|
}
|
|
|
|
// Handle regular images
|
|
$image_data = wp_get_attachment_metadata($attachment_id);
|
|
|
|
$schema = array(
|
|
'@type' => 'ImageObject',
|
|
'contentUrl' => $attachment_url,
|
|
'url' => $attachment_url,
|
|
'name' => $attachment->post_title ?: basename($attachment_url),
|
|
'description' => $attachment->post_content,
|
|
'caption' => $attachment->post_excerpt
|
|
);
|
|
|
|
// Add technical metadata for images
|
|
if (!empty($image_data)) {
|
|
$schema['width'] = $image_data['width'] ?? null;
|
|
$schema['height'] = $image_data['height'] ?? null;
|
|
$schema['encodingFormat'] = $this->get_image_format($attachment_url);
|
|
}
|
|
|
|
// Add file size
|
|
$file_path = get_attached_file($attachment_id);
|
|
if ($file_path && file_exists($file_path)) {
|
|
$schema['contentSize'] = filesize($file_path);
|
|
}
|
|
|
|
// Add license information if available
|
|
$license = get_post_meta($attachment_id, '_image_license', true);
|
|
if ($license) {
|
|
$schema['license'] = $license;
|
|
}
|
|
|
|
// Add creator information
|
|
$creator = get_post_meta($attachment_id, '_image_creator', true);
|
|
if ($creator) {
|
|
$schema['creator'] = array(
|
|
'@type' => 'Person',
|
|
'name' => $creator
|
|
);
|
|
}
|
|
|
|
return $schema;
|
|
}
|
|
|
|
/**
|
|
* Generate ItemList schema for carousel optimization
|
|
*/
|
|
private function generate_itemlist_schema($gallery, $post) {
|
|
$schema = array(
|
|
'@context' => 'https://schema.org',
|
|
'@type' => 'ItemList',
|
|
'name' => $post->post_title . ' Gallery',
|
|
'description' => $this->get_gallery_description($gallery, $post),
|
|
'numberOfItems' => count($gallery['images']),
|
|
'itemListElement' => array()
|
|
);
|
|
|
|
$position = 1;
|
|
foreach ($gallery['images'] as $image) {
|
|
$schema['itemListElement'][] = array(
|
|
'@type' => 'ListItem',
|
|
'position' => $position,
|
|
'item' => $image
|
|
);
|
|
$position++;
|
|
}
|
|
|
|
// Add webpage context
|
|
$schema['mainEntityOfPage'] = array(
|
|
'@type' => 'WebPage',
|
|
'@id' => get_permalink($post->ID)
|
|
);
|
|
|
|
return $schema;
|
|
}
|
|
|
|
/**
|
|
* Generate ImageGallery schema
|
|
*/
|
|
private function generate_imagegallery_schema($gallery, $post) {
|
|
$schema = array(
|
|
'@context' => 'https://schema.org',
|
|
'@type' => 'ImageGallery',
|
|
'name' => $post->post_title . ' Gallery',
|
|
'description' => $this->get_gallery_description($gallery, $post),
|
|
'associatedMedia' => $gallery['images']
|
|
);
|
|
|
|
// Add webpage context
|
|
$schema['mainEntityOfPage'] = array(
|
|
'@type' => 'WebPage',
|
|
'@id' => get_permalink($post->ID)
|
|
);
|
|
|
|
// Add creator information
|
|
$author_id = $post->post_author;
|
|
if ($author_id) {
|
|
$author = get_userdata($author_id);
|
|
$schema['creator'] = array(
|
|
'@type' => 'Person',
|
|
'name' => $author->display_name,
|
|
'url' => get_author_posts_url($author_id)
|
|
);
|
|
}
|
|
|
|
// Add publication information
|
|
$schema['datePublished'] = $post->post_date;
|
|
$schema['dateModified'] = $post->post_modified;
|
|
|
|
return $schema;
|
|
}
|
|
|
|
/**
|
|
* Get gallery description
|
|
*/
|
|
private function get_gallery_description($gallery, $post) {
|
|
$custom_description = TigerStyleSEO_Utils::get_option('visual_gallery_description', '');
|
|
|
|
if ($custom_description) {
|
|
return $custom_description;
|
|
}
|
|
|
|
// Generate automatic description
|
|
$count = count($gallery['images']);
|
|
$description = sprintf(
|
|
__('A collection of %d images from %s', 'tigerstyle-heat'),
|
|
$count,
|
|
$post->post_title
|
|
);
|
|
|
|
if ($post->post_excerpt) {
|
|
$description .= '. ' . $post->post_excerpt;
|
|
}
|
|
|
|
return $description;
|
|
}
|
|
|
|
/**
|
|
* Get media format from URL (supports both images and 3D models)
|
|
*/
|
|
private function get_image_format($url) {
|
|
$extension = strtolower(pathinfo($url, PATHINFO_EXTENSION));
|
|
|
|
$format_map = array(
|
|
// Image formats
|
|
'jpg' => 'image/jpeg',
|
|
'jpeg' => 'image/jpeg',
|
|
'png' => 'image/png',
|
|
'gif' => 'image/gif',
|
|
'webp' => 'image/webp',
|
|
'svg' => 'image/svg+xml',
|
|
'bmp' => 'image/bmp',
|
|
'tiff' => 'image/tiff',
|
|
'ico' => 'image/x-icon',
|
|
// 3D model formats
|
|
'gltf' => 'model/gltf+json',
|
|
'glb' => 'model/gltf-binary'
|
|
);
|
|
|
|
return $format_map[$extension] ?? 'image/jpeg';
|
|
}
|
|
|
|
/**
|
|
* Enhance gallery markup with microdata
|
|
*/
|
|
public function enhance_gallery_markup($css) {
|
|
if (!TigerStyleSEO_Utils::get_option('visual_gallery_microdata', false)) {
|
|
return $css;
|
|
}
|
|
|
|
// Add microdata attributes to gallery wrapper
|
|
$enhanced_css = str_replace(
|
|
'<div class="gallery',
|
|
'<div itemscope itemtype="https://schema.org/ImageGallery" class="gallery',
|
|
$css
|
|
);
|
|
|
|
return $enhanced_css;
|
|
}
|
|
|
|
/**
|
|
* Enhance gallery shortcode output
|
|
*/
|
|
public function enhance_gallery_shortcode($output, $atts, $instance) {
|
|
if (!TigerStyleSEO_Utils::get_option('visual_gallery_enhanced_markup', false)) {
|
|
return $output;
|
|
}
|
|
|
|
// Add structured data attributes to gallery items
|
|
$enhanced_output = preg_replace_callback(
|
|
'/<div class="gallery-item">.*?<\/div>/s',
|
|
array($this, 'add_gallery_item_markup'),
|
|
$output
|
|
);
|
|
|
|
return $enhanced_output;
|
|
}
|
|
|
|
/**
|
|
* Add markup to gallery items
|
|
*/
|
|
private function add_gallery_item_markup($matches) {
|
|
$item = $matches[0];
|
|
|
|
// Add ImageObject microdata
|
|
$enhanced_item = str_replace(
|
|
'<div class="gallery-item">',
|
|
'<div class="gallery-item" itemscope itemtype="https://schema.org/ImageObject">',
|
|
$item
|
|
);
|
|
|
|
// Add itemprop to images
|
|
$enhanced_item = str_replace(
|
|
'<img ',
|
|
'<img itemprop="contentUrl" ',
|
|
$enhanced_item
|
|
);
|
|
|
|
return $enhanced_item;
|
|
}
|
|
|
|
/**
|
|
* Handle form submission
|
|
*/
|
|
public function handle_form_submission() {
|
|
// Verify nonce
|
|
if (!TigerStyleSEO_Utils::verify_nonce('visual_gallery_nonce', 'update_visual_gallery')) {
|
|
wp_die(__('Security check failed.', 'tigerstyle-heat'));
|
|
}
|
|
|
|
// Check user capabilities
|
|
if (!TigerStyleSEO_Utils::current_user_can_manage()) {
|
|
wp_die(__('You do not have sufficient permissions to access this page.', 'tigerstyle-heat'));
|
|
}
|
|
|
|
try {
|
|
// Save Visual Elements Gallery settings
|
|
TigerStyleSEO_Utils::update_option('visual_gallery_enabled', isset($_POST['visual_gallery_enabled']) && $_POST['visual_gallery_enabled'] === '1');
|
|
TigerStyleSEO_Utils::update_option('visual_gallery_type', sanitize_text_field($_POST['visual_gallery_type'] ?? 'ImageGallery'));
|
|
TigerStyleSEO_Utils::update_option('visual_gallery_description', sanitize_textarea_field($_POST['visual_gallery_description'] ?? ''));
|
|
TigerStyleSEO_Utils::update_option('visual_gallery_microdata', isset($_POST['visual_gallery_microdata']) && $_POST['visual_gallery_microdata'] === '1');
|
|
TigerStyleSEO_Utils::update_option('visual_gallery_enhanced_markup', isset($_POST['visual_gallery_enhanced_markup']) && $_POST['visual_gallery_enhanced_markup'] === '1');
|
|
TigerStyleSEO_Utils::update_option('visual_gallery_min_images', intval($_POST['visual_gallery_min_images'] ?? 3));
|
|
TigerStyleSEO_Utils::update_option('visual_gallery_carousel_enabled', isset($_POST['visual_gallery_carousel_enabled']) && $_POST['visual_gallery_carousel_enabled'] === '1');
|
|
|
|
TigerStyleSEO_Utils::redirect_with_message('visual_gallery_updated');
|
|
|
|
} catch (Exception $e) {
|
|
TigerStyleSEO_Utils::redirect_with_message('error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if page has qualifying galleries for rich results
|
|
*/
|
|
public function has_qualifying_galleries($post_id = null) {
|
|
if (!$post_id) {
|
|
global $post;
|
|
$post_id = $post ? $post->ID : 0;
|
|
}
|
|
|
|
if (!$post_id) {
|
|
return false;
|
|
}
|
|
|
|
$post = get_post($post_id);
|
|
if (!$post) {
|
|
return false;
|
|
}
|
|
|
|
$galleries = $this->detect_galleries($post->post_content);
|
|
$min_images = TigerStyleSEO_Utils::get_option('visual_gallery_min_images', 3);
|
|
|
|
foreach ($galleries as $gallery) {
|
|
if (count($gallery['images']) >= $min_images) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get gallery performance data
|
|
*/
|
|
public function get_gallery_performance_data($post_id = null) {
|
|
if (!$post_id) {
|
|
global $post;
|
|
$post_id = $post ? $post->ID : 0;
|
|
}
|
|
|
|
$post = get_post($post_id);
|
|
if (!$post) {
|
|
return null;
|
|
}
|
|
|
|
$galleries = $this->detect_galleries($post->post_content);
|
|
|
|
return array(
|
|
'total_galleries' => count($galleries),
|
|
'total_images' => array_sum(array_map(function($g) { return count($g['images']); }, $galleries)),
|
|
'qualifying_galleries' => count(array_filter($galleries, function($g) {
|
|
return count($g['images']) >= TigerStyleSEO_Utils::get_option('visual_gallery_min_images', 3);
|
|
})),
|
|
'has_structured_data' => TigerStyleSEO_Utils::get_option('visual_gallery_enabled', false),
|
|
'schema_type' => TigerStyleSEO_Utils::get_option('visual_gallery_type', 'ImageGallery')
|
|
);
|
|
}
|
|
} |