$attachment->ID, 'post_type' => $attachment->post_type, 'status' => $attachment->post_status, 'mime_type' => $attachment->post_mime_type, 'attached_file' => get_attached_file($thumbnail_id), 'exists' => file_exists(get_attached_file($thumbnail_id)) ], true)); } else { error_log('[Bluesky Connector] Attachment post not found for ID: ' . $thumbnail_id); } } return $theme_support && $post_type_support; } public function __construct($access_token, $did) { error_log('[Bluesky Connector] Initializing Post_Formatter with DID: ' . $did); $this->api = new Bluesky_API($access_token, $did); } public function format_and_post($post, $formatted_content = null) { try { error_log('[Bluesky Connector] Starting format_and_post for post ' . $post->ID); // Use provided content if available, otherwise format it $content = $formatted_content ?: $this->get_formatted_content($post); error_log('[Bluesky Connector] Formatted content: ' . $content); $post_data = array( '$type' => 'app.bsky.feed.post', 'text' => $content, 'createdAt' => gmdate('c', strtotime($post->post_date_gmt)), 'langs' => array('en') ); // Add facets for the URL $post_data['facets'] = $this->parse_facets($post); error_log('[Bluesky Connector] Added facets: ' . wp_json_encode($post_data['facets'])); // Debug thumbnail support error_log('[Bluesky Connector] Checking for featured image...'); error_log('[Bluesky Connector] Post type: ' . $post->post_type); error_log('[Bluesky Connector] has_post_thumbnail result: ' . (has_post_thumbnail($post->ID) ? 'true' : 'false')); error_log('[Bluesky Connector] Theme supports thumbnails: ' . (current_theme_supports('post-thumbnails') ? 'true' : 'false')); error_log('[Bluesky Connector] Post thumbnail meta check:'); error_log('[Bluesky Connector] _thumbnail_id: ' . get_post_meta($post->ID, '_thumbnail_id', true)); error_log('[Bluesky Connector] Post status: ' . get_post_status($post->ID)); // Handle image embed if (has_post_thumbnail($post->ID)) { error_log('[Bluesky Connector] Processing featured image for post ' . $post->ID); $image_data = $this->handle_featured_image($post->ID); if (!empty($image_data) && !isset($image_data['error'])) { $post_data['embed'] = $image_data; error_log('[Bluesky Connector] Image data added to post: ' . wp_json_encode($image_data)); } else { error_log('[Bluesky Connector] Image processing error: ' . ($image_data['error'] ?? 'Unknown error')); } } error_log('[Bluesky Connector] Sending post data to API: ' . wp_json_encode($post_data)); $response = $this->api->create_post($post_data); error_log('[Bluesky Connector] API Response: ' . wp_json_encode($response)); return $response; } catch (Exception $e) { error_log('[Bluesky Connector] Error in format_and_post: ' . $e->getMessage()); return array('error' => $e->getMessage()); } } private function get_formatted_content($post) { try { error_log('[Bluesky Connector] Starting content formatting for post ' . $post->ID); $format = get_option('bluesky_post_format', 'image-title-excerpt-link'); $include_title = get_option('bluesky_include_title', true); error_log('[Bluesky Connector] Using format: ' . $format . ', Include title: ' . ($include_title ? 'yes' : 'no')); // Get individual components $title = $include_title ? $post->post_title : ''; $excerpt = $this->get_excerpt($post); $url = wp_get_shortlink($post->ID); error_log('[Bluesky Connector] Content components:'); error_log('[Bluesky Connector] - Title: ' . $title); error_log('[Bluesky Connector] - Excerpt length: ' . strlen($excerpt)); error_log('[Bluesky Connector] - URL: ' . $url); // Start building content $content = ''; // Add title with line break if it exists if (!empty($title)) { $content .= $title . "\n\n"; } // Add excerpt if it exists if (!empty($excerpt)) { $content .= $excerpt . "\n\n"; } // Add URL on its own line if (!empty($url)) { $content .= "Continue Reading: " . $url; } error_log('[Bluesky Connector] Final formatted content: ' . $content); return $content; } catch (Exception $e) { error_log('[Bluesky Connector] Error in get_formatted_content: ' . $e->getMessage()); throw $e; } } private function get_excerpt($post) { try { error_log('[Bluesky Connector] Getting excerpt for post ' . $post->ID); $text = wp_strip_all_tags($post->post_excerpt); if (empty($text)) { $text = wp_strip_all_tags($post->post_content); error_log('[Bluesky Connector] Using post content for excerpt (no excerpt found)'); } $url = wp_get_shortlink($post->ID); $include_title = get_option('bluesky_include_title', true); // Calculate available length $available_length = $this->max_length; $available_length -= strlen($url); $available_length -= strlen("Continue Reading: "); // Account for the prefix $available_length -= 4; // Account for \n\n before and after the URL if ($include_title) { $available_length -= strlen($post->post_title); $available_length -= 2; // Account for \n\n after title } error_log('[Bluesky Connector] Available length for excerpt: ' . $available_length); if (mb_strlen($text) > $available_length) { $text = mb_substr($text, 0, $available_length - 3) . '...'; error_log('[Bluesky Connector] Excerpt truncated to fit length limit'); } error_log('[Bluesky Connector] Final excerpt length: ' . mb_strlen($text)); return $text; } catch (Exception $e) { error_log('[Bluesky Connector] Error in get_excerpt: ' . $e->getMessage()); throw $e; } } private function parse_facets($post) { try { error_log('[Bluesky Connector] Parsing facets for post ' . $post->ID); $facets = array(); $content = $this->get_formatted_content($post); $url = wp_get_shortlink($post->ID); $text_bytes = mb_convert_encoding($content, 'UTF-8'); $url_position = mb_strrpos($text_bytes, $url); if ($url_position !== false) { $facets[] = array( 'index' => array( 'byteStart' => $url_position, 'byteEnd' => $url_position + strlen($url), ), 'features' => array( array( '$type' => 'app.bsky.richtext.facet#link', 'uri' => $url, ), ), ); error_log('[Bluesky Connector] Added URL facet for: ' . $url); } error_log('[Bluesky Connector] Generated facets: ' . wp_json_encode($facets)); return $facets; } catch (Exception $e) { error_log('[Bluesky Connector] Error in parse_facets: ' . $e->getMessage()); throw $e; } } private function handle_featured_image($post_id) { try { error_log('[Bluesky Connector] Starting featured image process for post ' . $post_id); // Run diagnostics first $thumbnail_support = $this->diagnose_thumbnail_support($post_id); if (!$thumbnail_support) { error_log('[Bluesky Connector] Thumbnail support is not properly configured'); } // Get image ID with multiple fallbacks $image_id = get_post_thumbnail_id($post_id); error_log('[Bluesky Connector] Initial image ID from get_post_thumbnail_id: ' . $image_id); if (!$image_id) { // Try direct meta approach $image_id = get_post_meta($post_id, '_thumbnail_id', true); error_log('[Bluesky Connector] Image ID from direct meta: ' . $image_id); // If still no image, check for fallback if (!$image_id) { $image_id = get_option('bluesky_fallback_image'); error_log('[Bluesky Connector] Using fallback image ID: ' . $image_id); } } if (!$image_id) { return array('error' => 'No valid image ID found'); } // Get the image file path $image_path = get_attached_file($image_id); error_log('[Bluesky Connector] Image path: ' . ($image_path ?: 'not found')); // Verify file exists and is accessible if (!$image_path || !file_exists($image_path)) { $upload_dir = wp_upload_dir(); error_log('[Bluesky Connector] Upload directory information: ' . print_r($upload_dir, true)); return array('error' => 'Image file not found or inaccessible'); } // Check file permissions error_log('[Bluesky Connector] File permissions: ' . decoct(fileperms($image_path) & 0777)); // Verify mime type $mime_type = get_post_mime_type($image_id); error_log('[Bluesky Connector] Mime type: ' . $mime_type); // Validate mime type $allowed_types = array('image/jpeg', 'image/png', 'image/gif'); if (!in_array($mime_type, $allowed_types)) { return array('error' => 'Unsupported image type: ' . $mime_type); } // Get image data $image_data = file_get_contents($image_path); if ($image_data === false) { return array('error' => 'Failed to read image file'); } // Check and handle file size $size = strlen($image_data); error_log('[Bluesky Connector] Original image size: ' . $size . ' bytes'); if ($size > 1000000) { error_log('[Bluesky Connector] Image exceeds size limit, attempting resize'); $resized = $this->resize_image($image_path); if ($resized) { error_log('[Bluesky Connector] Image resized successfully'); $image_path = $resized; // Verify new size $new_size = filesize($resized); error_log('[Bluesky Connector] New image size: ' . $new_size . ' bytes'); if ($new_size > 1000000) { error_log('[Bluesky Connector] Resized image still too large'); return array('error' => 'Unable to reduce image size below 1MB'); } } else { error_log('[Bluesky Connector] Image resize failed'); return array('error' => 'Image resize failed'); } } // Upload to Bluesky error_log('[Bluesky Connector] Uploading image to Bluesky'); $response = $this->api->upload_blob($image_path, $mime_type); if (isset($response['error'])) { error_log('[Bluesky Connector] Upload failed: ' . $response['error']); return $response; } // Clean up temporary file if it exists if (isset($resized) && file_exists($resized)) { unlink($resized); error_log('[Bluesky Connector] Cleaned up temporary resized file'); } // Get alt text $alt_text = get_post_meta($image_id, '_wp_attachment_image_alt', true) ?: ''; error_log('[Bluesky Connector] Using alt text: ' . $alt_text); // Prepare final image embed $image_embed = array( '$type' => 'app.bsky.embed.images', 'images' => array( array( 'alt' => $alt_text, 'image' => $response['blob'], ), ), ); error_log('[Bluesky Connector] Image embed prepared successfully'); return $image_embed; } catch (Exception $e) { error_log('[Bluesky Connector] Error in handle_featured_image: ' . $e->getMessage()); error_log('[Bluesky Connector] Stack trace: ' . $e->getTraceAsString()); return array('error' => $e->getMessage()); } } private function resize_image($image_path) { try { error_log('[Bluesky Connector] Starting image resize for: ' . $image_path); if (!function_exists('imagecreatefrompng')) { error_log('[Bluesky Connector] GD library not available'); return false; } $mime_type = mime_content_type($image_path); list($width, $height) = getimagesize($image_path); error_log('[Bluesky Connector] Original dimensions: ' . $width . 'x' . $height); // Calculate new dimensions $max_dimension = 1000; if ($width > $height) { $new_width = $max_dimension; $new_height = floor($height * ($max_dimension / $width)); } else { $new_height = $max_dimension; $new_width = floor($width * ($max_dimension / $height)); } error_log('[Bluesky Connector] New dimensions: ' . $new_width . 'x' . $new_height); $new_image = imagecreatetruecolor($new_width, $new_height); switch ($mime_type) { case 'image/jpeg': $source = imagecreatefromjpeg($image_path); break; case 'image/png': $source = imagecreatefrompng($image_path); imagealphablending($new_image, false); imagesavealpha($new_image, true); break; case 'image/gif': $source = imagecreatefromgif($image_path); break; default: error_log('[Bluesky Connector] Unsupported image type: ' . $mime_type); return false; } if (!$source) { error_log('[Bluesky Connector] Failed to create image resource'); return false; } imagecopyresampled( $new_image, $source, 0, 0, 0, 0, $new_width, $new_height, $width, $height ); // Use PHP's tempnam $temp_file = tempnam(sys_get_temp_dir(), 'bluesky_img_'); error_log('[Bluesky Connector] Created temp file: ' . $temp_file); $success = false; switch ($mime_type) { case 'image/jpeg': $success = imagejpeg($new_image, $temp_file, 85); break; case 'image/png': $success = imagepng($new_image, $temp_file, 8); break; case 'image/gif': $success = imagegif($new_image, $temp_file); break; } imagedestroy($source); imagedestroy($new_image); if ($success) { error_log('[Bluesky Connector] Image resized successfully'); return $temp_file; } else { error_log('[Bluesky Connector] Failed to save resized image'); return false; } } catch (Exception $e) { error_log('[Bluesky Connector] Error in resize_image: ' . $e->getMessage()); return false; } } }