diff --git a/dist/oribi-tech-sync.zip b/dist/oribi-tech-sync.zip index e1ed639..75a0610 100644 Binary files a/dist/oribi-tech-sync.zip and b/dist/oribi-tech-sync.zip differ diff --git a/includes/admin.php b/includes/admin.php index 35333c3..cfcd30c 100644 --- a/includes/admin.php +++ b/includes/admin.php @@ -8,27 +8,47 @@ if ( ! defined( 'ABSPATH' ) ) exit; -// ─── Admin bar pull button (front-end) ────────────────────────────────────── +// ─── Admin bar pull buttons (front-end) ───────────────────────────────────── add_action( 'admin_bar_menu', function ( WP_Admin_Bar $wp_admin_bar ) { - // Front-end only, logged-in admins, singular pages/posts if ( is_admin() ) return; if ( ! is_user_logged_in() ) return; if ( ! current_user_can( 'manage_options' ) ) return; - if ( ! is_singular() ) return; - - $post = get_queried_object(); - if ( ! $post instanceof WP_Post ) return; + // "Pull All" — visible everywhere on the front-end $wp_admin_bar->add_node( [ - 'id' => 'oribi-sync-pull', - 'title' => 'Pull Page', + 'id' => 'oribi-sync-pull-all', + 'title' => 'Pull All', 'href' => '#', 'meta' => [ - 'title' => 'Pull this page and theme from Git', + 'title' => 'Pull all pages and theme from Git', ], ] ); + + // "Pull Page" — only on singular pages/posts + if ( is_singular() ) { + $post = get_queried_object(); + if ( $post instanceof WP_Post ) { + $wp_admin_bar->add_node( [ + 'id' => 'oribi-sync-pull', + 'title' => 'Pull Page', + 'href' => '#', + 'meta' => [ + 'title' => 'Pull this page and theme from Git', + ], + ] ); + } + } }, 100 ); +// AJAX handler for the admin bar "Pull All" button +add_action( 'wp_ajax_oribi_sync_pull_all_pages', function () { + check_ajax_referer( 'oribi_sync_pull_all_pages' ); + if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Permission denied.', 403 ); + + $result = oribi_sync_run(); + $result['ok'] ? wp_send_json_success( $result ) : wp_send_json_error( $result, 500 ); +} ); + // AJAX handler for the admin bar pull button (no REST API exposure) add_action( 'wp_ajax_oribi_sync_pull_page', function () { check_ajax_referer( 'oribi_sync_pull_page' ); @@ -41,6 +61,57 @@ add_action( 'wp_ajax_oribi_sync_pull_page', function () { $result['ok'] ? wp_send_json_success( $result ) : wp_send_json_error( $result, 500 ); } ); +// Front-end script for the "Pull All" admin bar button +add_action( 'wp_footer', function () { + if ( ! is_user_logged_in() ) return; + if ( ! current_user_can( 'manage_options' ) ) return; + if ( ! is_admin_bar_showing() ) return; + + $ajax_url = admin_url( 'admin-ajax.php' ); + $nonce_all = wp_create_nonce( 'oribi_sync_pull_all_pages' ); + ?> + + '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' ); - }, - ] ); - - // ── 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. - */ diff --git a/oribi-tech-sync.php b/oribi-tech-sync.php index b0e535c..264bb06 100644 --- a/oribi-tech-sync.php +++ b/oribi-tech-sync.php @@ -22,7 +22,6 @@ require_once ORIBI_SYNC_DIR . 'includes/sync-engine.php'; require_once ORIBI_SYNC_DIR . 'includes/push-client.php'; require_once ORIBI_SYNC_DIR . 'includes/post-sync.php'; require_once ORIBI_SYNC_DIR . 'includes/admin.php'; -require_once ORIBI_SYNC_DIR . 'includes/rest.php'; require_once ORIBI_SYNC_DIR . 'includes/theme-preview.php'; // ─── Activation / Deactivation ────────────────────────────────────────────────