'boolean', 'sanitize_callback' => array($this, 'sanitize_boolean'), 'default' => 1 )); register_setting('igny8_bridge_connection', 'igny8_connection_enabled', array( 'type' => 'boolean', 'sanitize_callback' => array($this, 'sanitize_boolean'), 'default' => 1 )); register_setting('igny8_bridge_controls', 'igny8_enabled_post_types', array( 'type' => 'array', 'sanitize_callback' => array($this, 'sanitize_post_types'), 'default' => array_keys(igny8_get_supported_post_types()) )); register_setting('igny8_bridge_controls', 'igny8_enabled_taxonomies', array( 'type' => 'array', 'sanitize_callback' => array($this, 'sanitize_taxonomies'), 'default' => array('category', 'post_tag', 'product_cat', 'igny8_sectors', 'igny8_clusters') )); register_setting('igny8_bridge_controls', 'igny8_enable_woocommerce', array( 'type' => 'boolean', 'sanitize_callback' => array($this, 'sanitize_boolean'), 'default' => class_exists('WooCommerce') ? 1 : 0 )); register_setting('igny8_bridge_controls', 'igny8_control_mode', array( 'type' => 'string', 'sanitize_callback' => array($this, 'sanitize_control_mode'), 'default' => 'mirror' )); register_setting('igny8_bridge_controls', 'igny8_enabled_modules', array( 'type' => 'array', 'sanitize_callback' => array($this, 'sanitize_modules'), 'default' => array_keys(igny8_get_available_modules()) )); } /** * Enqueue admin scripts and styles * * @param string $hook Current admin page hook */ public function enqueue_scripts($hook) { // Enqueue on settings page if ($hook === 'settings_page_igny8-settings') { wp_enqueue_style( 'igny8-admin-style', IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/css/admin.css', array(), IGNY8_BRIDGE_VERSION ); wp_enqueue_script( 'igny8-admin-script', IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/js/admin.js', array('jquery'), IGNY8_BRIDGE_VERSION, true ); wp_localize_script('igny8-admin-script', 'igny8Admin', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('igny8_admin_nonce'), )); } // Enqueue on post/page/product list pages if (strpos($hook, 'edit.php') !== false) { $screen = get_current_screen(); if ($screen && in_array($screen->post_type, array('post', 'page', 'product', ''))) { wp_enqueue_style( 'igny8-admin-style', IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/css/admin.css', array(), IGNY8_BRIDGE_VERSION ); wp_enqueue_script( 'igny8-admin-script', IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/js/admin.js', array('jquery'), IGNY8_BRIDGE_VERSION, true ); wp_localize_script('igny8-admin-script', 'igny8Admin', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('igny8_admin_nonce'), )); } } } /** * Render settings page */ public function render_settings_page() { // Handle form submission (use wp_verify_nonce to avoid wp_die on failure) if (isset($_POST['igny8_connect'])) { if (empty($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'igny8_settings_nonce')) { add_settings_error( 'igny8_settings', 'igny8_nonce', __('Security check failed. Please refresh the page and try again.', 'igny8-bridge'), 'error' ); } else { $this->handle_connection(); } } // Handle revoke API key (use wp_verify_nonce) if (isset($_POST['igny8_revoke_api_key'])) { if (empty($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'igny8_revoke_api_key')) { add_settings_error( 'igny8_settings', 'igny8_nonce_revoke', __('Security check failed. Could not revoke API key.', 'igny8-bridge'), 'error' ); } else { self::revoke_api_key(); add_settings_error( 'igny8_settings', 'igny8_api_key_revoked', __('API key revoked and removed from this site.', 'igny8-bridge'), 'updated' ); } } // Webhook secret regeneration removed - using API key only // Include settings template include IGNY8_BRIDGE_PLUGIN_DIR . 'admin/settings.php'; } /** * Handle API connection - API key only */ private function handle_connection() { $api_key = sanitize_text_field($_POST['igny8_api_key'] ?? ''); // API key is required if (empty($api_key)) { add_settings_error( 'igny8_settings', 'igny8_error', __('API key is required to connect to IGNY8.', 'igny8-bridge'), 'error' ); return; } // Connect using API key only $api = new Igny8API(); if (!$api->connect($api_key)) { add_settings_error( 'igny8_settings', 'igny8_error', __('Failed to connect to IGNY8 API. Please verify your API key is correct.', 'igny8-bridge'), 'error' ); return; } // Store API key securely and also set access token to the API key for subsequent calls // Only store if it's not the placeholder if (!$is_placeholder) { if (function_exists('igny8_store_secure_option')) { igny8_store_secure_option('igny8_api_key', $api_key); igny8_store_secure_option('igny8_access_token', $api_key); } else { update_option('igny8_api_key', $api_key); update_option('igny8_access_token', $api_key); } } // Try to get site ID (if available) using the authenticated client $site_response = $api->get('/system/sites/'); if ($site_response['success'] && !empty($site_response['results'])) { $site = $site_response['results'][0]; update_option('igny8_site_id', $site['id']); } add_settings_error( 'igny8_settings', 'igny8_connected', __('Successfully connected to IGNY8 API and stored API key.', 'igny8-bridge'), 'updated' ); // Sync site structure to backend (post types, taxonomies, etc.) igny8_sync_site_structure_to_backend(); } /** * Revoke stored API key (secure delete) * * Public so tests can call it directly. */ public static function revoke_api_key() { if (function_exists('igny8_delete_secure_option')) { igny8_delete_secure_option('igny8_api_key'); igny8_delete_secure_option('igny8_access_token'); igny8_delete_secure_option('igny8_refresh_token'); } else { delete_option('igny8_api_key'); delete_option('igny8_access_token'); delete_option('igny8_refresh_token'); } // Also clear token-issued timestamps delete_option('igny8_token_refreshed_at'); delete_option('igny8_access_token_issued'); } /** * Test API connection (AJAX handler) */ public static function test_connection() { check_ajax_referer('igny8_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Unauthorized')); } if (!igny8_is_connection_enabled()) { wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations to test.')); } $api = new Igny8API(); if (!$api->is_authenticated()) { wp_send_json_error(array('message' => 'Not authenticated')); } // Try multiple endpoints to find one that works $test_endpoints = array( '/system/ping/' => 'System ping endpoint', '/planner/keywords/?page_size=1' => 'Planner keywords list', '/system/sites/' => 'Sites list' ); $last_error = ''; $last_status = 0; foreach ($test_endpoints as $endpoint => $description) { $response = $api->get($endpoint); if ($response['success']) { $checked_at = current_time('timestamp'); update_option('igny8_last_api_health_check', $checked_at); wp_send_json_success(array( 'message' => 'Connection successful (tested: ' . $description . ')', 'endpoint' => $endpoint, 'checked_at' => $checked_at )); return; } $last_error = $response['error'] ?? 'Unknown error'; $last_status = $response['http_status'] ?? 0; } // All endpoints failed wp_send_json_error(array( 'message' => 'Connection failed: ' . $last_error, 'http_status' => $last_status, 'full_error' => $last_error, 'endpoints_tested' => array_keys($test_endpoints) )); } /** * Sync posts to IGNY8 (AJAX handler) */ public static function sync_posts() { check_ajax_referer('igny8_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Unauthorized')); } if (!igny8_is_connection_enabled()) { wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); } $result = igny8_batch_sync_post_statuses(); wp_send_json_success(array( 'message' => sprintf('Synced %d posts, %d failed', $result['synced'], $result['failed']), 'data' => $result )); } /** * Sync taxonomies (AJAX handler) */ public static function sync_taxonomies() { check_ajax_referer('igny8_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Unauthorized')); } if (!igny8_is_connection_enabled()) { wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); } $api = new Igny8API(); if (!$api->is_authenticated()) { wp_send_json_error(array('message' => 'Not authenticated')); } // Sync sectors and clusters from IGNY8 $sectors_result = igny8_sync_igny8_sectors_to_wp(); $clusters_result = igny8_sync_igny8_clusters_to_wp(); wp_send_json_success(array( 'message' => sprintf('Synced %d sectors, %d clusters', $sectors_result['synced'] ?? 0, $clusters_result['synced'] ?? 0 ), 'data' => array( 'sectors' => $sectors_result, 'clusters' => $clusters_result ) )); } /** * Sync from IGNY8 (AJAX handler) */ public static function sync_from_igny8() { check_ajax_referer('igny8_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Unauthorized')); } if (!igny8_is_connection_enabled()) { wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); } $result = igny8_sync_igny8_tasks_to_wp(); if ($result['success']) { wp_send_json_success(array( 'message' => sprintf('Created %d posts, updated %d posts', $result['created'], $result['updated'] ), 'data' => $result )); } else { wp_send_json_error(array( 'message' => $result['error'] ?? 'Sync failed' )); } } /** * Collect and send site data (AJAX handler) */ public static function collect_site_data() { check_ajax_referer('igny8_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Unauthorized')); } if (!igny8_is_connection_enabled()) { wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); } $site_id = get_option('igny8_site_id'); if (!$site_id) { wp_send_json_error(array('message' => 'Site ID not set')); } $result = igny8_send_site_data_to_igny8($site_id); if ($result) { wp_send_json_success(array( 'message' => 'Site data collected and sent successfully', 'data' => $result )); } else { wp_send_json_error(array('message' => 'Failed to send site data')); } } /** * Get sync statistics (AJAX handler) */ public static function get_stats() { check_ajax_referer('igny8_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Unauthorized')); } global $wpdb; // Count synced posts $synced_posts = $wpdb->get_var(" SELECT COUNT(DISTINCT post_id) FROM {$wpdb->postmeta} WHERE meta_key = '_igny8_task_id' "); // Get last sync time $last_sync = get_option('igny8_last_site_sync', 0); $last_sync_formatted = $last_sync ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $last_sync) : 'Never'; wp_send_json_success(array( 'synced_posts' => intval($synced_posts), 'last_sync' => $last_sync_formatted )); } /** * Sanitize post types option * * @param mixed $value Raw value * @return array */ public function sanitize_post_types($value) { $supported = array_keys(igny8_get_supported_post_types()); if (!is_array($value)) { return $supported; } $clean = array(); foreach ($value as $post_type) { $post_type = sanitize_key($post_type); if (in_array($post_type, $supported, true)) { $clean[] = $post_type; } } return !empty($clean) ? $clean : $supported; } /** * Sanitize taxonomies option * * @param mixed $value Raw value * @return array */ public function sanitize_taxonomies($value) { $supported = array_keys(igny8_get_supported_taxonomies()); if (!is_array($value)) { return array('category', 'post_tag', 'product_cat', 'igny8_sectors', 'igny8_clusters'); } $clean = array(); foreach ($value as $taxonomy) { $taxonomy = sanitize_key($taxonomy); if (in_array($taxonomy, $supported, true)) { $clean[] = $taxonomy; } } // Return defaults if nothing selected return !empty($clean) ? $clean : array('category', 'post_tag'); } /** * Sanitize boolean option * * @param mixed $value Raw value * @return int */ public function sanitize_boolean($value) { return $value ? 1 : 0; } /** * Sanitize control mode * * @param mixed $value Raw value * @return string */ public function sanitize_control_mode($value) { $value = is_string($value) ? strtolower($value) : 'mirror'; return in_array($value, array('mirror', 'hybrid'), true) ? $value : 'mirror'; } /** * Sanitize module toggles * * @param mixed $value Raw value * @return array */ public function sanitize_modules($value) { $supported = array_keys(igny8_get_available_modules()); if (!is_array($value)) { return $supported; } $clean = array(); foreach ($value as $module) { $module = sanitize_key($module); if (in_array($module, $supported, true)) { $clean[] = $module; } } return !empty($clean) ? $clean : $supported; } } // Register AJAX handlers add_action('wp_ajax_igny8_test_connection', array('Igny8Admin', 'test_connection')); add_action('wp_ajax_igny8_sync_posts', array('Igny8Admin', 'sync_posts')); add_action('wp_ajax_igny8_sync_taxonomies', array('Igny8Admin', 'sync_taxonomies')); add_action('wp_ajax_igny8_sync_from_igny8', array('Igny8Admin', 'sync_from_igny8')); add_action('wp_ajax_igny8_collect_site_data', array('Igny8Admin', 'collect_site_data')); add_action('wp_ajax_igny8_get_stats', array('Igny8Admin', 'get_stats'));