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:
@@ -90,12 +90,13 @@ function oribi_sync_execute_php( string $php_source, string $slug ) {
|
||||
*/
|
||||
function oribi_sync_run( bool $dry_run = false ): array {
|
||||
$result = [
|
||||
'ok' => true,
|
||||
'created' => [],
|
||||
'updated' => [],
|
||||
'trashed' => [],
|
||||
'skipped' => [],
|
||||
'errors' => [],
|
||||
'ok' => true,
|
||||
'created' => [],
|
||||
'updated' => [],
|
||||
'trashed' => [],
|
||||
'skipped' => [],
|
||||
'errors' => [],
|
||||
'theme_updated' => [],
|
||||
];
|
||||
|
||||
// ── Gather settings ────────────────────────────────────────────────────
|
||||
@@ -128,18 +129,21 @@ function oribi_sync_run( bool $dry_run = false ): array {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// ── Filter to pages/ directory ─────────────────────────────────────────
|
||||
$page_files = oribi_sync_filter_tree( $tree, 'pages' );
|
||||
// ── Filter to selected Pages sub-folder ────────────────────────────────
|
||||
$pages_folder = get_option( 'oribi_sync_pages_folder', '' );
|
||||
$synced_slugs = [];
|
||||
|
||||
if ( empty( $page_files ) ) {
|
||||
$result['errors'][] = 'No files found under pages/ in the repository.';
|
||||
$result['ok'] = false;
|
||||
return $result;
|
||||
if ( empty( $pages_folder ) ) {
|
||||
$result['skipped'][] = 'Pages sync skipped — no folder selected in settings.';
|
||||
$page_files = [];
|
||||
} else {
|
||||
$page_files = oribi_sync_filter_tree( $tree, 'Pages/' . $pages_folder );
|
||||
if ( empty( $page_files ) ) {
|
||||
$result['errors'][] = 'No files found under Pages/' . $pages_folder . '/ in the repository.';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Process each page file ─────────────────────────────────────────────
|
||||
$synced_slugs = [];
|
||||
|
||||
foreach ( $page_files as $entry ) {
|
||||
$filename = basename( $entry['path'] );
|
||||
$slug = oribi_sync_filename_to_slug( $filename );
|
||||
@@ -239,7 +243,8 @@ function oribi_sync_run( bool $dry_run = false ): array {
|
||||
update_post_meta( $existing->ID, '_oribi_sync_last_run', current_time( 'mysql' ) );
|
||||
update_post_meta( $existing->ID, '_wp_page_template', 'default' );
|
||||
|
||||
$result['updated'][] = $slug;
|
||||
$content_size = strlen( $content );
|
||||
$result['updated'][] = $slug . ' (' . $content_size . ' bytes)';
|
||||
} else {
|
||||
// Create new page
|
||||
$title = oribi_sync_slug_to_title( $slug );
|
||||
@@ -263,16 +268,25 @@ function oribi_sync_run( bool $dry_run = false ): array {
|
||||
update_post_meta( $post_id, '_oribi_sync_last_run', current_time( 'mysql' ) );
|
||||
update_post_meta( $post_id, '_wp_page_template', 'default' );
|
||||
|
||||
$result['created'][] = $slug;
|
||||
$content_size = strlen( $content );
|
||||
$result['created'][] = $slug . ' (' . $content_size . ' bytes)';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Trash pages removed from repo ──────────────────────────────────────
|
||||
if ( ! $dry_run ) {
|
||||
if ( ! $dry_run && ! empty( $pages_folder ) ) {
|
||||
$trashed = oribi_sync_trash_removed_pages( $synced_slugs );
|
||||
$result['trashed'] = $trashed;
|
||||
}
|
||||
|
||||
// ── Auto-sync theme files ──────────────────────────────────────────────
|
||||
// Reuse the already-fetched $tree so we don't make a second API call.
|
||||
$theme_sync = oribi_sync_apply_theme_files( $api_base, $branch, $provider, $pat, $dry_run, $tree );
|
||||
$result['theme_updated'] = $theme_sync['updated'];
|
||||
foreach ( $theme_sync['errors'] as $err ) {
|
||||
$result['errors'][] = '[theme] ' . $err;
|
||||
}
|
||||
|
||||
// ── Record run ─────────────────────────────────────────────────────────
|
||||
if ( ! $dry_run ) {
|
||||
oribi_sync_record_run( $result );
|
||||
@@ -357,12 +371,13 @@ function oribi_sync_record_run( array $result ): void {
|
||||
if ( ! is_array( $log ) ) $log = [];
|
||||
|
||||
array_unshift( $log, [
|
||||
'time' => current_time( 'mysql' ),
|
||||
'created' => $result['created'],
|
||||
'updated' => $result['updated'],
|
||||
'trashed' => $result['trashed'],
|
||||
'skipped' => $result['skipped'],
|
||||
'errors' => $result['errors'],
|
||||
'time' => current_time( 'mysql' ),
|
||||
'created' => $result['created'],
|
||||
'updated' => $result['updated'],
|
||||
'trashed' => $result['trashed'],
|
||||
'skipped' => $result['skipped'],
|
||||
'errors' => $result['errors'],
|
||||
'theme_updated' => $result['theme_updated'] ?? [],
|
||||
] );
|
||||
|
||||
// Keep last 20 entries
|
||||
@@ -370,6 +385,125 @@ function oribi_sync_record_run( array $result ): void {
|
||||
update_option( 'oribi_sync_log', $log, 'no' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the ots-theme directory exists with a minimal style.css header.
|
||||
*
|
||||
* WordPress requires style.css with a "Theme Name:" header to recognise a theme.
|
||||
* If the repo's theme/ folder already contains a style.css it will overwrite
|
||||
* this stub during sync, so the stub is only a bootstrap.
|
||||
*/
|
||||
function oribi_sync_ensure_ots_theme( string $theme_dir ): void {
|
||||
if ( is_dir( $theme_dir ) && file_exists( $theme_dir . '/style.css' ) ) {
|
||||
return; // Already exists.
|
||||
}
|
||||
|
||||
if ( ! is_dir( $theme_dir ) ) {
|
||||
wp_mkdir_p( $theme_dir );
|
||||
}
|
||||
|
||||
// Only write the stub if style.css doesn't exist yet.
|
||||
if ( ! file_exists( $theme_dir . '/style.css' ) ) {
|
||||
$header = <<<'CSS'
|
||||
/*
|
||||
Theme Name: OTS Theme
|
||||
Description: Auto-created by Oribi Tech Sync. Theme files are managed via Git.
|
||||
Version: 1.0.0
|
||||
Author: Oribi Technology Services
|
||||
*/
|
||||
|
||||
CSS;
|
||||
file_put_contents( $theme_dir . '/style.css', $header );
|
||||
}
|
||||
|
||||
// Create a minimal index.php (required by WP theme standards).
|
||||
if ( ! file_exists( $theme_dir . '/index.php' ) ) {
|
||||
file_put_contents( $theme_dir . '/index.php', "<?php\n// Silence is golden.\n" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-apply changed theme files from the repo's theme/ directory to ots-theme.
|
||||
*
|
||||
* Called as part of every sync run.
|
||||
*
|
||||
* @param string $api_base Provider API base URL.
|
||||
* @param string $branch Branch name.
|
||||
* @param string $provider Provider key.
|
||||
* @param string $pat Personal access token.
|
||||
* @param bool $dry_run If true, report changes without writing files.
|
||||
*
|
||||
* @return array{updated: string[], errors: string[]}
|
||||
*/
|
||||
function oribi_sync_apply_theme_files( string $api_base, string $branch, string $provider, string $pat, bool $dry_run = false, ?array $tree = null ): array {
|
||||
$out = [ 'updated' => [], 'errors' => [] ];
|
||||
$allowed = [ 'css', 'js', 'json', 'php', 'html', 'htm', 'svg', 'txt' ];
|
||||
$theme_dir = get_theme_root() . '/ots-theme';
|
||||
|
||||
// Create the ots-theme if it does not exist yet.
|
||||
if ( ! $dry_run ) {
|
||||
oribi_sync_ensure_ots_theme( $theme_dir );
|
||||
}
|
||||
|
||||
// Fetch the tree only if one wasn't passed in (avoids a redundant API call during sync).
|
||||
if ( $tree === null ) {
|
||||
$tree = oribi_sync_fetch_tree( $api_base, $branch, $provider, $pat );
|
||||
if ( is_wp_error( $tree ) ) {
|
||||
$out['errors'][] = 'Tree fetch failed: ' . $tree->get_error_message();
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
$theme_entries = oribi_sync_filter_tree( $tree, 'theme', true );
|
||||
|
||||
foreach ( $theme_entries as $entry ) {
|
||||
$relative = substr( $entry['path'], strlen( 'theme/' ) );
|
||||
$ext = strtolower( pathinfo( $relative, PATHINFO_EXTENSION ) );
|
||||
|
||||
if ( ! in_array( $ext, $allowed, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch content from repo
|
||||
$content = oribi_sync_fetch_file( $api_base, $branch, $entry['path'], $provider, $pat );
|
||||
if ( is_wp_error( $content ) ) {
|
||||
$out['errors'][] = $entry['path'] . ': ' . $content->get_error_message();
|
||||
continue;
|
||||
}
|
||||
|
||||
$dest = $theme_dir . '/' . $relative;
|
||||
$local_exists = file_exists( $dest );
|
||||
$local_content = $local_exists ? file_get_contents( $dest ) : null;
|
||||
|
||||
// Skip unchanged files
|
||||
if ( $local_exists && $local_content === $content ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $dry_run ) {
|
||||
$out['updated'][] = $relative;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create subdirectory if needed
|
||||
$dir = dirname( $dest );
|
||||
if ( ! is_dir( $dir ) ) {
|
||||
if ( ! wp_mkdir_p( $dir ) ) {
|
||||
$out['errors'][] = $relative . ' — could not create directory.';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$written = file_put_contents( $dest, $content );
|
||||
if ( $written === false ) {
|
||||
$out['errors'][] = $relative . ' — write failed (check permissions).';
|
||||
} else {
|
||||
$out['updated'][] = $relative;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch theme files from the repo (for preview / apply).
|
||||
*
|
||||
@@ -414,9 +548,10 @@ function oribi_sync_fetch_theme_files(): array {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if a matching file exists in the active theme
|
||||
$theme_file = get_template_directory() . '/' . $relative;
|
||||
$local_exists = file_exists( $theme_file );
|
||||
// Check if a matching file exists in the ots-theme
|
||||
$theme_dir = get_theme_root() . '/ots-theme';
|
||||
$theme_file = $theme_dir . '/' . $relative;
|
||||
$local_exists = file_exists( $theme_file );
|
||||
$local_content = $local_exists ? file_get_contents( $theme_file ) : null;
|
||||
|
||||
$out['files'][] = [
|
||||
|
||||
Reference in New Issue
Block a user