670 lines
32 KiB
PHP
670 lines
32 KiB
PHP
<?php
|
|
/**
|
|
* Oribi Sync — Admin settings page.
|
|
*
|
|
* Registers the Settings → Oribi Sync page, handles saving,
|
|
* and provides the "Sync Now" action.
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
|
|
|
// ─── Admin bar pull buttons (front-end) ─────────────────────────────────────
|
|
add_action( 'admin_bar_menu', function ( WP_Admin_Bar $wp_admin_bar ) {
|
|
if ( is_admin() ) return;
|
|
if ( ! is_user_logged_in() ) return;
|
|
if ( ! current_user_can( 'manage_options' ) ) return;
|
|
|
|
// "Pull All" — visible everywhere on the front-end
|
|
$wp_admin_bar->add_node( [
|
|
'id' => 'oribi-sync-pull-all',
|
|
'title' => '<span class="ab-icon dashicons dashicons-update" aria-hidden="true"></span><span class="ab-label">Pull All</span>',
|
|
'href' => '#',
|
|
'meta' => [
|
|
'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' => '<span class="ab-icon dashicons dashicons-download" aria-hidden="true"></span><span class="ab-label">Pull Page</span>',
|
|
'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' );
|
|
if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Permission denied.', 403 );
|
|
|
|
$post_id = (int) ( $_POST['post_id'] ?? 0 );
|
|
if ( $post_id < 1 ) wp_send_json_error( 'Missing or invalid post_id.', 400 );
|
|
|
|
$result = oribi_sync_pull_page_from_repo( $post_id );
|
|
$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' );
|
|
?>
|
|
<script>
|
|
(function () {
|
|
'use strict';
|
|
var btn = document.getElementById('wp-admin-bar-oribi-sync-pull-all');
|
|
if (!btn) return;
|
|
|
|
btn.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
|
|
var link = btn.querySelector('a');
|
|
var label = btn.querySelector('.ab-label');
|
|
if (link) { link.style.opacity = '0.5'; link.style.pointerEvents = 'none'; }
|
|
if (label) { label.textContent = 'Pulling…'; }
|
|
|
|
var data = new URLSearchParams({
|
|
action: 'oribi_sync_pull_all_pages',
|
|
_ajax_nonce: <?php echo wp_json_encode( $nonce_all ); ?>
|
|
});
|
|
|
|
fetch(<?php echo wp_json_encode( $ajax_url ); ?>, {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: data.toString()
|
|
})
|
|
.then(function (r) { return r.json(); })
|
|
.then(function () {
|
|
var url = new URL(window.location.href);
|
|
url.searchParams.set('_nocache', Date.now());
|
|
window.location.replace(url.toString());
|
|
})
|
|
.catch(function (err) {
|
|
if (label) { label.textContent = 'Pull All'; }
|
|
if (link) { link.style.opacity = ''; link.style.pointerEvents = ''; }
|
|
alert('Oribi Sync pull failed: ' + err);
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
<?php
|
|
} );
|
|
|
|
// Front-end script that wires up the admin bar pull button
|
|
add_action( 'wp_footer', function () {
|
|
if ( ! is_user_logged_in() ) return;
|
|
if ( ! current_user_can( 'manage_options' ) ) return;
|
|
if ( ! is_singular() ) return;
|
|
if ( ! is_admin_bar_showing() ) return;
|
|
|
|
$post = get_queried_object();
|
|
if ( ! $post instanceof WP_Post ) return;
|
|
|
|
$ajax_url = admin_url( 'admin-ajax.php' );
|
|
$nonce = wp_create_nonce( 'oribi_sync_pull_page' );
|
|
$post_id = (int) $post->ID;
|
|
?>
|
|
<script>
|
|
(function () {
|
|
'use strict';
|
|
var btn = document.getElementById('wp-admin-bar-oribi-sync-pull');
|
|
if (!btn) return;
|
|
|
|
btn.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
|
|
var link = btn.querySelector('a');
|
|
var label = btn.querySelector('.ab-label');
|
|
if (link) { link.style.opacity = '0.5'; link.style.pointerEvents = 'none'; }
|
|
if (label) { label.textContent = 'Pulling…'; }
|
|
|
|
var data = new URLSearchParams({
|
|
action: 'oribi_sync_pull_page',
|
|
_ajax_nonce: <?php echo wp_json_encode( $nonce ); ?>,
|
|
post_id: <?php echo $post_id; ?>
|
|
});
|
|
|
|
fetch(<?php echo wp_json_encode( $ajax_url ); ?>, {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: data.toString()
|
|
})
|
|
.then(function (r) { return r.json(); })
|
|
.then(function () {
|
|
// Hard reload — cache-busting param forces a fresh response
|
|
var url = new URL(window.location.href);
|
|
url.searchParams.set('_nocache', Date.now());
|
|
window.location.replace(url.toString());
|
|
})
|
|
.catch(function (err) {
|
|
if (label) { label.textContent = 'Pull Page'; }
|
|
if (link) { link.style.opacity = ''; link.style.pointerEvents = ''; }
|
|
alert('Oribi Sync pull failed: ' + err);
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
<?php
|
|
} );
|
|
|
|
// ─── Register admin menu ──────────────────────────────────────────────────────
|
|
add_action( 'admin_menu', function () {
|
|
add_options_page(
|
|
'Oribi Sync',
|
|
'Oribi Sync',
|
|
'manage_options',
|
|
'oribi-sync',
|
|
'oribi_sync_settings_page'
|
|
);
|
|
} );
|
|
|
|
// ─── Enqueue admin CSS on our page only ───────────────────────────────────────
|
|
add_action( 'admin_enqueue_scripts', function ( $hook ) {
|
|
if ( $hook !== 'settings_page_oribi-sync' ) return;
|
|
wp_enqueue_style( 'oribi-sync-admin', ORIBI_SYNC_URL . 'assets/admin.css', [], ORIBI_SYNC_VERSION );
|
|
} );
|
|
|
|
// ─── Handle form submissions ──────────────────────────────────────────────────
|
|
add_action( 'admin_post_oribi_sync_save_settings', function () {
|
|
if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Permission denied.' );
|
|
check_admin_referer( 'oribi_sync_save_settings' );
|
|
|
|
$repo = sanitize_text_field( wp_unslash( $_POST['oribi_sync_repo'] ?? '' ) );
|
|
$branch = sanitize_text_field( wp_unslash( $_POST['oribi_sync_branch'] ?? 'main' ) );
|
|
$provider = sanitize_text_field( wp_unslash( $_POST['oribi_sync_provider'] ?? '' ) );
|
|
$pat = wp_unslash( $_POST['oribi_sync_pat'] ?? '' );
|
|
|
|
update_option( 'oribi_sync_repo', $repo, 'no' );
|
|
update_option( 'oribi_sync_branch', $branch, 'no' );
|
|
update_option( 'oribi_sync_provider', $provider, 'no' );
|
|
|
|
// Posts sync settings
|
|
$posts_enabled = isset( $_POST['oribi_sync_posts_enabled'] ) ? '1' : '0';
|
|
$posts_folder = sanitize_text_field( wp_unslash( $_POST['oribi_sync_posts_folder'] ?? 'posts' ) );
|
|
$posts_folder = trim( $posts_folder, '/' ) ?: 'posts';
|
|
update_option( 'oribi_sync_posts_enabled', $posts_enabled, 'no' );
|
|
update_option( 'oribi_sync_posts_folder', $posts_folder, 'no' );
|
|
|
|
// Only update PAT if a new one was provided (non-empty)
|
|
if ( ! empty( $pat ) ) {
|
|
oribi_sync_save_pat( $pat );
|
|
}
|
|
|
|
wp_redirect( add_query_arg( 'oribi_sync_saved', '1', admin_url( 'options-general.php?page=oribi-sync' ) ) );
|
|
exit;
|
|
} );
|
|
|
|
add_action( 'admin_post_oribi_sync_run', function () {
|
|
if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Permission denied.' );
|
|
check_admin_referer( 'oribi_sync_run' );
|
|
|
|
$result = oribi_sync_run();
|
|
|
|
// After pulling, push any local changes back to the repo
|
|
$push = oribi_sync_push_all();
|
|
$result['push'] = $push['results'];
|
|
|
|
set_transient( 'oribi_sync_result', $result, 60 );
|
|
|
|
wp_redirect( add_query_arg( 'oribi_sync_done', '1', admin_url( 'options-general.php?page=oribi-sync' ) ) );
|
|
exit;
|
|
} );
|
|
|
|
add_action( 'admin_post_oribi_sync_dry_run', function () {
|
|
if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Permission denied.' );
|
|
check_admin_referer( 'oribi_sync_dry_run' );
|
|
|
|
$result = oribi_sync_run( true );
|
|
|
|
set_transient( 'oribi_sync_result', $result, 60 );
|
|
|
|
wp_redirect( add_query_arg( 'oribi_sync_done', 'dry', admin_url( 'options-general.php?page=oribi-sync' ) ) );
|
|
exit;
|
|
} );
|
|
|
|
add_action( 'admin_post_oribi_sync_pull', function () {
|
|
if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Permission denied.' );
|
|
check_admin_referer( 'oribi_sync_pull' );
|
|
|
|
$result = oribi_sync_run();
|
|
|
|
set_transient( 'oribi_sync_result', $result, 60 );
|
|
|
|
wp_redirect( add_query_arg( 'oribi_sync_done', 'pull', admin_url( 'options-general.php?page=oribi-sync' ) ) );
|
|
exit;
|
|
} );
|
|
|
|
add_action( 'admin_post_oribi_sync_clear_pat', function () {
|
|
if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Permission denied.' );
|
|
check_admin_referer( 'oribi_sync_clear_pat' );
|
|
|
|
delete_option( 'oribi_sync_pat' );
|
|
|
|
wp_redirect( add_query_arg( 'oribi_sync_saved', 'pat_cleared', admin_url( 'options-general.php?page=oribi-sync' ) ) );
|
|
exit;
|
|
} );
|
|
|
|
// ─── Push page(s) to repo ─────────────────────────────────────────────────────
|
|
add_action( 'admin_post_oribi_sync_push', function () {
|
|
if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Permission denied.' );
|
|
check_admin_referer( 'oribi_sync_push' );
|
|
|
|
$post_id = (int) ( $_POST['oribi_sync_push_post_id'] ?? 0 );
|
|
|
|
if ( $post_id > 0 ) {
|
|
$post = get_post( $post_id );
|
|
$result = ( $post && $post->post_type === 'post' )
|
|
? oribi_sync_push_post( $post_id )
|
|
: oribi_sync_push_page( $post_id );
|
|
set_transient( 'oribi_sync_push_result', $result, 60 );
|
|
}
|
|
|
|
wp_redirect( add_query_arg( 'oribi_sync_pushed', '1', admin_url( 'options-general.php?page=oribi-sync' ) ) );
|
|
exit;
|
|
} );
|
|
|
|
add_action( 'admin_post_oribi_sync_push_all', function () {
|
|
if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Permission denied.' );
|
|
check_admin_referer( 'oribi_sync_push_all' );
|
|
|
|
$result = oribi_sync_push_all();
|
|
set_transient( 'oribi_sync_push_result', $result, 60 );
|
|
|
|
wp_redirect( add_query_arg( 'oribi_sync_pushed', 'all', admin_url( 'options-general.php?page=oribi-sync' ) ) );
|
|
exit;
|
|
} );
|
|
|
|
// ─── Settings page renderer ──────────────────────────────────────────────────
|
|
function oribi_sync_settings_page() {
|
|
if ( ! current_user_can( 'manage_options' ) ) return;
|
|
|
|
$repo = get_option( 'oribi_sync_repo', '' );
|
|
$branch = get_option( 'oribi_sync_branch', 'main' );
|
|
$provider = get_option( 'oribi_sync_provider', '' );
|
|
$has_pat = ! empty( get_option( 'oribi_sync_pat', '' ) );
|
|
$last_run = get_option( 'oribi_sync_last_run', '' );
|
|
$log = get_option( 'oribi_sync_log', [] );
|
|
|
|
// Transient results
|
|
$sync_result = get_transient( 'oribi_sync_result' );
|
|
if ( $sync_result ) delete_transient( 'oribi_sync_result' );
|
|
$push_result = get_transient( 'oribi_sync_push_result' );
|
|
if ( $push_result ) delete_transient( 'oribi_sync_push_result' );
|
|
|
|
$saved = $_GET['oribi_sync_saved'] ?? '';
|
|
$done = $_GET['oribi_sync_done'] ?? '';
|
|
?>
|
|
<div class="wrap oribi-sync-wrap">
|
|
<h1>Oribi Sync</h1>
|
|
|
|
<?php // ── Flash notices ──────────────────────────────────────────── ?>
|
|
<?php if ( $saved === '1' ): ?>
|
|
<div class="notice notice-success is-dismissible"><p>Settings saved.</p></div>
|
|
<?php elseif ( $saved === 'pat_cleared' ): ?>
|
|
<div class="notice notice-warning is-dismissible"><p>PAT cleared.</p></div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ( $sync_result ): ?>
|
|
<div class="notice <?php echo $sync_result['ok'] ? 'notice-success' : 'notice-error'; ?> is-dismissible">
|
|
<p><strong><?php
|
|
if ( $done === 'dry' ) echo 'Dry-run results';
|
|
elseif ( $done === 'pull' ) echo 'Pull complete';
|
|
else echo 'Sync complete';
|
|
?></strong></p>
|
|
<?php oribi_sync_render_result_list( $sync_result ); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ( $push_result ): ?>
|
|
<?php if ( isset( $push_result['results'] ) ): ?>
|
|
<div class="notice <?php echo $push_result['ok'] ? 'notice-success' : 'notice-warning'; ?> is-dismissible">
|
|
<p><strong>Push results</strong></p>
|
|
<ul class="oribi-sync-result-list">
|
|
<?php foreach ( $push_result['results'] as $pr ): ?>
|
|
<li>
|
|
<strong><?php echo esc_html( $pr['slug'] ?? '' ); ?></strong> —
|
|
<?php echo esc_html( $pr['message'] ?? '' ); ?>
|
|
<?php if ( ! empty( $pr['pr_url'] ) ): ?>
|
|
<a href="<?php echo esc_url( $pr['pr_url'] ); ?>" target="_blank" rel="noopener">PR →</a>
|
|
<?php endif; ?>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="notice <?php echo $push_result['ok'] ? 'notice-success' : 'notice-error'; ?> is-dismissible">
|
|
<p>
|
|
<?php if ( ! empty( $push_result['pr_url'] ) ): ?>
|
|
Conflict — opened <a href="<?php echo esc_url( $push_result['pr_url'] ); ?>" target="_blank" rel="noopener">pull request</a> for review.
|
|
<?php else: ?>
|
|
<?php echo esc_html( $push_result['message'] ?? '' ); ?>
|
|
<?php endif; ?>
|
|
</p>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
|
|
<?php // ── Settings form ──────────────────────────────────────────── ?>
|
|
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="oribi-sync-form">
|
|
<input type="hidden" name="action" value="oribi_sync_save_settings" />
|
|
<?php wp_nonce_field( 'oribi_sync_save_settings' ); ?>
|
|
|
|
<table class="form-table" role="presentation">
|
|
<tr>
|
|
<th scope="row"><label for="oribi_sync_repo">Repository URL</label></th>
|
|
<td>
|
|
<input type="url" name="oribi_sync_repo" id="oribi_sync_repo"
|
|
value="<?php echo esc_attr( $repo ); ?>"
|
|
class="regular-text" placeholder="https://gitea.example.com/owner/repo" />
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><label for="oribi_sync_provider">Provider</label></th>
|
|
<td>
|
|
<select name="oribi_sync_provider" id="oribi_sync_provider" class="regular-text">
|
|
<option value=""<?php selected( $provider, '' ); ?>>Auto-detect</option>
|
|
<?php foreach ( oribi_sync_providers() as $key => $label ): ?>
|
|
<option value="<?php echo esc_attr( $key ); ?>"<?php selected( $provider, $key ); ?>>
|
|
<?php echo esc_html( $label ); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><label for="oribi_sync_branch">Branch</label></th>
|
|
<td>
|
|
<input type="text" name="oribi_sync_branch" id="oribi_sync_branch"
|
|
value="<?php echo esc_attr( $branch ); ?>"
|
|
class="regular-text" placeholder="main" />
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><label for="oribi_sync_pat">Access Token</label></th>
|
|
<td>
|
|
<input type="password" name="oribi_sync_pat" id="oribi_sync_pat"
|
|
value="" class="regular-text"
|
|
placeholder="<?php echo $has_pat ? '•••••••• (saved)' : 'token'; ?>"
|
|
autocomplete="off" />
|
|
<p class="description">
|
|
Needs <strong>write</strong> scope to push pages. Stored encrypted.
|
|
<?php if ( $has_pat ): ?>
|
|
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=oribi_sync_clear_pat' ), 'oribi_sync_clear_pat' ) ); ?>"
|
|
onclick="return confirm('Clear the stored PAT?');"
|
|
class="oribi-sync-danger-link">Clear</a>
|
|
<?php endif; ?>
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h2 style="margin-top:1.5em;">Posts Sync</h2>
|
|
<p class="description">Import WordPress posts from Markdown files with YAML front-matter stored in a repo subfolder.</p>
|
|
<table class="form-table" role="presentation">
|
|
<tr>
|
|
<th scope="row">Enable Posts Sync</th>
|
|
<td>
|
|
<label>
|
|
<input type="checkbox" name="oribi_sync_posts_enabled" value="1"
|
|
<?php checked( get_option( 'oribi_sync_posts_enabled', '' ), '1' ); ?> />
|
|
Sync posts from the repository
|
|
</label>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row"><label for="oribi_sync_posts_folder">Posts Folder</label></th>
|
|
<td>
|
|
<input type="text" name="oribi_sync_posts_folder" id="oribi_sync_posts_folder"
|
|
value="<?php echo esc_attr( get_option( 'oribi_sync_posts_folder', 'posts' ) ); ?>"
|
|
class="regular-text" placeholder="posts" />
|
|
<p class="description">
|
|
Repo folder containing <code>.md</code> files (e.g. <code>posts</code> or <code>content/posts</code>).
|
|
Files may use <code>YYYY-MM-DD-slug.md</code> naming. Supported front-matter keys:
|
|
<code>title</code>, <code>slug</code>, <code>status</code>, <code>date</code>,
|
|
<code>author</code>, <code>categories</code>, <code>tags</code>,
|
|
<code>excerpt</code>, <code>featured_image</code>.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<?php submit_button( 'Save Settings' ); ?>
|
|
</form>
|
|
|
|
<hr />
|
|
|
|
<?php // ── Actions ────────────────────────────────────────────────── ?>
|
|
<h2>Actions</h2>
|
|
<p class="oribi-sync-actions">
|
|
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=oribi_sync_run' ), 'oribi_sync_run' ) ); ?>"
|
|
class="button button-primary"
|
|
onclick="return confirm('Pull from repo then push local changes. Continue?');">
|
|
Sync (Pull & Push)
|
|
</a>
|
|
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=oribi_sync_pull' ), 'oribi_sync_pull' ) ); ?>"
|
|
class="button"
|
|
onclick="return confirm('Pull content from the repo (no push). Continue?');">
|
|
Pull Only
|
|
</a>
|
|
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=oribi_sync_push_all' ), 'oribi_sync_push_all' ) ); ?>"
|
|
class="button"
|
|
onclick="return confirm('Push all synced pages to the repo (no pull). Continue?');">
|
|
Push Only
|
|
</a>
|
|
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=oribi_sync_dry_run' ), 'oribi_sync_dry_run' ) ); ?>"
|
|
class="button">
|
|
Dry Run
|
|
</a>
|
|
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=oribi_sync_theme_preview' ), 'oribi_sync_theme_preview' ) ); ?>"
|
|
class="button">
|
|
Preview Theme
|
|
</a>
|
|
</p>
|
|
<?php if ( $last_run ): ?>
|
|
<p class="oribi-sync-muted">Last sync: <?php echo esc_html( $last_run ); ?></p>
|
|
<?php endif; ?>
|
|
|
|
<?php // ── Synced pages ───────────────────────────────────────────── ?>
|
|
<?php
|
|
$synced_pages = new WP_Query( [
|
|
'post_type' => 'page',
|
|
'post_status' => 'publish',
|
|
'meta_key' => '_oribi_sync_checksum',
|
|
'posts_per_page' => -1,
|
|
'orderby' => 'title',
|
|
'order' => 'ASC',
|
|
] );
|
|
|
|
if ( $synced_pages->have_posts() ): ?>
|
|
<table class="widefat fixed striped oribi-sync-pages">
|
|
<thead><tr>
|
|
<th>Page</th>
|
|
<th style="width:80px;">Push</th>
|
|
</tr></thead>
|
|
<tbody>
|
|
<?php while ( $synced_pages->have_posts() ): $synced_pages->the_post();
|
|
$pid = get_the_ID();
|
|
$last_push = get_post_meta( $pid, '_oribi_sync_last_push', true );
|
|
$pr_url = get_post_meta( $pid, '_oribi_sync_pr_url', true );
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<strong><?php the_title(); ?></strong>
|
|
<span class="oribi-sync-muted">/<?php echo esc_html( get_post_field( 'post_name', $pid ) ); ?></span>
|
|
<?php if ( $pr_url ): ?>
|
|
<a href="<?php echo esc_url( $pr_url ); ?>" target="_blank" rel="noopener" class="oribi-sync-pr-badge">PR</a>
|
|
<?php endif; ?>
|
|
<?php if ( $last_push ): ?>
|
|
<br /><span class="oribi-sync-muted">pushed <?php echo esc_html( $last_push ); ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="margin:0;">
|
|
<input type="hidden" name="action" value="oribi_sync_push" />
|
|
<input type="hidden" name="oribi_sync_push_post_id" value="<?php echo esc_attr( $pid ); ?>" />
|
|
<?php wp_nonce_field( 'oribi_sync_push' ); ?>
|
|
<button type="submit" class="button button-small">Push</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<?php endwhile; wp_reset_postdata(); ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
|
|
<?php
|
|
// ── Synced posts ──────────────────────────────────────────────────
|
|
if ( get_option( 'oribi_sync_posts_enabled', '' ) ):
|
|
$synced_posts = new WP_Query( [
|
|
'post_type' => 'post',
|
|
'post_status' => [ 'publish', 'draft', 'pending', 'private' ],
|
|
'meta_key' => '_oribi_sync_checksum',
|
|
'posts_per_page' => -1,
|
|
'orderby' => 'date',
|
|
'order' => 'DESC',
|
|
] );
|
|
|
|
if ( $synced_posts->have_posts() ):
|
|
?>
|
|
<h2>Synced Posts</h2>
|
|
<table class="widefat fixed striped oribi-sync-pages">
|
|
<thead><tr>
|
|
<th>Post</th>
|
|
<th style="width:80px;">Push</th>
|
|
</tr></thead>
|
|
<tbody>
|
|
<?php while ( $synced_posts->have_posts() ): $synced_posts->the_post();
|
|
$pid = get_the_ID();
|
|
$last_push = get_post_meta( $pid, '_oribi_sync_last_push', true );
|
|
$pr_url = get_post_meta( $pid, '_oribi_sync_pr_url', true );
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<strong><?php the_title(); ?></strong>
|
|
<span class="oribi-sync-muted"><?php echo esc_html( get_post_field( 'post_name', $pid ) ); ?></span>
|
|
<span class="oribi-sync-muted"> — <?php echo esc_html( get_post_status( $pid ) ); ?></span>
|
|
<?php if ( $pr_url ): ?>
|
|
<a href="<?php echo esc_url( $pr_url ); ?>" target="_blank" rel="noopener" class="oribi-sync-pr-badge">PR</a>
|
|
<?php endif; ?>
|
|
<?php if ( $last_push ): ?>
|
|
<br /><span class="oribi-sync-muted">pushed <?php echo esc_html( $last_push ); ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td>
|
|
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="margin:0;">
|
|
<input type="hidden" name="action" value="oribi_sync_push" />
|
|
<input type="hidden" name="oribi_sync_push_post_id" value="<?php echo esc_attr( $pid ); ?>" />
|
|
<?php wp_nonce_field( 'oribi_sync_push' ); ?>
|
|
<button type="submit" class="button button-small">Push</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<?php endwhile; wp_reset_postdata(); ?>
|
|
</tbody>
|
|
</table>
|
|
<?php
|
|
endif; // have_posts
|
|
endif; // posts_enabled
|
|
?>
|
|
|
|
<?php // ── Theme preview (conditional) ────────────────────────────── ?>
|
|
<?php if ( isset( $_GET['oribi_sync_tab'] ) && $_GET['oribi_sync_tab'] === 'theme' ): ?>
|
|
<hr />
|
|
<h2>Theme Files Preview</h2>
|
|
<?php oribi_sync_render_theme_preview(); ?>
|
|
<?php endif; ?>
|
|
|
|
<?php // ── Log ────────────────────────────────────────────────────── ?>
|
|
<?php if ( ! empty( $log ) ): ?>
|
|
<hr />
|
|
<details>
|
|
<summary>Sync Log (<?php echo count( $log ); ?>)</summary>
|
|
<table class="widefat fixed striped oribi-sync-log">
|
|
<thead><tr>
|
|
<th style="width:150px;">Time</th>
|
|
<th>Created</th>
|
|
<th>Updated</th>
|
|
<th>Errors</th>
|
|
</tr></thead>
|
|
<tbody>
|
|
<?php foreach ( array_slice( $log, 0, 10 ) as $entry ): ?>
|
|
<tr>
|
|
<td><?php echo esc_html( $entry['time'] ?? '—' ); ?></td>
|
|
<td><?php echo esc_html( implode( ', ', $entry['created'] ?? [] ) ?: '—' ); ?></td>
|
|
<td><?php echo esc_html( implode( ', ', array_merge( $entry['updated'] ?? [], $entry['theme_updated'] ?? [] ) ) ?: '—' ); ?></td>
|
|
<td class="oribi-sync-error-text"><?php echo esc_html( implode( '; ', $entry['errors'] ?? [] ) ?: '—' ); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</details>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render a sync result as a compact <ul>.
|
|
*/
|
|
function oribi_sync_render_result_list( array $r ): void {
|
|
$items = [];
|
|
if ( ! empty( $r['created'] ) ) $items[] = 'Pages created: ' . implode( ', ', $r['created'] );
|
|
if ( ! empty( $r['updated'] ) ) $items[] = 'Pages updated: ' . implode( ', ', $r['updated'] );
|
|
if ( ! empty( $r['theme_updated'] ) ) $items[] = 'Theme: ' . implode( ', ', $r['theme_updated'] );
|
|
if ( ! empty( $r['trashed'] ) ) $items[] = 'Pages trashed: ' . implode( ', ', $r['trashed'] );
|
|
if ( ! empty( $r['posts_created'] ) ) $items[] = 'Posts created: ' . implode( ', ', $r['posts_created'] );
|
|
if ( ! empty( $r['posts_updated'] ) ) $items[] = 'Posts updated: ' . implode( ', ', $r['posts_updated'] );
|
|
if ( ! empty( $r['posts_trashed'] ) ) $items[] = 'Posts trashed: ' . implode( ', ', $r['posts_trashed'] );
|
|
if ( ! empty( $r['skipped'] ) ) $items[] = 'Skipped: ' . implode( ', ', $r['skipped'] );
|
|
if ( ! empty( $r['errors'] ) ) $items[] = 'Errors: ' . implode( '; ', $r['errors'] );
|
|
|
|
if ( empty( $items ) ) { echo '<p>No changes.</p>'; return; }
|
|
|
|
echo '<ul class="oribi-sync-result-list">';
|
|
foreach ( $items as $item ) {
|
|
$class = ( strpos( $item, 'Errors:' ) === 0 ) ? ' class="oribi-sync-error-text"' : '';
|
|
echo '<li' . $class . '>' . esc_html( $item ) . '</li>';
|
|
}
|
|
|
|
// Append push results if present
|
|
if ( ! empty( $r['push'] ) && is_array( $r['push'] ) ) {
|
|
foreach ( $r['push'] as $pr ) {
|
|
$slug = $pr['slug'] ?? '';
|
|
$msg = $pr['message'] ?? '';
|
|
$url = $pr['pr_url'] ?? '';
|
|
$text = "Pushed: {$slug} — {$msg}";
|
|
$cls = $pr['ok'] ? '' : ' class="oribi-sync-error-text"';
|
|
echo '<li' . $cls . '>' . esc_html( $text );
|
|
if ( $url ) {
|
|
echo ' <a href="' . esc_url( $url ) . '" target="_blank" rel="noopener">PR →</a>';
|
|
}
|
|
echo '</li>';
|
|
}
|
|
}
|
|
|
|
echo '</ul>';
|
|
}
|