plugin_name = $plugin_name;
$this->version = $version;
// Add admin menu
add_action('admin_menu', array($this, 'add_debug_menu'));
// Add AJAX handlers
add_action('wp_ajax_metasync_otto_debug_test_api', array($this, 'ajax_test_otto_api'));
add_action('wp_ajax_metasync_otto_debug_test_notification', array($this, 'ajax_test_notification_endpoint'));
add_action('wp_ajax_metasync_otto_debug_clear_cache', array($this, 'ajax_clear_otto_cache'));
add_action('wp_ajax_metasync_otto_debug_simulate_crawl', array($this, 'ajax_simulate_crawl_notification'));
add_action('wp_ajax_metasync_otto_debug_test_url', array($this, 'ajax_test_specific_url'));
add_action('wp_ajax_metasync_otto_debug_emulate_changes', array($this, 'ajax_emulate_otto_changes'));
add_action('wp_ajax_metasync_otto_debug_simple_test', array($this, 'ajax_simple_test'));
add_action('wp_ajax_metasync_otto_debug_test_db_permissions', array($this, 'ajax_test_db_permissions'));
}
/**
* Whether debug tooling is allowed to run at all right now.
*
* `manage_options` alone isn't a real restriction — every site admin has
* it, so this page and its AJAX handlers were reachable by anyone on any
* site. Hard-disabled: the menu item, the page, and every AJAX handler
* are unreachable for all users on all sites, no toggle or capability
* bypasses this. The class and its methods are left in place; only
* access is blocked.
*/
private static function is_debug_tools_enabled() {
return false;
}
/**
* Whether a URL's crawl pipeline has completed, per the bounded
* Metasync_Otto_Job_Status store. Replaces the legacy unbounded
* metasync_otto_crawldata option lookup: state history is pruned
* 48 hours after completion, so a URL finished longer ago reads as
* "not crawled" here without implying Otto never touched it.
*/
private static function is_url_crawled($url) {
if (!class_exists('Metasync_Otto_Job_Status')) {
return false;
}
$entry = Metasync_Otto_Job_Status::get($url);
return is_array($entry)
&& isset($entry['state'])
&& $entry['state'] === Metasync_Otto_Job_Status::STATE_COMPLETED;
}
/**
* Add debug menu (internal only - hidden on customer production sites)
*/
public function add_debug_menu() {
if (!current_user_can('manage_options') || !self::is_debug_tools_enabled()) {
return;
}
$menu_slug = Metasync_Admin::$page_slug;
add_submenu_page(
$menu_slug,
Metasync::get_whitelabel_otto_name() . ' Debug',
Metasync::get_whitelabel_otto_name() . ' Debug',
'manage_options',
$menu_slug . '-otto-debug',
array($this, 'create_debug_page')
);
}
/**
* Create the debug page
*/
public function create_debug_page() {
if (!current_user_can('manage_options') || !self::is_debug_tools_enabled()) {
wp_die('Unauthorized');
}
$whitelabel_otto_name = Metasync::get_whitelabel_otto_name();
?>
Administrator Access
Debug Access:
Granted via manage_options
Current User:
user_login); ?> (ID: ID; ?>)
User Roles:
roles)); ?>
Debug Access Active
You have access to all debug tools and diagnostics.
Configuration Status
SSR Enabled:
Yes (Always Active)
UUID:
Disable for Logged-in Users:
Current User Logged In:
Plugin Version:
Notification Endpoint Status
REST API Base URL:
Notification Endpoint:
REST API Available:
Endpoint Registered:
is_rest_endpoint_registered() ? 'Yes' : 'No'; ?>
Expected Method:
POST
Expected JSON Fields:
domain, urls
get_site_url(),
'uuid' => $otto_uuid
), $api_url);
?>
API Connectivity Status
API Endpoint:
Test URL:
API Reachable:
can_reach_otto_api() ? 'Yes' : 'No'; ?>
SSL Verification:
Enabled
Timeout:
30 seconds
User Agent:
MetaSync-WordPress-Plugin/1.0
get_current_url();
$is_crawled = self::is_url_crawled($current_url);
$render_diagnostics = $this->get_render_strategy_diagnostics();
?>
Processing Status
Current URL:
URL Crawled by :
Excluded:
is_otto_excluded() ? 'Yes' : 'No'; ?>
Page Type:
get_page_type()); ?>
Cache Status:
get_cache_status()); ?>
Processing Method:
get_processing_method()); ?>
Render Strategy:
- PHP:
- WP:
- Memory: (used: )
- Buffer Level:
- Headers Sent:
Detected Plugins:
$active):
?>
:
Detected Hosts:
$detected):
?>
:
get_routes();
return isset($routes['/metasync/v1/otto_crawl_notify']);
}
/**
* Check if OTTO API is reachable
*/
private function can_reach_otto_api() {
$general_options = Metasync::get_option('general');
$otto_uuid = $general_options['otto_pixel_uuid'] ?? '';
if (empty($otto_uuid)) {
return false;
}
# Use endpoint manager to get the correct API URL
$api_endpoint = class_exists('Metasync_Endpoint_Manager')
? Metasync_Endpoint_Manager::get_endpoint('OTTO_URL_DETAILS')
: 'https://sa.searchatlas.com/api/v2/otto-url-details';
$api_url = add_query_arg(array(
'url' => get_site_url(),
'uuid' => $otto_uuid
), $api_endpoint);
$response = wp_remote_get($api_url, array(
'timeout' => 10,
'sslverify' => true
));
return !is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200;
}
/**
* Get current URL
*/
private function get_current_url() {
$scheme = is_ssl() ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? '';
$uri = $_SERVER['REQUEST_URI'] ?? '';
return $scheme . '://' . $host . $uri;
}
/**
* Check if OTTO is excluded for current request
*/
private function is_otto_excluded() {
// Check AJAX requests
if (wp_doing_ajax() || defined('DOING_AJAX') && DOING_AJAX) {
return true;
}
// Check WooCommerce pages
if (function_exists('is_woocommerce') && is_woocommerce()) {
return true;
}
// Check logged-in user exclusion
$general_options = Metasync::get_option('general');
if (!empty($general_options['otto_disable_on_loggedin']) &&
$general_options['otto_disable_on_loggedin'] === 'true' &&
is_user_logged_in()) {
return true;
}
return false;
}
/**
* Get page type
*/
private function get_page_type() {
if (is_home()) return 'Home';
if (is_front_page()) return 'Front Page';
if (is_single()) return 'Single Post';
if (is_page()) return 'Page';
if (is_category()) return 'Category';
if (is_tag()) return 'Tag';
if (is_archive()) return 'Archive';
if (is_search()) return 'Search';
if (is_404()) return '404';
return 'Other';
}
/**
* Get cache status
*/
private function get_cache_status() {
// Cache is disabled in current implementation
return 'Disabled (SSR Mode)';
}
/**
* Get processing method
*/
private function get_processing_method() {
// OTTO SSR is always enabled by default
$otto_enabled = true;
if ($otto_enabled) {
// Check which render strategy would be used
if (class_exists('Metasync_Otto_Render_Strategy')) {
$method = Metasync_Otto_Render_Strategy::determine_method();
if ($method === Metasync_Otto_Render_Strategy::METHOD_BUFFER) {
return 'Server-Side Rendering (SSR) - Output Buffer (Fast)';
} elseif ($method === Metasync_Otto_Render_Strategy::METHOD_HTTP) {
return 'Server-Side Rendering (SSR) - HTTP Request (Fallback)';
}
}
return 'Server-Side Rendering (SSR)';
} else {
return 'Client-Side JavaScript';
}
}
/**
* Get render strategy diagnostics
*/
public function get_render_strategy_diagnostics() {
if (!class_exists('Metasync_Otto_Render_Strategy')) {
return array(
'available' => false,
'message' => 'Render Strategy class not loaded'
);
}
return Metasync_Otto_Render_Strategy::get_diagnostics();
}
/**
* AJAX handler for testing OTTO API
*/
public function ajax_test_otto_api() {
check_ajax_referer('metasync_otto_debug', 'nonce');
if (!current_user_can('manage_options') || !self::is_debug_tools_enabled()) {
wp_die('Unauthorized');
}
$general_options = Metasync::get_option('general');
$otto_uuid = $general_options['otto_pixel_uuid'] ?? '';
if (empty($otto_uuid)) {
wp_send_json_error(Metasync::get_whitelabel_otto_name() . ' UUID not configured');
}
$test_url = get_site_url();
# Use endpoint manager to get the correct API URL
$api_endpoint = class_exists('Metasync_Endpoint_Manager')
? Metasync_Endpoint_Manager::get_endpoint('OTTO_URL_DETAILS')
: 'https://sa.searchatlas.com/api/v2/otto-url-details';
$api_url = add_query_arg(array(
'url' => $test_url,
'uuid' => $otto_uuid
), $api_endpoint);
$response = wp_remote_get($api_url, array(
'timeout' => 30,
'sslverify' => true,
'headers' => array(
'User-Agent' => 'MetaSync-WordPress-Plugin/1.0'
)
));
if (is_wp_error($response)) {
wp_send_json_error(array(
'error' => $response->get_error_message(),
'url' => $api_url
));
}
$response_code = wp_remote_retrieve_response_code($response);
$body = wp_remote_retrieve_body($response);
wp_send_json_success(array(
'response_code' => $response_code,
'url' => $api_url,
'body' => $body,
'has_data' => !empty($body),
'data_valid' => json_decode($body, true) !== null
));
}
/**
* AJAX handler for testing notification endpoint
*/
public function ajax_test_notification_endpoint() {
check_ajax_referer('metasync_otto_debug', 'nonce');
if (!current_user_can('manage_options') || !self::is_debug_tools_enabled()) {
wp_die('Unauthorized');
}
$endpoint_url = rest_url('metasync/v1/otto_crawl_notify');
$test_data = array(
'domain' => get_site_url(),
'urls' => array('/', '/about/', '/contact/')
);
$response = wp_remote_post($endpoint_url, array(
'headers' => array(
'Content-Type' => 'application/json'
),
'body' => json_encode($test_data),
'timeout' => 30
));
if (is_wp_error($response)) {
wp_send_json_error(array(
'error' => $response->get_error_message(),
'url' => $endpoint_url
));
}
$response_code = wp_remote_retrieve_response_code($response);
$body = wp_remote_retrieve_body($response);
wp_send_json_success(array(
'response_code' => $response_code,
'url' => $endpoint_url,
'body' => $body,
'test_data' => $test_data
));
}
/**
* AJAX handler for clearing OTTO cache
*/
public function ajax_clear_otto_cache() {
check_ajax_referer('metasync_otto_debug', 'nonce');
if (!current_user_can('manage_options') || !self::is_debug_tools_enabled()) {
wp_die('Unauthorized');
}
// Clear the retired crawl-data option if an internal debug tool is
// ever re-enabled. This explicit action is harmless and preserves
// the old clear-cache contract; production never reaches this
// handler because debug tools are hard-disabled.
delete_option('metasync_otto_crawldata');
// Clear any cached API responses
$general_options = Metasync::get_option('general');
$otto_uuid = $general_options['otto_pixel_uuid'] ?? '';
if (!empty($otto_uuid)) {
delete_transient(Metasync_Heartbeat_Manager::public_hash_cache_key($otto_uuid));
}
delete_transient('metasync_otto_js_detected');
wp_send_json_success(array(
'message' => Metasync::get_whitelabel_otto_name() . ' cache cleared successfully',
'cleared_items' => array(
'crawl_data' => true,
'api_cache' => true
)
));
}
/**
* AJAX handler for simulating crawl notification
*/
public function ajax_simulate_crawl_notification() {
check_ajax_referer('metasync_otto_debug', 'nonce');
if (!current_user_can('manage_options') || !self::is_debug_tools_enabled()) {
wp_die('Unauthorized');
}
$general_options = Metasync::get_option('general');
$otto_uuid = $general_options['otto_pixel_uuid'] ?? '';
if (empty($otto_uuid)) {
wp_send_json_error(Metasync::get_whitelabel_otto_name() . ' UUID not configured');
}
// Simulate crawl notification
$test_data = array(
'domain' => get_site_url(),
'urls' => array('/', '/about/', '/contact/', '/blog/')
);
// Create a mock request object
$request = new WP_REST_Request('POST', '/metasync/v1/otto_crawl_notify');
$request->set_body(json_encode($test_data));
// Call the notification handler
$response = metasync_otto_crawl_notify($request);
wp_send_json_success(array(
'message' => 'Crawl notification simulated',
'test_data' => $test_data,
'response' => $response->get_data(),
'response_code' => $response->get_status()
));
}
/**
* AJAX handler for testing specific URL
*/
public function ajax_test_specific_url() {
try {
check_ajax_referer('metasync_otto_debug', 'nonce');
if (!current_user_can('manage_options') || !self::is_debug_tools_enabled()) {
wp_die('Unauthorized');
}
$test_url = sanitize_url($_POST['test_url'] ?? '');
if (empty($test_url)) {
wp_send_json_error('No URL provided');
}
$results = array(
'test_url' => $test_url,
'timestamp' => current_time('mysql'),
'tests' => array()
);
// Test 1: URL Accessibility
$results['tests']['url_accessibility'] = $this->test_url_accessibility($test_url);
// Test 2: OTTO API Data
$results['tests']['otto_api_data'] = $this->test_otto_api_for_url($test_url);
// Test 3: Crawl Status
$results['tests']['crawl_status'] = $this->test_crawl_status($test_url);
// Test 4: Page Type Detection
$results['tests']['page_type_detection'] = $this->test_page_type_detection($test_url);
// Test 5: Processing Eligibility
$results['tests']['processing_eligibility'] = $this->test_processing_eligibility($test_url);
wp_send_json_success($results);
} catch (Exception $e) {
error_log('MetaSync OTTO Debug: Exception in ajax_test_specific_url: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- unreachable in production
wp_send_json_error('Exception: ' . $e->getMessage());
}
}
/**
* AJAX handler for emulating OTTO changes
*/
public function ajax_emulate_otto_changes() {
check_ajax_referer('metasync_otto_debug', 'nonce');
if (!current_user_can('manage_options') || !self::is_debug_tools_enabled()) {
wp_die('Unauthorized');
}
$test_url = sanitize_url($_POST['test_url'] ?? '');
if (empty($test_url)) {
wp_send_json_error('No URL provided');
}
$results = array(
'test_url' => $test_url,
'timestamp' => current_time('mysql'),
'emulation' => array()
);
// Emulate OTTO processing
$results['emulation'] = $this->emulate_otto_processing($test_url);
wp_send_json_success($results);
}
/**
* Test URL accessibility
*/
private function test_url_accessibility($url) {
$response = wp_remote_get($url, array(
'timeout' => 10,
'user-agent' => 'MetaSync Debug Tool'
));
if (is_wp_error($response)) {
return array(
'status' => 'error',
'message' => $response->get_error_message(),
'accessible' => false
);
}
$status_code = wp_remote_retrieve_response_code($response);
$body = wp_remote_retrieve_body($response);
return array(
'status' => 'success',
'status_code' => $status_code,
'accessible' => $status_code === 200,
'content_length' => strlen($body),
'headers' => wp_remote_retrieve_headers($response)->getAll()
);
}
/**
* Test OTTO API data for specific URL
*/
private function test_otto_api_for_url($url) {
$general_options = Metasync::get_option('general');
$otto_uuid = $general_options['otto_pixel_uuid'] ?? '';
if (empty($otto_uuid)) {
return array(
'status' => 'error',
'message' => Metasync::get_whitelabel_otto_name() . ' UUID not configured'
);
}
// Use the existing OTTO API function
if (function_exists('metasync_fetch_otto_seo_data')) {
$data = metasync_fetch_otto_seo_data($url, $otto_uuid);
if (is_wp_error($data)) {
return array(
'status' => 'error',
'message' => $data->get_error_message(),
'has_data' => false
);
}
return array(
'status' => 'success',
'has_data' => !empty($data),
'data_keys' => array_keys($data ?? array()),
'recommendations_count' => count($data['recommendations'] ?? array()),
'sample_data' => array_slice($data ?? array(), 0, 3) // First 3 items for preview
);
}
return array(
'status' => 'error',
'message' => Metasync::get_whitelabel_otto_name() . ' API function not available'
);
}
/**
* Test crawl status for URL
*/
private function test_crawl_status($url) {
if (class_exists('Metasync_Otto_Job_Status')) {
$entry = Metasync_Otto_Job_Status::get($url);
$is_crawled = is_array($entry)
&& isset($entry['state'])
&& $entry['state'] === Metasync_Otto_Job_Status::STATE_COMPLETED;
return array(
'status' => 'success',
'is_crawled' => $is_crawled,
'job_state' => is_array($entry) && isset($entry['state']) ? $entry['state'] : 'unknown',
'job_stage' => is_array($entry) && isset($entry['stage']) ? $entry['stage'] : '',
'job_attempts' => is_array($entry) && isset($entry['attempts']) ? (int) $entry['attempts'] : 0,
'job_updated' => is_array($entry) && isset($entry['updated']) ? $entry['updated'] : null
);
}
return array(
'status' => 'error',
'message' => Metasync::get_whitelabel_otto_name() . ' job status class not available'
);
}
/**
* Test page type detection
*/
private function test_page_type_detection($url) {
$parsed_url = wp_parse_url($url);
$path = $parsed_url['path'] ?? '/';
$detection = array(
'url' => $url,
'path' => $path,
'is_ajax' => strpos($path, 'wp-admin/admin-ajax.php') !== false,
'is_woocommerce' => function_exists('is_woocommerce') ? false : 'WooCommerce not available',
'is_admin' => strpos($path, '/wp-admin/') !== false,
'is_login' => strpos($path, '/wp-login.php') !== false,
'is_cron' => strpos($path, '/wp-cron.php') !== false,
'is_xmlrpc' => strpos($path, '/xmlrpc.php') !== false
);
// Check if it's a WooCommerce page
if (function_exists('is_woocommerce')) {
// This would need to be tested with actual page context
$detection['is_woocommerce'] = 'Requires page context to determine';
}
return $detection;
}
/**
* Test processing eligibility
*/
private function test_processing_eligibility($url) {
$general_options = Metasync::get_option('general');
// OTTO SSR is always enabled by default
$otto_enabled = true;
$disable_logged_in = $general_options['otto_disable_on_loggedin'] ?? false;
$eligibility = array(
'otto_enabled' => $otto_enabled,
'disable_logged_in' => $disable_logged_in,
'user_logged_in' => is_user_logged_in(),
'eligible_for_processing' => false,
'exclusion_reasons' => array()
);
// OTTO SSR is always enabled, so this check is no longer needed
if ($disable_logged_in && is_user_logged_in()) {
$eligibility['exclusion_reasons'][] = 'User is logged in and ' . Metasync::get_whitelabel_otto_name() . ' disabled for logged-in users';
}
$eligibility['eligible_for_processing'] = empty($eligibility['exclusion_reasons']);
return $eligibility;
}
/**
* Emulate OTTO processing
*/
private function emulate_otto_processing($url) {
$emulation = array(
'url' => $url,
'steps' => array(),
'errors' => array(),
'changes_applied' => array()
);
// Step 1: Check if URL is crawled
$emulation['steps']['check_crawled'] = $this->test_crawl_status($url);
// Step 2: Fetch OTTO data
$emulation['steps']['fetch_otto_data'] = $this->test_otto_api_for_url($url);
// Step 3: Simulate HTML processing
if (class_exists('Metasync_otto_html')) {
try {
$otto_html = new Metasync_otto_html(false);
$emulation['steps']['html_processing'] = array(
'status' => 'success',
'message' => Metasync::get_whitelabel_otto_name() . ' HTML class available'
);
// Simulate processing (without actually modifying files)
$emulation['changes_applied'] = array(
'header_changes' => 'Simulated header modifications',
'body_changes' => 'Simulated body modifications',
'footer_changes' => 'Simulated footer modifications'
);
} catch (Exception $e) {
$emulation['errors'][] = 'HTML processing error: ' . $e->getMessage();
}
} else {
$emulation['errors'][] = Metasync::get_whitelabel_otto_name() . ' HTML class not available';
}
return $emulation;
}
/**
* Simple AJAX test to verify basic functionality
*/
public function ajax_simple_test() {
try {
check_ajax_referer('metasync_otto_debug', 'nonce');
if (!current_user_can('manage_options') || !self::is_debug_tools_enabled()) {
wp_send_json_error('Unauthorized');
}
wp_send_json_success(array(
'message' => 'AJAX is working!',
'timestamp' => current_time('mysql'),
'user_id' => get_current_user_id(),
'nonce_verified' => true
));
} catch (Exception $e) {
error_log('MetaSync OTTO Debug: Exception in ajax_simple_test: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- unreachable in production
wp_send_json_error('Exception: ' . $e->getMessage());
}
}
/**
* AJAX handler for testing database permissions
*/
public function ajax_test_db_permissions() {
try {
check_ajax_referer('metasync_otto_debug', 'nonce');
if (!current_user_can('manage_options') || !self::is_debug_tools_enabled()) {
wp_send_json_error('Unauthorized');
}
$results = array(
'timestamp' => current_time('mysql'),
'tests' => array()
);
// Test 1: Try to schedule a simple action
$test_action = 'metasync_debug_test_action';
$scheduled = wp_schedule_single_event(time() + 60, $test_action, array('test' => 'data'));
$results['tests']['action_scheduler'] = array(
'can_schedule' => $scheduled !== false,
'scheduled' => $scheduled,
'message' => $scheduled !== false ? 'Action Scheduler working' : 'Action Scheduler failed - likely database permissions issue'
);
// Test 2: Check if we can access Action Scheduler tables
global $wpdb;
$table_name = $wpdb->prefix . 'actionscheduler_actions';
$table_exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table_name))) == $table_name; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- admin debug tool — scratch diagnostics storage outside any WordPress API
$results['tests']['table_access'] = array(
'table_exists' => $table_exists,
'table_name' => $table_name,
'message' => $table_exists ? 'Action Scheduler table exists' : 'Action Scheduler table not found'
);
// Test 3: Try to insert a test record
if ($table_exists) {
$insert_result = $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- admin debug tool — scratch diagnostics storage outside any WordPress API
$table_name,
array(
'hook' => 'metasync_debug_test',
'status' => 'pending',
'scheduled_date_gmt' => current_time('mysql', 1),
'args' => json_encode(array('test' => true)),
'schedule' => 'once'
),
array('%s', '%s', '%s', '%s', '%s')
);
$results['tests']['insert_test'] = array(
'can_insert' => $insert_result !== false,
'insert_id' => $wpdb->insert_id,
'last_error' => $wpdb->last_error,
'message' => $insert_result !== false ? 'Can insert records' : 'Cannot insert records - ' . $wpdb->last_error
);
// Clean up test record
if ($insert_result !== false && $wpdb->insert_id) {
$wpdb->delete($table_name, array('ID' => $wpdb->insert_id)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- admin debug tool — scratch diagnostics storage outside any WordPress API
}
}
// Test 4: Check WordPress cron functionality
$cron_disabled = defined('DISABLE_WP_CRON') && DISABLE_WP_CRON;
$results['tests']['wp_cron'] = array(
'enabled' => !$cron_disabled,
'message' => $cron_disabled ? 'WordPress cron is disabled' : 'WordPress cron is enabled'
);
wp_send_json_success($results);
} catch (Exception $e) {
error_log('MetaSync OTTO Debug: Exception in ajax_test_db_permissions: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- unreachable in production
wp_send_json_error('Exception: ' . $e->getMessage());
}
}
}