options = $options; $this->logger = $logger; $this->api_handler = $api_handler; } /** * Updates the internal options array. * * @param array $options New plugin options. */ public function set_options( $options ) { $this->options = $options; } /** * Executes the asynchronous image generation task for a specific post. * Triggered by a WP Cron single event (`AINP_GENERATE_IMAGE_HOOK`). * * @param int $post_id The ID of the post to process. */ public function execute_image_generation_task( $post_id ) { $this->logger->add_log_entry( 'info', __( 'Starting asynchronous image generation task.', 'ainp-ai-native-publisher' ), 'Post ID: ' . $post_id ); $post = get_post( $post_id ); try { if ( ! $post || 'post' !== $post->post_type ) { /* translators: %d: Post ID */ throw new Exception( sprintf( __( 'Post with ID %d not found or invalid.', 'ainp-ai-native-publisher' ), $post_id ) ); } // Check if the post already has a featured image. if ( has_post_thumbnail( $post_id ) ) { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'info', sprintf( __( 'Image task skipped: Post ID %d already has a featured image.', 'ainp-ai-native-publisher' ), $post_id ) ); // Correct status if it was 'awaiting_image'. if ( AINP_AWAITING_IMAGE_STATUS === $post->post_status ) { $final_status = get_post_meta( $post_id, AINP_FINAL_STATUS_META_KEY, true ); if ( ! empty( $final_status ) && post_type_exists( $post->post_type ) && get_post_status_object( $final_status ) ) { wp_update_post( array( 'ID' => $post_id, 'post_status' => $final_status, ) ); /* translators: 1: Post ID, 2: Post status */ $this->logger->add_log_entry( 'success', sprintf( __( 'Post status for ID %1$d corrected to "%2$s".', 'ainp-ai-native-publisher' ), $post_id, $final_status ), __( 'Reason: Image already existed or was added externally.', 'ainp-ai-native-publisher' ) ); } else { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'warning', sprintf( __( 'Could not correct status for Post ID %d: Invalid or missing final status meta.', 'ainp-ai-native-publisher' ), $post_id ), 'Final status meta: ' . esc_html( $final_status ) ); } } } else { // Proceed with generation/fetching. $post_title = $post->post_title; $post_content = $post->post_content; $item_xml_string = get_post_meta( $post_id, AINP_FEED_ITEM_META_KEY, true ); $feed_item_obj = null; if ( ! empty( $item_xml_string ) ) { // Security Fix: Removed LIBXML_NOENT $libxml_previous_state = libxml_use_internal_errors( true ); $feed_item_obj = simplexml_load_string( wp_unslash( $item_xml_string ), 'SimpleXMLElement', LIBXML_NOCDATA ); libxml_use_internal_errors( $libxml_previous_state ); if ( false === $feed_item_obj ) { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'warning', sprintf( __( 'Failed to parse feed item XML for post ID %d. Image extraction from feed might fail.', 'ainp-ai-native-publisher' ), $post_id ) ); } } else { /* translators: 1: Meta key, 2: Post ID */ $this->logger->add_log_entry( 'warning', sprintf( __( 'Could not find feed item XML data (meta key %1$s) for post ID %2$d. Attempting AI image generation without feed data.', 'ainp-ai-native-publisher' ), AINP_FEED_ITEM_META_KEY, $post_id ) ); } /* translators: 1: Post ID, 2: Post title */ $this->logger->add_log_entry( 'info', sprintf( __( 'STARTING image processing for post ID %1$d ("%2$s").', 'ainp-ai-native-publisher' ), $post_id, esc_html( $post_title ) ) ); $image_set = $this->set_featured_image_from_feed_or_ia( $post_id, $feed_item_obj, $post_title, $post_content ); // Update post status based on result if ( AINP_AWAITING_IMAGE_STATUS === get_post_status( $post_id ) ) { $final_status_intended = get_post_meta( $post_id, AINP_FINAL_STATUS_META_KEY, true ); $final_status_actual = $final_status_intended; // RULE: If image failed AND intention was to publish, switch to draft. if ( false === $image_set && 'publish' === $final_status_intended ) { $final_status_actual = 'draft'; /* translators: %d: Post ID */ $this->logger->add_log_entry( 'warning', sprintf( __( 'Post ID %d was set to "Draft" instead of "Publish" because no image could be set.', 'ainp-ai-native-publisher' ), $post_id ) ); } elseif ( empty( $final_status_actual ) ) { $final_status_actual = $this->options['default_post_status'] ?? 'draft'; } if ( post_type_exists( $post->post_type ) && get_post_status_object( $final_status_actual ) ) { wp_update_post( array( 'ID' => $post_id, 'post_status' => $final_status_actual, ) ); if ( $image_set ) { /* translators: 1: Post ID, 2: Post status */ $this->logger->add_log_entry( 'success', sprintf( __( 'Post status for ID %1$d updated to "%2$s" after image processing.', 'ainp-ai-native-publisher' ), $post_id, $final_status_actual ) ); } else { /* translators: 1: Post ID, 2: Post status */ $this->logger->add_log_entry( 'warning', sprintf( __( 'No image was set for post ID %1$d. Post status updated to final status "%2$s".', 'ainp-ai-native-publisher' ), $post_id, $final_status_actual ) ); } } else { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'error', sprintf( __( 'Image task ran for Post ID %d, but could not update status: Invalid or missing final status meta.', 'ainp-ai-native-publisher' ), $post_id ), 'Intended: ' . esc_html( $final_status_intended ) . ' | Actual: ' . esc_html( $final_status_actual ) ); } } elseif ( ! $image_set ) { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'info', sprintf( __( 'No image was set for post ID %d during the async task. Post status remains unchanged.', 'ainp-ai-native-publisher' ), $post_id ) ); } } } catch ( Exception $e ) { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'error', sprintf( __( 'Fatal error in image task for Post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), $e->getMessage() ); } delete_post_meta( $post_id, AINP_FEED_ITEM_META_KEY ); delete_post_meta( $post_id, AINP_FINAL_STATUS_META_KEY ); $pending_count = max( 0, (int) get_option( AINP_PENDING_IMAGE_TASKS_OPTION, 1 ) - 1 ); update_option( AINP_PENDING_IMAGE_TASKS_OPTION, $pending_count ); /* translators: 1: Post ID, 2: Number of remaining tasks */ $this->logger->add_log_entry( 'info', __( 'Image task completed.', 'ainp-ai-native-publisher' ), sprintf( 'Post ID: %1$d | Remaining tasks in queue: %2$d', $post_id, $pending_count ) ); if ( 0 === $pending_count ) { $status = $this->logger->get_processing_status(); if ( 'completed' === $status['status'] || 'processing_images' === $status['status'] ) { $this->logger->add_log_entry( 'success', __( 'Image processing queue finished.', 'ainp-ai-native-publisher' ) ); $this->logger->update_processing_status( 'idle', $status['total'], $status['total'], __( 'Processing complete.', 'ainp-ai-native-publisher' ) ); } } } /** * Determines image source (feed, AI, or Unsplash) and sets the featured image. */ public function set_featured_image_from_feed_or_ia( $post_id, $feed_item_obj, $post_title, $post_content ) { $imagen_api_key = $this->options['imagen_api_key'] ?? ''; $unsplash_access_key = $this->options['unsplash_access_key'] ?? ''; $main_image_provider = $this->options['main_image_provider'] ?? 'imagen'; $force_ia_image = ! empty( $this->options['module_force_ia_image'] ); $enable_ia_for_no_image = ! empty( $this->options['module_generate_images'] ); // Fallback enabled $image_set = false; $image_url_from_feed = null; // --- Check if Provider Key is Missing --- // If key is missing, disable "Force AI" and rely on Feed Image. $has_valid_key = false; if ( 'unsplash' === $main_image_provider && ! empty( $unsplash_access_key ) ) { $has_valid_key = true; } elseif ( 'imagen' === $main_image_provider && ! empty( $imagen_api_key ) ) { $has_valid_key = true; } if ( ! $has_valid_key ) { if ( $force_ia_image ) { $this->logger->add_log_entry( 'info', __( 'Image Provider Key missing. Disabling "Force AI Image" and prioritizing Feed Image.', 'ainp-ai-native-publisher' ) ); $force_ia_image = false; } // Disable fallback if key is missing to avoid errors later if ( $enable_ia_for_no_image ) { $enable_ia_for_no_image = false; $this->logger->add_log_entry( 'info', __( 'Image Provider Key missing. Disabling AI Fallback.', 'ainp-ai-native-publisher' ) ); } } $call_image_provider = function() use ( $post_id, $post_title, $post_content, $main_image_provider, $imagen_api_key, $unsplash_access_key ) { if ( 'unsplash' === $main_image_provider && ! empty( $unsplash_access_key ) ) { return $this->generate_and_set_featured_image_with_unsplash( $post_id, $post_title ); } elseif ( 'imagen' === $main_image_provider && ! empty( $imagen_api_key ) ) { return $this->generate_and_set_featured_image_with_imagen( $post_id, $post_title, $post_content ); } return new WP_Error( 'no_provider_key', __( 'Selected image provider is not configured.', 'ainp-ai-native-publisher' ) ); }; // Step 1: Force Image Generation if ( $force_ia_image ) { $provider_label = ( 'unsplash' === $main_image_provider ) ? 'Unsplash' : 'AI (Imagen)'; /* translators: 1: Provider Label, 2: Post ID */ $this->logger->add_log_entry( 'info', sprintf( __( 'Forcing %1$s image generation for post ID %2$d.', 'ainp-ai-native-publisher' ), $provider_label, $post_id ), __( '"Force AI Image Generation" module is active.', 'ainp-ai-native-publisher' ) ); $ia_image_id = $call_image_provider(); if ( $ia_image_id && ! is_wp_error( $ia_image_id ) ) { update_post_meta( $post_id, AINP_IMAGE_SOURCE_META_KEY, 'ia_generated' ); $image_set = true; } else { $error_msg = is_wp_error( $ia_image_id ) ? $ia_image_id->get_error_message() : __( 'Unknown error.', 'ainp-ai-native-publisher' ); /* translators: %d: Post ID */ $this->logger->add_log_entry( 'warning', sprintf( __( 'Failed to force image generation for post ID %d. Attempting other sources.', 'ainp-ai-native-publisher' ), $post_id ), $error_msg ); } } // Step 2: Try extracting image from feed if ( ! $image_set && $feed_item_obj ) { $this->logger->add_log_entry( 'info', __( 'Searching for image URL within the feed item.', 'ainp-ai-native-publisher' ), 'Post ID: ' . $post_id ); $image_url_from_feed = $this->extract_image_url_from_feed_item( $feed_item_obj ); if ( ! empty( $image_url_from_feed ) ) { $hq_image_url = $this->try_get_high_quality_image_url( $image_url_from_feed ); if ( $hq_image_url !== $image_url_from_feed ) { $this->logger->add_log_entry( 'info', __( 'Attempting to use High Quality version of image.', 'ainp-ai-native-publisher' ), 'Original: ' . esc_url( $image_url_from_feed ) . ' | HQ: ' . esc_url( $hq_image_url ) ); } /* translators: %d: Post ID */ $this->logger->add_log_entry( 'info', sprintf( __( 'Attempting to set featured image from feed URL for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), 'URL: ' . esc_url( $hq_image_url ) ); $feed_image_id = $this->set_featured_image_from_url( $post_id, $hq_image_url, $post_title ); if ( $feed_image_id && ! is_wp_error( $feed_image_id ) ) { update_post_meta( $post_id, AINP_IMAGE_SOURCE_META_KEY, 'feed' ); $image_set = true; } else { if ( $hq_image_url !== $image_url_from_feed ) { $this->logger->add_log_entry( 'warning', __( 'Failed to download/validate HQ image. Trying original URL.', 'ainp-ai-native-publisher' ) ); $feed_image_id_orig = $this->set_featured_image_from_url( $post_id, $image_url_from_feed, $post_title ); if ( $feed_image_id_orig && ! is_wp_error( $feed_image_id_orig ) ) { update_post_meta( $post_id, AINP_IMAGE_SOURCE_META_KEY, 'feed' ); $image_set = true; } else { $error_msg = is_wp_error( $feed_image_id_orig ) ? $feed_image_id_orig->get_error_message() : __( 'Unknown error.', 'ainp-ai-native-publisher' ); /* translators: %d: Post ID */ $this->logger->add_log_entry( 'warning', sprintf( __( 'Failed to set image from original feed URL for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), $error_msg ); } } else { $error_msg = is_wp_error( $feed_image_id ) ? $feed_image_id->get_error_message() : __( 'Unknown error during sideload.', 'ainp-ai-native-publisher' ); /* translators: %d: Post ID */ $this->logger->add_log_entry( 'warning', sprintf( __( 'Failed to set image from feed URL for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), 'URL: ' . esc_url( $image_url_from_feed ) . ' | Error: ' . $error_msg ); } } } else { $this->logger->add_log_entry( 'info', __( 'No usable image URL found in the feed item.', 'ainp-ai-native-publisher' ), 'Post ID: ' . $post_id ); } } elseif ( ! $image_set && false === $feed_item_obj ) { $this->logger->add_log_entry( 'info', __( 'Skipping feed image check due to earlier XML parsing failure.', 'ainp-ai-native-publisher' ), 'Post ID: ' . $post_id ); } // Step 3: Use Fallback if ( ! $image_set && empty( $image_url_from_feed ) && $enable_ia_for_no_image ) { $provider_label = ( 'unsplash' === $main_image_provider ) ? 'Unsplash' : 'AI (Imagen)'; /* translators: 1: Provider Label, 2: Post ID */ $this->logger->add_log_entry( 'info', sprintf( __( 'Feed item has no image. Attempting %1$s image generation (fallback) for post ID %2$d.', 'ainp-ai-native-publisher' ), $provider_label, $post_id ), __( '"AI Image Generation (Fallback)" module is active.', 'ainp-ai-native-publisher' ) ); $ia_image_id_fallback = $call_image_provider(); if ( $ia_image_id_fallback && ! is_wp_error( $ia_image_id_fallback ) ) { update_post_meta( $post_id, AINP_IMAGE_SOURCE_META_KEY, 'ia_generated_fallback' ); $image_set = true; } else { $error_msg = is_wp_error( $ia_image_id_fallback ) ? $ia_image_id_fallback->get_error_message() : __( 'Unknown error.', 'ainp-ai-native-publisher' ); /* translators: %d: Post ID */ $this->logger->add_log_entry( 'warning', sprintf( __( 'Failed to generate image (fallback) for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), $error_msg ); } } if ( ! $image_set ) { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'warning', sprintf( __( 'No image could be found in feed or generated by AI/Unsplash for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ) ); delete_post_meta( $post_id, AINP_IMAGE_SOURCE_META_KEY ); } return $image_set; } /** * Tries to guess a High Quality image URL based on common patterns. */ private function try_get_high_quality_image_url( $url ) { $new_url = $url; $new_url = preg_replace( '/-\d+x\d+(\.[a-z]{3,4})$/i', '$1', $new_url ); $new_url = str_replace( '/thumbs/', '/images/', $new_url ); $new_url = str_replace( '/thumbnail/', '/full/', $new_url ); $new_url = str_replace( '/medium/', '/large/', $new_url ); if ( strpos( $new_url, '?' ) !== false ) { $new_url = preg_replace( '/([?&])(width|height|w|h)=\d+/i', '', $new_url ); $new_url = rtrim( $new_url, '?&' ); } return $new_url; } /** * Extracts a usable image URL from a SimpleXMLElement feed item. */ public function extract_image_url_from_feed_item( $item ) { if ( ! $item instanceof SimpleXMLElement ) { return null; } $media_ns = $item->getNamespaces( true ); if ( isset( $media_ns['media'] ) ) { if ( isset( $item->children( $media_ns['media'] )->group ) ) { foreach ( $item->children( $media_ns['media'] )->group->children( $media_ns['media'] )->content as $content ) { $url = $this->check_media_content_attributes($content); if ($url) return $url; } } foreach ( $item->children( $media_ns['media'] )->content as $content ) { $url = $this->check_media_content_attributes($content); if ($url) return $url; } $media_thumbnail = $item->children( $media_ns['media'] )->thumbnail; if ( count($media_thumbnail) > 0 ) { $thumb = $media_thumbnail[0] ?? $media_thumbnail; if ( isset( $thumb->attributes()->url ) ) { $this->logger->add_log_entry( 'info', __( 'Image URL extracted from tag.', 'ainp-ai-native-publisher' ) ); return (string) $thumb->attributes()->url; } } } if ( isset( $item->enclosure ) ) { foreach ( $item->enclosure as $enclosure ) { if ( isset( $enclosure['url'] ) && isset( $enclosure['type'] ) && false !== stripos( (string) $enclosure['type'], 'image' ) ) { $this->logger->add_log_entry( 'info', __( 'Image URL extracted from tag.', 'ainp-ai-native-publisher' ) ); return (string) $enclosure['url']; } } } if ( isset( $item->image ) ) { $img_node = $item->image; if ( isset( $img_node->url ) ) { $this->logger->add_log_entry( 'info', __( 'Image URL extracted from item tag.', 'ainp-ai-native-publisher' ) ); return (string) $img_node->url; } elseif ( filter_var( (string) $img_node, FILTER_VALIDATE_URL ) ) { $this->logger->add_log_entry( 'info', __( 'Image URL extracted from item tag.', 'ainp-ai-native-publisher' ) ); return (string) $img_node; } } $content_fields = array(); if ( isset( $item->description ) ) { $content_fields[] = html_entity_decode( $item->description->asXML(), ENT_QUOTES | ENT_XML1, 'UTF-8' ); } if ( isset( $item->children( 'content', true )->encoded ) ) { $content_fields[] = html_entity_decode( $item->children( 'content', true )->encoded->asXML(), ENT_QUOTES | ENT_XML1, 'UTF-8' ); } if ( isset( $item->content ) ) { $content_fields[] = html_entity_decode( $item->content->asXML(), ENT_QUOTES | ENT_XML1, 'UTF-8' ); } foreach ( $content_fields as $content_html ) { if ( preg_match( '/]+src\s*=\s*[\'"]([^\'"]+?)[\'"]/i', $content_html, $matches ) ) { $img_src = html_entity_decode( $matches[1] ); if ( filter_var( $img_src, FILTER_VALIDATE_URL ) ) { $this->logger->add_log_entry( 'info', __( 'Image URL extracted from an tag within content/description.', 'ainp-ai-native-publisher' ) ); return $img_src; } } } if ( isset( $media_ns['itunes'] ) ) { $itunes_image = $item->children( $media_ns['itunes'] )->image; if ( $itunes_image && isset( $itunes_image->attributes()->href ) ) { $this->logger->add_log_entry( 'info', __( 'Image URL extracted from tag.', 'ainp-ai-native-publisher' ) ); return (string) $itunes_image->attributes()->href; } } return null; } /** * Helper to check attributes of media:content for valid image. */ private function check_media_content_attributes($content) { $attrs = $content->attributes(); $is_image = false; if ( isset( $attrs->medium ) && 'image' === strtolower( (string) $attrs->medium ) ) { $is_image = true; } elseif ( isset( $attrs->type ) && strpos( (string) $attrs->type, 'image/' ) === 0 ) { $is_image = true; } if ( ! $is_image && isset( $attrs->url ) ) { if ( preg_match( '/\.(jpg|jpeg|png|gif|webp)$/i', (string) $attrs->url ) ) { $is_image = true; } } if ( $is_image && isset( $attrs->url ) ) { $this->logger->add_log_entry( 'info', __( 'Image URL extracted from tag.', 'ainp-ai-native-publisher' ) ); return (string) $attrs->url; } return false; } /** * Generates an image using Imagen, saves it, adds watermark, attaches to post, sets as featured. */ public function generate_and_set_featured_image_with_imagen( $post_id, $title, $content_summary ) { $response = $this->api_handler->call_imagen_api( $post_id, $title, $content_summary ); if ( is_wp_error( $response ) ) { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'error', sprintf( __( 'Imagen API call failed for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), $response->get_error_message() ); return $response; } if ( isset( $response['predictions'][0]['bytesBase64Encoded'] ) ) { $base64_image_data = $response['predictions'][0]['bytesBase64Encoded']; $image_data = base64_decode( $base64_image_data ); if ( ! $image_data ) { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'error', sprintf( __( 'Failed to decode base64 image data from Imagen API for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ) ); return new WP_Error( 'imagen_decode_error', __( 'Failed to decode base64 image data from Imagen API.', 'ainp-ai-native-publisher' ) ); } $this->logger->add_log_entry( 'info', __( 'Base64 image decoded successfully.', 'ainp-ai-native-publisher' ), 'Post ID: ' . $post_id ); $upload_dir = wp_upload_dir(); $safe_title = sanitize_title( $title ); if ( strlen( $safe_title ) > 100 ) { $safe_title = substr( $safe_title, 0, 100 ); } $filename = $safe_title . '-' . time() . '.png'; $filepath = trailingslashit( $upload_dir['path'] ) . $filename; if ( ! wp_mkdir_p( $upload_dir['path'] ) ) { /* translators: %s: Directory Path */ $this->logger->add_log_entry( 'error', sprintf( __( 'Could not create upload directory %s.', 'ainp-ai-native-publisher' ), esc_html( $upload_dir['path'] ) ), 'Post ID: ' . $post_id ); return new WP_Error( 'imagen_mkdir_error', __( 'Could not create upload directory.', 'ainp-ai-native-publisher' ) ); } if ( ! file_put_contents( $filepath, $image_data ) ) { /* translators: %s: File path */ $this->logger->add_log_entry( 'error', sprintf( __( 'Could not save AI-generated image to %s.', 'ainp-ai-native-publisher' ), esc_html( $filepath ) ), 'Post ID: ' . $post_id ); return new WP_Error( 'imagen_save_error', __( 'Could not save AI-generated image.', 'ainp-ai-native-publisher' ) ); } $this->logger->add_log_entry( 'info', __( 'AI-generated image saved to server.', 'ainp-ai-native-publisher' ), 'Path: ' . $filepath ); if ( ! empty( $this->options['module_add_watermark'] ) ) { $this->add_watermark_to_image( $filepath ); } $filetype = wp_check_filetype( basename( $filepath ), null ); $attachment = array( 'guid' => $upload_dir['url'] . '/' . basename( $filepath ), 'post_mime_type' => $filetype['type'], 'post_title' => preg_replace( '/\.[^.]+$/', '', basename( $filename ) ), 'post_content' => '', 'post_status' => 'inherit', ); $attach_id = wp_insert_attachment( $attachment, $filepath, $post_id ); if ( is_wp_error( $attach_id ) ) { if ( file_exists( $filepath ) ) { wp_delete_file( $filepath ); // Compliance fix: no @ } /* translators: %d: Post ID */ $this->logger->add_log_entry( 'error', sprintf( __( 'Error inserting AI-generated image attachment for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), $attach_id->get_error_message() ); return $attach_id; } require_once ABSPATH . 'wp-admin/includes/image.php'; $attach_data = wp_generate_attachment_metadata( $attach_id, $filepath ); wp_update_attachment_metadata( $attach_id, $attach_data ); set_post_thumbnail( $post_id, $attach_id ); /* translators: %d: Post ID */ $this->logger->add_log_entry( 'success', sprintf( __( 'Image generated by Imagen API and set as featured for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), 'Attachment ID: ' . $attach_id ); return $attach_id; } else { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'error', sprintf( __( 'Unexpected prediction structure from Imagen API response for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), __( 'Response did not contain `predictions[0][bytesBase64Encoded]`.', 'ainp-ai-native-publisher' ) ); return new WP_Error( 'imagen_prediction_structure_error', __( 'Unexpected prediction structure from Imagen API.', 'ainp-ai-native-publisher' ), $response ); } } /** * Searches Unsplash for a photo based on title, downloads it, and sets as featured. */ public function generate_and_set_featured_image_with_unsplash( $post_id, $title ) { $search_query = wp_strip_all_tags( $title ); $words = explode( ' ', $search_query ); if ( count( $words ) > 6 ) { $search_query = implode( ' ', array_slice( $words, 0, 6 ) ); } $response = $this->api_handler->call_unsplash_api( $search_query ); if ( is_wp_error( $response ) ) { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'error', sprintf( __( 'Unsplash API call failed for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), $response->get_error_message() ); return $response; } if ( empty( $response['results'] ) || ! isset( $response['results'][0]['urls']['regular'] ) ) { /* translators: 1: Search Query, 2: Post ID */ $this->logger->add_log_entry( 'warning', sprintf( __( 'No suitable Unsplash image found for query "%1$s" (Post ID %2$d).', 'ainp-ai-native-publisher' ), $search_query, $post_id ) ); return new WP_Error( 'unsplash_no_results', __( 'No images found on Unsplash.', 'ainp-ai-native-publisher' ) ); } $image_url = $response['results'][0]['urls']['regular']; $this->logger->add_log_entry( 'info', __( 'Unsplash image found.', 'ainp-ai-native-publisher' ), 'URL: ' . esc_url( $image_url ) ); $attach_id = $this->set_featured_image_from_url( $post_id, $image_url, $title ); if ( ! is_wp_error( $attach_id ) ) { /* translators: %d: Post ID */ $this->logger->add_log_entry( 'success', sprintf( __( 'Unsplash image set as featured for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), 'Attachment ID: ' . $attach_id ); if ( ! empty( $this->options['module_add_watermark'] ) ) { $filepath = get_attached_file( $attach_id ); if ( $filepath ) { $this->add_watermark_to_image( $filepath ); require_once ABSPATH . 'wp-admin/includes/image.php'; $attach_data = wp_generate_attachment_metadata( $attach_id, $filepath ); wp_update_attachment_metadata( $attach_id, $attach_data ); } } } return $attach_id; } /** * Downloads image from URL, saves to media library, sets as featured image. */ public function set_featured_image_from_url( $post_id, $image_url, $post_title ) { if ( ! function_exists( 'media_handle_sideload' ) ) { require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; } $this->logger->add_log_entry( 'info', __( 'Starting download of external image from URL.', 'ainp-ai-native-publisher' ), 'URL: ' . esc_url( $image_url ) ); add_filter( 'http_request_timeout', array( $this, 'increase_timeout_for_image_download' ), 10, 1 ); add_filter( 'http_headers_useragent', array( $this, 'set_custom_user_agent' ) ); $tmp = download_url( $image_url ); remove_filter( 'http_request_timeout', array( $this, 'increase_timeout_for_image_download' ), 10 ); remove_filter( 'http_headers_useragent', array( $this, 'set_custom_user_agent' ) ); if ( is_wp_error( $tmp ) ) { /* translators: 1: Image URL, 2: Post ID */ $this->logger->add_log_entry( 'error', sprintf( __( 'Error downloading image from %1$s for post ID %2$d.', 'ainp-ai-native-publisher' ), esc_url( $image_url ), $post_id ), $tmp->get_error_message() ); return $tmp; } // *** INÍCIO DA VERIFICAÇÃO DE TAMANHO *** // Compliance Fix: Removed '@' $img_size = getimagesize( $tmp ); if ( $img_size ) { $width = $img_size[0]; $height = $img_size[1]; // Define mínimos: Reduzido para 200x150 para aceitar mais imagens de feed na versão Free if ( $width < 200 || $height < 150 ) { if ( file_exists( $tmp ) ) { wp_delete_file( $tmp ); } /* translators: 1: Width, 2: Height */ $this->logger->add_log_entry( 'warning', sprintf( __( 'Skipped image from feed: too small (%1$dx%2$dpx).', 'ainp-ai-native-publisher' ), $width, $height ), 'URL: ' . esc_url( $image_url ) ); return new WP_Error( 'image_too_small', __( 'Image downloaded from feed is too small.', 'ainp-ai-native-publisher' ) ); } } // *** FIM DA VERIFICAÇÃO DE TAMANHO *** $this->logger->add_log_entry( 'info', __( 'External image download complete.', 'ainp-ai-native-publisher' ), 'Temporary file path: ' . $tmp ); $file_array = array(); $url_path = wp_parse_url( $image_url, PHP_URL_PATH ); $filename_from_url = basename( $url_path ); $path_parts = pathinfo( $filename_from_url ); $base_name = sanitize_title( $post_title ); if ( strlen( $base_name ) > 100 ) { $base_name = substr( $base_name, 0, 100 ); } if ( isset( $path_parts['extension'] ) && preg_match( '/\.(jpe?g|gif|png|webp)$/i', $filename_from_url ) ) { $file_array['name'] = $filename_from_url; } else { $finfo = finfo_open( FILEINFO_MIME_TYPE ); if ( $finfo && file_exists( $tmp ) ) { $mime_type = finfo_file( $finfo, $tmp ); finfo_close( $finfo ); $ext = 'jpg'; if ( $mime_type ) { if ( false !== strpos( $mime_type, 'jpeg' ) ) { $ext = 'jpg'; } elseif ( false !== strpos( $mime_type, 'png' ) ) { $ext = 'png'; } elseif ( false !== strpos( $mime_type, 'gif' ) ) { $ext = 'gif'; } elseif ( false !== strpos( $mime_type, 'webp' ) ) { $ext = 'webp'; } } $file_array['name'] = $base_name . '-' . time() . '.' . $ext; $this->logger->add_log_entry( 'info', __( 'Filename generated based on MIME type detection.', 'ainp-ai-native-publisher' ), 'Detected Type: ' . ( $mime_type ?: 'Unknown' ) . ' | Generated Name: ' . $file_array['name'] ); } else { $file_array['name'] = $base_name . '-' . time() . '.jpg'; $this->logger->add_log_entry( 'warning', __( 'Could not determine MIME type, using default fallback filename.', 'ainp-ai-native-publisher' ), 'Filename: ' . $file_array['name'] ); } } $file_array['tmp_name'] = $tmp; $attach_id = media_handle_sideload( $file_array, $post_id, wp_strip_all_tags( $post_title ) ); if ( is_wp_error( $attach_id ) ) { if ( file_exists( $file_array['tmp_name'] ) ) { wp_delete_file( $file_array['tmp_name'] ); } /* translators: 1: Image URL, 2: Post ID */ $this->logger->add_log_entry( 'error', sprintf( __( 'Error sideloading image from %1$s for post ID %2$d.', 'ainp-ai-native-publisher' ), esc_url( $image_url ), $post_id ), $attach_id->get_error_message() ); return $attach_id; } set_post_thumbnail( $post_id, $attach_id ); /* translators: %d: Post ID */ $this->logger->add_log_entry( 'success', sprintf( __( 'Image from feed URL set as featured for post ID %d.', 'ainp-ai-native-publisher' ), $post_id ), 'Attachment ID: ' . $attach_id ); return $attach_id; } /** * Filter hook callback to increase HTTP request timeout for image downloads. */ public function increase_timeout_for_image_download( $timeout ) { return 60; } /** * Filter hook to set a custom User-Agent for image downloads. * * Compliance Fix: Use a custom User-Agent identifying the plugin, * instead of mimicking a browser, to adhere to WP.org guidelines. */ public function set_custom_user_agent( $user_agent ) { return 'AINP-Native-Publisher/' . AINP_VERSION . '; +https://ainewspublisher.shop/'; } /** * Adds a text watermark to an image file using GD library. */ public function add_watermark_to_image( $filepath ) { if ( ! ( extension_loaded( 'gd' ) && function_exists( 'gd_info' ) ) ) { $this->logger->add_log_entry( 'warning', __( 'Watermark skipped: The GD image processing library is not installed or enabled on the server.', 'ainp-ai-native-publisher' ) ); return; } // Compliance Fix: Removed '@' $image_info = getimagesize( $filepath ); if ( ! $image_info ) { $this->logger->add_log_entry( 'error', __( 'Watermark failed: Could not retrieve image information.', 'ainp-ai-native-publisher' ), 'File Path: ' . esc_html( $filepath ) ); return; } $mime_type = $image_info['mime']; $image = null; switch ( $mime_type ) { case 'image/png': $image = imagecreatefrompng( $filepath ); break; case 'image/jpeg': $image = imagecreatefromjpeg( $filepath ); break; case 'image/gif': $image = imagecreatefromgif( $filepath ); break; default: $this->logger->add_log_entry( 'warning', __( 'Watermark skipped: Unsupported image format.', 'ainp-ai-native-publisher' ), 'MIME Type: ' . esc_html( $mime_type ) ); return; } if ( ! $image ) { $this->logger->add_log_entry( 'error', __( 'Watermark failed: Could not load the image resource for processing.', 'ainp-ai-native-publisher' ), 'File Path: ' . esc_html( $filepath ) ); return; } imagealphablending( $image, true ); imagesavealpha( $image, true ); $watermark_text = $this->options['watermark_text'] ?? __( 'AI Generated Image', 'ainp-ai-native-publisher' ); $padding = isset( $this->options['watermark_padding'] ) ? absint( $this->options['watermark_padding'] ) : 10; $font_size = isset( $this->options['watermark_font_size'] ) ? absint( $this->options['watermark_font_size'] ) : 5; $font_size = max( 1, min( 5, $font_size ) ); $text_color = imagecolorallocate( $image, 255, 255, 255 ); $shadow_color = imagecolorallocate( $image, 0, 0, 0 ); $img_width = imagesx( $image ); $img_height = imagesy( $image ); $text_width = imagefontwidth( $font_size ) * strlen( $watermark_text ); $text_height = imagefontheight( $font_size ); $pos_x = $img_width - $text_width - $padding; $pos_y = $img_height - $text_height - $padding; imagestring( $image, $font_size, $pos_x + 1, $pos_y + 1, $watermark_text, $shadow_color ); imagestring( $image, $font_size, $pos_x, $pos_y, $watermark_text, $text_color ); $saved = false; switch ( $mime_type ) { case 'image/png': $saved = imagepng( $image, $filepath, 9 ); break; case 'image/jpeg': $saved = imagejpeg( $image, $filepath, 90 ); break; case 'image/gif': $saved = imagegif( $image, $filepath ); break; } imagedestroy( $image ); if ( $saved ) { $this->logger->add_log_entry( 'info', __( 'Watermark added successfully to the image.', 'ainp-ai-native-publisher' ), 'File: ' . basename( $filepath ) ); } else { $this->logger->add_log_entry( 'error', __( 'Watermark failed: Could not save the modified image.', 'ainp-ai-native-publisher' ), 'File: ' . basename( $filepath ) ); } } } // End Class AINP_Image_Handler