Preview: category-sync-importer.php
Size: 17.85 KB
/home/certprox/zang.certproxywizard.com/wp/wp-content/plugins/category-sync-importer/category-sync-importer.php
<?php
/**
* Plugin Name: Category Sync — Importer (NEW SITE)
* Description: Connects to the old site's API, imports correct categories, and auto-reassigns all posts. Install on NEW site only.
* Version: 1.0
* Author: Migration Tool
*/
if (!defined('ABSPATH')) exit;
class CategorySyncImporter {
private $settings_key = 'cat_sync_importer_settings';
private $log_key = 'cat_sync_import_log';
public function __construct() {
add_action('admin_menu', [$this, 'add_menu']);
add_action('admin_init', [$this, 'handle_actions']);
add_action('wp_ajax_cat_sync_run', [$this, 'ajax_run_sync']);
add_action('wp_ajax_cat_sync_preview', [$this, 'ajax_preview']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
}
public function enqueue_assets($hook) {
if ($hook !== 'toplevel_page_cat-sync-importer') return;
wp_enqueue_style('google-fonts-importer', 'https://fonts.googleapis.com/css2?family=Syne:wght@400;700;800&family=DM+Mono:wght@400;500&display=swap', [], null);
}
public function add_menu() {
add_menu_page(
'Category Sync Importer',
'📥 Cat Sync Importer',
'manage_options',
'cat-sync-importer',
[$this, 'render_page'],
'dashicons-database-import',
80
);
}
public function handle_actions() {
if (isset($_POST['cat_sync_save_settings']) && check_admin_referer('cat_sync_importer')) {
update_option($this->settings_key, [
'api_url' => sanitize_url($_POST['api_url']),
'api_key' => sanitize_text_field($_POST['api_key']),
]);
}
if (isset($_POST['cat_sync_clear_log']) && check_admin_referer('cat_sync_importer')) {
delete_option($this->log_key);
}
}
private function fetch_from_old_site($settings) {
$response = wp_remote_get($settings['api_url'], [
'timeout' => 60,
'headers' => ['X-Cat-Sync-Key' => $settings['api_key']],
]);
if (is_wp_error($response)) return ['error' => $response->get_error_message()];
$code = wp_remote_retrieve_response_code($response);
if ($code !== 200) return ['error' => "HTTP $code — Check your API URL and Key"];
$body = json_decode(wp_remote_retrieve_body($response), true);
if (!$body || !$body['success']) return ['error' => 'Invalid response from old site'];
return $body;
}
public function ajax_preview() {
check_ajax_referer('cat_sync_ajax', 'nonce');
if (!current_user_can('manage_options')) wp_send_json_error('Unauthorized');
$settings = get_option($this->settings_key, []);
if (empty($settings['api_url']) || empty($settings['api_key'])) {
wp_send_json_error('Please save your API URL and Key first.');
}
$data = $this->fetch_from_old_site($settings);
if (isset($data['error'])) wp_send_json_error($data['error']);
wp_send_json_success($this->build_sync_plan($data));
}
public function ajax_run_sync() {
check_ajax_referer('cat_sync_ajax', 'nonce');
if (!current_user_can('manage_options')) wp_send_json_error('Unauthorized');
$settings = get_option($this->settings_key, []);
if (empty($settings['api_url']) || empty($settings['api_key'])) {
wp_send_json_error('Please save your API URL and Key first.');
}
$data = $this->fetch_from_old_site($settings);
if (isset($data['error'])) wp_send_json_error($data['error']);
$result = $this->run_sync($data);
update_option($this->log_key, $result);
wp_send_json_success($result);
}
private function build_sync_plan($data) {
$plan = [
'categories_to_create' => [],
'posts_to_reassign' => 0,
'matches' => [],
];
foreach ($data['categories'] as $cat) {
if (!get_term_by('slug', $cat['slug'], 'category')) {
$plan['categories_to_create'][] = $cat;
}
}
foreach ($data['post_category_map'] as $item) {
$local = get_posts(['name' => $item['post_slug'], 'post_status' => 'any', 'numberposts' => 1]);
if ($local) {
$plan['posts_to_reassign']++;
$plan['matches'][] = [
'post_id' => $local[0]->ID,
'post_title' => $item['post_title'],
'categories' => $item['categories'],
];
}
}
return $plan;
}
private function run_sync($data) {
$plan = $this->build_sync_plan($data);
$log = [
'ran_at' => current_time('mysql'),
'categories_created' => [],
'posts_fixed' => 0,
'posts_skipped' => 0,
'junk_deleted' => 0,
'errors' => [],
];
foreach ($plan['categories_to_create'] as $cat) {
$result = wp_insert_term($cat['name'], 'category', ['slug' => $cat['slug'], 'description' => $cat['description']]);
if (!is_wp_error($result)) {
$log['categories_created'][] = $cat['name'];
} else {
$log['errors'][] = "Failed to create: {$cat['name']} — " . $result->get_error_message();
}
}
foreach ($plan['matches'] as $match) {
$cat_ids = [];
foreach ($match['categories'] as $slug) {
$term = get_term_by('slug', $slug, 'category');
if ($term) $cat_ids[] = $term->term_id;
}
if (!empty($cat_ids)) {
wp_set_post_categories($match['post_id'], $cat_ids, false);
$log['posts_fixed']++;
} else {
$log['posts_skipped']++;
$log['errors'][] = "No categories found for: {$match['post_title']}";
}
}
$old_slugs = array_column($data['categories'], 'slug');
$all_new_cats = get_terms(['taxonomy' => 'category', 'hide_empty' => false]);
foreach ($all_new_cats as $cat) {
if (!in_array($cat->slug, $old_slugs) && $cat->count == 0 && $cat->slug !== 'uncategorized') {
wp_delete_term($cat->term_id, 'category');
$log['junk_deleted']++;
}
}
return $log;
}
public function render_page() {
$settings = get_option($this->settings_key, ['api_url' => '', 'api_key' => '']);
$last_log = get_option($this->log_key, null);
$nonce = wp_create_nonce('cat_sync_ajax');
?>
<style>
*{box-sizing:border-box;margin:0;padding:0}
#csi-wrap{font-family:'Syne',sans-serif;background:#060610;min-height:100vh;padding:40px;color:#e8e8f0}
.csi-badge{display:inline-block;background:linear-gradient(135deg,#6366f1,#a5b4fc);color:#fff;font-size:11px;font-weight:700;letter-spacing:2px;text-transform:uppercase;padding:4px 12px;border-radius:20px;margin-bottom:10px}
.csi-h1{font-size:32px;font-weight:800;background:linear-gradient(90deg,#6366f1,#a5b4fc);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.csi-sub{color:#555;font-size:14px;margin-top:4px;font-family:'DM Mono',monospace}
.csi-layout{display:grid;grid-template-columns:380px 1fr;gap:24px;max-width:1100px;margin-top:36px}
.csi-card{background:#0e0e1a;border:1px solid #1a1a2e;border-radius:16px;padding:28px;margin-bottom:24px}
.csi-card h2{font-size:13px;font-weight:700;color:#6366f1;margin-bottom:18px;text-transform:uppercase;letter-spacing:1px}
.csi-field{margin-bottom:16px}
.csi-field label{display:block;font-size:11px;color:#777;margin-bottom:7px;text-transform:uppercase;letter-spacing:1px}
.csi-field input{width:100%;background:#060610;border:1px solid #1a1a2e;border-radius:8px;padding:11px 14px;font-family:'DM Mono',monospace;font-size:13px;color:#a5b4fc;outline:none;transition:border-color .2s}
.csi-field input:focus{border-color:#6366f1}
.csi-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:12px 20px;border-radius:10px;font-family:'Syne',sans-serif;font-size:14px;font-weight:700;cursor:pointer;border:none;transition:all .2s;width:100%;margin-bottom:10px}
.csi-btn:hover{transform:translateY(-1px)}
.csi-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}
.btn-save{background:#1a1a2e;color:#a5b4fc;border:1px solid #2a2a4e}
.btn-preview{background:#0f0f22;color:#818cf8;border:1px solid #2a2a4e}
.btn-run{background:linear-gradient(135deg,#6366f1,#818cf8);color:#fff}
.btn-danger{background:#1a0a0a;color:#f87171;border:1px solid #3a1a1a;font-size:12px;padding:8px 16px;width:auto}
#csi-status{display:none;background:#060610;border:1px solid #1a1a2e;border-radius:10px;padding:16px;margin-top:14px;font-family:'DM Mono',monospace;font-size:13px;line-height:1.8}
#csi-status.on{display:block}
.spin{display:inline-block;width:13px;height:13px;border:2px solid #333;border-top-color:#6366f1;border-radius:50%;animation:sp .7s linear infinite;vertical-align:middle;margin-right:8px}
@keyframes sp{to{transform:rotate(360deg)}}
.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:18px}
.stat-box{background:#060610;border:1px solid #1a1a2e;border-radius:10px;padding:16px;text-align:center}
.stat-val{font-size:28px;font-weight:800;color:#6366f1;font-family:'DM Mono',monospace}
.stat-lbl{font-size:11px;color:#555;margin-top:4px}
.log-list{list-style:none;max-height:260px;overflow-y:auto}
.log-list li{padding:6px 0;border-bottom:1px solid #1a1a2e;font-family:'DM Mono',monospace;font-size:12px;color:#aaa;display:flex;gap:8px;align-items:center}
.log-list li:last-child{border-bottom:none}
.tag{font-size:10px;padding:2px 8px;border-radius:10px;font-weight:700;text-transform:uppercase;white-space:nowrap}
.tg{background:#052e16;color:#4ade80}
.tr{background:#2e0505;color:#f87171}
.tb{background:#05142e;color:#60a5fa}
.preview-table{width:100%;border-collapse:collapse;font-size:12px}
.preview-table th{text-align:left;color:#6366f1;font-size:11px;text-transform:uppercase;letter-spacing:1px;padding:8px 0;border-bottom:1px solid #1a1a2e}
.preview-table td{padding:7px 0;border-bottom:1px solid #0e0e1a;color:#bbb;font-family:'DM Mono',monospace}
.section-title{color:#6366f1;font-size:12px;text-transform:uppercase;letter-spacing:1px;margin:18px 0 10px;font-weight:700}
</style>
<div id="csi-wrap">
<span class="csi-badge">New Site</span>
<h1 class="csi-h1">Category Sync — Importer</h1>
<p class="csi-sub">Pull categories from old site → create them → fix all post assignments automatically</p>
<div class="csi-layout">
<div>
<div class="csi-card">
<h2>⚙️ Connection</h2>
<form method="post">
<?php wp_nonce_field('cat_sync_importer'); ?>
<div class="csi-field">
<label>Old Site API URL</label>
<input type="url" name="api_url" placeholder="https://oldsite.com/wp-json/cat-sync/v1/export" value="<?= esc_attr($settings['api_url']) ?>">
</div>
<div class="csi-field">
<label>Secret Key</label>
<input type="text" name="api_key" placeholder="cse_xxxxxxxxxxxx" value="<?= esc_attr($settings['api_key']) ?>">
</div>
<button type="submit" name="cat_sync_save_settings" class="csi-btn btn-save">💾 Save Settings</button>
</form>
<button id="btn-preview" class="csi-btn btn-preview" onclick="runPreview()">👁 Preview Changes</button>
<button id="btn-run" class="csi-btn btn-run" onclick="runSync()">🚀 Run Full Sync</button>
<div id="csi-status"></div>
</div>
<?php if ($last_log): ?>
<div class="csi-card">
<h2>🕐 Last Sync — <?= esc_html($last_log['ran_at']) ?></h2>
<div class="stat-grid">
<div class="stat-box"><div class="stat-val" style="color:#4ade80"><?= $last_log['posts_fixed'] ?></div><div class="stat-lbl">Posts Fixed</div></div>
<div class="stat-box"><div class="stat-val"><?= count($last_log['categories_created']) ?></div><div class="stat-lbl">Cats Created</div></div>
<div class="stat-box"><div class="stat-val"><?= $last_log['junk_deleted'] ?></div><div class="stat-lbl">Junk Deleted</div></div>
<div class="stat-box"><div class="stat-val" style="color:#f87171"><?= $last_log['posts_skipped'] ?></div><div class="stat-lbl">Skipped</div></div>
</div>
<form method="post">
<?php wp_nonce_field('cat_sync_importer'); ?>
<button type="submit" name="cat_sync_clear_log" class="csi-btn btn-danger">🗑 Clear Log</button>
</form>
</div>
<?php endif; ?>
</div>
<div>
<div class="csi-card" style="min-height:340px">
<h2>📋 Output</h2>
<div id="results-content" style="color:#444;font-family:'DM Mono',monospace;font-size:13px">
↖ Save settings, then click Preview or Run Sync.
</div>
</div>
</div>
</div>
</div>
<script>
const nonce='<?= esc_js($nonce) ?>',ajaxUrl='<?= esc_js(admin_url('admin-ajax.php')) ?>';
function setStatus(msg,loading=true){const e=document.getElementById('csi-status');e.classList.add('on');e.innerHTML=loading?`<span class="spin"></span>${msg}`:msg}
function setResults(html){document.getElementById('results-content').innerHTML=html}
function setBtns(disabled){['btn-preview','btn-run'].forEach(id=>document.getElementById(id).disabled=disabled)}
async function runPreview(){
setBtns(true);setStatus('Connecting to old site...');setResults('<span style="color:#555">Loading preview...</span>');
const res=await fetch(ajaxUrl,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({action:'cat_sync_preview',nonce})});
const json=await res.json();setBtns(false);
if(!json.success){setStatus('❌ '+json.data,false);setResults(`<span style="color:#f87171">${json.data}</span>`);return}
setStatus('✅ Preview ready. Click Run Sync to apply.',false);
const d=json.data;
let html=`<div class="stat-grid"><div class="stat-box"><div class="stat-val">${d.posts_to_reassign}</div><div class="stat-lbl">Posts to Fix</div></div><div class="stat-box"><div class="stat-val">${d.categories_to_create.length}</div><div class="stat-lbl">Cats to Create</div></div></div>`;
if(d.categories_to_create.length){html+=`<p class="section-title">Categories to Create</p><ul class="log-list">`;d.categories_to_create.forEach(c=>{html+=`<li><span class="tag tb">new</span>${c.name}</li>`});html+=`</ul>`}
if(d.matches.length){html+=`<p class="section-title">Post Matches (preview)</p><table class="preview-table"><thead><tr><th>Post</th><th>Will be assigned to</th></tr></thead><tbody>`;d.matches.slice(0,25).forEach(m=>{html+=`<tr><td>${m.post_title}</td><td>${m.categories.join(', ')}</td></tr>`});if(d.matches.length>25)html+=`<tr><td colspan="2" style="color:#444">...and ${d.matches.length-25} more</td></tr>`;html+=`</tbody></table>`}
setResults(html);
}
async function runSync(){
if(!confirm('This will reassign post categories on this site. Continue?'))return;
setBtns(true);setStatus('🚀 Syncing... please wait.');setResults('<span style="color:#555">Working...</span>');
const res=await fetch(ajaxUrl,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:new URLSearchParams({action:'cat_sync_run',nonce})});
const json=await res.json();setBtns(false);
if(!json.success){setStatus('❌ '+json.data,false);setResults(`<span style="color:#f87171">${json.data}</span>`);return}
const d=json.data;setStatus('🎉 Done! Page will reload in 3s.',false);
let html=`<div class="stat-grid"><div class="stat-box"><div class="stat-val" style="color:#4ade80">${d.posts_fixed}</div><div class="stat-lbl">Posts Fixed</div></div><div class="stat-box"><div class="stat-val">${d.categories_created.length}</div><div class="stat-lbl">Cats Created</div></div><div class="stat-box"><div class="stat-val">${d.junk_deleted}</div><div class="stat-lbl">Junk Deleted</div></div><div class="stat-box"><div class="stat-val" style="color:#f87171">${d.posts_skipped}</div><div class="stat-lbl">Skipped</div></div></div>`;
if(d.categories_created.length){html+=`<p class="section-title">Created</p><ul class="log-list">`;d.categories_created.forEach(c=>{html+=`<li><span class="tag tg">✓</span>${c}</li>`});html+=`</ul>`}
if(d.errors.length){html+=`<p class="section-title">Errors</p><ul class="log-list">`;d.errors.forEach(e=>{html+=`<li><span class="tag tr">!</span>${e}</li>`});html+=`</ul>`}
setResults(html);setTimeout(()=>location.reload(),3000);
}
</script>
<?php
}
}
new CategorySyncImporter();
Directory Contents
Dirs: 0 × Files: 1