'post', ); // Only set status if explicitly provided; on update, preserve existing status. if ( isset( $payload['status'] ) && in_array( $payload['status'], array( 'publish', 'draft', 'pending' ), true ) ) { $post_data['post_status'] = $payload['status']; } elseif ( ! $post_id || ! get_post( $post_id ) ) { // New post defaults to draft. $post_data['post_status'] = 'draft'; } if ( isset( $payload['title'] ) ) { $post_data['post_title'] = sanitize_text_field( $payload['title'] ); } if ( isset( $payload['content'] ) ) { $post_data['post_content'] = wp_kses_post( $payload['content'] ); // Download external images to Media Library before saving. try { $post_data['post_content'] = $this->download_content_images( $post_data['post_content'] ); } catch ( \Throwable $e ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( '[AEO] download_content_images failed: ' . $e->getMessage() ); } } if ( isset( $payload['slug'] ) ) { $post_data['post_name'] = sanitize_title( $payload['slug'] ); } if ( isset( $payload['excerpt'] ) ) { $post_data['post_excerpt'] = sanitize_textarea_field( $payload['excerpt'] ); } // Handle categories. if ( isset( $payload['categories'] ) && is_array( $payload['categories'] ) ) { $cat_ids = array(); foreach ( $payload['categories'] as $name ) { $term = get_term_by( 'name', $name, 'category' ); if ( $term ) { $cat_ids[] = $term->term_id; } else { $result = wp_insert_term( $name, 'category' ); if ( ! is_wp_error( $result ) ) { $cat_ids[] = $result['term_id']; } } } if ( ! empty( $cat_ids ) ) { $post_data['post_category'] = $cat_ids; } } // Handle tags. if ( isset( $payload['tags'] ) && is_array( $payload['tags'] ) ) { $post_data['tags_input'] = array_map( 'sanitize_text_field', $payload['tags'] ); } // Create or update. if ( $post_id && get_post( $post_id ) ) { $post_data['ID'] = $post_id; $result = wp_update_post( $post_data, true ); } else { $result = wp_insert_post( $post_data, true ); } if ( is_wp_error( $result ) ) { return $result; } $post_id = $result; // Set AEO post meta. if ( isset( $payload['faq'] ) && is_array( $payload['faq'] ) ) { update_post_meta( $post_id, '_aeocas_faq_schema', $this->sanitize_schema( $payload['faq'] ) ); } elseif ( ! empty( $post_data['post_content'] ) ) { // Auto-extract FAQ from content only when content was provided. try { $this->auto_extract_faq( $post_id, $post_data['post_content'] ); } catch ( \Throwable $e ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( '[AEO] auto_extract_faq failed: ' . $e->getMessage() ); } } if ( isset( $payload['author'] ) ) { update_post_meta( $post_id, '_aeocas_author_schema', $this->sanitize_schema( $payload['author'] ) ); } if ( isset( $payload['speakable'] ) ) { update_post_meta( $post_id, '_aeocas_speakable', array_map( 'sanitize_text_field', (array) $payload['speakable'] ) ); } if ( isset( $payload['canonical'] ) ) { update_post_meta( $post_id, '_aeocas_canonical_url', esc_url_raw( $payload['canonical'] ) ); } // Download and set featured image. if ( ! empty( $payload['featured_image_url'] ) ) { $image_url = esc_url_raw( $payload['featured_image_url'], array( 'http', 'https' ) ); if ( $image_url ) { try { $this->set_featured_image( $post_id, $image_url ); } catch ( \Throwable $e ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( '[AEO] set_featured_image failed: ' . $e->getMessage() ); } } } return rest_ensure_response( array( 'ok' => true, 'post_id' => $post_id, 'url' => get_permalink( $post_id ), 'edit' => get_edit_post_link( $post_id, 'raw' ), ) ); } /** * Recursively sanitize a schema array for safe storage. * * @param mixed $data Input data. * @return mixed Sanitized data. */ private function sanitize_schema( $data ) { if ( is_array( $data ) ) { $clean = array(); foreach ( $data as $key => $value ) { $clean[ sanitize_text_field( $key ) ] = $this->sanitize_schema( $value ); } return $clean; } if ( is_string( $data ) ) { if ( filter_var( $data, FILTER_VALIDATE_URL ) ) { return esc_url_raw( $data ); } return sanitize_text_field( $data ); } if ( is_bool( $data ) || is_int( $data ) || is_float( $data ) ) { return $data; } return ''; } /** * Auto-extract FAQ pairs from HTML content. * Looks for Rank Math FAQ blocks or generic H2/H3 FAQ patterns. */ private function auto_extract_faq( $post_id, $content ) { if ( empty( $content ) ) { return; } $pairs = array(); // Pattern 1: Rank Math FAQ block. if ( false !== strpos( $content, 'rank-math-faq' ) ) { $pattern = '/]*class\s*=\s*["\']rank-math-question[^"\']*["\'][^>]*>([\s\S]*?)<\/h3>\s*]*class\s*=\s*["\']rank-math-answer[^"\']*["\'][^>]*>([\s\S]*?)<\/div>/i'; if ( preg_match_all( $pattern, $content, $matches, PREG_SET_ORDER ) ) { foreach ( $matches as $match ) { $pairs[] = array( 'question' => wp_strip_all_tags( $match[1] ), 'answer' => wp_strip_all_tags( $match[2] ), ); } } } // Pattern 2: Generic FAQ heading + Q&A pairs. if ( empty( $pairs ) ) { $faq_pattern = '/]*>[\s\S]*?(?:FAQ|Frequently\s+Asked\s+Questions)[\s\S]*?<\/h2>/i'; if ( preg_match( $faq_pattern, $content, $faq_match, PREG_OFFSET_MATCH ) ) { $faq_section = substr( $content, $faq_match[0][1] ); // Find next H2 to limit scope. $next_h2 = strpos( $faq_section, ' $question, 'answer' => $answer, ); } } } } } if ( ! empty( $pairs ) ) { update_post_meta( $post_id, '_aeocas_faq_schema', $pairs ); } } /** * Download external images in HTML content to the Media Library. * * Finds all tags with external src URLs, downloads each image, * and replaces the src with the local WordPress URL. * * @param string $content HTML content. * @return string Content with localized image URLs. */ private function download_content_images( $content ) { if ( empty( $content ) ) { return $content; } if ( ! function_exists( 'media_sideload_image' ) ) { require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; } $site_host = wp_parse_url( home_url(), PHP_URL_HOST ); // Find all img tags with src attribute. if ( ! preg_match_all( '/]*src\s*=\s*["\']([^"\']+)["\'][^>]*>/i', $content, $matches, PREG_SET_ORDER ) ) { return $content; } foreach ( $matches as $match ) { $original_url = $match[1]; // Only process http/https URLs. if ( 0 !== strpos( $original_url, 'http://' ) && 0 !== strpos( $original_url, 'https://' ) ) { continue; } // Skip images already on this WordPress site. $img_host = wp_parse_url( $original_url, PHP_URL_HOST ); if ( $img_host === $site_host ) { continue; } // Download to Media Library (without attaching to a post yet). $local_url = media_sideload_image( $original_url, 0, '', 'src' ); if ( is_wp_error( $local_url ) ) { continue; } // Replace the external URL with the local one. $content = str_replace( $original_url, $local_url, $content ); } return $content; } /** * Download an image URL and set as featured image. */ private function set_featured_image( $post_id, $url ) { if ( ! function_exists( 'media_sideload_image' ) ) { require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; } $attachment_id = media_sideload_image( $url, $post_id, '', 'id' ); if ( ! is_wp_error( $attachment_id ) ) { set_post_thumbnail( $post_id, $attachment_id ); } } }