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\n"; foreach ($gallery_data as $schema) { echo '' . "\n"; } echo "\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 = '/.*?/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( '