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 );
?>
'Person', 'name' => $user->display_name, 'url' => $author_url );
if ( ! empty( $job_title ) ) { $person['jobTitle'] = $job_title; }
if ( ! empty( $expertise ) ) { $person['description'] = $expertise; }
if ( ! empty( $same_as_urls ) ) { $person['sameAs'] = $same_as_urls; }
return $person;
}
private function get_author_expert_data( $author_id ) {
$person = $this->build_person_schema( $author_id );
return empty( $person ) ? array() : $person;
}
/* ===== Frontend-Ausgabe ===== */
public function output_person_schema_in_head() {
if ( ! is_singular() ) { return; }
global $post;
if ( empty( $post ) || ! is_object( $post ) ) { return; }
$o = $this->get_opts();
if ( ! in_array( $post->post_type, $o['enabled_post_types'], true ) ) { return; }
if ( get_post_meta( $post->ID, self::META_NO_EMBED, true ) ) { return; }
$person = $this->build_person_schema( $post->post_author );
if ( empty( $person ) ) { return; }
$graph = array(
'@context' => 'https://schema.org',
'@graph' => array(
$person,
array(
'@type' => 'Article',
'url' => get_permalink( $post ),
'headline' => wp_strip_all_tags( get_the_title( $post ) ),
'datePublished' => get_post_time( 'c', true, $post ),
'dateModified' => get_post_modified_time( 'c', true, $post ),
'author' => array( '@type' => 'Person', 'name' => $person['name'], 'url' => $person['url'] ),
'reviewedBy' => array( '@type' => 'Person', 'name' => $person['name'], 'url' => $person['url'], 'jobTitle' => $person['jobTitle'] ?? '' ),
'publisher' => array( '@type' => 'Organization', 'name' => get_bloginfo( 'name' ), 'url' => home_url() ),
),
),
);
$flags = 0;
if ( defined( 'JSON_UNESCAPED_UNICODE' ) ) { $flags |= JSON_UNESCAPED_UNICODE; }
if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) { $flags |= JSON_UNESCAPED_SLASHES; }
if ( defined( 'JSON_PRETTY_PRINT' ) ) { $flags |= JSON_PRETTY_PRINT; }
$json = function_exists( 'wp_json_encode' ) ? wp_json_encode( $graph, $flags ) : json_encode( $graph, $flags );
echo "\n\n";
echo '' . "\n";
}
public function output_aije_meta_description() {
if ( ! is_singular() ) { return; }
global $post;
if ( empty( $post ) ) { return; }
$seo = $this->get_active_seo_plugin();
if ( $seo !== 'own' ) { return; }
$seo_title = get_post_meta( $post->ID, '_aije_seo_title', true );
if ( ! empty( $seo_title ) ) {
add_filter( 'pre_get_document_title', function() use ( $seo_title ) { return esc_html( $seo_title ); } );
}
$meta = get_post_meta( $post->ID, '_aije_metadesc', true );
if ( ! empty( $meta ) ) { echo '' . "\n"; }
$robots = get_post_meta( $post->ID, '_aije_robots', true );
if ( ! empty( $robots ) && $robots !== 'index,follow' ) { echo '' . "\n"; }
$canonical = get_post_meta( $post->ID, '_aije_canonical', true );
if ( ! empty( $canonical ) ) { echo '' . "\n"; }
}
public function output_tracking_head() {
$opts = $this->get_opts();
$gtm_id = trim( $opts['gtm_id'] ?? '' );
$ga4_id = trim( $opts['ga4_id'] ?? '' );
$gsc_verify = trim( $opts['gsc_verify'] ?? '' );
if ( $gsc_verify ) {
$code = str_replace( 'google-site-verification=', '', $gsc_verify );
echo '' . "\n";
}
if ( $gtm_id ) {
echo "\n\n\n";
}
if ( $ga4_id && ! $gtm_id ) {
echo '' . "\n";
echo '' . "\n";
}
$meta_pixel = trim( $opts['meta_pixel'] ?? '' );
if ( $meta_pixel ) {
echo "\n";
echo '' . "\n";
echo '' . "\n";
echo "\n";
}
}
/* ===== Auto-Embed (JSON-Blob) ===== */
public function auto_embed() {
if ( ! function_exists( 'is_singular' ) || ! is_singular() ) { return; }
global $post;
if ( empty( $post ) || ! is_object( $post ) ) { return; }
$o = $this->get_opts();
if ( ! in_array( $post->post_type, $o['enabled_post_types'], true ) ) { return; }
if ( get_post_meta( $post->ID, self::META_NO_EMBED, true ) ) { return; }
$data = $this->extract_payload( $post );
$flags = 0;
if ( defined( 'JSON_UNESCAPED_UNICODE' ) ) { $flags |= JSON_UNESCAPED_UNICODE; }
if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) { $flags |= JSON_UNESCAPED_SLASHES; }
if ( empty( $o['minify'] ) && defined( 'JSON_PRETTY_PRINT' ) ) { $flags |= JSON_PRETTY_PRINT; }
$json = function_exists( 'wp_json_encode' ) ? wp_json_encode( $data, $flags ) : json_encode( $data );
echo "\n\n";
echo '' . "\n";
if ( ! empty( $o['enable_schema'] ) ) {
echo '' . "\n";
}
echo "\n";
}
private function extract_payload( $post ) {
$o = $this->get_opts();
$u = get_userdata( $post->post_author );
$acf = array();
$content = apply_filters( 'the_content', $post->post_content );
$text = wp_strip_all_tags( $content );
$lines = preg_split( "/\r\n|\n|\r/", $text );
$paragraphs = array();
foreach ( $lines as $line ) {
$line = trim( preg_replace( '/\s+/u', ' ', $line ) );
if ( $line !== '' ) { $paragraphs[] = $line; }
}
$links = array();
if ( preg_match_all( '/]+href=[\'\"]?([^\'\" >]+)[\'\"]?[^>]*>(.*?)<\/a>/i', $content, $m, PREG_SET_ORDER ) ) {
foreach ( $m as $a ) {
$href = trim( $a[1] );
$txt = trim( wp_strip_all_tags( $a[2] ) );
if ( $href !== '' ) { $links[] = array( 'text' => $txt, 'href' => $href ); }
}
}
if ( ! empty( $o['acf_include'] ) && function_exists( 'get_fields' ) ) {
$acf_fields = get_fields( $post->ID );
if ( is_array( $acf_fields ) && ! empty( $acf_fields ) ) { $acf = $acf_fields; }
}
$data = array(
'title' => get_the_title( $post ),
'author' => $u ? $u->display_name : null,
'date' => get_post_time( 'Y-m-d', true, $post ),
'paragraphs' => $paragraphs,
'links' => $links,
'meta' => array( 'post_id' => (int) $post->ID, 'site' => get_bloginfo( 'name' ), 'url' => get_permalink( $post ), 'lang' => get_bloginfo( 'language' ) ),
);
$expert = $this->get_author_expert_data( $post->post_author );
if ( ! empty( $expert ) ) { $data['author_expert'] = $expert; }
if ( ! empty( $acf ) ) { $data['acf'] = $acf; }
return $data;
}
private function schema_article( $post ) {
$obj = array(
'@context' => 'https://schema.org',
'@type' => 'Article',
'url' => get_permalink( $post ),
'headline' => wp_strip_all_tags( get_the_title( $post ) ),
'publisher' => array( '@type' => 'Organization', 'name' => get_bloginfo( 'name' ) ),
);
$u = get_userdata( $post->post_author );
if ( $u && $u->display_name ) { $obj['author'] = array( '@type' => 'Person', 'name' => $u->display_name ); }
$flags = 0;
if ( defined( 'JSON_UNESCAPED_UNICODE' ) ) { $flags |= JSON_UNESCAPED_UNICODE; }
if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) { $flags |= JSON_UNESCAPED_SLASHES; }
return function_exists( 'wp_json_encode' ) ? wp_json_encode( $obj, $flags ) : json_encode( $obj );
}
/* ===== llms.txt ===== */
public function register_llms_rewrite() {
add_rewrite_rule( '^llms\.txt/?$', 'index.php?aije_llms=1', 'top' );
add_rewrite_tag( '%aije_llms%', '([0-9]+)' );
}
public function serve_llms_txt() {
if ( ! get_query_var( 'aije_llms' ) ) { return; }
if ( ! get_option( 'aije_llms_enabled', 1 ) ) { return; }
nocache_headers();
header( 'Content-Type: text/plain; charset=utf-8' );
header( 'X-Robots-Tag: noindex' );
echo $this->generate_llms_content();
exit;
}
public function append_llms_to_robots( $output, $public ) {
if ( get_option( 'aije_llms_block_bots', 0 ) ) {
foreach ( array( 'GPTBot', 'CCBot', 'anthropic-ai', 'Claude-Web', 'Bytespider', 'FacebookBot', 'Applebot-Extended' ) as $bot ) {
$output .= "\nUser-agent: " . $bot . "\nDisallow: /\n";
}
}
if ( get_option( 'aije_llms_enabled', 1 ) ) {
$output .= "\n# LLMs\nLLMs: " . home_url( '/llms.txt' ) . "\n";
}
return $output;
}
/**
* Generiert die llms.txt.
*
* Strategie: Erst alles aufbauen, dann von hinten Artikel entfernen
* bis die Datei unter 2.800 Zeichen liegt. Keine Schätzformeln.
*/
public function generate_llms_content() {
$o = $this->get_opts();
$custom = get_option( 'aije_llms_custom', '' );
$max_posts = max( 1, min( 25, (int) get_option( 'aije_llms_max_posts', 10 ) ) );
$excerpt_len = max( 0, min( 200, (int) get_option( 'aije_llms_excerpt_len', 0 ) ) );
$key_pages = (array) get_option( 'aije_llms_key_pages', array() );
$show_cats = (int) get_option( 'aije_llms_show_cats', 1 );
$link_sitemap= (int) get_option( 'aije_llms_sitemap', 1 );
$limit = 2800;
// ── Autor-Daten ────────────────────────────────────────────────
$admins = get_users( array( 'role' => 'administrator', 'number' => 1 ) );
$admin = $admins ? $admins[0] : null;
$job = $admin ? get_user_meta( $admin->ID, self::USER_META_JOB_TITLE, true ) : '';
$bio = $admin ? get_user_meta( $admin->ID, self::USER_META_EXPERTISE, true ) : '';
$same_as = $admin ? get_user_meta( $admin->ID, self::USER_META_SAME_AS, true ) : '';
$disp = $admin ? $admin->display_name : get_bloginfo( 'name' );
// ── Block A: Header (immer vollständig) ────────────────────────
$header = '';
$header .= '# ' . $disp . ( get_bloginfo( 'name' ) !== $disp ? ' – ' . get_bloginfo( 'name' ) : '' ) . "\n\n";
$desc_parts = array();
if ( $job ) { $desc_parts[] = $job; }
if ( $bio ) {
$bio_clean = html_entity_decode( wp_strip_all_tags( $bio ), ENT_QUOTES, 'UTF-8' );
$bio_clean = preg_replace( '/\s+/', ' ', trim( $bio_clean ) );
if ( mb_strlen( $bio_clean ) > 160 ) {
$bio_clean = mb_substr( $bio_clean, 0, 157 );
$p = mb_strrpos( $bio_clean, ' ' );
$bio_clean = ( $p ? mb_substr( $bio_clean, 0, $p ) : $bio_clean ) . '...';
}
$desc_parts[] = $bio_clean;
} elseif ( get_bloginfo( 'description' ) ) {
$desc_parts[] = get_bloginfo( 'description' );
}
if ( $desc_parts ) {
$header .= '> ' . implode( ' – ', $desc_parts ) . "\n\n";
}
// ── Block B: Wichtige Seiten (immer vollständig) ───────────────
$pages_block = "## Wichtige Seiten\n\n";
$pages_block .= '- [' . get_bloginfo( 'name' ) . '](' . trailingslashit( home_url() ) . ')';
$tagline = get_bloginfo( 'description' );
if ( $tagline ) { $pages_block .= ': ' . $this->llms_truncate( $tagline, 80 ); }
$pages_block .= "\n";
foreach ( $key_pages as $pid ) {
$pinned = get_post( (int) $pid );
if ( ! $pinned || $pinned->post_status !== 'publish' ) { continue; }
$exc = $pinned->post_excerpt ? $this->llms_truncate( wp_strip_all_tags( $pinned->post_excerpt ), 80 ) : '';
if ( ! $exc && $pinned->post_content ) {
$exc = $this->llms_excerpt( $pinned, 80 );
}
$pages_block .= '- [' . get_the_title( $pinned ) . '](' . get_permalink( $pinned ) . ')' . ( $exc ? ': ' . $exc : '' ) . "\n";
}
if ( $same_as ) {
foreach ( preg_split( "/\r\n|\n|\r/", $same_as ) as $profile_url ) {
$profile_url = trim( $profile_url );
if ( ! $profile_url || $profile_url[0] === '#' ) { continue; }
$host = preg_replace( '/^www\./', '', parse_url( $profile_url, PHP_URL_HOST ) ?? '' );
$label = ucfirst( explode( '.', $host )[0] ?? 'Profil' );
$pages_block .= '- [' . $disp . ' auf ' . $label . '](' . esc_url( $profile_url ) . '): Berufliches Profil' . "\n";
}
}
$pages_block .= "\n";
// ── Block C: Footer (immer vollständig) ────────────────────────
$footer = "## Nutzungshinweise\n\n";
$footer .= "> Zitate mit Quellenangabe sind erlaubt. Kommerzielle Nutzung ohne Genehmigung ist nicht gestattet.\n";
$footer .= "> Generiert mit [WP Rankify](https://wprankify.de).\n";
if ( $link_sitemap ) {
$footer .= "\n## Vollständige Inhalte\n\n";
$sitemap_url = method_exists( $this, 'sitemap_get_url' ) ? $this->sitemap_get_url() : home_url( '/sitemap_index.xml' );
$footer .= '- [XML-Sitemap](' . esc_url( $sitemap_url ) . '): Vollständiger Index aller URLs' . "\n";
}
if ( $custom ) {
$footer .= "\n" . trim( $custom ) . "\n";
}
// ── Block D: Beiträge – KI-Highlight zuerst, dann Auffüllung ────
// Priorität 1: Von Nutzer als KI-Suche markiert (_aije_ki_highlight)
// oder als Cornerstone (Yoast) / Pillar (Rank Math)
// Davon nur die 5 zuletzt aktualisierten
$ki_posts = get_posts( array(
'post_type' => $o['enabled_post_types'],
'post_status' => 'publish',
'posts_per_page' => 5,
'orderby' => 'modified',
'order' => 'DESC',
'no_found_rows' => true,
'meta_query' => array(
'relation' => 'OR',
array( 'key' => '_aije_ki_highlight', 'value' => '1', 'compare' => '=' ),
array( 'key' => '_yoast_wpseo_is_cornerstone', 'value' => '1', 'compare' => '=' ),
array( 'key' => 'rank_math_pillar_content', 'value' => 'on', 'compare' => '=' ),
),
) );
// Priorität 2: Neueste Beiträge als Auffüllung (ohne bereits enthaltene)
$ki_ids = wp_list_pluck( $ki_posts, 'ID' );
$fill_limit = max( 0, $max_posts - count( $ki_posts ) );
$fill_posts = $fill_limit > 0 ? get_posts( array(
'post_type' => $o['enabled_post_types'],
'post_status' => 'publish',
'posts_per_page' => $fill_limit,
'orderby' => 'date',
'order' => 'DESC',
'no_found_rows' => true,
'post__not_in' => $ki_ids ?: array( 0 ),
) ) : array();
$posts = array_merge( $ki_posts, $fill_posts );
// Jede Beitragszeile vorberechnen
$post_lines = array();
foreach ( $posts as $p ) {
$line = '- [' . get_the_title( $p ) . '](' . get_permalink( $p ) . ')';
if ( $excerpt_len > 0 ) {
$exc = $this->llms_excerpt( $p, $excerpt_len );
if ( $exc ) { $line .= ': ' . $exc; }
}
$post_lines[] = $line;
}
// Solange Zeilen entfernen bis alles passt
while ( ! empty( $post_lines ) ) {
$posts_block = "## Aktuelle Inhalte\n\n";
$posts_block .= implode( "\n", $post_lines ) . "\n\n";
$total = mb_strlen( $header . $pages_block . $posts_block . $footer );
if ( $total <= $limit ) { break; }
array_pop( $post_lines ); // letzten Eintrag entfernen
}
// Falls immer noch zu lang (Header+Pages+Footer allein > Limit): ohne Beiträge
$posts_block = '';
if ( ! empty( $post_lines ) ) {
$posts_block = "## Aktuelle Inhalte\n\n";
$posts_block .= implode( "\n", $post_lines ) . "\n\n";
}
return $header . $pages_block . $posts_block . $footer;
}
/**
* Kurzen Excerpt für Link-Descriptions extrahieren.
*/
private function llms_excerpt( $post, $max = 100 ) {
if ( ! empty( $post->post_excerpt ) ) {
$text = html_entity_decode( wp_strip_all_tags( $post->post_excerpt ), ENT_QUOTES, 'UTF-8' );
return $this->llms_truncate( preg_replace( '/\s+/', ' ', trim( $text ) ), $max );
}
$content = html_entity_decode( wp_strip_all_tags( $post->post_content ), ENT_QUOTES, 'UTF-8' );
$content = preg_replace( '/\s+/', ' ', trim( $content ) );
if ( preg_match( '/^(.{20,' . min( $max, 150 ) . '}[.!?])\s/', $content, $m ) ) {
return trim( $m[1] );
}
return $this->llms_truncate( $content, $max );
}
/**
* Text an Wortgrenze kürzen.
*/
private function llms_truncate( $text, $max ) {
$text = trim( $text );
if ( mb_strlen( $text ) <= $max ) { return $text; }
$t = mb_substr( $text, 0, $max );
$pos = mb_strrpos( $t, ' ' );
return ( $pos ? mb_substr( $t, 0, $pos ) : $t ) . '...';
}
}