'GET', 'callback' => 'rbr_get_all_post_ids', 'permission_callback' => '__return_true', 'args' => array( 'post_type' => array( 'default' => 'post', 'sanitize_callback' => 'sanitize_text_field', ), 'per_page' => array( 'default' => -1, 'sanitize_callback' => 'rbr_sanitize_per_page', ), ), )); // 2. Endpunkt: Content & Medien für eine bestimmte Post-ID register_rest_route('custom/v1', '/post-details/(?P\d+)', array( 'methods' => 'GET', 'callback' => 'rbr_get_post_details_with_media', 'permission_callback' => '__return_true', )); // 3. Endpunkt: Kategorien/Tags für alle Beiträge register_rest_route('custom/v1', '/post-taxonomies/(?P\d+)', array( 'methods' => 'GET', 'callback' => 'rbr_get_post_taxonomies', 'permission_callback' => '__return_true', )); // 4. Endpunkt: Alle Medien-Dateien (Attachments) abrufen register_rest_route('custom/v1', '/media', array( 'methods' => 'GET', 'callback' => 'rbr_get_all_media', 'permission_callback' => '__return_true', 'args' => array( 'per_page' => array( 'default' => -1, 'sanitize_callback' => 'rbr_sanitize_per_page', ), ), )); // 5. Endpunkt: Medien-Datei direkt über ID abrufen register_rest_route('custom/v1', '/media/(?P\d+)', array( 'methods' => 'GET', 'callback' => 'rbr_get_media_by_id', 'permission_callback' => '__return_true', )); }); /** * Liefert ein Array mit allen veröffentlichten Post-IDs. * Unterstützt verschiedene Post-Types (post, page, custom). */ function rbr_get_all_post_ids($request) { $post_type = sanitize_text_field($request['post_type']); $per_page = rbr_sanitize_per_page($request['per_page']); $args = array( 'post_type' => $post_type, 'post_status' => 'publish', 'numberposts' => $per_page, 'fields' => 'ids', 'orderby' => 'date', 'order' => 'ASC', ); $post_ids = get_posts($args); if (empty($post_ids)) { return new WP_Error('no_posts', 'Keine Beiträge gefunden.', array('status' => 404)); } return rest_ensure_response(array( 'count' => count($post_ids), 'post_type' => $post_type, 'ids' => $post_ids, )); } /** * Liefert Content, Featured Image, Kategorien, Keywords, Comments und alle im HTML/Post genutzten Medien-URLs. */ function rbr_get_post_details_with_media($data) { $post_id = intval($data['id']); $post = get_post($post_id); if (!$post || $post->post_status !== 'publish') { return new WP_Error('no_post', 'Beitrag nicht gefunden.', array('status' => 404)); } $content = apply_filters('the_content', $post->post_content); $media_urls = array(); // 1. Featured Image (Titelbild) auslesen $featured_image_url = ''; $featured_image_id = get_post_thumbnail_id($post_id); if ($featured_image_id) { $featured_image_url = wp_get_attachment_url($featured_image_id); if ($featured_image_url) { $media_urls[] = $featured_image_url; } } // 2. Inhaltsbilder aus dem HTML extrahieren (via Regex für Tags) preg_match_all('/]+src=["\']([^"\']+)["\']/i', $post->post_content, $matches); if (!empty($matches[1])) { foreach ($matches[1] as $img_url) { if (filter_var($img_url, FILTER_VALIDATE_URL)) { $media_urls[] = $img_url; } } } // 3. Attachments die mit dem Post verknüpft sind $attachments = get_posts(array( 'post_type' => 'attachment', 'numberposts' => -1, 'post_status' => null, 'post_parent' => $post_id, 'fields' => 'ids', 'post_mime_type' => 'image', )); foreach ($attachments as $att_id) { $att_url = wp_get_attachment_url($att_id); if ($att_url) { $media_urls[] = $att_url; } } // Duplikate entfernen $unique_media = array_values(array_unique($media_urls)); // 4. Kategorien - bestimme das Primäre (erste Kategorie oder nach slug sortiert) $categories = array(); $primary_category = null; $cats = get_the_category($post_id); if (!empty($cats) && !is_wp_error($cats)) { // Sortiere nach term_id (älteste/erste Kategorie wird primär) usort($cats, function($a, $b) { return $a->term_id <=> $b->term_id; }); foreach ($cats as $cat) { $is_primary = ($cat->term_id === $cats[0]->term_id); $categories[] = array( 'id' => intval($cat->term_id), 'name' => $cat->name, 'slug' => $cat->slug, 'primary' => $is_primary, ); if ($is_primary) { $primary_category = $cat->name; } } } // 5. Tags (post_tag Taxonomy) $tags = array(); $post_tags = get_the_tags($post_id); if (!empty($post_tags) && !is_wp_error($post_tags)) { foreach ($post_tags as $tag) { $tags[] = array( 'id' => intval($tag->term_id), 'name' => $tag->name, 'slug' => $tag->slug, ); } } // 6. Keywords (aus Yoast SEO oder Rank Math oder Custom Field) $keywords = ''; // Yoast SEO: Prüfe auf Meta-Key direkt (funktioniert in allen Versionen) $yoast_kw = get_post_meta($post_id, '_yoast_wpseo_focuskw', true); if (!empty($yoast_kw)) { $keywords = $yoast_kw; } // Rank Math if (empty($keywords) && function_exists('rank_math_get_single_post_data')) { $rank_data = rank_math_get_single_post_data($post_id); if (method_exists($rank_data, 'get_keywords')) { $kw = $rank_data->get_keywords(); if (!empty($kw)) { $keywords = $kw; } } } // Fallback: Custom Field 'post_keywords' if (empty($keywords)) { $keywords = get_post_meta($post_id, 'post_keywords', true); } // Fallback: Tags als Keywords if (empty($keywords) && !empty($tags)) { $keywords = implode(', ', array_column($tags, 'name')); } // Fallback: Kategorien als Keywords if (empty($keywords) && !empty($categories)) { $keywords = implode(', ', array_column($categories, 'name')); } // 6. Kommentare $comments = array(); $wp_comments = get_comments(array( 'post_id' => $post_id, 'status' => 'approve', 'number' => 500, 'orderby' => 'comment_date_gmt', 'order' => 'ASC', )); foreach ($wp_comments as $comment) { $comments[] = array( 'id' => intval($comment->comment_ID), 'author_name' => $comment->comment_author, 'author_email' => $comment->comment_author_email, 'author_url' => $comment->comment_author_url, 'content' => $comment->comment_content, 'date' => mysql_to_rfc3339($comment->comment_date), // Typ: 'comment', 'pingback', 'trackback' oder Reaktionen anderer Plugins ('like', 'repost') 'type' => get_comment_type($comment), // Antwort auf diesen Kommentar (0 = keine Antwort) 'parent' => intval($comment->comment_parent), ); } $response = array( 'id' => intval($post->ID), 'title' => $post->post_title, 'slug' => $post->post_name, 'date' => mysql_to_rfc3339($post->post_date), // Dasselbe Datum in UTC, damit die Zeitzone beim Import eindeutig ist 'date_gmt' => mysql_to_rfc3339($post->post_date_gmt), 'content' => $content, 'excerpt' => wp_trim_words(strip_tags($post->post_excerpt), 55), 'author' => array( 'id' => intval($post->post_author), 'name' => get_the_author_meta('display_name', $post->post_author), ), 'featured_image' => $featured_image_url, 'categories' => $categories, 'primary_category' => $primary_category, 'tags' => $tags, 'keywords' => $keywords, 'comments' => $comments, 'media_files' => $unique_media, ); return rest_ensure_response($response); } /** * Liefert Kategorien und Tags für einen bestimmten Beitrag. */ function rbr_get_post_taxonomies($data) { $post_id = intval($data['id']); $post = get_post($post_id); if (!$post || $post->post_status !== 'publish') { return new WP_Error('no_post', 'Beitrag nicht gefunden.', array('status' => 404)); } $taxonomies = get_object_taxonomies('post'); $terms = array(); foreach ($taxonomies as $taxonomy) { $terms_list = get_the_terms($post_id, $taxonomy); if (!empty($terms_list) && !is_wp_error($terms_list)) { foreach ($terms_list as $term) { $terms[] = array( 'id' => intval($term->term_id), 'name' => $term->name, 'slug' => $term->slug, 'type' => $taxonomy, ); } } } return rest_ensure_response(array( 'post_id' => $post_id, 'taxonomies' => $terms, )); } /** * Liefert alle Medien-Dateien (Attachments) der WordPress-Instanz. */ function rbr_get_all_media($request) { $per_page = rbr_sanitize_per_page($request['per_page']); $args = array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'numberposts' => $per_page, 'orderby' => 'date', 'order' => 'ASC', 'post_mime_type' => array('image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'), ); $attachments = get_posts($args); $media_list = array(); foreach ($attachments as $att) { $media_list[] = array( 'id' => intval($att->ID), 'url' => wp_get_attachment_url($att->ID), 'title' => $att->post_title, 'alt' => get_post_meta($att->ID, '_wp_attachment_image_alt', true), 'mime' => $att->post_mime_type, 'date' => mysql_to_rfc3339($att->post_date), ); } return rest_ensure_response(array( 'count' => count($media_list), 'media' => $media_list, )); } /** * Liefert eine einzelne Medien-Datei anhand ihrer ID. */ function rbr_get_media_by_id($data) { $media_id = intval($data['id']); $attachment = get_post($media_id); if (!$attachment || $attachment->post_type !== 'attachment') { return new WP_Error('no_media', 'Medien-Datei nicht gefunden.', array('status' => 404)); } return rest_ensure_response(array( 'id' => intval($attachment->ID), 'url' => wp_get_attachment_url($attachment->ID), 'title' => $attachment->post_title, 'alt' => get_post_meta($attachment->ID, '_wp_attachment_image_alt', true), 'mime' => $attachment->post_mime_type, 'date' => mysql_to_rfc3339($attachment->post_date), )); } /** * Shortcode: [rbr_export] – Zeigt eine Export-Statistik im Frontend an. */ function rbr_export_shortcode($atts) { $atts = shortcode_atts(array( 'post_type' => 'post', ), $atts, 'rbr_export'); $count = wp_count_posts($atts['post_type']); $published = $count->publish ?? 0; return sprintf( '

Es gibt %d veröffentlichte %s-Beiträge, die über die REST-API exportiert werden können.

', intval($published), esc_html($atts['post_type']) ); } add_shortcode('rbr_export', 'rbr_export_shortcode'); /** * Admin-Notiz: Plugin ist aktiv und bereit für den Export. */ add_action('admin_notices', function () { if (current_user_can('manage_options')) { echo '

'; printf( __('RBR Export to Ghost ist aktiv. REST-API-Endpunkte stehen unter /wp-json/custom/v1/ zur Verfügung.', 'rbr-export-ghost'), admin_url('tools.php') ); echo '

'; } }); /** * CORS-Header erlauben (falls nötig für den C#-Importer) */ add_filter('rest_pre_serve_request', function ($served, $result, $request) { if (strpos($request->get_route(), 'custom/v1/') === 0) { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, OPTIONS'); header('Access-Control-Allow-Headers: Authorization, Content-Type'); } return $served; }, 10, 3); add_action('rest_api_init', function () { // OPTIONS-Requests für CORS erlauben register_rest_route('custom/v1', '/cors', array( 'methods' => 'OPTIONS', 'callback' => function () { return new WP_REST_Response(null, 200); }, 'permission_callback' => '__return_true', )); });