'Keine Berechtigung.' ) ); }
$post_id = intval( $_POST['post_id'] ?? 0 );
$post = get_post( $post_id );
if ( ! $post ) { wp_send_json_error( array( 'msg' => 'Beitrag nicht gefunden.' ) ); }
$result = $this->ai_generate_meta_variants( $post );
if ( ! empty( $result['error'] ) ) {
$err_data = array( 'msg' => $result['error'] );
if ( ! empty( $result['limit'] ) ) {
$err_data['limit'] = true;
$err_data['upgrade_url'] = $result['upgrade_url'] ?? 'https://wprankify.de/#pricing';
}
wp_send_json_error( $err_data );
}
$variants = $result['variants'] ?? array();
if ( empty( $variants ) ) { wp_send_json_error( array( 'msg' => 'Nicht genug Inhalt.' ) ); }
$existing = get_post_meta( $post_id, '_yoast_wpseo_metadesc', true );
if ( empty( $existing ) ) { $existing = get_post_meta( $post_id, 'rank_math_description', true ); }
if ( empty( $existing ) ) { $existing = get_post_meta( $post_id, '_aioseo_description', true ); }
$seo = $this->get_active_seo_plugin();
$target = $seo === 'rankmath' ? 'rankmath' : ( $seo === 'aioseo' ? 'aioseo' : ( $seo === 'yoast' ? 'yoast' : 'none' ) );
wp_send_json_success( array( 'variants' => $variants, 'existing_meta' => $existing ?: '', 'target' => $target, 'post_id' => $post_id, 'quota' => $result['quota'] ?? array() ) );
}
/**
* Ersetzt die frühere rein PHP-template-basierte Generierung (immer Deutsch,
* feste Textbausteine) durch einen KI-Aufruf am Proxy — analog zur
* Themen-Analyse. Sprache folgt der Einstellung content_language:
* 'auto' = Proxy erkennt Sprache am Artikelinhalt selbst; sonst wird die
* gewählte Sprache erzwungen, damit KI-Ausgaben einheitlich bleiben.
*/
private function ai_generate_meta_variants( $post ) {
$title = wp_strip_all_tags( get_the_title( $post ) );
$content = apply_filters( 'the_content', $post->post_content );
$content = preg_replace( '/
]*>\s*(SEO\s*·|Lesedauer|Reading time)[^<]*<\/p>/iu', '', $content );
$content = wp_strip_all_tags( $content );
$content = preg_replace( '/\s+/', ' ', trim( $content ) );
if ( mb_strlen( $content ) < 100 ) { return array( 'variants' => array() ); }
$o = $this->get_opts();
$language = $o['content_language'] ?? 'auto';
$payload = array(
'secret' => self::stats_secret(),
'site_url' => home_url(),
'title' => $title,
'content' => $content,
'action' => 'meta',
'language' => $language,
);
$response = wp_remote_post( self::AI_PROXY_URL, array( 'timeout' => 35, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $payload ), 'sslverify' => false ) );
if ( is_wp_error( $response ) ) { return array( 'error' => 'Verbindungsfehler: ' . $response->get_error_message() ); }
$http_code = wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $body['success'] ) ) {
$err = $body['error'] ?? 'Fehler';
if ( $http_code === 429 ) { return array( 'error' => $err, 'limit' => true, 'upgrade_url' => $body['upgrade_url'] ?? 'https://wprankify.de/#pricing' ); }
return array( 'error' => $err );
}
$variants = array();
foreach ( (array) ( $body['data']['variants'] ?? array() ) as $v ) {
$text = trim( (string) ( $v['text'] ?? '' ) );
if ( $text === '' ) { continue; }
$variants[] = array(
'strategy' => sanitize_text_field( (string) ( $v['strategy'] ?? '' ) ),
'text' => $text,
'chars' => mb_strlen( $text ),
'score' => max( 0, min( 100, (int) ( $v['score'] ?? 0 ) ) ),
);
}
return array( 'variants' => $variants, 'quota' => $body['quota'] ?? array() );
}
public function aije_save_meta_handler() {
if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( array( 'msg' => 'Keine Berechtigung.' ) ); }
$post_id = intval( $_POST['post_id'] ?? 0 );
$meta = sanitize_text_field( wp_unslash( $_POST['meta_text'] ?? '' ) );
$target = sanitize_key( $_POST['target'] ?? 'none' );
if ( ! $post_id || empty( $meta ) ) { wp_send_json_error( array( 'msg' => 'Ungültige Parameter.' ) ); }
switch ( $target ) {
case 'rankmath': update_post_meta( $post_id, 'rank_math_description', $meta ); break;
case 'aioseo': update_post_meta( $post_id, '_aioseo_description', $meta ); break;
case 'none': update_post_meta( $post_id, '_aije_metadesc', $meta ); break;
default: update_post_meta( $post_id, '_yoast_wpseo_metadesc', $meta ); break;
}
update_post_meta( $post_id, '_aije_metadesc', $meta );
clean_post_cache( $post_id );
wp_send_json_success( array( 'msg' => 'Gespeichert.', 'chars' => mb_strlen( $meta ), 'meta' => $meta ) );
}
private function render_meta_generator_box( $post ) {
$nonce = wp_create_nonce( 'aije_meta_nonce' );
$seo = $this->get_active_seo_plugin();
$target = array( 'rankmath' => 'rankmath', 'aioseo' => 'aioseo', 'yoast' => 'yoast' )[ $seo ] ?? 'none';
$tl = array( 'rankmath' => 'RankMath', 'aioseo' => 'AIOSEO', 'yoast' => 'Yoast', 'none' => 'WP Rankify' )[ $target ] ?? 'WP Rankify';
$ex = get_post_meta( $post->ID, '_yoast_wpseo_metadesc', true ) ?: get_post_meta( $post->ID, 'rank_math_description', true ) ?: get_post_meta( $post->ID, '_aioseo_description', true ) ?: get_post_meta( $post->ID, '_aije_metadesc', true );
$el = mb_strlen( $ex ); $ec = ( $el >= 140 && $el <= 160 ) ? '#22c55e' : ( $el > 0 ? '#f59e0b' : '#ef4444' );
echo '
';
// JS aus separater Datei laden
require plugin_dir_path( AIJE_PLUGIN_FILE ) . 'admin/metabox-meta-js.php';
}
public function aije_topic_analysis_handler() {
check_ajax_referer( 'aije_topic_nonce', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( array( 'msg' => 'Keine Berechtigung.' ) ); }
$post_id = intval( $_POST['post_id'] ?? 0 );
$post = get_post( $post_id );
if ( ! $post ) { wp_send_json_error( array( 'msg' => 'Beitrag nicht gefunden.' ) ); }
if ( ! in_array( $post->post_status, array( 'publish','draft','pending','private','future' ), true ) ) { wp_send_json_error( array( 'msg' => 'Ungültiger Status.' ) ); }
$lock_key = 'aije_analysis_lock_' . $post_id . '_' . get_current_user_id();
if ( get_transient( $lock_key ) ) { wp_send_json_error( array( 'msg' => 'Bitte kurz warten.' ) ); }
set_transient( $lock_key, 1, 15 );
$title = get_the_title( $post ) ?: '(Kein Titel)';
$content = // Aufbereitung: HTML-Tags entfernen, Entities dekodieren, Whitespace normalisieren
$plain = html_entity_decode( wp_strip_all_tags( $post->post_content ), ENT_QUOTES, 'UTF-8' );
$plain = preg_replace( '/[ \t]+/', ' ', $plain );
$plain = preg_replace( '/\n{3,}/', "\n\n", $plain );
$plain = trim( $plain );
// H2-Überschriften als Inhaltsübersicht extrahieren
// Das Modell sieht damit die Struktur des gesamten Artikels – auch was nach dem Textlimit steht
preg_match_all( '/]*>(.*?)<\/h2>/is', $post->post_content, $h2_matches );
$h2_list = array();
foreach ( $h2_matches[1] as $h2 ) {
$h2_list[] = '- ' . trim( wp_strip_all_tags( $h2 ) );
}
$structure_note = ! empty( $h2_list )
? "Artikelstruktur (alle Abschnitte):\n" . implode( "\n", $h2_list ) . "\n\n"
: '';
// Volltext auf 15.000 Zeichen begrenzen (reicht für ~3.000 Wörter)
$char_limit = 15000;
$is_truncated = mb_strlen( $plain ) > $char_limit;
$content = $structure_note . ( $is_truncated ? mb_substr( $plain, 0, $char_limit ) : $plain );
$force = ! empty( $_POST['force'] );
if ( ! $force ) { $cached = get_transient( 'aije_topic_' . $post_id ); if ( $cached ) { wp_send_json_success( array( 'data' => $cached, 'quota' => array(), 'cached' => true ) ); } }
$o = $this->get_opts();
$payload = array( 'secret' => self::stats_secret(), 'site_url' => home_url(), 'title' => $title, 'content' => $content, 'action' => 'analyze', 'language' => $o['content_language'] ?? 'auto' );
$response = wp_remote_post( self::AI_PROXY_URL, array( 'timeout' => 35, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $payload ), 'sslverify' => false ) );
if ( is_wp_error( $response ) ) { wp_send_json_error( array( 'msg' => 'Verbindungsfehler: ' . $response->get_error_message() ) ); }
$http_code = wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $body['success'] ) ) { $err = $body['error'] ?? 'Fehler'; if ( $http_code === 429 ) { wp_send_json_error( array( 'msg' => $err, 'limit' => true, 'upgrade_url' => $body['upgrade_url'] ?? 'https://wprankify.de/#pricing' ) ); } wp_send_json_error( array( 'msg' => $err ) ); }
delete_transient( 'aije_topic_' . $post_id );
set_transient( 'aije_topic_' . $post_id, $body['data'], DAY_IN_SECONDS );
wp_send_json_success( array( 'data' => $body['data'], 'quota' => $body['quota'] ?? array() ) );
}
private function render_topic_analysis_box( $post ) {
$nonce = wp_create_nonce( 'aije_topic_nonce' );
$cached = get_transient( 'aije_topic_' . $post->ID );
$pro_status = $this->get_pro_status();
$tier = $pro_status['tier'] ?? 'free';
$remaining = $pro_status['remaining'] ?? 3;
$is_pro = ! empty( $pro_status['is_pro'] );
$tc = array( 'free' => '#888', 'starter' => '#22c55e', 'pro' => '#f29400', 'agency' => '#3b82f6' )[ $tier ] ?? '#888';
echo '';
echo '
🧠 KI-Themen-Analyse
';
echo '
' . esc_html( strtoupper( $tier ) ) . '';
if ( ! $is_pro ) { echo '' . (int) $remaining . ' verbleibend'; }
echo '
';
echo '
';
echo '
KI analysiert… (ca. 5–10 Sek.)
';
echo '
';
if ( $cached ) { echo '
💾 Letzte Analyse verfügbar.
'; }
echo '
';
require plugin_dir_path( AIJE_PLUGIN_FILE ) . 'admin/metabox-topic-js.php';
}
public function aije_insert_link_handler() {
check_ajax_referer( 'aije_insert_link', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( array( 'msg' => 'Keine Berechtigung.' ) ); }
$post_id = intval( $_POST['post_id'] ?? 0 );
$target_id = intval( $_POST['target_id'] ?? 0 );
if ( ! $post_id || ! $target_id ) { wp_send_json_error( array( 'msg' => 'Ungültige Parameter.' ) ); }
$post = get_post( $post_id ); $target = get_post( $target_id );
if ( ! $post || ! $target ) { wp_send_json_error( array( 'msg' => 'Beitrag nicht gefunden.' ) ); }
$content = $post->post_content; $target_url = get_permalink( $target_id ); $target_title = get_the_title( $target_id );
$stopwords = array( 'der','die','das','den','dem','des','ein','eine','einer','eines','und','oder','aber','wenn','weil','dass','dann','ist','sind','hat','haben','wird','werden','kann','muss','soll','sein','nicht','auch','noch','nur','sehr','mehr','alle','hier','dort','jetzt','mit','von','zu','bei','in','an','auf','aus','nach','the','and','for','with','this','that','are','was','have','not','artikel','beitrag','tipps','hilfe','guide','alles','wissen' );
$keywords = array_values( array_filter( preg_split( '/\s+/', mb_strtolower( wp_strip_all_tags( $target_title ) ), -1, PREG_SPLIT_NO_EMPTY ), function( $w ) use ( $stopwords ) { return mb_strlen( $w ) >= 5 && ! in_array( $w, $stopwords ); } ) );
usort( $keywords, function( $a, $b ) { return mb_strlen( $b ) - mb_strlen( $a ); } );
$linked = false; $linked_kw = ''; $plain = wp_strip_all_tags( $content );
$work = preg_replace( '/^(\s*
]*>.*?<\/p>)/is', '___FIRST_P___$1', $content, 1 );
foreach ( $keywords as $kw ) {
if ( stripos( $plain, $kw ) === false ) { continue; }
$found = false;
$new = preg_replace_callback( '/(]*>.*?<\/h[1-6]>)|(]*>.*?<\/a>)|(___FIRST_P___]*>.*?<\/p>)|(' . preg_quote( $kw, '/' ) . ')/isu', function( $m ) use ( $target_url, $target_title, &$found ) { if ( ! empty( $m[1] ) ) { return $m[1]; } if ( ! empty( $m[2] ) ) { return $m[2]; } if ( ! empty( $m[3] ) ) { return str_replace( '___FIRST_P___', '', $m[3] ); } if ( ! $found ) { $found = true; return '' . $m[4] . ''; } return $m[4]; }, $work );
$new = str_replace( '___FIRST_P___', '', $new );
if ( $found && $new !== $content ) { $linked = true; $linked_kw = $kw; $content = $new; break; }
}
$content = str_replace( '___FIRST_P___', '', $content );
if ( ! $linked ) { $content .= "\n\n" . '
📖 Wenn dich dieses Thema interessiert, lies auch unseren Artikel: ' . esc_html( $target_title ) . '
'; }
global $wpdb;
$updated = $wpdb->update( $wpdb->posts, array( 'post_content' => $content, 'post_modified' => current_time( 'mysql' ), 'post_modified_gmt' => current_time( 'mysql', 1 ) ), array( 'ID' => $post_id ), array( '%s', '%s', '%s' ), array( '%d' ) );
clean_post_cache( $post_id );
if ( $updated === false ) { wp_send_json_error( array( 'msg' => 'Datenbankfehler.' ) ); }
// Nutzungszähler fürs Heartbeat-Reporting (siehe trait-stats.php) — nur ein
// Zähler, keine Inhalte, damit die "anonyme Nutzungsstatistik"-Zusage aus
// der readme.txt eingehalten wird.
update_option( 'aije_internal_link_uses', (int) get_option( 'aije_internal_link_uses', 0 ) + 1, false );
$method = $linked ? 'keyword' : 'paragraph';
wp_send_json_success( array( 'method' => $method, 'keyword' => $linked_kw, 'msg' => $method === 'keyword' ? '✓ „' . esc_html( $linked_kw ) . '" verlinkt.' : '✓ Lese-auch-Absatz eingefügt.', 'new_content' => $content ) );
}
}