Add REST API endpoints for repo folder listing and page pushing

- Implemented `GET /repo-folders` to list available sub-folders in the configured repository.
- Added `POST /push` to push a single page to the repository.
- Introduced `POST /push-all` to push all synced pages back to the repository.
- Enhanced `oribi_sync_rest_sync` to push local changes after pulling, except during dry runs.
- Created `oribi_sync_push_page` and `oribi_sync_push_all` functions to handle page pushing logic.
- Updated post meta on successful pushes to track last push time and SHA.
- Added logging for push actions and errors.

Enhance sync engine to support theme file synchronization

- Added functionality to auto-apply changed theme files from the repository's theme directory.
- Created `oribi_sync_apply_theme_files` to handle theme file updates during sync.
- Ensured the existence of a minimal theme structure in the `ots-theme` directory.

Refactor uninstall process to clean up additional post meta

- Updated `uninstall.php` to remove new post meta related to push operations.
- Ensured comprehensive cleanup of options and metadata upon plugin uninstallation.

Introduce push client for handling page pushes to Gitea

- Created `push-client.php` to encapsulate logic for pushing pages back to the Git repository.
- Implemented conflict resolution by creating branches and opening pull requests when necessary.
- Added helper functions for authenticated API requests to Gitea.
This commit is contained in:
Matt Batchelder
2026-02-20 21:03:48 -05:00
parent f528f21573
commit d2228ed0fb
9 changed files with 1144 additions and 115 deletions

View File

@@ -30,6 +30,33 @@ add_action( 'rest_api_init', function () {
},
] );
// ── 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',
@@ -45,6 +72,12 @@ 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 );
}
@@ -103,3 +136,77 @@ function oribi_sync_rest_webhook( WP_REST_Request $request ): WP_REST_Response {
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 );
}