'model/gltf+json', 'glb' => 'model/gltf-binary' ); /** * 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() { // Media handling hooks add_filter('wp_check_filetype_and_ext', array($this, 'check_gltf_filetype'), 10, 4); add_filter('upload_mimes', array($this, 'add_gltf_mime_types')); add_action('add_attachment', array($this, 'process_gltf_attachment')); add_action('edit_attachment', array($this, 'process_gltf_attachment')); // Admin hooks if (is_admin()) { add_filter('attachment_fields_to_edit', array($this, 'add_gltf_attachment_fields'), 10, 2); add_filter('attachment_fields_to_save', array($this, 'save_gltf_attachment_fields'), 10, 2); } // Frontend hooks add_filter('wp_get_attachment_metadata', array($this, 'enhance_gltf_metadata'), 10, 2); } /** * Check if file is a valid glTF file */ public function check_gltf_filetype($data, $file, $filename, $mimes) { error_log('TigerStyle Heat: check_gltf_filetype called for: ' . $filename); $wp_filetype = wp_check_filetype($filename, $mimes); $ext = $wp_filetype['ext']; $type = $wp_filetype['type']; error_log('TigerStyle Heat: wp_check_filetype ext=' . $ext . ', type=' . $type); if (in_array($ext, $this->supported_extensions)) { error_log('TigerStyle Heat: File extension supported, validating: ' . $file); // Validate glTF file structure - pass the extension since temp files don't have extensions if ($this->validate_gltf_file_with_extension($file, $ext)) { error_log('TigerStyle Heat: File validation passed'); $data['ext'] = $ext; $data['type'] = $this->mime_types[$ext]; } else { error_log('TigerStyle Heat: File validation failed'); // Invalid glTF file $data['ext'] = false; $data['type'] = false; } } else { error_log('TigerStyle Heat: File extension not supported: ' . $ext); } error_log('TigerStyle Heat: Final data: ' . print_r($data, true)); return $data; } /** * Add glTF MIME types to WordPress */ public function add_gltf_mime_types($mimes) { // Debug logging error_log('TigerStyle Heat: Adding glTF MIME types'); $mimes['gltf'] = 'model/gltf+json'; $mimes['glb'] = 'model/gltf-binary'; // Log the updated mimes array error_log('TigerStyle Heat: Updated MIME types: ' . print_r($mimes, true)); return $mimes; } /** * Validate glTF file structure with known extension */ private function validate_gltf_file_with_extension($file_path, $extension) { error_log('TigerStyle Heat: validate_gltf_file_with_extension called with: ' . $file_path . ', ext: ' . $extension); if (!file_exists($file_path)) { error_log('TigerStyle Heat: File does not exist: ' . $file_path); return false; } if ($extension === 'gltf') { error_log('TigerStyle Heat: Validating as glTF JSON'); return $this->validate_gltf_json($file_path); } elseif ($extension === 'glb') { error_log('TigerStyle Heat: Validating as GLB binary'); return $this->validate_glb_binary($file_path); } error_log('TigerStyle Heat: Unknown extension: ' . $extension); return false; } /** * Validate glTF file structure (legacy method - kept for compatibility) */ private function validate_gltf_file($file_path) { error_log('TigerStyle Heat: validate_gltf_file called with: ' . $file_path); if (!file_exists($file_path)) { error_log('TigerStyle Heat: File does not exist: ' . $file_path); return false; } $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION)); error_log('TigerStyle Heat: File extension detected: ' . $extension); if ($extension === 'gltf') { error_log('TigerStyle Heat: Validating as glTF JSON'); return $this->validate_gltf_json($file_path); } elseif ($extension === 'glb') { error_log('TigerStyle Heat: Validating as GLB binary'); return $this->validate_glb_binary($file_path); } error_log('TigerStyle Heat: Unknown extension: ' . $extension); return false; } /** * Validate glTF JSON file */ private function validate_gltf_json($file_path) { $content = file_get_contents($file_path); if ($content === false) { return false; } $json = json_decode($content, true); if (json_last_error() !== JSON_ERROR_NONE) { return false; } // Check for required glTF structure return $this->validate_gltf_structure($json); } /** * Validate GLB binary file */ private function validate_glb_binary($file_path) { error_log('TigerStyle Heat: validate_glb_binary called'); $handle = fopen($file_path, 'rb'); if (!$handle) { error_log('TigerStyle Heat: Could not open file for reading'); return false; } // Read GLB header (12 bytes) $header = fread($handle, 12); if (strlen($header) < 12) { error_log('TigerStyle Heat: Header too short: ' . strlen($header) . ' bytes'); fclose($handle); return false; } error_log('TigerStyle Heat: Header read successfully, length: ' . strlen($header)); // Check magic number (first 4 bytes should be "glTF") $magic = substr($header, 0, 4); if ($magic !== 'glTF') { fclose($handle); return false; } // Check version (bytes 4-7, should be 2 for glTF 2.0) $version = unpack('V', substr($header, 4, 4))[1]; if ($version !== 2) { fclose($handle); return false; } fclose($handle); return true; } /** * Validate glTF JSON structure */ private function validate_gltf_structure($json) { // Check required properties if (!isset($json['asset'])) { return false; } // Check asset version if (!isset($json['asset']['version']) || $json['asset']['version'] !== '2.0') { return false; } // Basic structure validation $required_arrays = array('scenes', 'nodes', 'meshes', 'accessors', 'bufferViews', 'buffers'); foreach ($required_arrays as $array_name) { if (isset($json[$array_name]) && !is_array($json[$array_name])) { return false; } } return true; } /** * Extract metadata from glTF file */ public function extract_gltf_metadata($file_path) { if (!$this->validate_gltf_file($file_path)) { return false; } $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION)); $metadata = array( 'file_format' => $extension, 'mime_type' => $this->mime_types[$extension], 'file_size' => filesize($file_path), 'version' => '2.0' ); if ($extension === 'gltf') { $gltf_data = $this->extract_gltf_json_metadata($file_path); } else { $gltf_data = $this->extract_glb_metadata($file_path); } return array_merge($metadata, $gltf_data); } /** * Extract metadata from glTF JSON file */ private function extract_gltf_json_metadata($file_path) { $content = file_get_contents($file_path); $json = json_decode($content, true); if (!$json) { return array(); } return $this->parse_gltf_json($json); } /** * Extract metadata from GLB binary file */ private function extract_glb_metadata($file_path) { $handle = fopen($file_path, 'rb'); if (!$handle) { return array(); } // Skip header (12 bytes) fseek($handle, 12); // Read first chunk header (8 bytes) $chunk_header = fread($handle, 8); if (strlen($chunk_header) < 8) { fclose($handle); return array(); } $chunk_length = unpack('V', substr($chunk_header, 0, 4))[1]; $chunk_type = substr($chunk_header, 4, 4); // First chunk should be JSON if ($chunk_type !== 'JSON') { fclose($handle); return array(); } // Read JSON data $json_data = fread($handle, $chunk_length); fclose($handle); $json = json_decode($json_data, true); if (!$json) { return array(); } return $this->parse_gltf_json($json); } /** * Parse glTF JSON structure and extract metadata */ private function parse_gltf_json($json) { $metadata = array(); // Asset information if (isset($json['asset'])) { $asset = $json['asset']; $metadata['version'] = $asset['version'] ?? '2.0'; $metadata['generator'] = $asset['generator'] ?? ''; $metadata['copyright'] = $asset['copyright'] ?? ''; $metadata['min_version'] = $asset['minVersion'] ?? ''; } // Scene information $metadata['scene_count'] = isset($json['scenes']) ? count($json['scenes']) : 0; $metadata['node_count'] = isset($json['nodes']) ? count($json['nodes']) : 0; // Mesh information $metadata['mesh_count'] = isset($json['meshes']) ? count($json['meshes']) : 0; $metadata['primitive_count'] = $this->count_primitives($json); $metadata['vertex_count'] = $this->estimate_vertex_count($json); // Material information $metadata['material_count'] = isset($json['materials']) ? count($json['materials']) : 0; $metadata['texture_count'] = isset($json['textures']) ? count($json['textures']) : 0; $metadata['image_count'] = isset($json['images']) ? count($json['images']) : 0; // Animation information $metadata['animation_count'] = isset($json['animations']) ? count($json['animations']) : 0; $metadata['has_animations'] = $metadata['animation_count'] > 0; // Extension information $metadata['extensions_used'] = $json['extensionsUsed'] ?? array(); $metadata['extensions_required'] = $json['extensionsRequired'] ?? array(); // Buffer information $metadata['buffer_count'] = isset($json['buffers']) ? count($json['buffers']) : 0; $metadata['total_buffer_size'] = $this->calculate_total_buffer_size($json); // Accessor information $metadata['accessor_count'] = isset($json['accessors']) ? count($json['accessors']) : 0; $metadata['buffer_view_count'] = isset($json['bufferViews']) ? count($json['bufferViews']) : 0; // Camera information $metadata['camera_count'] = isset($json['cameras']) ? count($json['cameras']) : 0; // Light information (if KHR_lights_punctual extension is used) if (isset($json['extensions']['KHR_lights_punctual']['lights'])) { $metadata['light_count'] = count($json['extensions']['KHR_lights_punctual']['lights']); } else { $metadata['light_count'] = 0; } // Complexity assessment $metadata['complexity_score'] = $this->calculate_complexity_score($metadata); $metadata['complexity_level'] = $this->get_complexity_level($metadata['complexity_score']); return $metadata; } /** * Count total primitives across all meshes */ private function count_primitives($json) { $total = 0; if (isset($json['meshes'])) { foreach ($json['meshes'] as $mesh) { if (isset($mesh['primitives'])) { $total += count($mesh['primitives']); } } } return $total; } /** * Estimate total vertex count */ private function estimate_vertex_count($json) { $total = 0; if (isset($json['meshes']) && isset($json['accessors'])) { foreach ($json['meshes'] as $mesh) { if (isset($mesh['primitives'])) { foreach ($mesh['primitives'] as $primitive) { if (isset($primitive['attributes']['POSITION'])) { $accessor_index = $primitive['attributes']['POSITION']; if (isset($json['accessors'][$accessor_index])) { $total += $json['accessors'][$accessor_index]['count'] ?? 0; } } } } } } return $total; } /** * Calculate total buffer size */ private function calculate_total_buffer_size($json) { $total = 0; if (isset($json['buffers'])) { foreach ($json['buffers'] as $buffer) { $total += $buffer['byteLength'] ?? 0; } } return $total; } /** * Calculate complexity score for the 3D model */ private function calculate_complexity_score($metadata) { $score = 0; // Mesh complexity $score += $metadata['mesh_count'] * 10; $score += $metadata['primitive_count'] * 5; $score += ($metadata['vertex_count'] / 1000) * 2; // Per thousand vertices // Material complexity $score += $metadata['material_count'] * 8; $score += $metadata['texture_count'] * 12; // Animation complexity $score += $metadata['animation_count'] * 15; // Extension complexity $score += count($metadata['extensions_used']) * 5; $score += count($metadata['extensions_required']) * 10; // Buffer size impact $score += ($metadata['total_buffer_size'] / (1024 * 1024)) * 3; // Per MB return round($score); } /** * Get complexity level based on score */ private function get_complexity_level($score) { if ($score < 50) { return 'Low'; } elseif ($score < 150) { return 'Medium'; } elseif ($score < 300) { return 'High'; } else { return 'Very High'; } } /** * Process glTF attachment and extract metadata */ public function process_gltf_attachment($attachment_id) { $file_path = get_attached_file($attachment_id); if (!$file_path) { return; } $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION)); if (!in_array($extension, $this->supported_extensions)) { return; } // Extract glTF metadata $gltf_metadata = $this->extract_gltf_metadata($file_path); if ($gltf_metadata) { // Store metadata as post meta foreach ($gltf_metadata as $key => $value) { update_post_meta($attachment_id, '_gltf_' . $key, $value); } // Store structured metadata update_post_meta($attachment_id, '_gltf_metadata', $gltf_metadata); // Set attachment as 3D model update_post_meta($attachment_id, '_wp_attachment_image_alt', '3D Model'); } } /** * Enhance WordPress attachment metadata with glTF data */ public function enhance_gltf_metadata($metadata, $attachment_id) { $file_path = get_attached_file($attachment_id); if (!$file_path) { return $metadata; } $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION)); if (!in_array($extension, $this->supported_extensions)) { return $metadata; } // Get cached glTF metadata $gltf_metadata = get_post_meta($attachment_id, '_gltf_metadata', true); if (!$gltf_metadata) { // Extract and cache metadata $gltf_metadata = $this->extract_gltf_metadata($file_path); if ($gltf_metadata) { update_post_meta($attachment_id, '_gltf_metadata', $gltf_metadata); } } if ($gltf_metadata) { $metadata['gltf'] = $gltf_metadata; } return $metadata; } /** * Add glTF-specific fields to attachment edit screen */ public function add_gltf_attachment_fields($form_fields, $post) { $file_path = get_attached_file($post->ID); if (!$file_path) { return $form_fields; } $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION)); if (!in_array($extension, $this->supported_extensions)) { return $form_fields; } $gltf_metadata = get_post_meta($post->ID, '_gltf_metadata', true); if (!$gltf_metadata) { return $form_fields; } // Add 3D model information fields $form_fields['gltf_info'] = array( 'label' => __('3D Model Information', 'tigerstyle-heat'), 'input' => 'html', 'html' => $this->render_gltf_info_html($gltf_metadata), 'helps' => __('Technical information about this glTF 3D model.', 'tigerstyle-heat') ); // Add custom license field $license = get_post_meta($post->ID, '_gltf_license', true); $form_fields['gltf_license'] = array( 'label' => __('3D Model License', 'tigerstyle-heat'), 'input' => 'text', 'value' => $license, 'helps' => __('License information for this 3D model (e.g., CC BY 4.0, MIT, etc.)', 'tigerstyle-heat') ); // Add creator field $creator = get_post_meta($post->ID, '_gltf_creator', true); $form_fields['gltf_creator'] = array( 'label' => __('3D Model Creator', 'tigerstyle-heat'), 'input' => 'text', 'value' => $creator, 'helps' => __('Name of the person or organization who created this 3D model.', 'tigerstyle-heat') ); return $form_fields; } /** * Save glTF-specific attachment fields */ public function save_gltf_attachment_fields($post, $attachment) { if (isset($attachment['gltf_license'])) { update_post_meta($post['ID'], '_gltf_license', sanitize_text_field($attachment['gltf_license'])); } if (isset($attachment['gltf_creator'])) { update_post_meta($post['ID'], '_gltf_creator', sanitize_text_field($attachment['gltf_creator'])); } return $post; } /** * Render glTF information HTML for admin */ private function render_gltf_info_html($metadata) { ob_start(); ?>







post_type !== 'attachment') { return null; } $file_path = get_attached_file($attachment_id); $file_url = wp_get_attachment_url($attachment_id); $gltf_metadata = get_post_meta($attachment_id, '_gltf_metadata', true); if (!$file_url || !$gltf_metadata) { return null; } $schema = array( '@type' => '3DModel', 'contentUrl' => $file_url, 'encodingFormat' => $gltf_metadata['mime_type'], 'name' => $post->post_title ?: basename($file_url), 'description' => $post->post_content, 'caption' => $post->post_excerpt ); // Add technical metadata $schema['fileSize'] = $gltf_metadata['file_size']; $schema['fileFormat'] = $gltf_metadata['file_format']; // Add 3D-specific properties if (isset($gltf_metadata['mesh_count'])) { $schema['additionalProperty'] = array( array( '@type' => 'PropertyValue', 'name' => 'meshCount', 'value' => $gltf_metadata['mesh_count'] ), array( '@type' => 'PropertyValue', 'name' => 'materialCount', 'value' => $gltf_metadata['material_count'] ), array( '@type' => 'PropertyValue', 'name' => 'complexityLevel', 'value' => $gltf_metadata['complexity_level'] ) ); } // Add license information $license = get_post_meta($attachment_id, '_gltf_license', true); if ($license) { $schema['license'] = $license; } // Add creator information $creator = get_post_meta($attachment_id, '_gltf_creator', true); if ($creator) { $schema['creator'] = array( '@type' => 'Person', 'name' => $creator ); } return $schema; } /** * Check if attachment is a glTF 3D model */ public function is_gltf_attachment($attachment_id) { $file_path = get_attached_file($attachment_id); if (!$file_path) { return false; } $extension = strtolower(pathinfo($file_path, PATHINFO_EXTENSION)); return in_array($extension, $this->supported_extensions); } /** * Get glTF metadata for attachment */ public function get_gltf_metadata($attachment_id) { if (!$this->is_gltf_attachment($attachment_id)) { return null; } return get_post_meta($attachment_id, '_gltf_metadata', true); } }