array( 'ai_write_nightly' => (bool) $on ) ) ); if ( self::available() && class_exists( 'AIEO_Outbound_Notice' ) ) { AIEO_Outbound_Notice::record( 'seo_nightly', (bool) $on ); } } return (bool) $on; } private static function has_drafts() { global $wpdb; $sp = $wpdb->prefix . 'aieo_seo_posts'; if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $sp ) ) ) !== $sp ) { return false; } return (bool) $wpdb->get_var( "SELECT 1 FROM `{$sp}` WHERE ai_state = 'suggested' LIMIT 1" ); } public static function available() { return class_exists( 'AIEO_SEO_AI_Assist' ) && AIEO_SEO_AI_Assist::available() && class_exists( 'AIEO_DataForSEO' ) && AIEO_DataForSEO::is_connected(); } public static function run( $limit = null ) { $out = array( 'drafted' => 0, 'applied' => 0, 'skipped' => 0 ); if ( ! self::available() ) { $out['skipped'] = 'ai_or_dfs_unavailable'; return $out; } $limit = null === $limit ? self::daily_cap() : max( 0, (int) $limit ); if ( 0 === $limit ) { $out['skipped'] = 'cap_zero'; return $out; } foreach ( self::candidates( $limit ) as $c ) { $r = self::process( (string) $c['object_type'], (int) $c['object_id'] ); if ( 'applied' === $r ) { $out['applied']++; } elseif ( 'drafted' === $r ) { $out['drafted']++; } else { $out['skipped'] = is_int( $out['skipped'] ) ? $out['skipped'] + 1 : 1; } } return $out; } public static function candidates( $limit ) { global $wpdb; $sp = $wpdb->prefix . 'aieo_seo_posts'; $sc = $wpdb->prefix . 'aieo_seo_scores'; $rows = $wpdb->get_results( $wpdb->prepare( "SELECT sp.object_type, sp.object_id FROM `{$sp}` sp LEFT JOIN `{$sc}` sc ON sc.object_type = sp.object_type AND sc.object_id = sp.object_id WHERE sp.search_volume_total > 0 AND COALESCE(sp.ai_locked, 0) = 0 AND COALESCE(sp.ai_state, 'none') <> 'suggested' AND ( sp.striking_count > 0 OR COALESCE(sc.score, 0) < 70 ) ORDER BY (sp.striking_count > 0) DESC, sp.search_volume_total DESC LIMIT %d", (int) $limit ), ARRAY_A ); $out = array(); foreach ( (array) $rows as $r ) { if ( post_type_exists( (string) $r['object_type'] ) ) { $out[] = array( 'object_type' => (string) $r['object_type'], 'object_id' => (int) $r['object_id'] ); } } return $out; } public static function process( $object_type, $object_id ) { $post = get_post( $object_id ); if ( ! $post instanceof WP_Post || 'publish' !== $post->post_status ) { return 'skipped:no_post'; } $row = (array) AIEO_SEO_Store::get( $object_type, $object_id ); $keyword = trim( (string) ( $row['focus_keyphrase'] ?? '' ) ); if ( '' === $keyword ) { $ranked = AIEO_DFS_Rankings::keywords_for( $object_type, $object_id, 1 ); $keyword = isset( $ranked[0]['keyword'] ) ? (string) $ranked[0]['keyword'] : ''; } if ( '' === $keyword ) { return 'skipped:no_keyword'; } $serp = AIEO_DFS_Rankings::serp_context( $keyword ); if ( is_wp_error( $serp ) ) { return 'skipped:' . $serp->get_error_code(); } $extra = array( 'TARGET SEARCH QUERY: ' . $keyword, 'LIVE GOOGLE SERP TO BEAT (top organic results):' ); foreach ( array_slice( $serp['competitors'], 0, 8 ) as $i => $c ) { $extra[] = ( $i + 1 ) . '. ' . $c['title'] . ' — ' . mb_substr( (string) $c['description'], 0, 140 ); } if ( ! empty( $serp['paa'] ) ) { $extra[] = 'PEOPLE ALSO ASK: ' . implode( ' | ', $serp['paa'] ); } $secondary = AIEO_DFS_Rankings::keywords_for( $object_type, $object_id, 6 ); if ( $secondary ) { $extra[] = 'SECONDARY KEYWORDS THIS PAGE ALREADY RANKS FOR: ' . implode( ', ', array_map( static function ( $k ) { return $k['keyword'] . ' (pos ' . $k['position'] . ', vol ' . $k['volume'] . ')'; }, $secondary ) ); } if ( class_exists( 'AIEO_Goal_Tracker' ) || file_exists( AIEO_DIR . 'includes/class-aieo-goal-tracker.php' ) ) { if ( ! class_exists( 'AIEO_Goal_Tracker' ) ) { require_once AIEO_DIR . 'includes/class-aieo-goal-tracker.php'; } $goals = AIEO_Goal_Tracker::goal_context_for( $post ); if ( $goals ) { $extra[] = 'THIS PAGE\'S CONVERSION GOAL(S) — write the description with a call to action toward these: ' . implode( ' | ', $goals ); } } $res = AIEO_SEO_AI_Assist::suggest( $post, $extra ); if ( is_wp_error( $res ) ) { return 'skipped:' . $res->get_error_code(); } if ( '' === (string) $res['title'] && '' === (string) $res['description'] ) { return 'skipped:empty_ai_result'; } if ( self::autopilot() ) { return self::apply_fields( $object_type, $object_id, $res ) ? 'applied' : 'skipped:apply_failed'; } AIEO_SEO_Store::save( $object_type, $object_id, array( 'suggested_title' => (string) $res['title'], 'suggested_description' => (string) $res['description'], 'suggested_keyphrase' => (string) $res['keyphrase'], 'suggested_at' => current_time( 'mysql', true ), 'ai_state' => 'suggested', ) ); return 'drafted'; } public static function approve( $object_type, $object_id ) { $row = (array) AIEO_SEO_Store::get( $object_type, $object_id ); if ( 'suggested' !== (string) ( $row['ai_state'] ?? '' ) ) { return false; } $fields = array( 'title' => (string) ( $row['suggested_title'] ?? '' ), 'description' => (string) ( $row['suggested_description'] ?? '' ), 'keyphrase' => (string) ( $row['suggested_keyphrase'] ?? '' ), ); return self::apply_fields( $object_type, $object_id, $fields ); } public static function dismiss( $object_type, $object_id ) { return (bool) AIEO_SEO_Store::save( $object_type, $object_id, array( 'suggested_title' => null, 'suggested_description' => null, 'suggested_keyphrase' => null, 'suggested_at' => null, 'ai_state' => 'none', ) ); } private static function apply_fields( $object_type, $object_id, array $fields ) { $save = array( 'suggested_title' => null, 'suggested_description' => null, 'suggested_keyphrase' => null, 'suggested_at' => null, 'ai_state' => 'applied', ); if ( '' !== (string) ( $fields['title'] ?? '' ) ) { $save['title'] = (string) $fields['title']; } if ( '' !== (string) ( $fields['description'] ?? '' ) ) { $save['description'] = (string) $fields['description']; } if ( '' !== (string) ( $fields['keyphrase'] ?? '' ) ) { $save['focus_keyphrase'] = (string) $fields['keyphrase']; $save['focus_keyphrase_source'] = 'ai_write_queue'; } $ok = AIEO_SEO_Store::save( $object_type, $object_id, $save ); if ( $ok && class_exists( 'AIEO_SEO_Head_Builder' ) && method_exists( 'AIEO_SEO_Head_Builder', 'build' ) ) { AIEO_SEO_Head_Builder::build( $object_type, $object_id ); } return (bool) $ok; } public static function pending( $limit = 50 ) { global $wpdb; $sp = $wpdb->prefix . 'aieo_seo_posts'; $rows = $wpdb->get_results( $wpdb->prepare( "SELECT object_type, object_id, title, description, suggested_title, suggested_description, suggested_keyphrase, suggested_at FROM `{$sp}` WHERE ai_state = 'suggested' ORDER BY suggested_at DESC LIMIT %d", (int) $limit ), ARRAY_A ); return is_array( $rows ) ? $rows : array(); } public static function body_available() { return class_exists( 'AIEO_SEO_AI_Assist' ) && AIEO_SEO_AI_Assist::available(); } public static function process_body( $object_type, $object_id ) { $post = get_post( $object_id ); if ( ! $post instanceof WP_Post || 'publish' !== $post->post_status || $post->post_type !== $object_type ) { return 'skipped:no_post'; } $row = (array) AIEO_SEO_Store::get( $object_type, $object_id ); if ( ! empty( $row['ai_locked'] ) ) { return 'skipped:ai_locked'; } if ( ! self::body_available() ) { return 'skipped:ai_unavailable'; } $extra = array(); $secondary = class_exists( 'AIEO_DFS_Rankings' ) ? AIEO_DFS_Rankings::keywords_for( $object_type, $object_id, 6 ) : array(); if ( $secondary ) { $extra[] = 'RELATED SEARCH TERMS THIS PAGE COULD COVER (weave in only where relevant): ' . implode( ', ', array_map( static function ( $k ) { return (string) $k['keyword']; }, $secondary ) ); } $res = AIEO_SEO_AI_Assist::suggest_body( $post, $extra ); if ( is_wp_error( $res ) ) { return 'skipped:' . $res->get_error_code(); } AIEO_SEO_Store::save( $object_type, $object_id, array( 'suggested_body' => (string) $res['body'], 'suggested_body_at' => current_time( 'mysql', true ), ) ); return 'drafted'; } public static function approve_body( $object_type, $object_id ) { $row = (array) AIEO_SEO_Store::get( $object_type, $object_id ); $body = (string) ( $row['suggested_body'] ?? '' ); if ( '' === trim( $body ) ) { return new WP_Error( 'aieo_no_draft', __( 'No body draft to approve.', 'ai-eshop-optimizer' ) ); } $post = get_post( $object_id ); if ( ! $post instanceof WP_Post || 'publish' !== $post->post_status || $post->post_type !== $object_type ) { return new WP_Error( 'aieo_no_post', __( 'No published page for that object.', 'ai-eshop-optimizer' ) ); } $result = wp_update_post( array( 'ID' => (int) $object_id, 'post_content' => wp_kses_post( $body ), ), true ); if ( is_wp_error( $result ) || 0 === $result ) { return is_wp_error( $result ) ? $result : new WP_Error( 'aieo_update_failed', __( 'Could not update the page content.', 'ai-eshop-optimizer' ) ); } AIEO_SEO_Store::save( $object_type, $object_id, array( 'suggested_body' => null, 'suggested_body_at' => null, ) ); if ( class_exists( 'AIEO_SEO_Head_Builder' ) && method_exists( 'AIEO_SEO_Head_Builder', 'build' ) ) { AIEO_SEO_Head_Builder::build( $object_type, $object_id ); } return true; } public static function dismiss_body( $object_type, $object_id ) { return (bool) AIEO_SEO_Store::save( $object_type, $object_id, array( 'suggested_body' => null, 'suggested_body_at' => null, ) ); } public static function pending_body( $limit = 50 ) { global $wpdb; $sp = $wpdb->prefix . 'aieo_seo_posts'; $rows = $wpdb->get_results( $wpdb->prepare( "SELECT object_type, object_id, suggested_body, suggested_body_at FROM `{$sp}` WHERE suggested_body IS NOT NULL AND suggested_body <> '' ORDER BY suggested_body_at DESC LIMIT %d", (int) $limit ), ARRAY_A ); return is_array( $rows ) ? $rows : array(); } }