Refactor Oribi Sync settings: remove pages folder option and enhance case-insensitive directory handling
This commit is contained in:
BIN
dist/oribi-tech-sync.zip
vendored
BIN
dist/oribi-tech-sync.zip
vendored
Binary file not shown.
@@ -35,12 +35,9 @@ add_action( 'admin_post_oribi_sync_save_settings', function () {
|
||||
$provider = sanitize_text_field( wp_unslash( $_POST['oribi_sync_provider'] ?? '' ) );
|
||||
$pat = wp_unslash( $_POST['oribi_sync_pat'] ?? '' );
|
||||
|
||||
$pages_folder = sanitize_text_field( wp_unslash( $_POST['oribi_sync_pages_folder'] ?? '' ) );
|
||||
|
||||
update_option( 'oribi_sync_repo', $repo, 'no' );
|
||||
update_option( 'oribi_sync_branch', $branch, 'no' );
|
||||
update_option( 'oribi_sync_provider', $provider, 'no' );
|
||||
update_option( 'oribi_sync_pages_folder', $pages_folder, 'no' );
|
||||
|
||||
// Only update PAT if a new one was provided (non-empty)
|
||||
if ( ! empty( $pat ) ) {
|
||||
@@ -135,7 +132,6 @@ function oribi_sync_settings_page() {
|
||||
$repo = get_option( 'oribi_sync_repo', '' );
|
||||
$branch = get_option( 'oribi_sync_branch', 'main' );
|
||||
$provider = get_option( 'oribi_sync_provider', '' );
|
||||
$pages_folder = get_option( 'oribi_sync_pages_folder', '' );
|
||||
$has_pat = ! empty( get_option( 'oribi_sync_pat', '' ) );
|
||||
$last_run = get_option( 'oribi_sync_last_run', '' );
|
||||
$log = get_option( 'oribi_sync_log', [] );
|
||||
@@ -251,22 +247,6 @@ function oribi_sync_settings_page() {
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="oribi_sync_pages_folder">Pages Folder</label></th>
|
||||
<td>
|
||||
<select name="oribi_sync_pages_folder" id="oribi_sync_pages_folder" class="regular-text">
|
||||
<option value="">— Select —</option>
|
||||
<?php if ( ! empty( $pages_folder ) ): ?>
|
||||
<option value="<?php echo esc_attr( $pages_folder ); ?>" selected>
|
||||
<?php echo esc_html( $pages_folder ); ?>
|
||||
</option>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
<button type="button" id="oribi-load-folders" class="button">Load</button>
|
||||
<span id="oribi-folders-status" class="oribi-sync-muted"></span>
|
||||
<p class="description">Sub-folder inside <code>Pages/</code> to sync from.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<?php submit_button( 'Save Settings' ); ?>
|
||||
@@ -386,42 +366,6 @@ function oribi_sync_settings_page() {
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var btn = document.getElementById('oribi-load-folders');
|
||||
var select = document.getElementById('oribi_sync_pages_folder');
|
||||
var status = document.getElementById('oribi-folders-status');
|
||||
if ( ! btn || ! select ) return;
|
||||
|
||||
btn.addEventListener('click', function () {
|
||||
status.textContent = 'Loading…';
|
||||
btn.disabled = true;
|
||||
|
||||
fetch('<?php echo esc_js( rest_url( 'oribi-sync/v1/repo-folders' ) ); ?>', {
|
||||
method: 'GET',
|
||||
headers: { 'X-WP-Nonce': '<?php echo esc_js( wp_create_nonce( 'wp_rest' ) ); ?>' }
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
if ( data.error ) { status.textContent = '✗ ' + data.error; btn.disabled = false; return; }
|
||||
var currentVal = select.value;
|
||||
while ( select.options.length > 1 ) select.remove(1);
|
||||
if ( ! data.folders || ! data.folders.length ) { status.textContent = 'No folders found.'; btn.disabled = false; return; }
|
||||
data.folders.forEach(function (f) {
|
||||
var o = document.createElement('option');
|
||||
o.value = f; o.textContent = f;
|
||||
if ( f === currentVal ) o.selected = true;
|
||||
select.appendChild(o);
|
||||
});
|
||||
if ( currentVal && select.querySelector('option[value="' + currentVal + '"]') ) select.value = currentVal;
|
||||
status.textContent = data.folders.length + ' folder(s).';
|
||||
btn.disabled = false;
|
||||
})
|
||||
.catch(function () { status.textContent = '✗ Request failed.'; btn.disabled = false; });
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
@@ -384,21 +384,23 @@ function oribi_sync_fetch_file( string $api_base, string $branch, string $file_p
|
||||
|
||||
/**
|
||||
* Filter tree entries to only those under a given directory prefix.
|
||||
* Matching is case-insensitive so Pages/, pages/, PAGES/ etc. all work.
|
||||
*
|
||||
* @param array $tree Tree from oribi_sync_fetch_tree().
|
||||
* @param string $prefix Directory prefix (e.g. 'pages/').
|
||||
* @param string $prefix Directory prefix (e.g. 'Pages').
|
||||
* @param bool $recursive Whether to include files in subdirectories (default: false).
|
||||
*
|
||||
* @return array Filtered entries (blobs only).
|
||||
*/
|
||||
function oribi_sync_filter_tree( array $tree, string $prefix, bool $recursive = false ): array {
|
||||
$prefix = rtrim( $prefix, '/' ) . '/';
|
||||
$plen = strlen( $prefix );
|
||||
$out = [];
|
||||
|
||||
foreach ( $tree as $entry ) {
|
||||
if ( $entry['type'] !== 'blob' ) continue;
|
||||
if ( strpos( $entry['path'], $prefix ) !== 0 ) continue;
|
||||
$relative = substr( $entry['path'], strlen( $prefix ) );
|
||||
if ( strncasecmp( $entry['path'], $prefix, $plen ) !== 0 ) continue;
|
||||
$relative = substr( $entry['path'], $plen );
|
||||
// Skip sub-directory files unless recursive is enabled
|
||||
if ( ! $recursive && strpos( $relative, '/' ) !== false ) continue;
|
||||
$out[] = $entry;
|
||||
|
||||
@@ -11,6 +11,39 @@
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Detect the actual casing of the Pages directory in the repo tree.
|
||||
*
|
||||
* Looks at existing synced pages for a stored repo path, extracts the
|
||||
* directory prefix. Falls back to 'Pages/' if nothing found.
|
||||
*/
|
||||
function oribi_sync_detect_pages_prefix(): string {
|
||||
// Check post meta of any previously-synced page for the real path
|
||||
$existing = get_posts( [
|
||||
'post_type' => 'page',
|
||||
'meta_key' => '_oribi_sync_source',
|
||||
'numberposts' => 1,
|
||||
'fields' => 'ids',
|
||||
] );
|
||||
|
||||
if ( ! empty( $existing ) ) {
|
||||
$source = get_post_meta( $existing[0], '_oribi_sync_source', true );
|
||||
// Extract the repo-path portion after the last colon (skip 'https:').
|
||||
$colon = strrpos( $source, ':' );
|
||||
if ( $colon !== false ) {
|
||||
$path_part = substr( $source, $colon + 1 ); // e.g. 'pages/about.php'
|
||||
// Validate it looks like a pages/ path before trusting it.
|
||||
if ( strncasecmp( $path_part, 'pages/', 6 ) === 0 ) {
|
||||
return substr( $path_part, 0, 6 ); // preserve original casing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'pages/';
|
||||
}
|
||||
|
||||
// ─── Auto-push on page save ──────────────────────────────────────────────────
|
||||
add_action( 'save_post_page', 'oribi_sync_maybe_push_on_save', 20, 3 );
|
||||
|
||||
@@ -242,18 +275,15 @@ function oribi_sync_generate_php_wrapper( string $content, string $slug, string
|
||||
$title = oribi_sync_slug_to_title( $slug );
|
||||
}
|
||||
|
||||
$date = current_time( 'Y-m-d H:i:s' );
|
||||
|
||||
// Use a nowdoc so the content is treated as a literal string (no interpolation).
|
||||
// Escape the content if it accidentally contains the heredoc delimiter on its own line.
|
||||
$delimiter = 'ORIBI_SYNC_CONTENT';
|
||||
$safe_content = str_replace( "\n" . $delimiter, "\n " . $delimiter, $content );
|
||||
|
||||
$php = "<?php\n";
|
||||
$php .= "/**\n";
|
||||
$php .= " * Page: {$title}\n";
|
||||
$php .= "/*\n";
|
||||
$php .= " * Title: {$title}\n";
|
||||
$php .= " * Slug: {$slug}\n";
|
||||
$php .= " * Pushed by Oribi Tech Sync on {$date}.\n";
|
||||
$php .= " * Post Type: page\n";
|
||||
$php .= " */\n\n";
|
||||
$php .= "return <<<'{$delimiter}'\n";
|
||||
$php .= $safe_content . "\n";
|
||||
@@ -262,6 +292,38 @@ function oribi_sync_generate_php_wrapper( string $content, string $slug, string
|
||||
return $php;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the content body in an existing PHP page-data file.
|
||||
*
|
||||
* Preserves the original header (everything before the `return` statement)
|
||||
* and only replaces the body between the heredoc / nowdoc delimiters.
|
||||
* If the file format can't be parsed, falls back to generating a new wrapper.
|
||||
*
|
||||
* @param string $existing_source Current PHP source from the repo.
|
||||
* @param string $new_content New Gutenberg block HTML.
|
||||
* @param string $slug Page slug (used for fallback wrapper).
|
||||
* @param string $title Page title (used for fallback wrapper).
|
||||
*
|
||||
* @return string Updated PHP source code.
|
||||
*/
|
||||
function oribi_sync_replace_php_body( string $existing_source, string $new_content, string $slug, string $title ): string {
|
||||
// Match: return <<<'DELIMITER' or return <<<DELIMITER (heredoc / nowdoc)
|
||||
if ( preg_match( '/^(.*?return\s+<<<\'?)(\w+)(\'?\s*\n)(.*)(\n\2;?\s*)$/s', $existing_source, $m ) ) {
|
||||
$header = $m[1]; // everything up to and including "return <<<"
|
||||
$delimiter = $m[2]; // e.g. ORIBI_SYNC_CONTENT
|
||||
$quote_end = $m[3]; // closing quote + newline
|
||||
$suffix = $m[5]; // closing delimiter + semicolon
|
||||
|
||||
// Escape content if it contains the delimiter string on its own line
|
||||
$safe_content = str_replace( "\n" . $delimiter, "\n " . $delimiter, $new_content );
|
||||
|
||||
return $header . $delimiter . $quote_end . $safe_content . $suffix;
|
||||
}
|
||||
|
||||
// Couldn't parse the existing file — fall back to a fresh wrapper.
|
||||
return oribi_sync_generate_php_wrapper( $new_content, $slug, $title );
|
||||
}
|
||||
|
||||
// ─── Push orchestrator ───────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -315,31 +377,30 @@ function oribi_sync_push_page( int $post_id, array $opts = [] ): array {
|
||||
|
||||
if ( ! empty( $source_meta ) ) {
|
||||
// Format: {repo_url}@{branch}:{path}
|
||||
$colon_pos = strpos( $source_meta, ':' );
|
||||
// Use strrpos to find the LAST colon (skips the one in 'https:').
|
||||
$colon_pos = strrpos( $source_meta, ':' );
|
||||
if ( $colon_pos !== false ) {
|
||||
// Find the last occurrence of the pattern @branch:
|
||||
$at_pos = strrpos( substr( $source_meta, 0, $colon_pos ), '@' );
|
||||
if ( $at_pos !== false ) {
|
||||
$repo_path = substr( $source_meta, $colon_pos + 1 );
|
||||
$candidate = substr( $source_meta, $colon_pos + 1 );
|
||||
// Validate: path must start with 'pages/' (case-insensitive).
|
||||
// Discard corrupted values left by earlier bugs.
|
||||
if ( strncasecmp( $candidate, 'pages/', 6 ) === 0 ) {
|
||||
$repo_path = $candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $repo_path ) ) {
|
||||
// Derive from settings + slug
|
||||
$pages_folder = get_option( 'oribi_sync_pages_folder', '' );
|
||||
if ( empty( $pages_folder ) ) {
|
||||
return [ 'ok' => false, 'action' => 'error', 'message' => 'Cannot determine repo path — no pages folder configured and no source meta on this page.' ];
|
||||
}
|
||||
$repo_path = 'Pages/' . $pages_folder . '/' . $post->post_name . '.php';
|
||||
// Derive from slug — files live under pages/
|
||||
$repo_path = 'pages/' . $post->post_name . '.php';
|
||||
}
|
||||
|
||||
// ── Generate content ──────────────────────────────────────────────────
|
||||
$slug = $post->post_name;
|
||||
$title = $post->post_title;
|
||||
$wp_content = $post->post_content;
|
||||
$php_source = oribi_sync_generate_php_wrapper( $wp_content, $slug, $title );
|
||||
$new_checksum = hash( 'sha256', $php_source );
|
||||
|
||||
$commit_msg = $opts['message'] ?? "Sync: update {$slug} from WordPress";
|
||||
|
||||
@@ -350,6 +411,14 @@ function oribi_sync_push_page( int $post_id, array $opts = [] ): array {
|
||||
return [ 'ok' => false, 'action' => 'error', 'message' => 'Failed to check remote file: ' . $remote->get_error_message() ];
|
||||
}
|
||||
|
||||
// Build PHP source: preserve original header for existing files, fresh wrapper for new ones.
|
||||
if ( $remote !== null && ! empty( $remote['content'] ) ) {
|
||||
$php_source = oribi_sync_replace_php_body( $remote['content'], $wp_content, $slug, $title );
|
||||
} else {
|
||||
$php_source = oribi_sync_generate_php_wrapper( $wp_content, $slug, $title );
|
||||
}
|
||||
$new_checksum = hash( 'sha256', $php_source );
|
||||
|
||||
$stored_sha = get_post_meta( $post_id, '_oribi_sync_git_sha', true );
|
||||
|
||||
// ── Decide strategy ───────────────────────────────────────────────────
|
||||
|
||||
@@ -30,15 +30,6 @@ 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',
|
||||
@@ -160,53 +151,3 @@ function oribi_sync_rest_push( WP_REST_Request $request ): WP_REST_Response {
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
@@ -9,6 +9,21 @@
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Strip a case-insensitive directory prefix from a file path.
|
||||
*
|
||||
* Example: oribi_sync_strip_prefix( 'Theme/header.php', 'theme' ) → 'header.php'
|
||||
*/
|
||||
function oribi_sync_strip_prefix( string $path, string $prefix ): string {
|
||||
$prefix = rtrim( $prefix, '/' ) . '/';
|
||||
if ( strncasecmp( $path, $prefix, strlen( $prefix ) ) === 0 ) {
|
||||
return substr( $path, strlen( $prefix ) );
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
// ─── Gutenberg block helpers ──────────────────────────────────────────────────
|
||||
|
||||
/** Generate a self-closing block comment (standalone or child blocks). */
|
||||
@@ -129,18 +144,12 @@ function oribi_sync_run( bool $dry_run = false ): array {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// ── Filter to selected Pages sub-folder ────────────────────────────────
|
||||
$pages_folder = get_option( 'oribi_sync_pages_folder', '' );
|
||||
// ── Filter to Pages/ directory ─────────────────────────────────────────
|
||||
$synced_slugs = [];
|
||||
$page_files = oribi_sync_filter_tree( $tree, 'Pages' );
|
||||
|
||||
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.';
|
||||
}
|
||||
if ( empty( $page_files ) ) {
|
||||
$result['skipped'][] = 'No files found under Pages/ in the repository.';
|
||||
}
|
||||
|
||||
// ── Process each page file ─────────────────────────────────────────────
|
||||
@@ -274,7 +283,7 @@ function oribi_sync_run( bool $dry_run = false ): array {
|
||||
}
|
||||
|
||||
// ── Trash pages removed from repo ──────────────────────────────────────
|
||||
if ( ! $dry_run && ! empty( $pages_folder ) ) {
|
||||
if ( ! $dry_run ) {
|
||||
$trashed = oribi_sync_trash_removed_pages( $synced_slugs );
|
||||
$result['trashed'] = $trashed;
|
||||
}
|
||||
@@ -456,7 +465,7 @@ function oribi_sync_apply_theme_files( string $api_base, string $branch, string
|
||||
$theme_entries = oribi_sync_filter_tree( $tree, 'theme', true );
|
||||
|
||||
foreach ( $theme_entries as $entry ) {
|
||||
$relative = substr( $entry['path'], strlen( 'theme/' ) );
|
||||
$relative = oribi_sync_strip_prefix( $entry['path'], 'theme' );
|
||||
$ext = strtolower( pathinfo( $relative, PATHINFO_EXTENSION ) );
|
||||
|
||||
if ( ! in_array( $ext, $allowed, true ) ) {
|
||||
@@ -540,7 +549,7 @@ function oribi_sync_fetch_theme_files(): array {
|
||||
|
||||
foreach ( $theme_entries as $entry ) {
|
||||
// Derive relative path by stripping the 'theme/' prefix
|
||||
$relative = substr( $entry['path'], strlen( 'theme/' ) );
|
||||
$relative = oribi_sync_strip_prefix( $entry['path'], 'theme' );
|
||||
$content = oribi_sync_fetch_file( $api_base, $branch, $entry['path'], $provider, $pat );
|
||||
|
||||
if ( is_wp_error( $content ) ) {
|
||||
|
||||
Reference in New Issue
Block a user