5, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'secret' => hash( 'sha256', home_url() . '1111111' ), 'site_url' => home_url(), 'plugin_version' => $pv, 'event' => 'uninstalled', ) ), 'blocking' => true, 'sslverify' => true, ) ); } register_deactivation_hook(__FILE__, function () { delete_option('aije_stats_notice_dismissed'); delete_option('aije_first_ping_sent'); delete_option('aije_last_heartbeat'); // Dashboard informieren $pv = defined('AIJE_PLUGIN_VERSION') ? AIJE_PLUGIN_VERSION : '1.3.7'; $payload = array( 'secret' => hash( 'sha256', home_url() . '1111111' ), 'site_url' => home_url(), 'plugin_version' => $pv, 'event' => 'deactivated', ); wp_remote_post( 'https://www.muench-media.com/aije-stats/heartbeat', array( 'timeout' => 5, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $payload ), 'blocking' => true, 'sslverify' => true, ) ); }); /* -------- Guard: keine Doppel-Loads -------- */ if (defined('AIJE_LITE_1_0_0')) { return; } define('AIJE_LITE_1_0_0', true); /* -------- Aktivierungs-Check -------- */ register_activation_hook(__FILE__, function () { global $wp_version; if (version_compare(PHP_VERSION, '7.2', '<')) { deactivate_plugins(plugin_basename(__FILE__)); wp_die(esc_html__('AI AUTO EMBED benötigt mindestens PHP 7.2.', 'ai-json-extractor-auto-embed-lite')); } if (!isset($wp_version) || version_compare($wp_version, '5.2', '<')) { deactivate_plugins(plugin_basename(__FILE__)); wp_die(esc_html__('AI AUTO EMBED benötigt mindestens WordPress 5.2.', 'ai-json-extractor-auto-embed-lite')); } }); /* -------- Aktivierung: optionaler Ping an eigenen Endpoint -------- */ register_activation_hook(__FILE__, function () { $payload = array( 'event' => 'plugin_activation', 'site_name' => get_bloginfo('name'), 'site_url' => home_url(), 'admin_email' => get_option('admin_email'), 'php_version' => PHP_VERSION, 'wp_version' => get_bloginfo('version'), 'plugin_version' => AIJE_PLUGIN_VERSION, 'secret' => AIJE_Lite_100::stats_secret(), 'time' => date_i18n('c'), ); $args = array( 'method' => 'POST', 'timeout' => 8, 'redirection' => 5, 'headers' => array('Content-Type' => 'application/json'), 'body' => wp_json_encode($payload), 'sslverify' => true, ); $endpoint = AIJE_Lite_100::STATS_ENDPOINT . 'activate'; @wp_remote_post($endpoint, $args); }); /* -------- Plugin-Update: Heartbeat sofort senden (kein Deaktivieren nötig) -------- */ add_action('upgrader_process_complete', function( $upgrader, $options ) { if ( $options['action'] === 'update' && $options['type'] === 'plugin' && isset( $options['plugins'] ) ) { foreach ( (array) $options['plugins'] as $plugin ) { if ( strpos( $plugin, 'ai-json-extractor-auto-embed' ) !== false ) { // Plugin wurde gerade aktualisiert → sofort Heartbeat senden $instance = new AIJE_Lite_100(); // Boot nicht nötig – direkt heartbeat // Sperre zurücksetzen damit sofort neuer Heartbeat mit neuer Version gesendet wird delete_option( 'aije_last_heartbeat' ); if ( method_exists( $instance, 'cron_heartbeat_runner' ) ) { delete_option( 'aije_last_heartbeat' ); // Cache löschen → sofortiger Heartbeat mit neuer Version $instance->cron_heartbeat_runner(); } break; } } } }, 10, 2); /* -------- Admin-Init: Einmalig Heartbeat senden wenn noch kein Heartbeat -------- */ add_action('admin_init', function() { // Ersten Aktivierungs-Ping senden (einmalig, unabhängig vom Opt-out) // Danach übernimmt der wöchentliche Heartbeat (nur bei Opt-in) if ( ! get_option('aije_first_ping_sent') ) { if ( ! wp_next_scheduled('aije_first_heartbeat') ) { wp_schedule_single_event( time() + 5, 'aije_first_heartbeat' ); } } }); add_action('aije_first_heartbeat', function() { // Ersten Ping IMMER senden – zählt die Installation // Klasse ist zur Laufzeit geladen, daher kein Problem update_option('aije_first_ping_sent', time(), false); update_option('aije_last_heartbeat', time(), false); if ( class_exists('AIJE_Lite_100') ) { $instance = new AIJE_Lite_100(); $instance->send_first_ping(); } }); define( 'AIJE_PLUGIN_VERSION', '1.3.7' ); if (!class_exists('AIJE_Lite_100')) : class AIJE_Lite_100 { const OPT_KEY = 'aije_pro_opts'; const OPT_LICENSE = 'aije_license_code'; const META_NO_EMBED = '_aije_no_embed'; const EXPORT_DIR = 'ai-json-exports'; const LOG_FILE = 'ai-json-export-log.csv'; const CRON_EXPORT = 'aije_pro_cron_export'; const CRON_PING = 'aije_pro_cron_ping'; const PING_QUEUE_OPT = 'aije_pro_ping_queue'; const PING_ERRORS_OPT = 'aije_pro_last_ping_errors'; const LAST_ARGS_OPT = 'aije_pro_last_export'; const BRAND_LOGO_URL = 'https://www.muench-media.com/img/MMLogo.jpg'; const STATS_SALT = '1111111'; // Salt – kein direkter Secret, sicher im Code const STATS_ENDPOINT = 'https://www.muench-media.com/aije-stats/'; const AI_PROXY_URL = 'https://www.muench-media.com/aije-proxy/'; // KI-Proxy const CRON_HEARTBEAT = 'aije_pro_cron_heartbeat'; const OPT_STATS_OPTOUT = 'aije_stats_optout'; // 1 = Nutzer hat Telemetrie deaktiviert const TRIAL_DAYS = 14; // ========================================================================= // NEU: Konstanten für Experten-Profil User-Meta-Keys // ========================================================================= const USER_META_JOB_TITLE = 'aije_job_title'; const USER_META_EXPERTISE = 'aije_expertise'; const USER_META_WEBSITE = 'aije_expert_website'; const USER_META_SAME_AS = 'aije_same_as_links'; public function __construct() { add_action('plugins_loaded', array($this, 'boot'), 20); } // ========================================================================= // NEU: Experten-Profil – Felder im WordPress-Backend registrieren // ========================================================================= /** * Felder zum Benutzerprofil hinzufügen (Anzeige). */ public function add_expert_profile_fields($user) { $job_title = get_user_meta($user->ID, self::USER_META_JOB_TITLE, true); $expertise = get_user_meta($user->ID, self::USER_META_EXPERTISE, true); $website = get_user_meta($user->ID, self::USER_META_WEBSITE, true); $same_as = get_user_meta($user->ID, self::USER_META_SAME_AS, true); ?>
|
|
|
|
|
|
|
|
|
|
|
]*>.*?<\/p>)/is', '___FIRST_P___$1', $content, 1 );
$work_content = $content_no_first_p;
foreach ( $keywords as $kw ) {
if ( stripos( $plain, $kw ) === false ) { continue; }
$found = false;
$new_content = preg_replace_callback(
'/( ]*>.*?<\/p>)|(' . preg_quote( $kw, '/' ) . ')/isu',
function( $m ) use ( $target_url, $target_title, &$found ) {
// Überschriften: unverändert lassen
if ( ! empty( $m[1] ) ) { return $m[1]; }
// Bestehende Links: unverändert lassen
if ( ! empty( $m[2] ) ) { return $m[2]; }
// Erster Absatz: Platzhalter entfernen, Absatz unverändert lassen
if ( ! empty( $m[3] ) ) { return str_replace( '___FIRST_P___', '', $m[3] ); }
// Keyword: nur einmal verlinken
if ( ! $found ) {
$found = true;
return '' . $m[4] . '';
}
return $m[4];
},
$work_content
);
// Platzhalter entfernen falls Keyword nicht gefunden
$new_content = str_replace( '___FIRST_P___', '', $new_content );
if ( $found && $new_content !== $content ) {
$linked = true;
$linked_kw = $kw;
$content = $new_content;
break;
}
}
// Platzhalter sauber entfernen
$content = str_replace( '___FIRST_P___', '', $content );
// Strategie 2: Kein Keyword gefunden → "Lese auch"-Absatz
$method = $linked ? 'keyword' : 'paragraph';
if ( ! $linked ) {
$content .= "\n\n" . ' '
. '📖 Wenn dich dieses Thema interessiert, lies auch unseren Artikel: '
. ''
. esc_html( $target_title ) . ' Kein API Key hinterlegt. API Fehler: ' . esc_html($result['error']) . ' ' . esc_html($faq['question']) . ' ';
$html .= esc_html($result['intro_suggestion']);
$html .= ' ]*>\s*(SEO\s*·|Lesedauer|Reading time)[^<]*<\/p>/iu', '', $content );
$content = wp_strip_all_tags( $content );
$content = preg_replace( '/\s+/', ' ', trim( $content ) );
// Ersten Satz überspringen wenn er zu kurz oder nur Metainfo ist
if ( mb_strlen( $content ) < 100 ) { return array(); }
$sentences = $this->meta_extract_sentences( $content );
preg_match_all( '/(\d+)\s*(%|Tipps?|Schritte?|Methoden?|Wege?)?/u', $content, $nums );
$number = ! empty( $nums[1] ) ? $nums[1][0] : '';
// Ersten sinnvollen Satz finden – mind. 60 Zeichen, kein Metainfo-Satz
$first_s = '';
$meta_patterns = array( '/^SEO\s*·/i', '/^Lesedauer/i', '/^Reading time/i', '/^\d+\s*Minuten/i' );
foreach ( $sentences as $s ) {
$s = trim( $s );
$is_meta = false;
foreach ( $meta_patterns as $pat ) { if ( preg_match($pat, $s) ) { $is_meta = true; break; } }
if ( ! $is_meta && mb_strlen($s) >= 60 && mb_strlen($s) <= 200 ) { $first_s = $s; break; }
}
// main_kw: kurzes Keyword aus Titel extrahieren
// Bei langen Titeln/Fragen: ersten sinnvollen Substantivkern nehmen
$raw_title = trim( preg_replace('/\s+/', ' ', wp_strip_all_tags( get_the_title($post) ) ) );
if ( empty($raw_title) ) { $raw_title = $title; }
$main_kw = $this->meta_extract_short_keyword( $raw_title );
$variants = array();
$v1 = $this->meta_variant_keyword( $main_kw, $first_s );
if ( $v1 ) { $variants[] = array( 'strategy' => 'Keyword-Fokus', 'text' => $v1, 'chars' => mb_strlen($v1), 'score' => $this->meta_quality_score($v1,$title) ); }
$v2 = $this->meta_variant_benefit( $main_kw, $number );
if ( $v2 ) { $variants[] = array( 'strategy' => 'Nutzen-Fokus', 'text' => $v2, 'chars' => mb_strlen($v2), 'score' => $this->meta_quality_score($v2,$title) ); }
$v3 = $this->meta_variant_question( $main_kw, $title );
if ( $v3 ) { $variants[] = array( 'strategy' => 'Frage-Format', 'text' => $v3, 'chars' => mb_strlen($v3), 'score' => $this->meta_quality_score($v3,$title) ); }
$v4 = $this->meta_variant_cta( $main_kw, $first_s );
if ( $v4 ) { $variants[] = array( 'strategy' => 'Handlungsaufforderung', 'text' => $v4, 'chars' => mb_strlen($v4), 'score' => $this->meta_quality_score($v4,$title) ); }
return $variants;
}
private function meta_variant_keyword( $kw, $first_s ) {
$k = $kw;
$templates = array(
$k . ' – verständlich erklärt mit konkreten Tipps und Beispielen.',
'Alles über ' . $k . ': Hintergründe, Tipps und häufige Fehler vermeiden.',
$k . ' im Überblick: Das Wichtigste kompakt zusammengefasst.',
);
return $this->meta_finalize( $templates[ array_rand($templates) ] );
}
private function meta_variant_benefit( $kw, $number ) {
$k = $kw;
if ( ! empty($number) && intval($number) > 1 && intval($number) < 20 ) {
$n = intval($number);
$templates = array(
"Mit $n praktischen Tipps zu $k – konkret und direkt anwendbar.",
"$n hilfreiche Wege für $k – Schritt für Schritt erklärt.",
);
} else {
$templates = array(
'So gelingt ' . $k . ' wirklich – mit Tipps aus der Praxis.',
$k . ': Was wirklich hilft und worauf es ankommt.',
'Alles was du über ' . $k . ' wissen musst – kompakt auf den Punkt.',
);
}
return $this->meta_finalize( $templates[ array_rand($templates) ] );
}
private function meta_variant_question( $kw, $title ) {
$k = $kw;
$t = mb_strtolower($title);
if ( mb_strpos($t,'wie ') !== false ) {
$templates = array(
'Wie gelingt ' . $k . '? Schritt für Schritt erklärt mit Tipps aus der Praxis.',
'Wie du ' . $k . ' richtig angehst – klar erklärt und sofort umsetzbar.',
);
} elseif ( mb_strpos($t,'warum') !== false ) {
$templates = array(
'Warum ' . $k . ' so wichtig ist – und was du konkret tun kannst.',
$k . ': Die Gründe, die Hintergründe und die besten Lösungen.',
);
} elseif ( mb_strpos($t,'was ') !== false ) {
$templates = array(
'Was ist ' . $k . '? Einfach erklärt mit konkreten Beispielen.',
$k . ' einfach erklärt: Was es ist, was es bringt und wie du startest.',
);
} else {
$templates = array(
$k . ' – alle wichtigen Fragen beantwortet, kompakt und verständlich.',
'Rund um ' . $k . ': Die häufigsten Fragen klar beantwortet.',
);
}
return $this->meta_finalize( $templates[ array_rand($templates) ] );
}
private function meta_variant_cta( $kw, $first_s ) {
$k = $kw;
$templates = array(
$k . ' – jetzt mehr erfahren und direkt umsetzen.',
$k . ' verstehen und anwenden: Der praktische Einstieg.',
'Starte jetzt mit ' . $k . ' – Schritt für Schritt erklärt.',
);
return $this->meta_finalize( $templates[ array_rand($templates) ] );
}
private function meta_extract_sentences( $text ) {
$raw = preg_split( '/(?<=[.!?])\s+/', $text, -1, PREG_SPLIT_NO_EMPTY );
return array_values( array_filter( $raw, function($s){ return mb_strlen(trim($s)) >= 20; } ) );
}
/**
* Extrahiert ein kurzes, sinnvolles Keyword aus einem Titel.
* Bei Fragen ("Was ist X?", "Warum Y?") wird der Kern X/Y extrahiert.
* Bei kurzen Titeln wird der Titel direkt verwendet.
*/
private function meta_extract_short_keyword( $title ) {
$kw = rtrim( trim($title), '?!.:,;' );
// Fragewörter am Anfang entfernen
$starts = array(
'/^(Was ist|Was sind|Was bedeutet|Was macht|Was kostet)\s+/iu',
'/^(Wie funktioniert|Wie geht|Wie kann|Wie wird)\s+/iu',
'/^(Warum ist|Warum sind|Warum rankt|Warum funktioniert)\s+/iu',
'/^(Wann ist|Wann sollte|Welche|Welcher|Welches)\s+/iu',
);
foreach ( $starts as $p ) { $kw = preg_replace( $p, '', $kw ); }
$kw = rtrim( trim($kw), '?!.:,;' );
// Bei Titel mit Doppelpunkt: nur Teil vor dem Doppelpunkt nehmen
// ABER nur wenn davor mindestens 2 Wörter stehen
if ( strpos($kw, ':') !== false ) {
$p = explode(':', $kw, 2);
$before = trim($p[0]);
if ( str_word_count($before) >= 2 ) { $kw = $before; }
}
// Bei em-dash: nur Teil davor nehmen wenn mind. 2 Wörter
foreach ( array(' – ', ' — ', ' - ') as $sep ) {
if ( strpos($kw, $sep) !== false ) {
$p = explode($sep, $kw, 2);
$before = trim($p[0]);
if ( str_word_count($before) >= 2 ) { $kw = $before; break; }
}
}
$kw = rtrim( trim($kw), '?!.:,;–- ' );
if ( mb_strlen($kw) < 3 ) { $kw = $title; }
return $kw;
}
private function meta_extract_keywords( $text ) {
$stops = array( 'der','die','das','den','dem','des','ein','eine','und','oder','aber','wenn','weil','dass','dann','ist','sind','war','hat','haben','wird','werden','kann','muss','sein','nicht','auch','noch','nur','sehr','mehr','alle','hier','dort','mit','von','zu','bei','in','an','auf','aus','nach','the','and','for','with','are','was','beim','zum','zur','im','am','dein','deine','deiner','deines','deinem','ihnen','ihrer','diese','diesen','diesem','dieser','dieses','durch','eine','einem','einen','einer','eines','mein','meine','unser','unsere','kein','keine','jeder','jede','jedes','haben','dabei','sowie','daher','darum','somit','damit' );
// Originalwörter behalten für korrekte Schreibweise
$words_orig = preg_split( '/\s+/', preg_replace('/[^\wäöüÄÖÜß\s]/u', ' ', $text), -1, PREG_SPLIT_NO_EMPTY );
// Frequenzanalyse auf Lowercase
$freq = array();
$orig_map = array(); // lowercase → erstes Originalwort
foreach ( $words_orig as $w ) {
if ( mb_strlen($w) < 4 ) { continue; }
$wl = mb_strtolower($w, 'UTF-8');
if ( in_array($wl, $stops) ) { continue; }
$freq[$wl] = ( $freq[$wl] ?? 0 ) + 1;
// Ersten Fund in Originalschreibweise merken
if ( ! isset($orig_map[$wl]) ) {
$orig_map[$wl] = $w;
}
}
arsort( $freq );
$top = array_keys( array_slice($freq, 0, 10, true) );
// Originalschreibweise zurückgeben
return array_map( function($kl) use ($orig_map) {
return $orig_map[$kl] ?? $kl;
}, $top );
}
private function meta_trim_to_word( $text, $max ) {
if ( mb_strlen($text) <= $max ) { return $text; }
$cut = mb_substr($text, 0, $max);
$pos = mb_strrpos($cut, ' ');
if ( $pos !== false ) { $cut = mb_substr($cut, 0, $pos); }
return rtrim( $cut, ',:;' );
}
private function meta_finalize( $text ) {
$text = trim( preg_replace('/\s+/', ' ', $text) );
if ( mb_strlen($text) > 160 ) {
$text = $this->meta_trim_to_word($text, 157);
if ( ! preg_match('/[.!?]$/', $text) ) { $text = rtrim($text,',:; ') . '.'; }
}
if ( mb_strlen($text) < 120 ) {
foreach ( array(' Jetzt informieren.',' Hier nachlesen.',' Alle Infos hier.') as $f ) {
if ( mb_strlen($text.$f) <= 160 ) { $text .= $f; break; }
}
}
return mb_strtoupper( mb_substr($text,0,1) ) . mb_substr($text,1);
}
private function meta_quality_score( $meta, $title ) {
$score = 0;
$len = mb_strlen($meta);
if ( $len >= 140 && $len <= 160 ) { $score += 40; }
elseif ( $len >= 120 && $len < 140 ) { $score += 25; }
elseif ( $len > 160 && $len <= 170 ) { $score += 20; }
elseif ( $len > 0 ) { $score += 10; }
$stops = array('der','die','das','und','oder','mit','von','zu','in','an','auf','bei');
$twords = array_filter( preg_split('/\s+/', mb_strtolower($title)), fn($w) => mb_strlen($w)>=4 && !in_array($w,$stops) );
$ml = mb_strtolower($meta);
$hits = 0;
foreach ($twords as $w) { if ( mb_strpos($ml,$w) !== false ) { $hits++; } }
$total = count($twords);
if ($total > 0) { $score += (int)round(($hits/$total)*40); }
if ( preg_match('/[.!?]$/', $meta) ) { $score += 20; }
return min(100, $score);
}
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);
if ( empty($ex) ) { $ex = get_post_meta($post->ID, 'rank_math_description', true); }
if ( empty($ex) ) { $ex = get_post_meta($post->ID, '_aioseo_description', true); }
$el = mb_strlen($ex);
$ec = ($el>=140&&$el<=160)?'#22c55e':($el>0?'#f59e0b':'#ef4444');
echo ' -Tags entfernen kann.
preg_match_all( '/ ]*>(.*?)<\/p>/si', $content, $p_matches );
$html_paras = array_filter(
array_map( 'strip_tags', $p_matches[1] ),
function( $p ) { return strlen( trim( $p ) ) > 20; }
);
$para_count = count( $html_paras );
// Fallback: falls kein -Markup vorhanden (z.B. reiner Text-Content),
// auf Zeilenumbrüche zurückfallen
if ( $para_count === 0 ) {
$fallback = array_filter(
array_map( 'trim', preg_split( '/\n{2,}/', $text ) ),
function( $p ) { return strlen( $p ) > 20; }
);
$para_count = count( $fallback );
}
preg_match_all( '/]+href=["\']?([^"\'> ]+)["\']?/i', $content, $link_matches );
$internal_links = 0;
$external_links = 0;
// Schema-unabhängig vergleichen: http und https beide als intern erkennen
$home_norm = preg_replace( '/^https?:/', '', home_url() ); // z.B. '//example.com'
foreach ( $link_matches[1] as $href ) {
$href_norm = preg_replace( '/^https?:/', '', $href );
if ( strpos( $href_norm, $home_norm ) !== false || strpos( $href, '/' ) === 0 ) {
$internal_links++;
} elseif ( strpos( $href, 'http' ) === 0 ) {
$external_links++;
}
}
$has_list = ( strpos( $content, ' '
. ''
. '⭐ WP Rankify bewerten'
. ' ';
echo '' . esc_html__('Lizenz aktiv.', 'ai-json-extractor-auto-embed-lite') . '';
echo ' Wähle das Preset, das zu deiner Website passt, und klicke danach auf Speichern. Die Berufsbezeichnung, Fachbiografie und SameAs-Links werden direkt im WordPress-Benutzerprofil hinterlegt (Bereich: Experten-Profil). Das Plugin liest diese Daten automatisch aus und gibt sie als ';
echo esc_html__('Export abgeschlossen.', 'ai-json-extractor-auto-embed-lite') . ' ';
echo esc_html__('Beiträge:', 'ai-json-extractor-auto-embed-lite') . ' ' . (int) $_GET['count'] . '. ';
echo esc_html__('Größe:', 'ai-json-extractor-auto-embed-lite') . ' ' . esc_html(isset($_GET['size_kb']) ? wp_kses_post(wp_unslash($_GET['size_kb'])) : '–') . ' KB. ';
if ($url) {
echo esc_html__('Datei:', 'ai-json-extractor-auto-embed-lite') . ' ' . esc_html($file) . '';
}
echo ' ' . esc_html($fl) . ' – ' . (int)$filter_counts[$fk] . ' Artikel betroffen Keine ausgeschlossenen Artikel. ' . esc_html__('Nächster Lauf:', 'ai-json-extractor-auto-embed-lite') . ' ' . ($t ? esc_html(date_i18n('Y-m-d H:i:s', $t)) : '–') . ' ' . esc_html__('Noch keine Logs vorhanden.', 'ai-json-extractor-auto-embed-lite') . ' ' . esc_html__('Benachrichtigt Webhooks/LLM-Endpoints bei Veröffentlichung/Aktualisierung.', 'ai-json-extractor-auto-embed-lite') . ' ✓ Google Search Console erfolgreich verbunden! Fehler beim Verbinden. Bitte prüfe Client-ID und Secret. ✓ Zugangsdaten gespeichert. Sieh direkt im Plugin welche Keywords deine Artikel ranken, wie oft sie geklickt werden und wo sie in den Suchergebnissen stehen. ' . esc_html($step['desc']) . ' Nach dem Erstellen der Zugangsdaten zeigt Google dir Client-ID und Client-Secret an. Trage sie hier ein. Jetzt mit deinem Google-Account verbinden der Zugriff auf die Search Console hat. ⚠ Google Token abgelaufen. Bitte Verbindung trennen und neu verbinden. Fehler beim Abrufen der Properties. ⚠ Google Token abgelaufen. Bitte Verbindung trennen und neu verbinden. Keine Search Console Properties gefunden. Stelle sicher dass deine Website in der Google Search Console verifiziert ist. Keine Daten gefunden. ✓ llms.txt Einstellungen gespeichert. Mehr KI-Analysen pro Monat. Jederzeit kündbar. Trage deine PayPal Subscription-ID ein. Du findest sie in der Bestätigungsmail von PayPal (Format: I-XXXXXXXXXXXX). Deaktiviert sowohl JSON-Embed als auch Person-Schema für diesen Beitrag. ';
echo 'REST API öffnen';
if (!$is_published) {
echo ' Kein API Key hinterlegt.AI Content Score: ' . $score . '/100
';
}
if (!empty($result['issues'])) {
$html .= 'Verbesserungen
';
foreach ($result['issues'] as $issue) {
$html .= '
';
}
if (!empty($result['suggested_headings'])) {
$html .= '
→ ' . esc_html($issue['fix']) . '';
}
$html .= 'Neue H2 Vorschläge
';
foreach ($result['suggested_headings'] as $h2) {
$html .= '
';
}
if (!empty($result['faq'])) {
$html .= 'FAQ Ideen
';
foreach ($result['faq'] as $faq) {
$html .= '
';
$html .= esc_html($faq['answer']) . 'Interne Linkideen
';
foreach ($result['internal_link_ideas'] as $idea) {
$html .= '
';
}
if (!empty($result['intro_suggestion'])) {
$html .= 'Verbesserte Einleitung
';
$html .= '
';
echo '
' . esc_html($data['overall_assessment']);
echo '';
}
if ( ! empty($data['missing_topics']) ) {
echo '
';
echo '' . esc_html($t['reason']??'') . '';
echo ']*>(.*?)<\/h2>/si', $content, $h2_matches );
$existing_h2 = array_map( 'strip_tags', $h2_matches[1] ?? array() );
$h2_list = ! empty( $existing_h2 )
? implode( ', ', array_slice( $existing_h2, 0, 3 ) )
: 'keine vorhanden';
$advice['why'] = 'H2-Überschriften sind das Inhaltsverzeichnis deines Artikels – für Google UND für den Leser. Sie helfen Suchmaschinen zu verstehen, welche Teilthemen du behandelst. Ohne Struktur wird dein Artikel wie ein langer Fließtext behandelt, der schwerer einzuordnen ist.';
$advice['situation'] = 'In „' . $title_short . '" ' . ( empty( $existing_h2 ) ? 'gibt es noch keine H2-Überschriften.' : 'gibt es erst ' . count( $existing_h2 ) . ' H2-Überschrift(en): ' . $h2_list . '.' ) . ' Für einen gut strukturierten Artikel empfehlen sich 3–5 H2-Abschnitte.';
$advice['example'] = 'Für einen Artikel wie „' . $title_short . '" könnten sinnvolle H2-Abschnitte sein:' . "
" . '→ Was ist [Thema] – und warum ist es wichtig?' . "
" . '→ So funktioniert [Thema] in der Praxis' . "
" . '→ Die häufigsten Fehler und wie du sie vermeidest' . "
" . '→ Schritt-für-Schritt-Anleitung' . "
" . '→ Häufige Fragen (FAQ)';
$advice['checklist'] = array(
'Teile deinen Artikel in 3–5 logische Hauptabschnitte auf',
'Jede H2-Überschrift sollte eine eigene Frage oder ein eigenes Teilthema behandeln',
'Formuliere H2-Überschriften als Nutzerversprechen: Was bekommt der Leser in diesem Abschnitt?',
'Nutze in langen Abschnitten auch H3-Unterüberschriften für weitere Struktur',
);
$advice['tip'] = 'SEO-Tipp: Baue das Hauptkeyword natürlich in mindestens eine H2-Überschrift ein – nicht erzwungen, sondern wo es inhaltlich passt.';
}
// Satzlänge / Lesbarkeit
elseif ( strpos( $issue['msg'], 'Satz' ) !== false ) {
$sentences = preg_split( '/[.!?]+/', $text, -1, PREG_SPLIT_NO_EMPTY );
$sentences = array_filter( $sentences, function($s) { return str_word_count(trim($s)) > 3; } );
$avg = count($sentences) > 0 ? round( $word_count / count($sentences) ) : 0;
$advice['why'] = 'Lange, verschachtelte Sätze ermüden den Leser – besonders auf Mobilgeräten wo über 60% der Besucher lesen. Google misst indirekt die Absprungrate: Wenn Leser schnell abspringen weil der Text zu schwer verständlich ist, signalisiert das dem Algorithmus „nicht hilfreich".';
$advice['situation'] = 'In „' . $title_short . '" sind deine Sätze im Schnitt ' . $avg . ' Wörter lang. Der Zielwert für gut lesbare Texte liegt bei 12–18 Wörtern pro Satz.';
$advice['example'] = 'Vorher (zu lang): „Da die Verarbeitung von komplexen Informationen durch das menschliche Gehirn bei langen und verschachtelten Satzstrukturen deutlich länger dauert, empfiehlt es sich, Texte in kürzere Einheiten aufzuteilen."' . "
" . 'Nachher (besser): „Lange Sätze kosten Energie. Das menschliche Gehirn verarbeitet kurze Einheiten viel schneller. Deshalb: Satz aufteilen, wenn er zu lang wird."';
$advice['checklist'] = array(
'Lies deinen Artikel laut vor – stolperst du über Sätze? Teile sie auf.',
'Suche nach „und", „sowie", „wobei", „welcher" – das sind oft Stellen für Satztrennungen',
'Wechsle bewusst zwischen kurzen und mittellangen Sätzen für Rhythmus',
'Ziel: kein Satz länger als 25 Wörter – lieber zwei kurze als einer langer',
);
$advice['tip'] = 'Schreib wie du sprichst. Wenn du den Satz einem Freund erklären würdest, wärst du wahrscheinlich kürzer und direkter.';
}
// Listen
elseif ( strpos( $issue['msg'], 'Listen' ) !== false || strpos( $issue['msg'], 'Aufzählung' ) !== false ) {
$advice['why'] = 'Aufzählungen erhöhen die Scannbarkeit eines Textes drastisch. Menschen lesen Artikel nicht Wort für Wort – sie scannen. Listen bleiben sofort hängen, werden öfter gelesen und erhöhen die Verweildauer. Außerdem zeigt Google Listen häufig als Featured Snippets an.';
$advice['situation'] = 'In „' . $title_short . '" gibt es aktuell keine Aufzählungslisten. Dabei bietet fast jeder Artikel Stellen, an denen eine Liste die Information verdichtet und verständlicher macht.';
$advice['example'] = 'Statt: „Du brauchst dafür Geduld, die richtige Ausrüstung, etwas Erfahrung und außerdem solltest du gut vorbereitet sein."' . "
" . 'Besser als Liste:' . "
" . '• Geduld und Ausdauer' . "
" . '• Die passende Ausrüstung' . "
" . '• Grundlegende Erfahrung' . "
" . '• Gute Vorbereitung';
$advice['checklist'] = array(
'Suche nach Stellen mit 3+ Elementen in einem Satz – das sind ideale Listen',
'Verwende ul für ungeordnete Listen (Tipps, Vorteile, Merkmale)',
'Verwende ol für Schritt-für-Schritt-Anleitungen mit Reihenfolge',
'Achte darauf: jeder Listenpunkt beginnt mit einem aktiven Verb oder einem Substantiv',
);
$advice['tip'] = 'Google liebt Listen mit 4–8 Punkten für Featured Snippets. Formuliere die Listenüberschrift als Frage: „Was brauchst du für...?" oder „Wie funktioniert...?"';
}
// Absätze
elseif ( strpos( $issue['msg'], 'Absatz' ) !== false ) {
$para_count = count( array_filter( preg_split( '/
{2,}/', $text ) ) );
$advice['why'] = 'Große Textblöcke ohne Absätze wirken wie eine Wand aus Text. Leser brechen hier ab – besonders auf dem Smartphone. Kurze, klar getrennte Absätze erhöhen die Lesbarkeit und signalisieren Struktur.';
$advice['situation'] = 'Dein Artikel „' . $title_short . '" hat aktuell ca. ' . $para_count . ' Absätze. Für einen gut lesbaren Text empfehlen sich mindestens 6–8 Absätze, die jeweils 2–4 Sätze enthalten.';
$advice['example'] = 'Grundregel: Ein Gedanke = ein Absatz.' . "
" . 'Faustregel für Absatzlänge: Wenn ein Absatz mehr als 4 Sätze hat, prüfe ob du ihn teilen kannst. Auf dem Smartphone sollte kein Absatz länger als einen Bildschirm sein.';
$advice['checklist'] = array(
'Teile lange Textblöcke nach jedem abgeschlossenen Gedanken auf',
'Jeder Absatz sollte eine einzige Kernaussage haben',
'Nutze kurze „Brücken-Sätze" zwischen Absätzen für Lesefluss',
'Prüfe die mobile Ansicht: Sieht der Text auf dem Handy noch lesbar aus?',
);
$advice['tip'] = 'Schreib deinen ersten Satz jedes Absatzes so, dass jemand der nur die Erstzeilen liest trotzdem den Kerninhalt versteht.';
}
// E-E-A-T: Berufsbezeichnung
elseif ( strpos( $issue['msg'], 'Berufsbezeichnung' ) !== false ) {
$advice['why'] = 'Google bewertet seit dem E-E-A-T Update besonders stark, wer einen Artikel geschrieben hat. Bei Gesundheits-, Rechts- oder Finanzthemen (YMYL) ist die Autorenautorität ein direkter Rankingfaktor. Eine fehlende Berufsbezeichnung signalisiert: „Wir wissen nicht wer das ist."';
$advice['situation'] = 'Der Autor dieses Artikels hat noch keine Berufsbezeichnung im Profil hinterlegt. Google sieht dann nur einen Namen – ohne Kontext über die Expertise hinter dem Inhalt von „' . $title_short . '".';
$advice['example'] = 'Statt keiner Angabe:' . "
" . '→ „Staatlich geprüfte Hebamme" (konkret + Qualifikation)' . "
" . '→ „Ernährungsberaterin (DGE)" (konkret + Zertifizierung)' . "
" . '→ „Fachanwalt für Arbeitsrecht" (konkret + Fachgebiet)' . "
" . 'Vermeide vage Bezeichnungen wie „Experte", „Berater" oder „Spezialist" ohne Kontext.';
$advice['checklist'] = array(
'Gehe zu Benutzer → Profil → Experten-Profil (Bereich weiter unten)',
'Trage deine genaue Berufsbezeichnung ein – so wie sie auf deinem Zeugnis/Diplom steht',
'Optional: Ergänze Zertifizierungen oder Fachgebiete in Klammern',
'Speichern – das Plugin trägt die Bezeichnung automatisch in das Schema.org Person-Markup ein',
);
$advice['tip'] = 'Google prüft die Berufsbezeichnung im Kontext anderer Signale. Je konsistenter sie mit deiner Website, deinen Social-Media-Profilen und deiner Biografie übereinstimmt, desto stärker der Vertrauenseffekt.';
}
// E-E-A-T: Fachbiografie
elseif ( strpos( $issue['msg'], 'Fachbiografie' ) !== false || strpos( $issue['msg'], 'biografie' ) !== false ) {
$expertise = get_user_meta( $post->post_author, self::USER_META_EXPERTISE, true );
$bio_len = mb_strlen( $expertise );
$advice['why'] = 'Die Autorenbiografie ist das stärkste E-E-A-T-Signal nach der Berufsbezeichnung. Sie erklärt nicht nur wer jemand ist, sondern warum er oder sie die Autorität hat, über dieses Thema zu schreiben. Google nutzt Biografie-Signale um Inhalte als vertrauenswürdig einzustufen.';
$advice['situation'] = $bio_len > 0
? 'Die aktuelle Biografie ist mit ' . $bio_len . ' Zeichen noch zu kurz. Google und Leser erwarten mindestens 80–150 Zeichen – eine kurze, substanzielle Aussage über die Expertise.'
: 'Für den Autor dieses Artikels ist noch keine Fachbiografie hinterlegt. Das fehlt komplett im Schema.org-Markup.';
$advice['example'] = 'Zu kurz: „Ich bin Hebamme und helfe Familien."' . "
" . 'Besser: „Staatlich geprüfte Hebamme mit 12 Jahren Erfahrung in München. Schwerpunkt: natürliche Geburtsvorbereitung und Wochenbettbegleitung. Autorin von zwei Fachbüchern zum Thema Stillberatung."';
$advice['checklist'] = array(
'Benutzer → Profil → Experten-Profil → Fachbiografie ausfüllen',
'Nenne: Qualifikation + Jahre Erfahrung + konkretes Fachgebiet',
'Optional: Publikationen, Auszeichnungen, Zusatzqualifikationen',
'Ziel: 100–200 Zeichen – prägnant und substanziell',
);
$advice['tip'] = 'Schreib die Biografie in der dritten Person – das wirkt professioneller und eignet sich besser für automatische Schema-Ausgabe.';
}
// E-E-A-T: SameAs
elseif ( strpos( $issue['msg'], 'SameAs' ) !== false ) {
$advice['why'] = 'SameAs-Links verbinden dein WordPress-Profil mit externen Autoritätsquellen. Google nutzt diese Verbindungen um zu verifizieren, dass der Autor eine reale Fachperson ist – nicht eine anonyme Stimme im Internet. LinkedIn, Instagram und Wikipedia-Einträge sind dabei besonders wertvoll.';
$advice['situation'] = 'Für den Autor von „' . $title_short . '" sind noch keine externen Profillinks hinterlegt. Ohne SameAs-Links fehlt Google die Möglichkeit, die Autorität des Autors extern zu verifizieren.';
$advice['example'] = 'Empfohlene SameAs-Links in Reihenfolge der SEO-Wirkung:' . "
" . '1. LinkedIn-Profil (stärkstes Autorität-Signal)' . "
" . '2. Instagram mit fachlichem Content' . "
" . '3. XING-Profil' . "
" . '4. Google Scholar / ResearchGate (für Wissenschaftler)' . "
" . '5. Eigene Praxis-/Unternehmenswebsite';
$advice['checklist'] = array(
'Benutzer → Profil → Experten-Profil → SameAs-Links',
'Eine URL pro Zeile eintragen',
'Priorität: LinkedIn + Instagram + eigene Website = 3 Links = maximaler Score-Bonus',
'Stelle sicher dass der Name auf den Profilen identisch ist',
);
$advice['tip'] = 'Konsistenz ist entscheidend: Der Name, die Berufsbezeichnung und das Profilbild sollten auf allen verlinkten Profilen übereinstimmen. Google vergleicht diese Signale aktiv.';
}
// E-E-A-T: Externe Quellen
elseif ( strpos( $issue['msg'], 'extern' ) !== false || strpos( $issue['msg'], 'Quell' ) !== false ) {
$advice['why'] = 'Externe Links zu seriösen Quellen signalisieren Google: „Dieser Autor hat recherchiert und belegt seine Aussagen." Das erhöht die Glaubwürdigkeit des Inhalts – besonders bei medizinischen, rechtlichen oder wissenschaftlichen Themen. Fehlende Quellenangaben wirken bei Fachthemen wie unbelegte Behauptungen.';
$advice['situation'] = 'In „' . $title_short . '" gibt es noch keine externen Quellenlinks. Das ist besonders bei einem Fachthema wie diesem eine verpasste Chance für Autorität und Vertrauen.';
$advice['example'] = 'Gute externe Quellen je nach Thema:' . "
" . '• Medizin: PubMed, WHO, Robert Koch Institut, Bundeszentrale für gesundheitliche Aufklärung' . "
" . '• Recht: Gesetze im Internet (gesetze-im-internet.de), Bundesjustizamt' . "
" . '• Statistiken: Destatis, Eurostat, Statista' . "
" . '• Allgemein: Wikipedia als Kontext-Link ist akzeptabel, aber nicht als Hauptquelle';
$advice['checklist'] = array(
'Identifiziere 1–3 konkrete Behauptungen in deinem Artikel, die eine Quelle verdienen',
'Verlinke direkt auf die Quelle mit beschreibendem Ankertext (nicht „hier klicken")',
'Nutze das title-Attribut für Quellenangaben: ',
'Externe Links können mit target="_blank" rel="noopener noreferrer" in neuem Tab öffnen',
);
$advice['tip'] = 'Externe Links schaden deiner SEO nicht – sie stärken sie. Google interpretiert ausgehende Links zu Autoritätsquellen als positives Qualitätssignal, nicht als „Abfluss von Link-Juice".';
}
// Technical: Featured Image
elseif ( strpos( $issue['msg'], 'Beitragsbild' ) !== false ) {
$advice['why'] = 'Das Featured Image ist das erste was Nutzer sehen – in Google-Suchergebnissen, in Google Discover, beim Teilen in Social Media. Artikel ohne Featured Image werden in Google Discover praktisch nie ausgespielt und haben deutlich niedrigere Klickraten in den Suchergebnissen.';
$advice['situation'] = 'Der Artikel „' . $title_short . '" hat noch kein Beitragsbild. Das bedeutet: kein Thumbnail in Google Discover, kein Preview-Bild beim Teilen auf LinkedIn oder WhatsApp, schwächere Klickrate in den Suchergebnissen.';
$advice['example'] = 'Was ein gutes Featured Image ausmacht:' . "
" . '• Mindestgröße: 1200 × 630 Pixel (optimal für Social Sharing)' . "
" . '• Aussagekräftig: Das Bild erklärt auf einen Blick worum es geht' . "
" . '• Kein Stock-Foto-Klischee: Authentische Bilder performen besser' . "
" . '• Alt-Text nicht vergessen: Beschreibt das Bild für Screenreader und Google';
$advice['checklist'] = array(
'Beitrag öffnen → rechts „Beitragsbild" → Bild hochladen oder auswählen',
'Bildgröße: mindestens 1200 × 630 Pixel',
'Dateiname vor dem Upload sprechend benennen: z.B. „hebamme-geburtvorbereitung.jpg" statt „IMG_4521.jpg"',
'Alt-Text beim Hochladen ausfüllen – beschreibe was auf dem Bild zu sehen ist',
);
$advice['tip'] = 'Wenn du kein eigenes Bild hast: Unsplash.com und Pexels.com bieten kostenlose, hochwertige Fotos. Suche nach deinem Hauptthema und wähle ein authentisch wirkendes Bild.';
}
// Technical: Alt-Texte
elseif ( strpos( $issue['msg'], 'Alt-Text' ) !== false ) {
preg_match_all( '/
]+>/i', $content, $imgs );
$img_count = count( $imgs[0] );
$advice['why'] = 'Alt-Texte erfüllen zwei Funktionen: Sie machen Bilder für Suchmaschinen lesbar (Google-Bildersuche!) und für sehbehinderte Nutzer zugänglich (Screenreader). Fehlende Alt-Texte sind verschenkte SEO-Punkte und ein Barrierefreiheits-Problem.';
$advice['situation'] = 'In „' . $title_short . '" ' . ( $img_count > 0 ? 'fehlen bei ' . $img_count . ' Bild(ern) die Alt-Texte.' : 'sind keine Bilder vorhanden.' );
$advice['example'] = 'Schlechter Alt-Text: „Bild1.jpg" oder „Foto"' . "
" . 'Guter Alt-Text: „Hebamme erklärt werdender Mutter die Atemlibung zur Geburtsvorbereitung"' . "
" . 'Regel: Beschreibe was auf dem Bild passiert – nicht was du dir dabei gedacht hast.';
$advice['checklist'] = array(
'Klicke auf jedes Bild im Editor → „Bild bearbeiten" → Alt-Text eintragen',
'Beschreibe konkret was auf dem Bild zu sehen ist (Person, Aktion, Kontext)',
'Baue das Hauptkeyword ein wenn es natürlich passt – nicht erzwungen',
'Maximal 125 Zeichen – Screenreader lesen nicht mehr vor',
);
$advice['tip'] = 'Stelle dir vor, du beschreibst das Bild am Telefon jemandem der es nicht sehen kann. Genau das ist ein guter Alt-Text.';
}
// Technical: Meta-Description
elseif ( strpos( $issue['msg'], 'Meta-Description' ) !== false || strpos( $issue['msg'], 'Meta Description' ) !== false ) {
$meta_desc = get_post_meta( $post->ID, '_yoast_wpseo_metadesc', true );
if ( empty( $meta_desc ) ) $meta_desc = get_post_meta( $post->ID, 'rank_math_description', true );
if ( empty( $meta_desc ) ) $meta_desc = get_post_meta( $post->ID, '_aije_metadesc', true );
$meta_len = mb_strlen( $meta_desc );
$advice['why'] = 'Die Meta-Description erscheint unter deinem Seitentitel in den Google-Suchergebnissen. Sie entscheidet ob jemand klickt oder nicht. Eine fehlende oder zu kurze Meta-Description wird oft durch Google automatisch ersetzt – und das Ergebnis ist meist schlechter als ein manuell geschriebener Text.';
$advice['situation'] = empty( $meta_desc )
? 'Für „' . $title_short . '" ist noch keine Meta-Description hinterlegt. Google generiert dann automatisch einen Auszug – meistens den ersten Satz des Artikels, was selten optimal ist.'
: 'Die Meta-Description von „' . $title_short . '" ist mit ' . $meta_len . ' Zeichen ' . ( $meta_len < 120 ? 'zu kurz (unter 120 Zeichen).' : 'zu lang (über 160 Zeichen, wird abgeschnitten).' );
$advice['example'] = 'Für einen Artikel über „' . $title_short . '" könnte eine gute Meta-Description so aussehen:' . "
" . '„[Konkrete Zahl oder Aussage] + [Hauptnutzen für den Leser] + [Handlungsaufforderung]"' . "
" . 'Beispiel: „In diesem Artikel erfährst du 5 erprobte Methoden zur Geburtsvorbereitung – von einer staatlich geprüften Hebamme. Jetzt lesen →"';
$advice['checklist'] = array(
'Öffne Yoast SEO oder RankMath weiter unten im Beitrag',
'Trage eine Meta-Description zwischen 120 und 160 Zeichen ein',
'Baue das Hauptkeyword früh ein (idealerweise in den ersten 70 Zeichen)',
'Formuliere einen konkreten Nutzen oder eine Handlungsaufforderung am Ende',
);
$advice['tip'] = 'Schreib die Meta-Description wie eine Mini-Werbeanzeige: Was bekommt der Leser wenn er klickt? Warum sollte er jetzt klicken und nicht auf das andere Ergebnis?';
}
// Technical: Titel
elseif ( strpos( $issue['msg'], 'Titel' ) !== false ) {
$tlen = mb_strlen( get_the_title( $post ) );
$advice['why'] = 'Der Titel-Tag ist der wichtigste SEO-Faktor einer Seite. Er erscheint als blauer Link in den Suchergebnissen. Ist er zu kurz, verschenkst du Keyword-Potenzial. Ist er zu lang, schneidet Google ihn ab – und der Klickanreiz geht verloren.';
$advice['situation'] = 'Der Titel „' . $title_short . '" hat ' . $tlen . ' Zeichen. ' . ( $tlen < 35 ? 'Das ist zu kurz – du vergibst wertvollen Platz für Keywords und Kaufargumente.' : 'Das überschreitet die empfohlene Länge von 65 Zeichen – Google schneidet ihn wahrscheinlich ab.' );
$advice['example'] = 'Schwach: „Geburtsvorbereitung" (zu kurz, kein Nutzen)' . "
" . 'Besser: „Geburtsvorbereitung: 7 Methoden die wirklich helfen | Hebamme erklärt"' . "
" . 'Gut funktionieren: Zahlen (7 Tipps), Fragen (Wie...?), Versprechen (So funktioniert...), Zielgruppe (Für Erstgebärende)';
$advice['checklist'] = array(
'Ziel: 50–65 Zeichen inkl. Leerzeichen',
'Baue das Hauptkeyword in die ersten 50 Zeichen ein',
'Füge einen Nutzenversprechen oder eine Zahl hinzu wenn sinnvoll',
'Prüfe mit einem Title-Tag-Vorschau-Tool (z.B. seobility.net/tools/serp-snippet-generator)',
);
$advice['tip'] = 'Titel mit Zahlen ("5 Methoden", "7 Tipps") haben nachweislich höhere Klickraten. Fragestellungen ("Wie...", "Was ist...") sprechen Informations-Suchende direkt an.';
}
// Linking: Interne Links
elseif ( strpos( $issue['msg'], 'intern' ) !== false && $category === 'linking' ) {
// Thematisch passende Beitraege per Keyword-Extraktion finden
$related = $this->find_related_posts( $post, 6 );
$advice['why'] = 'Interne Links sind das Nervensystem deiner Website. Sie verteilen Autorität zwischen deinen Seiten, helfen Google deine Website-Struktur zu verstehen und halten Leser länger auf deiner Website. Artikel ohne interne Links sind wie Inseln – sie geben ihre SEO-Kraft nicht weiter.';
$advice['situation'] = 'Der Artikel „' . $title_short . '“ verlinkt noch auf keine anderen Seiten deiner Website. Das bedeutet: Leser, die mehr wissen wollen, verlassen deine Website. Und Google sieht keinen Kontext zu anderen Inhalten.';
if ( ! empty( $related ) ) {
$advice['related_posts'] = $related;
$advice['example'] = 'Basierend auf deinem Inhalt haben wir thematisch passende Artikel gefunden (siehe Vorschlaege unten). Gute Ankertexte:' . "\n\n"
. '✓ „Mehr dazu: [Titel des Artikels]“' . "\n"
. '✓ „Wie wir in [Titel] beschreiben...“' . "\n"
. '✓ „Weitere Infos: [Titel des Artikels]“' . "\n\n"
. '✗ Vermeide: „Hier klicken“ oder „Mehr erfahren“ ohne Kontext';
} else {
$advice['example'] = 'Gute Ankertexte fuer interne Links:' . "\n\n"
. '✓ „Mehr dazu in unserem Artikel über [verwandtes Thema]“' . "\n"
. '✓ „Wie wir in [verlinktem Artikel] beschreiben...“' . "\n\n"
. '✗ Vermeide: „Hier klicken“, „Mehr erfahren“ als alleinigen Ankertext';
}
$advice['checklist'] = array(
'Wähle 2–3 der unten vorgeschlagenen Artikel aus',
'Füge die Links an thematisch passenden Stellen im Text ein',
'Ideal: Ein Link früh im Artikel, einer am Ende als weiterführende Empfehlung',
'Prüfe auch: Verlinken andere Artikel schon auf diesen hier? (Orphan-Content vermeiden)',
);
$advice['tip'] = 'Die stärksten internen Links kommen von deinen Seiten mit dem meisten Traffic. Ergänze dort Links zu neuen oder schwächeren Seiten um deren Autorität zu stärken.';
}
// Aktualität
elseif ( strpos( $issue['msg'], 'aktualisiert' ) !== false ) {
$months_ago = round( ( time() - strtotime( $post->post_modified ) ) / MONTH_IN_SECONDS );
$advice['why'] = 'Google bevorzugt aktuellen Content – besonders in Bereichen wo sich Informationen schnell ändern. Das „Freshness"-Signal fließt direkt in das Ranking ein. Ein Artikel der seit über einem Jahr nicht angefasst wurde, verliert schrittweise an Sichtbarkeit.';
$advice['situation'] = 'Der Artikel „' . $title_short . '" wurde zuletzt vor ca. ' . $months_ago . ' Monaten aktualisiert. In vielen Themenbereichen haben sich in dieser Zeit Fakten, Zahlen oder Empfehlungen geändert.';
$advice['example'] = 'Was bei einer Aktualisierung wirklich zählt:' . "
" . '• Veraltete Zahlen, Statistiken oder Gesetze aktualisieren' . "
" . '• Neue wissenschaftliche Erkenntnisse einarbeiten' . "
" . '• Veraltete Links auf neue Quellen umleiten' . "
" . '• Ein kurzes Update-Datum am Anfang: „Zuletzt aktualisiert: März 2025"';
$advice['checklist'] = array(
'Lies den Artikel durch: Gibt es veraltete Informationen, Zahlen oder Empfehlungen?',
'Aktualisiere mindestens 1-2 Absätze mit neuen Erkenntnissen',
'Ergänze ein „Zuletzt aktualisiert"-Datum am Anfang des Artikels',
'Speichere – WordPress aktualisiert den „post_modified" Timestamp automatisch',
);
$advice['tip'] = 'Du musst nicht alles neu schreiben. Manchmal reicht es, einen Abschnitt zu ergänzen, eine neue Studie zu verlinken oder die Einleitung zu modernisieren. Google erkennt Änderungen ab ca. 10% des Inhalts.';
}
// Fallback für unbekannte Issue-Typen
else {
$advice['why'] = 'Dieser Faktor hat direkten Einfluss auf die Sichtbarkeit und Bewertung deines Inhalts durch Suchmaschinen und KI-Systeme.';
$advice['situation'] = $issue['msg'];
$advice['example'] = $issue['action'] ?? '';
$advice['checklist'] = array( $issue['action'] ?? 'Prüfe die Details und nimm die empfohlene Änderung vor.' );
$advice['tip'] = 'Kleine Verbesserungen summieren sich. Jede behobene Issue erhöht deinen AI Readiness Score.';
}
return $advice;
}
private function calculate_ai_analysis( $post ) {
$content = $post->post_content;
$text = wp_strip_all_tags( $content );
$word_count = str_word_count( $text );
$issues = array();
// ── Hilfsdaten ────────────────────────────────────────────────────────
preg_match_all( '/
]*>/i', $content, $h2 );
preg_match_all( '/
]*>/i', $content, $h3 );
$h2_count = count( $h2[0] );
$h3_count = count( $h3[0] );
// Absätze direkt aus dem HTML zählen – zuverlässiger als Newline-Split auf reinem Text,
// weil wp_strip_all_tags() Zeilenumbrüche zwischen
2; } );
$sentence_count = count( $sentences );
$avg_words_per_sentence = $sentence_count > 0 ? round( $word_count / $sentence_count ) : 0;
preg_match_all( '/
';
// ── Score-Gewichtung / Preset ──────────────────────────────────────────
$preset = isset( $o['score_preset'] ) ? $o['score_preset'] : 'standard';
$presets_data = array(
'ymyl' => array(
'label' => 'YMYL / Experten',
'icon' => '🏥',
'desc' => 'Medizin, Recht, Finanzen, Beratung. E-E-A-T steht im Vordergrund.',
'tags' => 'Hebamme · Arzt · Anwalt · Finanzberater',
'content' => 30, 'eeat' => 40, 'technical' => 20, 'linking' => 10,
),
'standard' => array(
'label' => 'Standard Blog',
'icon' => '📝',
'desc' => 'Typischer Content-Blog. Ausgewogene Gewichtung.',
'tags' => 'Blog · Magazin · News · Tutorial',
'content' => 35, 'eeat' => 30, 'technical' => 20, 'linking' => 15,
),
'ecommerce' => array(
'label' => 'E-Commerce / Lokal',
'icon' => '🛒',
'desc' => 'Shop und lokale Unternehmen. Technik besonders wichtig.',
'tags' => 'Shop · Restaurant · Dienstleister · Agentur',
'content' => 30, 'eeat' => 20, 'technical' => 35, 'linking' => 15,
),
'custom' => array(
'label' => 'Individuell',
'icon' => '⚙️',
'desc' => 'Gewichtung manuell einstellen.',
'tags' => '',
'content' => $o['score_weight_content'] ?? 35,
'eeat' => $o['score_weight_eeat'] ?? 30,
'technical' => $o['score_weight_technical'] ?? 20,
'linking' => $o['score_weight_linking'] ?? 15,
),
);
echo ']+>/i', $content, $img_matches );
$img_count = count( $img_matches[0] );
$imgs_with_alt = 0;
foreach ( $img_matches[0] as $img ) {
if ( preg_match( '/alt=["\'][^"\']+["\']/i', $img ) ) {
$imgs_with_alt++;
}
}
$has_featured_image = has_post_thumbnail( $post->ID );
$meta_desc = get_post_meta( $post->ID, '_yoast_wpseo_metadesc', true );
if ( empty( $meta_desc ) ) { $meta_desc = get_post_meta( $post->ID, 'rank_math_description', true ); }
if ( empty( $meta_desc ) ) { $meta_desc = get_post_meta( $post->ID, '_aioseo_description', true ); }
if ( empty( $meta_desc ) ) { $meta_desc = get_post_meta( $post->ID, '_aije_metadesc', true ); } // eigene Meta-Box
$has_meta_desc = ! empty( $meta_desc );
$job_title = get_user_meta( $post->post_author, self::USER_META_JOB_TITLE, true );
$expertise = get_user_meta( $post->post_author, self::USER_META_EXPERTISE, true );
$expert_url = get_user_meta( $post->post_author, self::USER_META_WEBSITE, true );
$same_as_raw = get_user_meta( $post->post_author, self::USER_META_SAME_AS, true );
$same_as_count = 0;
if ( ! empty( $same_as_raw ) ) {
foreach ( preg_split( "/\r\n|\n|\r/", $same_as_raw ) as $line ) {
$line = trim( $line );
if ( $line !== '' && substr( $line, 0, 1 ) !== '#' ) { $same_as_count++; }
}
}
$is_recent = strtotime( $post->post_modified ) > strtotime( '-12 months' );
$title = $post->post_title;
$title_len = mb_strlen( $title );
$title_has_nr = (bool) preg_match( '/\d/', $title );
$title_has_q = ( strpos( $title, '?' ) !== false );
// ── Kategorie 1: Content-Qualität (35 %) ─────────────────────────────
$c_score = 0;
if ( $word_count >= 1500 ) { $c_score += 40; }
elseif ( $word_count >= 1000 ) { $c_score += 32; }
elseif ( $word_count >= 700 ) { $c_score += 22; }
elseif ( $word_count >= 400 ) { $c_score += 12; }
elseif ( $word_count >= 200 ) { $c_score += 5; }
else {
$issues[] = array( 'msg' => 'Inhalt zu kurz (' . $word_count . ' Wörter).', 'action' => 'Mindestens 700 Wörter anstreben; 1.000+ für konkurrenzfähige Themen.', 'points' => 30, 'category' => 'content', 'severity' => 'high' );
}
if ( $word_count > 0 && $word_count < 700 ) {
$issues[] = array( 'msg' => 'Artikel etwas kurz (' . $word_count . ' Wörter).', 'action' => 'Erweitere auf mindestens 700 Wörter für bessere Sichtbarkeit.', 'points' => 18, 'category' => 'content', 'severity' => 'medium' );
}
if ( $h2_count >= 4 ) { $c_score += 25; }
elseif ( $h2_count >= 2 ) { $c_score += 18; }
elseif ( $h2_count === 1 ) { $c_score += 10; }
else {
$issues[] = array( 'msg' => 'Keine H2-Zwischenüberschriften.', 'action' => 'Strukturiere den Text mit mindestens 3 H2-Überschriften.', 'points' => 20, 'category' => 'content', 'severity' => 'high' );
}
if ( $h3_count >= 2 ) { $c_score += 5; }
if ( $avg_words_per_sentence >= 10 && $avg_words_per_sentence <= 20 ) {
$c_score += 15;
} elseif ( $avg_words_per_sentence > 20 && $avg_words_per_sentence <= 28 ) {
$c_score += 8;
$issues[] = array( 'msg' => 'Satzlänge leicht erhöht (' . $avg_words_per_sentence . ' Wörter/Satz).', 'action' => 'Lange Sätze aufteilen; Zielwert: 12–18 Wörter.', 'points' => 7, 'category' => 'content', 'severity' => 'low' );
} elseif ( $avg_words_per_sentence > 28 ) {
$issues[] = array( 'msg' => 'Sätze sehr lang (' . $avg_words_per_sentence . ' Wörter/Satz).', 'action' => 'Text in kürzere Sätze umstrukturieren.', 'points' => 15, 'category' => 'content', 'severity' => 'medium' );
} elseif ( $avg_words_per_sentence > 0 ) {
$c_score += 10;
}
if ( $has_list ) { $c_score += 7; }
else { $issues[] = array( 'msg' => 'Keine Aufzählungslisten.', 'action' => 'Mindestens eine ul/ol-Liste einfügen.', 'points' => 5, 'category' => 'content', 'severity' => 'low' ); }
if ( $has_faq ) { $c_score += 3; }
if ( $para_count >= 6 ) { $c_score += 10; }
elseif ( $para_count >= 3 ) { $c_score += 6; }
else { $issues[] = array( 'msg' => 'Zu wenige Absätze (' . $para_count . ').', 'action' => 'Text in mindestens 5–6 Absätze gliedern.', 'points' => 8, 'category' => 'content', 'severity' => 'medium' ); }
$c_score = min( 100, $c_score );
// ── Kategorie 2: E-E-A-T / Autorität (30 %) ──────────────────────────
$e_score = 0;
if ( ! empty( $job_title ) ) { $e_score += 20; }
else { $issues[] = array( 'msg' => 'Keine Berufsbezeichnung im Autorenprofil.', 'action' => 'Unter Benutzer → Profil → "Experten-Profil" Berufsbezeichnung hinterlegen.', 'points' => 20, 'category' => 'eeat', 'severity' => 'high' ); }
if ( ! empty( $expertise ) && mb_strlen( $expertise ) >= 80 ) { $e_score += 20; }
elseif ( ! empty( $expertise ) ) {
$e_score += 10;
$issues[] = array( 'msg' => 'Fachbiografie sehr kurz (' . mb_strlen( $expertise ) . ' Zeichen).', 'action' => 'Biografie auf mindestens 80 Zeichen ausbauen.', 'points' => 10, 'category' => 'eeat', 'severity' => 'medium' );
} else { $issues[] = array( 'msg' => 'Keine Fachbiografie im Autorenprofil.', 'action' => 'Kurze Fachbiografie im Benutzerprofil eintragen.', 'points' => 20, 'category' => 'eeat', 'severity' => 'high' ); }
if ( $same_as_count >= 3 ) { $e_score += 25; }
elseif ( $same_as_count === 2 ) { $e_score += 18; }
elseif ( $same_as_count === 1 ) { $e_score += 10; }
else { $issues[] = array( 'msg' => 'Keine SameAs-Links (Social Media / Profilseiten).', 'action' => 'Instagram, LinkedIn o.ä. im Benutzerprofil hinterlegen.', 'points' => 25, 'category' => 'eeat', 'severity' => 'high' ); }
if ( ! empty( $expert_url ) ) { $e_score += 15; }
else { $issues[] = array( 'msg' => 'Keine Experten-/Praxiswebsite hinterlegt.', 'action' => 'URL der eigenen Website/Praxis im Benutzerprofil eintragen.', 'points' => 15, 'category' => 'eeat', 'severity' => 'medium' ); }
if ( $external_links >= 3 ) { $e_score += 20; }
elseif ( $external_links === 2 ) { $e_score += 15; }
elseif ( $external_links === 1 ) { $e_score += 8; }
else { $issues[] = array( 'msg' => 'Keine externen Quellen verlinkt.', 'action' => 'Verlinke 1–3 vertrauenswürdige Quellen (Studien, Behörden, Fachportale).', 'points' => 15, 'category' => 'eeat', 'severity' => 'medium' ); }
$e_score = min( 100, $e_score );
// ── Kategorie 3: Technische SEO (20 %) ───────────────────────────────
$t_score = 0;
if ( $has_featured_image ) { $t_score += 30; }
else { $issues[] = array( 'msg' => 'Kein Beitragsbild gesetzt.', 'action' => 'Featured Image setzen – wichtig für Social Shares und Google Discover.', 'points' => 30, 'category' => 'technical', 'severity' => 'high' ); }
if ( $img_count === 0 ) {
$issues[] = array( 'msg' => 'Keine Bilder im Artikelinhalt.', 'action' => 'Mindestens 1–2 thematisch passende Bilder einfügen.', 'points' => 15, 'category' => 'technical', 'severity' => 'low' );
} elseif ( $imgs_with_alt === $img_count ) {
$t_score += 25;
} elseif ( $imgs_with_alt > 0 ) {
$t_score += 12;
$issues[] = array( 'msg' => ( $img_count - $imgs_with_alt ) . ' Bild(er) ohne Alt-Text.', 'action' => 'Jedem Bild einen beschreibenden Alt-Text hinzufügen.', 'points' => 13, 'category' => 'technical', 'severity' => 'medium' );
} else {
$issues[] = array( 'msg' => 'Alle ' . $img_count . ' Bilder ohne Alt-Text.', 'action' => 'Alt-Texte für alle Bilder ergänzen – wichtig für Barrierefreiheit & SEO.', 'points' => 25, 'category' => 'technical', 'severity' => 'high' );
}
if ( $has_meta_desc ) {
$meta_len = mb_strlen( $meta_desc );
if ( $meta_len >= 120 && $meta_len <= 160 ) { $t_score += 25; }
elseif ( $meta_len >= 70 ) {
$t_score += 15;
$issues[] = array( 'msg' => 'Meta-Description Länge nicht optimal (' . $meta_len . ' Zeichen).', 'action' => 'Zielbereich: 120–160 Zeichen.', 'points' => 10, 'category' => 'technical', 'severity' => 'low' );
} else {
$t_score += 8;
$issues[] = array( 'msg' => 'Meta-Description zu kurz (' . $meta_len . ' Zeichen).', 'action' => 'Auf 120–160 Zeichen ausbauen.', 'points' => 17, 'category' => 'technical', 'severity' => 'medium' );
}
} else {
$issues[] = array( 'msg' => 'Keine Meta-Description gefunden.', 'action' => 'Meta-Description im WP Rankify Meta-Generator oder in Yoast/RankMath/AIOSEO hinterlegen.', 'points' => 25, 'category' => 'technical', 'severity' => 'high' );
}
if ( $title_len >= 50 && $title_len <= 65 ) { $t_score += 20; }
elseif ( $title_len >= 35 ) {
$t_score += 12;
if ( $title_len > 65 ) { $issues[] = array( 'msg' => 'Titel zu lang (' . $title_len . ' Zeichen).', 'action' => 'Auf max. 65 Zeichen kürzen.', 'points' => 8, 'category' => 'technical', 'severity' => 'low' ); }
} else {
$issues[] = array( 'msg' => 'Titel sehr kurz (' . $title_len . ' Zeichen).', 'action' => 'Titel auf 50–65 Zeichen ausbauen.', 'points' => 15, 'category' => 'technical', 'severity' => 'medium' );
}
if ( $title_has_nr || $title_has_q ) { $t_score += 5; }
$t_score = min( 100, $t_score );
// ── Kategorie 4: Interne Vernetzung (15 %) ───────────────────────────
$l_score = 0;
if ( $internal_links >= 4 ) { $l_score += 60; }
elseif ( $internal_links === 3 ) { $l_score += 50; }
elseif ( $internal_links === 2 ) { $l_score += 38; }
elseif ( $internal_links === 1 ) { $l_score += 22; }
else { $issues[] = array( 'msg' => 'Keine internen Links.', 'action' => 'Verlinke 2–4 thematisch verwandte eigene Artikel.', 'points' => 50, 'category' => 'linking', 'severity' => 'high' ); }
if ( $is_recent ) { $l_score += 40; }
else {
$age_months = round( ( time() - strtotime( $post->post_modified ) ) / MONTH_IN_SECONDS );
$issues[] = array( 'msg' => 'Artikel seit ' . $age_months . ' Monaten nicht aktualisiert.', 'action' => 'Inhalte, Zahlen und Links prüfen und aktualisieren.', 'points' => 35, 'category' => 'linking', 'severity' => 'medium' );
}
$l_score = min( 100, $l_score );
// ── Gewichteter Gesamt-Score (aus Einstellungen / Preset) ───────────────
$weights = self::get_score_weights( $this->get_opts() );
$total = (int) round(
( $c_score * $weights['content'] / 100 ) +
( $e_score * $weights['eeat'] / 100 ) +
( $t_score * $weights['technical'] / 100 ) +
( $l_score * $weights['linking'] / 100 )
);
// Issues sortieren: high → medium → low, dann points desc
$sev_order = array( 'high' => 0, 'medium' => 1, 'low' => 2 );
usort( $issues, function( $a, $b ) use ( $sev_order ) {
$s = $sev_order[ $a['severity'] ] <=> $sev_order[ $b['severity'] ];
return $s !== 0 ? $s : ( $b['points'] <=> $a['points'] );
});
return array(
'score' => $total,
'categories' => array(
'content' => array( 'score' => $c_score, 'label' => 'Content-Qualität', 'weight' => $weights['content'] ),
'eeat' => array( 'score' => $e_score, 'label' => 'E-E-A-T / Autorität', 'weight' => $weights['eeat'] ),
'technical' => array( 'score' => $t_score, 'label' => 'Technische SEO', 'weight' => $weights['technical'] ),
'linking' => array( 'score' => $l_score, 'label' => 'Interne Vernetzung', 'weight' => $weights['linking'] ),
),
'issues' => $issues,
);
}
public function register_settings() {
register_setting(self::OPT_KEY, self::OPT_KEY, array($this, 'sanitize_settings'));
register_setting('aije_ai_settings', 'aije_openai_api_key');
}
/**
* Output-Buffer starten um Header-Konflikte durch andere Plugins zu vermeiden.
* Wird bei allen admin_post und options-save Aktionen ausgeführt.
*/
public function start_output_buffer() {
if ( ! ob_get_level() ) {
ob_start();
}
}
public function sanitize_settings($in) {
$d = self::defaults();
$o = array();
$o['enabled_post_types'] = isset($in['enabled_post_types'])
? array_values(array_map('sanitize_key', (array) $in['enabled_post_types']))
: $d['enabled_post_types'];
$o['enable_schema'] = !empty($in['enable_schema']) ? 1 : 0;
$o['minify'] = !empty($in['minify']) ? 1 : 0;
$o['acf_include'] = !empty($in['acf_include']) ? 1 : 0;
$o['cron_enabled'] = !empty($in['cron_enabled']) ? 1 : 0;
$o['cron_frequency']= isset($in['cron_frequency']) ? sanitize_key($in['cron_frequency']) : 'daily';
$o['ping_enabled'] = !empty($in['ping_enabled']) ? 1 : 0;
$o['ping_urls'] = isset($in['ping_urls']) ? (string) $in['ping_urls'] : $d['ping_urls'];
$events = array();
if (!empty($in['ping_events']) && is_array($in['ping_events'])) {
foreach ($in['ping_events'] as $e) {
$e = sanitize_key($e);
if (in_array($e, array('publish', 'update'), true)) {
$events[] = $e;
}
}
}
$o['ping_events'] = array_values(array_unique($events));
$o['ping_secret'] = isset($in['ping_secret']) ? (string) $in['ping_secret'] : '';
$o['ping_timeout'] = isset($in['ping_timeout']) ? max(2, (int) $in['ping_timeout']) : 5;
$o['ping_retry'] = !empty($in['ping_retry']) ? 1 : 0;
$o['openai_api_key'] = isset($in['openai_api_key'])
? sanitize_text_field($in['openai_api_key'])
: '';
$o['openai_prompt'] = isset($in['openai_prompt'])
? trim($in['openai_prompt'])
: $d['openai_prompt'];
// Score-Gewichtung
$valid_presets = array( 'ymyl', 'standard', 'ecommerce', 'custom' );
$o['score_preset'] = ( isset( $in['score_preset'] ) && in_array( $in['score_preset'], $valid_presets, true ) )
? $in['score_preset']
: 'standard';
$o['score_weight_content'] = isset( $in['score_weight_content'] ) ? max( 5, (int) $in['score_weight_content'] ) : 35;
$o['score_weight_eeat'] = isset( $in['score_weight_eeat'] ) ? max( 5, (int) $in['score_weight_eeat'] ) : 30;
$o['score_weight_technical'] = isset( $in['score_weight_technical'] ) ? max( 5, (int) $in['score_weight_technical'] ) : 20;
$o['score_weight_linking'] = isset( $in['score_weight_linking'] ) ? max( 5, (int) $in['score_weight_linking'] ) : 15;
$valid_seo = array( 'auto', 'yoast', 'rankmath', 'aioseo', 'own' );
$old_opts = get_option( self::OPT_KEY, array() );
$old_seo = is_array($old_opts) && isset($old_opts['seo_plugin']) ? $old_opts['seo_plugin'] : 'auto';
$o['seo_plugin'] = ( isset($in['seo_plugin']) && in_array($in['seo_plugin'], $valid_seo, true) )
? $in['seo_plugin'] : $old_seo;
$o['remove_category_base'] = ! empty($in['remove_category_base']) ? 1 : 0;
$this->maybe_reschedule_export($o['cron_enabled'], $o['cron_frequency']);
$this->ensure_ping_cron();
// Tracking
$o['gtm_id'] = sanitize_text_field( $in['gtm_id'] ?? '' );
$o['ga4_id'] = sanitize_text_field( $in['ga4_id'] ?? '' );
$o['gsc_verify'] = sanitize_text_field( $in['gsc_verify'] ?? '' );
$o['meta_pixel'] = sanitize_text_field( $in['meta_pixel'] ?? '' );
$o['gsc_client_id'] = sanitize_text_field( $in['gsc_client_id'] ?? '' );
$o['gsc_client_secret'] = sanitize_text_field( $in['gsc_client_secret'] ?? '' );
return $o;
}
/* ===== Admin UI ===== */
public function tools_menu() {
add_management_page(
esc_html__('WP Rankify', 'ai-json-extractor-auto-embed-lite'),
esc_html__('WP Rankify', 'ai-json-extractor-auto-embed-lite'),
'manage_options',
'aije-lite',
array($this, 'render_tools')
);
}
public function render_tools() {
if (!current_user_can('manage_options')) {
return;
}
$licensed = self::license_is_valid();
$tabs = array(
'dashboard' => esc_html__('Dashboard', 'ai-json-extractor-auto-embed-lite'),
'settings' => esc_html__('Einstellungen', 'ai-json-extractor-auto-embed-lite'),
'tracking' => esc_html__('Tracking & Analytics', 'ai-json-extractor-auto-embed-lite'),
'export' => esc_html__('Bulk Export', 'ai-json-extractor-auto-embed-lite'),
'automation' => esc_html__('Automation', 'ai-json-extractor-auto-embed-lite'),
'pinghub' => esc_html__('PingHub', 'ai-json-extractor-auto-embed-lite'),
'gsc' => esc_html__('Google Suche', 'ai-json-extractor-auto-embed-lite'),
'llms' => esc_html__('llms.txt', 'ai-json-extractor-auto-embed-lite'),
'license' => esc_html__('Lizenz', 'ai-json-extractor-auto-embed-lite'),
);
$tab = isset($_GET['tab']) ? sanitize_key($_GET['tab']) : 'dashboard';
echo '
' . esc_html__('WP Rankify', 'ai-json-extractor-auto-embed-lite') . ' (AI Auto Embed)
';
echo '';
foreach ($tabs as $k => $label) {
$class = ($tab === $k) ? ' nav-tab nav-tab-active' : ' nav-tab';
$url = add_query_arg(array('page' => 'aije-lite', 'tab' => $k), admin_url('tools.php'));
echo '' . esc_html($label) . '';
}
echo '
';
// ── Bewertungs-Button ──────────────────────────────────────────────────
echo '
';
echo 'Score-Gewichtung
';
echo '
';
echo 'Experten-Profile pflegen
';
echo 'application/ld+json im <head> aus.' . esc_html__('Bulk Export', 'ai-json-extractor-auto-embed-lite') . '
';
echo '';
if (isset($_GET['export_done'])) {
$uploads = wp_upload_dir();
$file = isset($_GET['file']) ? sanitize_file_name(wp_unslash($_GET['file'])) : '';
$url = $file ? trailingslashit($uploads['baseurl']) . self::EXPORT_DIR . '/' . $file : '';
echo '';
$found = false;
foreach ( $posts as $post ) {
$analysis = $this->calculate_ai_analysis( $post );
$score = $analysis['score'];
// Score-Tabs
$show = false;
if ( $type === 'bad' && $score < 50 ) { $show = true; }
if ( $type === 'medium' && $score >= 50 && $score < 75 ) { $show = true; }
if ( $type === 'good' && $score >= 75 ) { $show = true; }
if ( $type === 'filter' ) { $show = true; }
if ( ! $show ) { continue; }
// Filter anwenden
if ( ! empty( $filter ) && isset( $filter_map[ $filter ] ) ) {
$needle = $filter_map[ $filter ];
$matched = false;
foreach ( $analysis['issues'] as $iss ) {
if ( mb_stripos( $iss['msg'], $needle ) !== false ) {
$matched = true;
break;
}
}
if ( ! $matched ) { continue; }
}
$found = true;
// Top-3 Issues direkt in der Liste
$top_issues = array_slice( $analysis['issues'], 0, 3 );
$sev_bg = array( 'high' => '#fdecea', 'medium' => '#fef9e7', 'low' => '#eafaf1' );
$cat_icons = array( 'content' => '✍', 'eeat' => '🏅', 'technical' => '⚙', 'linking' => '🔗' );
echo '
';
}
public function aije_exclude_post_handler() {
check_ajax_referer( 'aije_dashboard_nonce', 'nonce' );
if ( ! current_user_can('manage_options') ) { wp_send_json_error(); }
$post_id = intval( $_POST['post_id'] ?? 0 );
if ( ! $post_id ) { wp_send_json_error(); }
$excluded = get_option( 'aije_excluded_posts', array() );
$excluded[] = $post_id;
$excluded = array_unique( $excluded );
update_option( 'aije_excluded_posts', $excluded );
wp_send_json_success();
}
public function aije_include_post_handler() {
check_ajax_referer( 'aije_dashboard_nonce', 'nonce' );
if ( ! current_user_can('manage_options') ) { wp_send_json_error(); }
$post_id = intval( $_POST['post_id'] ?? 0 );
$excluded = get_option( 'aije_excluded_posts', array() );
$excluded = array_values( array_filter( $excluded, fn($id) => $id !== $post_id ) );
update_option( 'aije_excluded_posts', $excluded );
wp_send_json_success();
}
private function tab_dashboard() {
wp_enqueue_script( 'chartjs', 'https://cdn.jsdelivr.net/npm/chart.js', array(), null, true );
$types = $this->get_opts()['enabled_post_types'];
$excluded_ids = get_option( 'aije_excluded_posts', array() );
$nonce_dash = wp_create_nonce( 'aije_dashboard_nonce' );
$query = new WP_Query( array(
'post_type' => $types,
'posts_per_page' => -1,
'post_status' => 'publish',
'post__not_in' => $excluded_ids,
) );
$scores = array();
$distribution = array( 'ready' => 0, 'mid' => 0, 'low' => 0 );
$cat_totals = array( 'content' => 0, 'eeat' => 0, 'technical' => 0, 'linking' => 0 );
$post_count = 0;
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
$p = get_post();
$analysis = $this->calculate_ai_analysis( $p );
$score = $analysis['score'];
$scores[] = array( 'id' => get_the_ID(), 'title' => get_the_title(), 'score' => $score );
if ( $score >= 75 ) { $distribution['ready']++; }
elseif ( $score >= 50 ) { $distribution['mid']++; }
else { $distribution['low']++; }
foreach ( $cat_totals as $k => $_ ) {
$cat_totals[ $k ] += $analysis['categories'][ $k ]['score'] ?? 0;
}
$post_count++;
}
wp_reset_postdata();
}
$avg = $post_count ? round( array_sum( array_column( $scores, 'score' ) ) / $post_count ) : 0;
$cat_avgs = array();
foreach ( $cat_totals as $k => $total ) {
$cat_avgs[ $k ] = $post_count ? round( $total / $post_count ) : 0;
}
usort( $scores, function( $a, $b ) { return $a['score'] <=> $b['score']; } );
$avg_color = $avg >= 75 ? '#27ae60' : ( $avg >= 50 ? '#e67e22' : '#e74c3c' );
echo 'AI Readiness Dashboard '
. ''
. esc_html( $post_count ) . ' Artikel · Ø ' . esc_html( $avg ) . ' Punkte
';
echo 'Artikel-Qualität';
if ( ! empty($excluded_ids) ) {
echo ''
. '↩ ' . count($excluded_ids) . ' ausgeschlossene Artikel zurücksetzen';
}
echo '
';
// Reset-Handler
if ( isset($_GET['reset_excluded']) ) {
delete_option('aije_excluded_posts');
wp_safe_redirect( admin_url('tools.php?page=aije-lite&tab=dashboard') );
exit;
}
echo '';
// Score-Tabs
echo '';
foreach ( $excluded_ids as $ex_id ) {
$ex_post = get_post( $ex_id );
if ( ! $ex_post ) { continue; }
echo '
';
}
echo '' . esc_html__('Automation', 'ai-json-extractor-auto-embed-lite') . '
';
echo '';
echo '' . esc_html__('Log Viewer', 'ai-json-extractor-auto-embed-lite') . '
';
if (file_exists($log)) {
echo '
';
} else {
echo '';
echo ' ';
$lines = @file($log);
if ($lines) {
$lines = array_reverse($lines);
$i = 0;
foreach ($lines as $line) {
$i++;
if ($i > 200) { break; }
$cols = str_getcsv(trim($line));
if (count($cols) >= 5) {
echo 'timestamp filename count size_kb filters ';
echo '';
foreach (array_slice($cols, 0, 5) as $col) {
echo ' ';
}
}
}
echo '' . esc_html($col) . ' ';
}
echo '' . esc_html__('PingHub', 'ai-json-extractor-auto-embed-lite') . '
';
echo 'Letzte Ping-Fehler
';
echo '
';
}
}
private function tab_status() {
$o = $this->get_opts();
$uploads = wp_upload_dir();
$ok = isset($uploads['basedir']) && is_dir($uploads['basedir']) && is_writable($uploads['basedir']);
$licensed = self::license_is_valid();
echo ' ';
$i = 0;
foreach (array_reverse($errs) as $e) {
$i++;
if ($i > 50) { break; }
echo 'Zeit URL Status/Fehler Event Post-ID ';
echo ' ';
}
echo '' . esc_html($e['time']) . ' ';
echo '' . esc_html($e['url']) . ' ';
echo '' . esc_html($e['error']) . ' ';
echo '' . esc_html($e['event']) . ' ';
echo '' . esc_html($e['post_id']) . ' ';
echo '' . esc_html__('Status & Hilfe', 'ai-json-extractor-auto-embed-lite') . '
';
echo '';
echo '
';
}
/**
* Hook für künftige Pro-Features. Aktuell nicht im Einsatz.
*/
private function render_pro_locked( $feature ) {
// Reserviert für zukünftige Premium-Features
}
public function handle_gsc_oauth_callback() {
if ( ! isset($_GET['page']) || $_GET['page'] !== 'aije-lite' ) { return; }
if ( ! isset($_GET['tab']) || $_GET['tab'] !== 'gsc' ) { return; }
// OAuth Callback
if ( isset($_GET['gsc_callback']) && isset($_GET['code']) ) {
$o = $this->get_opts();
$client_id = $o['gsc_client_id'] ?? '';
$client_secret = $o['gsc_client_secret'] ?? '';
$redirect_uri = admin_url( 'tools.php?page=aije-lite&tab=gsc&gsc_callback=1' );
$resp = wp_remote_post( 'https://oauth2.googleapis.com/token', array(
'body' => array(
'code' => sanitize_text_field( $_GET['code'] ),
'client_id' => $client_id,
'client_secret' => $client_secret,
'redirect_uri' => $redirect_uri,
'grant_type' => 'authorization_code',
),
));
if ( ! is_wp_error($resp) ) {
$token = json_decode( wp_remote_retrieve_body($resp), true );
if ( ! empty($token['access_token']) ) {
update_option( 'aije_gsc_access_token', $token['access_token'] );
update_option( 'aije_gsc_refresh_token', $token['refresh_token'] ?? '' );
update_option( 'aije_gsc_token_expiry', time() + ( $token['expires_in'] ?? 3600 ) );
wp_safe_redirect( admin_url('tools.php?page=aije-lite&tab=gsc&gsc_connected=1') );
exit;
}
}
wp_safe_redirect( admin_url('tools.php?page=aije-lite&tab=gsc&gsc_error=1') );
exit;
}
// Trennen
if ( isset($_GET['gsc_disconnect']) ) {
delete_option('aije_gsc_access_token');
delete_option('aije_gsc_refresh_token');
delete_option('aije_gsc_token_expiry');
wp_safe_redirect( admin_url('tools.php?page=aije-lite&tab=gsc') );
exit;
}
}
private function tab_gsc() {
$o = $this->get_opts();
$client_id = $o['gsc_client_id'] ?? '';
$client_secret= $o['gsc_client_secret'] ?? '';
$access_token = $this->get_valid_gsc_token();
$redirect_uri = admin_url( 'tools.php?page=aije-lite&tab=gsc&gsc_callback=1' );
// Erfolgsmeldungen
if ( isset($_GET['gsc_connected']) ) {
echo ' ';
echo 'Plugin WP Rankify (AI Auto Embed) v1.2.6 ';
echo 'WordPress ' . esc_html(get_bloginfo('version')) . ' ';
echo 'PHP ' . esc_html(PHP_VERSION) . ' ';
echo 'Upload-Ordner ' . ($ok ? 'beschreibbar' : 'NICHT beschreibbar') . ' ';
echo 'PingHub aktiv ' . (!empty($o['ping_enabled']) ? 'Ja' : 'Nein') . ' ';
echo 'Lizenz ' . ($licensed ? 'aktiv' : 'nicht aktiv') . ' post_content) ) {
$issues[] = array('msg'=>'Kein H2','severity'=>'medium');
}
// Keine internen Links
if ( strpos( $p->post_content, home_url() ) === false ) {
$issues[] = array('msg'=>'Kein internes Linking','severity'=>'medium');
}
// Kein E-E-A-T
$job = get_user_meta( $p->post_author, 'aije_job_title', true );
if ( empty($job) ) {
$issues[] = array('msg'=>'Kein E-E-A-T Profil','severity'=>'high');
}
if ( ! empty($issues) ) {
$purl = trailingslashit( get_permalink($p->ID) );
$url_issues[$purl] = $issues;
}
}
// ── Gesamtstatistiken ──────────────────────────────────────────────────
$total_clicks = array_sum( array_column( $rows, 'clicks' ) );
$total_impr = array_sum( array_column( $rows, 'impressions' ) );
// Gewichteter Durchschnitt (wie GSC): nach Impressionen gewichtet
$total_impr_for_avg = array_sum( array_column( $rows, 'impressions' ) );
if ( $total_impr_for_avg > 0 ) {
$weighted_ctr = 0;
$weighted_pos = 0;
foreach ( $rows as $r ) {
$impr_weight = ($r['impressions'] ?? 0) / $total_impr_for_avg;
$weighted_ctr += ($r['ctr'] ?? 0) * $impr_weight;
$weighted_pos += ($r['position'] ?? 0) * $impr_weight;
}
$avg_ctr = round( $weighted_ctr * 100, 1 );
$avg_pos = round( $weighted_pos, 1 );
} else {
$avg_ctr = 0;
$avg_pos = 0;
}
$cmp_total_clicks = array_sum( array_column( $cmp_rows, 'clicks' ) );
$cmp_total_impr = array_sum( array_column( $cmp_rows, 'impressions' ) );
$cmp_impr_total = array_sum( array_column( $cmp_rows, 'impressions' ) );
if ( $cmp_impr_total > 0 ) {
$cmp_w_pos = 0;
foreach ( $cmp_rows as $r ) {
$cmp_w_pos += ($r['position'] ?? 0) * ( ($r['impressions'] ?? 0) / $cmp_impr_total );
}
$cmp_avg_pos = round( $cmp_w_pos, 1 );
} else {
$cmp_avg_pos = 0;
}
$positions_won = 0;
$positions_lost = 0;
foreach ( $rows as $row ) {
$url = $row['keys'][0] ?? '';
$cmp = $cmp_index[$url] ?? null;
if ( ! $cmp ) { continue; }
$delta = round($row['position'] ?? 0, 1) - round($cmp['position'] ?? 0, 1);
if ( $delta < -0.5 ) { $positions_won++; }
elseif ( $delta > 0.5 ) { $positions_lost++; }
}
$dc_clicks = $total_clicks - $cmp_total_clicks;
$dc_impr = $total_impr - $cmp_total_impr;
$dc_pos = $avg_pos - $cmp_avg_pos;
$fmt_delta = function($v, $invert=false) {
if ($v == 0) return '±0';
$pos = $invert ? $v < 0 : $v > 0;
$c = $pos ? '#22c55e' : '#ef4444';
return '' . ($v > 0 ? '+' : '') . round($v,1) . '';
};
echo '
';
echo '
';
$cols = array(
'page' => array('Seite', 'left', ''),
'tasks' => array('Tasks', 'left', ''),
'clicks' => array('Klicks', 'right', 'desc'),
'clicks_d' => array('±', 'right', ''),
'impr' => array('Impressionen', 'right', ''),
'ctr' => array('CTR', 'right', ''),
'pos' => array('Ø Position', 'right', ''),
'pos_d' => array('±', 'right', ''),
);
foreach ( $cols as $key => $col ) {
$align = $col[1];
$sort = $col[2] ? "aije_gsc_sort('$key',this)" : ($key !== 'page' && $key !== 'tasks' ? "aije_gsc_sort('$key',this)" : '');
$no_sort = in_array($key, array('tasks'));
$cursor = ! $no_sort ? 'cursor:pointer;user-select:none;' : '';
$onclick = ! $no_sort ? ' onclick="aije_gsc_sort(\'' . esc_js($key) . '\',this)"' : '';
echo ' ';
foreach ( $rows as $row ) {
$url = $row['keys'][0] ?? '';
$url_trail = trailingslashit($url);
$path = str_replace( home_url(), '', $url );
$clicks = (int)($row['clicks'] ?? 0);
$impr = (int)($row['impressions'] ?? 0);
$ctr_raw = round( ($row['ctr'] ?? 0) * 100, 1 );
$pos = round( $row['position'] ?? 0, 1 );
$pos_c = $pos <= 3 ? '#22c55e' : ($pos <= 10 ? '#f59e0b' : '#ef4444');
// Tasks für diese URL
$issues = $url_issues[$url_trail] ?? array();
$task_html = '';
$task_count = 0;
if ( ! empty($issues) ) {
foreach ( $issues as $issue ) {
$sev = $issue['severity'] ?? 'medium';
$sev_c = $sev === 'high' ? '#ef4444' : ($sev === 'medium' ? '#f59e0b' : '#888');
$task_html .= '';
$task_count++;
}
}
echo '';
echo esc_html($col[0]);
if ( ! $no_sort ) { echo ' '; }
echo ' ';
}
echo '';
echo '';
echo ' ';
}
echo '';
if ( $task_html ) {
echo ' ';
// Vergleichsdaten
$cmp = $cmp_index[$url] ?? null;
$d_clicks = $cmp ? $clicks - (int)($cmp['clicks'] ?? 0) : null;
$d_pos = $cmp ? $pos - round($cmp['position'] ?? 0, 1) : null;
$delta_clicks = '';
if ( $d_clicks !== null ) {
$dc = $d_clicks > 0 ? '+' . $d_clicks : (string)$d_clicks;
$dc_c = $d_clicks > 0 ? '#22c55e' : ($d_clicks < 0 ? '#ef4444' : '#888');
$delta_clicks = '' . $dc . '';
}
$delta_pos = '';
if ( $d_pos !== null && $d_pos != 0 ) {
$dp = round($d_pos, 1);
$dp_str = $dp > 0 ? '+' . $dp : (string)$dp;
$dp_c = $dp < 0 ? '#22c55e' : '#ef4444';
$delta_pos = '' . $dp_str . '';
}
echo '' . number_format_i18n($clicks) . ' ';
echo '' . ($delta_clicks ?: '–') . ' ';
echo '' . number_format_i18n($impr) . ' ';
echo '' . $ctr_raw . '% ';
echo '' . $pos . ' ';
echo '' . ($delta_pos ?: '–') . ' ';
echo '' . esc_html($preview) . '
';
echo 'Lizenz & Abo-Status
';
echo '';
$rows = array(
array('Domain', home_url()),
array('KI-Analysen', $tier === 'agency'
? 'Unlimitiert'
: ( $tier === 'pro'
? (int)$remaining . ' verbleibend · 100/Monat'
: ( $tier === 'starter'
? (int)$remaining . ' verbleibend · 50/Monat'
: (int)$remaining . ' verbleibend · 3/Monat (Free)' ) )
),
);
if ( $sub_id ) {
$rows[] = array('Abo-ID', $sub_id);
}
if ( $since ) {
$rows[] = array('Aktiv seit', date_i18n('d.m.Y', strtotime($since)));
}
foreach ( $rows as $idx => $row ) {
echo '
';
echo '';
echo ' ';
}
echo '' . esc_html($row[0]) . ' ';
$cell_id = $row[0] === 'KI-Analysen' ? ' id="aije-license-ki"' : '';
echo '' . esc_html($row[1]) . ' ';
echo '
Hinweis: Beitrag ist nicht veröffentlicht. Login erforderlich.';
}
echo '
';
echo '';
echo 'Das Plugin sendet anonyme Daten an den Entwickler: Site-URL, PHP-Version und aggregierte Score-Durchschnitte (keine Inhalte, keine E-Mails, keine personenbezogenen Daten). ';
echo 'Das hilft dabei, das Plugin zu verbessern. Du kannst das jederzeit deaktivieren.';
echo '';
echo '