'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 = '/