'POST', 'callback' => 'oribi_sync_rest_sync', 'permission_callback' => function () { return current_user_can( 'manage_options' ); }, ] ); // ── Sync status ─────────────────────────────────────────────────────── register_rest_route( 'oribi-sync/v1', '/status', [ 'methods' => 'GET', 'callback' => 'oribi_sync_rest_status', 'permission_callback' => function () { return current_user_can( 'manage_options' ); }, ] ); // ── List Pages sub-folders from the repo ─────────────────────────── register_rest_route( 'oribi-sync/v1', '/repo-folders', [ 'methods' => 'GET', 'callback' => 'oribi_sync_rest_repo_folders', 'permission_callback' => function () { return current_user_can( 'manage_options' ); }, ] ); // ── Push page to repo ────────────────────────────────────────────────── register_rest_route( 'oribi-sync/v1', '/push', [ 'methods' => 'POST', 'callback' => 'oribi_sync_rest_push', 'permission_callback' => function () { return current_user_can( 'manage_options' ); }, ] ); // ── Push all synced pages to repo ────────────────────────────────────── register_rest_route( 'oribi-sync/v1', '/push-all', [ 'methods' => 'POST', 'callback' => 'oribi_sync_rest_push_all', 'permission_callback' => function () { return current_user_can( 'manage_options' ); }, ] ); // ── Webhook (secret-based auth, no WP login required) ───────────────── register_rest_route( 'oribi-sync/v1', '/webhook', [ 'methods' => 'POST', 'callback' => 'oribi_sync_rest_webhook', 'permission_callback' => '__return_true', // Auth handled in callback ] ); } ); /** * REST: Trigger sync. */ function oribi_sync_rest_sync( WP_REST_Request $request ): WP_REST_Response { $dry_run = (bool) $request->get_param( 'dry_run' ); $result = oribi_sync_run( $dry_run ); // After pulling, push local changes back (skip during dry-run) if ( ! $dry_run ) { $push = oribi_sync_push_all(); $result['push'] = $push['results']; } return new WP_REST_Response( $result, $result['ok'] ? 200 : 500 ); } /** * REST: Get last sync status. */ function oribi_sync_rest_status(): WP_REST_Response { return new WP_REST_Response( [ 'last_run' => get_option( 'oribi_sync_last_run', null ), 'log' => array_slice( get_option( 'oribi_sync_log', [] ), 0, 5 ), 'repo' => get_option( 'oribi_sync_repo', '' ), 'branch' => get_option( 'oribi_sync_branch', 'main' ), 'provider' => oribi_sync_get_provider(), 'has_pat' => ! empty( get_option( 'oribi_sync_pat', '' ) ), ] ); } /** * REST: Webhook trigger. * * Validates using a shared secret stored in the WP option oribi_sync_webhook_secret * or the constant ORIBI_SYNC_WEBHOOK_SECRET. * * Accepts GitHub-style X-Hub-Signature-256 header, or a simple * Authorization: Bearer header. */ function oribi_sync_rest_webhook( WP_REST_Request $request ): WP_REST_Response { $secret = defined( 'ORIBI_SYNC_WEBHOOK_SECRET' ) ? ORIBI_SYNC_WEBHOOK_SECRET : get_option( 'oribi_sync_webhook_secret', '' ); if ( empty( $secret ) ) { return new WP_REST_Response( [ 'error' => 'Webhook secret not configured.' ], 403 ); } // Check Authorization: Bearer $auth = $request->get_header( 'Authorization' ); if ( $auth && preg_match( '/^Bearer\s+(.+)$/i', $auth, $m ) ) { if ( ! hash_equals( $secret, $m[1] ) ) { return new WP_REST_Response( [ 'error' => 'Invalid secret.' ], 403 ); } } // Check GitHub X-Hub-Signature-256 elseif ( $sig = $request->get_header( 'X-Hub-Signature-256' ) ) { $body = $request->get_body(); $expected = 'sha256=' . hash_hmac( 'sha256', $body, $secret ); if ( ! hash_equals( $expected, $sig ) ) { return new WP_REST_Response( [ 'error' => 'Invalid signature.' ], 403 ); } } else { return new WP_REST_Response( [ 'error' => 'Missing authentication.' ], 403 ); } // Run sync $result = oribi_sync_run(); return new WP_REST_Response( $result, $result['ok'] ? 200 : 500 ); } /** * REST: Push a single page to the repo. */ function oribi_sync_rest_push( WP_REST_Request $request ): WP_REST_Response { $post_id = (int) $request->get_param( 'post_id' ); if ( $post_id < 1 ) { return new WP_REST_Response( [ 'ok' => false, 'message' => 'Missing or invalid post_id.' ], 400 ); } $opts = []; $message = $request->get_param( 'message' ); if ( ! empty( $message ) ) { $opts['message'] = sanitize_text_field( $message ); } $result = oribi_sync_push_page( $post_id, $opts ); return new WP_REST_Response( $result, $result['ok'] ? 200 : 500 ); } /** * REST: Push all synced pages to the repo. */ function oribi_sync_rest_push_all( WP_REST_Request $request ): WP_REST_Response { $result = oribi_sync_push_all(); return new WP_REST_Response( $result, $result['ok'] ? 200 : 500 ); } /** * REST: List available sub-folders under Pages/ in the configured repository. * * Returns a JSON object: { folders: ["folder-a", "folder-b", …] } */ function oribi_sync_rest_repo_folders(): WP_REST_Response { $repo_url = get_option( 'oribi_sync_repo', '' ); $branch = get_option( 'oribi_sync_branch', 'main' ) ?: 'main'; $pat = oribi_sync_get_pat(); if ( empty( $repo_url ) || empty( $pat ) ) { return new WP_REST_Response( [ 'error' => 'Repository URL or PAT is not configured.' ], 400 ); } $parsed = oribi_sync_parse_repo_url( $repo_url ); if ( is_wp_error( $parsed ) ) { return new WP_REST_Response( [ 'error' => $parsed->get_error_message() ], 400 ); } $provider = oribi_sync_get_provider(); $api_base = oribi_sync_api_base( $provider, $parsed ); $tree = oribi_sync_fetch_tree( $api_base, $branch, $provider, $pat ); if ( is_wp_error( $tree ) ) { return new WP_REST_Response( [ 'error' => 'Tree fetch failed: ' . $tree->get_error_message() ], 500 ); } // Find direct sub-directories of Pages/ $folders = []; $prefix = 'Pages/'; foreach ( $tree as $entry ) { if ( $entry['type'] !== 'tree' ) continue; if ( strpos( $entry['path'], $prefix ) !== 0 ) continue; $relative = substr( $entry['path'], strlen( $prefix ) ); // Only direct children (no nested slash) if ( strpos( $relative, '/' ) !== false ) continue; if ( $relative === '' ) continue; $folders[] = $relative; } sort( $folders ); return new WP_REST_Response( [ 'folders' => $folders ], 200 ); }