'', 'github_token' => '', ), '', false // Do not autoload — contains encrypted token. ); } } /** * Plugin deactivation * * @since 1.0.0 * * @return void */ function robotstxt_docmd_deactivate() { flush_rewrite_rules(); robotstxt_docmd_unschedule_all_crons(); } /** * Initialize plugin * * @since 1.0.0 * * @return void */ function robotstxt_docmd_init() { // Register CPT. robotstxt_docmd_register_post_types(); } /** * Register Custom Post Type for mappings * * @since 1.0.0 * * @return void */ function robotstxt_docmd_register_post_types() { register_post_type( 'robotstxt_map', array( 'labels' => array( 'name' => __( 'Doc Mappings', 'robotstxt-documentation-markdown' ), 'singular_name' => __( 'Doc Mapping', 'robotstxt-documentation-markdown' ), ), 'public' => false, 'publicly_queryable' => false, 'show_ui' => false, 'show_in_menu' => false, 'show_in_nav_menus' => false, 'show_in_admin_bar' => false, 'capability_type' => 'post', 'hierarchical' => false, 'supports' => array( 'title' ), 'has_archive' => false, 'rewrite' => false, 'can_export' => true, 'delete_with_user' => false, ) ); } /** * Register admin menu * * @since 1.0.0 * * @return void */ function robotstxt_docmd_register_admin_menu() { // Main menu. add_menu_page( __( 'Documentation', 'robotstxt-documentation-markdown' ), __( 'Documentation', 'robotstxt-documentation-markdown' ), 'edit_pages', 'robotstxt-docmd-mappings', 'robotstxt_docmd_render_mappings_page', 'dashicons-media-document', 30 ); // Mappings (replaces auto-generated first item). add_submenu_page( 'robotstxt-docmd-mappings', __( 'Mappings', 'robotstxt-documentation-markdown' ), __( 'Mappings', 'robotstxt-documentation-markdown' ), 'edit_pages', 'robotstxt-docmd-mappings', 'robotstxt_docmd_render_mappings_page' ); // Discover Files. add_submenu_page( 'robotstxt-docmd-mappings', __( 'Discover Files', 'robotstxt-documentation-markdown' ), __( 'Discover Files', 'robotstxt-documentation-markdown' ), 'edit_pages', 'robotstxt-docmd-discover', 'robotstxt_docmd_render_discover_page' ); // Add New (manual). add_submenu_page( 'robotstxt-docmd-mappings', __( 'Add New', 'robotstxt-documentation-markdown' ), __( 'Add New', 'robotstxt-documentation-markdown' ), 'edit_pages', 'robotstxt-docmd-add-mapping', 'robotstxt_docmd_render_add_mapping_page' ); // Settings. add_submenu_page( 'robotstxt-docmd-mappings', __( 'Settings', 'robotstxt-documentation-markdown' ), __( 'Settings', 'robotstxt-documentation-markdown' ), 'edit_pages', 'robotstxt-docmd-settings', 'robotstxt_docmd_render_settings_page' ); } /** * Enqueue admin assets * * @since 1.0.0 * * @param string $hook Current admin page hook. * @return void */ function robotstxt_docmd_enqueue_admin_assets( $hook ) { // Only on our pages. if ( false === strpos( $hook, 'robotstxt-docmd' ) ) { return; } wp_enqueue_style( 'robotstxt-docmd-admin', ROBOTSTXT_DOCMD_PLUGIN_URL . 'assets/css/admin.css', array(), ROBOTSTXT_DOCMD_VERSION ); } /** * Handle admin actions (sync, delete from Mappings page) * * @since 1.0.0 * * @return void */ function robotstxt_docmd_handle_admin_actions() { if ( ! current_user_can( 'edit_pages' ) ) { return; } // Handle mapping form submission (POST). // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( isset( $_POST['robotstxt_docmd_nonce'] ) && isset( $_POST['title'] ) ) { if ( wp_verify_nonce( sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'robotstxt_docmd_nonce' ) ) ), 'robotstxt_docmd_save_mapping' ) ) { robotstxt_docmd_handle_save_mapping(); // The function above will redirect and exit. } } // Handle settings form submission (POST). // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( isset( $_POST['robotstxt_docmd_nonce'] ) && isset( $_POST['github_url'] ) ) { if ( wp_verify_nonce( sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'robotstxt_docmd_nonce' ) ) ), 'robotstxt_docmd_save_settings' ) ) { robotstxt_docmd_handle_save_settings(); // The function above will redirect and exit. } } // Handle debug actions (only on settings page). // phpcs:disable WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['page'] ) && 'robotstxt-docmd-settings' === $_GET['page'] && isset( $_GET['debug_action'] ) ) { $debug_action = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_GET, 'debug_action' ) ) ); switch ( $debug_action ) { case 'test_github': check_admin_referer( 'robotstxt_docmd_debug_test_github' ); robotstxt_docmd_debug_test_github(); return; case 'test_token': check_admin_referer( 'robotstxt_docmd_debug_test_token' ); robotstxt_docmd_debug_test_token(); return; case 'run_cron': if ( isset( $_GET['mapping_id'] ) ) { $mapping_id = robotstxt_docmd_input_int( $_GET, 'mapping_id' ); check_admin_referer( 'robotstxt_docmd_debug_run_cron_' . $mapping_id ); robotstxt_docmd_debug_run_cron( $mapping_id ); } return; case 'clear_cache': check_admin_referer( 'robotstxt_docmd_debug_clear_cache' ); robotstxt_docmd_debug_clear_cache(); return; case 'fix_crons': check_admin_referer( 'robotstxt_docmd_debug_fix_crons' ); robotstxt_docmd_debug_fix_crons(); return; } } // Handle GET actions (sync, delete) on mappings page. if ( ! isset( $_GET['page'] ) || 'robotstxt-docmd-mappings' !== $_GET['page'] ) { return; } if ( ! isset( $_GET['action'] ) || ! isset( $_GET['mapping_id'] ) ) { return; } $action = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_GET, 'action' ) ) ); $mapping_id = robotstxt_docmd_input_int( $_GET, 'mapping_id' ); // phpcs:enable WordPress.Security.NonceVerification.Recommended if ( 'sync' === $action ) { check_admin_referer( 'robotstxt_docmd_sync_' . $mapping_id ); $result = robotstxt_docmd_sync_mapping( $mapping_id ); if ( is_wp_error( $result ) ) { wp_safe_redirect( add_query_arg( 'error', rawurlencode( $result->get_error_message() ), admin_url( 'admin.php?page=robotstxt-docmd-mappings' ) ) ); } else { wp_safe_redirect( add_query_arg( 'message', 'sync_complete', admin_url( 'admin.php?page=robotstxt-docmd-mappings' ) ) ); } exit; } if ( 'delete' === $action ) { check_admin_referer( 'robotstxt_docmd_delete_' . $mapping_id ); robotstxt_docmd_delete_mapping( $mapping_id ); wp_safe_redirect( add_query_arg( 'message', 'mapping_deleted', admin_url( 'admin.php?page=robotstxt-docmd-mappings' ) ) ); exit; } } /** * Render Mappings page * * @since 1.0.0 * * @return void */ function robotstxt_docmd_render_mappings_page() { if ( ! current_user_can( 'edit_pages' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions.', 'robotstxt-documentation-markdown' ) ); } // Get all mappings. $mappings = robotstxt_docmd_get_all_mappings(); ?>



#

' . esc_html__( 'Settings', 'robotstxt-documentation-markdown' ) . '' ); ?>



✓ ' . esc_html__( 'Using cached data (24 hour cache)', 'robotstxt-documentation-markdown' ) . ''; } else { echo '⟲ ' . esc_html__( 'Fetching fresh data from GitHub API', 'robotstxt-documentation-markdown' ) . ''; } ?>

get_error_message() ); ?>

'', 'repo_owner' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'owner' ) ) ), 'repo_name' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'repo' ) ) ), 'file_path' => urldecode( sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'file_path' ) ) ) ), 'branch' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'branch', 'main' ) ) ), // phpcs:enable WordPress.Security.NonceVerification.Recommended 'target_post_type' => 'page', 'target_post_id' => 0, 'target_author' => get_current_user_id(), 'target_parent' => 0, 'target_order' => 0, 'sync_enabled' => true, 'sync_frequency' => 'daily', ); $data = $mapping ? $mapping : $defaults; // Auto-generate title from file path. if ( empty( $data['title'] ) && ! empty( $data['file_path'] ) ) { $data['title'] = robotstxt_docmd_generate_title_from_path( $data['file_path'] ); } ?>












true ), 'names' ); if ( ! in_array( $target_post_type, $public_post_types, true ) ) { $target_post_type = 'page'; } $data = array( 'title' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'title' ) ) ), 'repo_owner' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'repo_owner' ) ) ), 'repo_name' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'repo_name' ) ) ), 'file_path' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'file_path' ) ) ), 'branch' => sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'branch', 'main' ) ) ), 'target_post_type' => $target_post_type, 'target_author' => robotstxt_docmd_input_int( $_POST, 'target_author', get_current_user_id() ), 'target_parent' => robotstxt_docmd_input_int( $_POST, 'target_parent' ), 'target_order' => robotstxt_docmd_input_int( $_POST, 'target_order' ), 'sync_enabled' => ! empty( $_POST['sync_enabled'] ), 'sync_frequency' => sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_POST, 'sync_frequency', 'daily' ) ) ), ); $target_type = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_POST, 'target_type', 'new' ) ) ); if ( 'existing' === $target_type ) { $data['target_post_id'] = robotstxt_docmd_input_int( $_POST, 'target_post_id' ); } if ( $mapping_id ) { $result = robotstxt_docmd_update_mapping( $mapping_id, $data ); $message = 'mapping_updated'; } else { $result = robotstxt_docmd_create_mapping( $data ); $message = 'mapping_created'; } if ( is_wp_error( $result ) ) { wp_safe_redirect( add_query_arg( 'error', rawurlencode( $result->get_error_message() ), admin_url( 'admin.php?page=robotstxt-docmd-add-mapping' ) ) ); } else { wp_safe_redirect( add_query_arg( 'message', $message, admin_url( 'admin.php?page=robotstxt-docmd-mappings' ) ) ); } exit; } /** * Render admin notices * * Shows success/error messages and debug results. * * @since 1.0.0 * * @return void */ function robotstxt_docmd_render_admin_notices() { // Check for regular message parameter. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $message = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_GET, 'message' ) ) ); $success_messages = array( 'settings_saved' => __( 'Settings saved successfully.', 'robotstxt-documentation-markdown' ), 'mapping_created' => __( 'Mapping created successfully.', 'robotstxt-documentation-markdown' ), 'mapping_updated' => __( 'Mapping updated successfully.', 'robotstxt-documentation-markdown' ), 'mapping_deleted' => __( 'Mapping deleted.', 'robotstxt-documentation-markdown' ), 'sync_complete' => __( 'Sync completed successfully.', 'robotstxt-documentation-markdown' ), ); if ( isset( $success_messages[ $message ] ) ) { echo '

'; echo esc_html( $success_messages[ $message ] ); echo '

'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $error_text = sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'error' ) ) ); if ( '' !== $error_text ) { echo '

'; echo esc_html( $error_text ); echo '

'; } // Check for debug results. $debug_result = get_transient( 'robotstxt_docmd_debug_result' ); if ( is_array( $debug_result ) && isset( $debug_result['type'], $debug_result['message'] ) && is_string( $debug_result['type'] ) && is_string( $debug_result['message'] ) ) { delete_transient( 'robotstxt_docmd_debug_result' ); $notice_class = 'notice-' . $debug_result['type']; echo '
'; echo '

' . wp_kses_post( $debug_result['message'] ) . '

'; if ( ! empty( $debug_result['details'] ) && is_array( $debug_result['details'] ) ) { echo ''; } echo '
'; } } /** * Render Settings page * * @since 1.0.0 * * @return void */ function robotstxt_docmd_render_settings_page() { if ( ! current_user_can( 'edit_pages' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions.', 'robotstxt-documentation-markdown' ) ); } $raw_settings = get_option( 'robotstxt_docmd_settings', array() ); $settings = is_array( $raw_settings ) ? $raw_settings : array(); ?>



' . esc_html__( 'No mappings configured yet.', 'robotstxt-documentation-markdown' ) . '

'; } else { echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ( $mappings as $mapping ) { $hook = 'robotstxt_docmd_sync_event'; $args = array( $mapping['id'] ); $timestamp = wp_next_scheduled( $hook, $args ); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html__( 'Mapping', 'robotstxt-documentation-markdown' ) . '' . esc_html__( 'Sync Enabled', 'robotstxt-documentation-markdown' ) . '' . esc_html__( 'Frequency', 'robotstxt-documentation-markdown' ) . '' . esc_html__( 'Next Run', 'robotstxt-documentation-markdown' ) . '' . esc_html__( 'Last Sync', 'robotstxt-documentation-markdown' ) . '' . esc_html__( 'Actions', 'robotstxt-documentation-markdown' ) . '
' . esc_html( $mapping['title'] ) . '
'; echo '' . esc_html( $mapping['repo_owner'] . '/' . $mapping['repo_name'] . '/' . $mapping['file_path'] ) . '
' . ( $mapping['sync_enabled'] ? '✓ ' . esc_html__( 'Yes', 'robotstxt-documentation-markdown' ) : '✗ ' . esc_html__( 'No', 'robotstxt-documentation-markdown' ) ) . '' . esc_html( $mapping['sync_frequency'] ) . ''; if ( $timestamp ) { $gmt_offset_raw = get_option( 'gmt_offset', 0 ); $gmt_offset_sec = is_numeric( $gmt_offset_raw ) ? (int) ( (float) $gmt_offset_raw * HOUR_IN_SECONDS ) : 0; echo esc_html( gmdate( 'Y-m-d H:i:s', $timestamp + $gmt_offset_sec ) ); } else { echo '' . esc_html__( 'Not scheduled', 'robotstxt-documentation-markdown' ) . ''; } echo '' . ( $mapping['last_sync'] ? esc_html( $mapping['last_sync'] ) : '' . esc_html__( 'Never', 'robotstxt-documentation-markdown' ) . '' ) . ''; echo ''; echo esc_html__( 'Run Now', 'robotstxt-documentation-markdown' ); echo ''; echo '
'; echo '

'; echo ''; echo esc_html__( 'Fix All Cron Jobs', 'robotstxt-documentation-markdown' ); echo ' '; echo ''; echo esc_html__( 'Unschedule and reschedule all cron jobs. Use this if crons show "Not scheduled" incorrectly.', 'robotstxt-documentation-markdown' ); echo ''; echo '

'; } ?>

'error', 'message' => '', 'details' => array(), ); if ( empty( $repo_url ) ) { $result['message'] = __( 'No GitHub repository URL configured. Please configure it in Settings first.', 'robotstxt-documentation-markdown' ); set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } if ( empty( $token ) ) { $result['message'] = __( 'No GitHub token configured. Please configure it in Settings first.', 'robotstxt-documentation-markdown' ); set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } // Decrypt token. $token = robotstxt_docmd_decrypt_token( $token ); // Parse repository URL. if ( ! preg_match( '#github\.com/([^/]+)/([^/]+?)(?:\.git)?/?$#', $repo_url, $matches ) ) { $result['message'] = __( 'Invalid GitHub repository URL format.', 'robotstxt-documentation-markdown' ); set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } $owner = $matches[1]; $repo = $matches[2]; // Test repository access. $api_url = sprintf( 'https://api.github.com/repos/%s/%s', $owner, $repo ); $response = wp_remote_get( $api_url, array( 'headers' => array( 'Authorization' => 'token ' . $token, 'Accept' => 'application/vnd.github.v3+json', ), 'timeout' => 15, ) ); $status_code = wp_remote_retrieve_response_code( $response ); if ( is_wp_error( $response ) ) { $result['message'] = sprintf( /* translators: %s: error message */ __( 'Connection Failed: %s', 'robotstxt-documentation-markdown' ), $response->get_error_message() ); } elseif ( 200 === $status_code ) { $body = json_decode( wp_remote_retrieve_body( $response ), true ); $full_name = is_array( $body ) && isset( $body['full_name'] ) && is_string( $body['full_name'] ) ? $body['full_name'] : 'N/A'; $repo_name = is_array( $body ) && isset( $body['name'] ) && is_string( $body['name'] ) ? $body['name'] : 'N/A'; $is_private = is_array( $body ) && ! empty( $body['private'] ); $default_branch = is_array( $body ) && isset( $body['default_branch'] ) && is_string( $body['default_branch'] ) ? $body['default_branch'] : 'N/A'; $result['type'] = 'success'; $result['message'] = sprintf( /* translators: %s: repository full name */ __( 'Successfully connected to repository: %s', 'robotstxt-documentation-markdown' ), $full_name ); $result['details'][] = sprintf( '%s: %s', __( 'Name', 'robotstxt-documentation-markdown' ), $repo_name ); $result['details'][] = sprintf( '%s: %s', __( 'Private', 'robotstxt-documentation-markdown' ), $is_private ? __( 'Yes', 'robotstxt-documentation-markdown' ) : __( 'No', 'robotstxt-documentation-markdown' ) ); $result['details'][] = sprintf( '%s: %s', __( 'Default Branch', 'robotstxt-documentation-markdown' ), $default_branch ); } elseif ( 404 === $status_code ) { $result['message'] = __( 'Repository Not Found: The repository does not exist or your token does not have access to it.', 'robotstxt-documentation-markdown' ); } elseif ( 401 === $status_code ) { $result['message'] = __( 'Authentication Failed: Your GitHub token is invalid or expired.', 'robotstxt-documentation-markdown' ); } else { $result['message'] = sprintf( /* translators: %d: HTTP status code */ __( 'Unexpected Error - HTTP Status Code: %d', 'robotstxt-documentation-markdown' ), $status_code ); } set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } /** * Debug: Test GitHub token * * Validates the GitHub token and shows rate limit information. * * @since 1.0.0 * * @return void */ function robotstxt_docmd_debug_test_token() { if ( ! current_user_can( 'edit_pages' ) ) { wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) ); } $raw_settings = get_option( 'robotstxt_docmd_settings', array() ); $settings = is_array( $raw_settings ) ? $raw_settings : array(); $token = robotstxt_docmd_input_string( $settings, 'github_token' ); $result = array( 'type' => 'error', 'message' => '', 'details' => array(), ); if ( empty( $token ) ) { $result['message'] = __( 'No GitHub token configured. Please configure it in Settings first.', 'robotstxt-documentation-markdown' ); set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } // Decrypt token. $token = robotstxt_docmd_decrypt_token( $token ); // Test token by checking rate limit. $api_url = 'https://api.github.com/rate_limit'; $response = wp_remote_get( $api_url, array( 'headers' => array( 'Authorization' => 'token ' . $token, 'Accept' => 'application/vnd.github.v3+json', ), 'timeout' => 15, ) ); $status_code = wp_remote_retrieve_response_code( $response ); if ( is_wp_error( $response ) ) { $result['message'] = sprintf( /* translators: %s: error message */ __( 'Connection Failed: %s', 'robotstxt-documentation-markdown' ), $response->get_error_message() ); } elseif ( 200 === $status_code ) { $body = json_decode( wp_remote_retrieve_body( $response ), true ); $result['type'] = 'success'; $result['message'] = __( 'Token Valid! Your GitHub token is working correctly.', 'robotstxt-documentation-markdown' ); $resources = is_array( $body ) && isset( $body['resources'] ) && is_array( $body['resources'] ) ? $body['resources'] : null; $core = is_array( $resources ) && isset( $resources['core'] ) && is_array( $resources['core'] ) ? $resources['core'] : null; if ( null !== $core ) { $remaining = isset( $core['remaining'] ) && is_int( $core['remaining'] ) ? $core['remaining'] : 0; $limit = isset( $core['limit'] ) && is_int( $core['limit'] ) ? $core['limit'] : 0; $reset = isset( $core['reset'] ) && is_int( $core['reset'] ) ? $core['reset'] : 0; $result['details'][] = sprintf( '%s: %d / %d', __( 'Requests Remaining', 'robotstxt-documentation-markdown' ), $remaining, $limit ); if ( $reset > 0 ) { $gmt_offset_raw = get_option( 'gmt_offset', 0 ); $gmt_offset_sec = is_numeric( $gmt_offset_raw ) ? (int) ( (float) $gmt_offset_raw * HOUR_IN_SECONDS ) : 0; $reset_time = gmdate( 'Y-m-d H:i:s', $reset + $gmt_offset_sec ); $result['details'][] = sprintf( '%s: %s', __( 'Resets At', 'robotstxt-documentation-markdown' ), $reset_time ); } if ( $remaining < 100 ) { $result['type'] = 'warning'; $result['message'] = __( 'Token Valid but Low Rate Limit! You are running low on GitHub API requests. Consider waiting for the rate limit to reset.', 'robotstxt-documentation-markdown' ); } } } elseif ( 401 === $status_code ) { $result['message'] = __( 'Authentication Failed: Your GitHub token is invalid or expired.', 'robotstxt-documentation-markdown' ); } else { $result['message'] = sprintf( /* translators: %d: HTTP status code */ __( 'Unexpected Error - HTTP Status Code: %d', 'robotstxt-documentation-markdown' ), $status_code ); } set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } /** * Debug: Run cron manually * * Executes synchronization for a specific mapping immediately. * * @since 1.0.0 * * @param int $mapping_id Mapping ID to sync. * @return void */ function robotstxt_docmd_debug_run_cron( int $mapping_id ) { if ( ! current_user_can( 'edit_pages' ) ) { wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) ); } $mapping = robotstxt_docmd_get_mapping( $mapping_id ); $result = array( 'type' => 'error', 'message' => '', 'details' => array(), ); if ( ! $mapping ) { $result['message'] = __( 'Mapping not found.', 'robotstxt-documentation-markdown' ); set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } // Run the sync. $sync_result = robotstxt_docmd_sync_mapping( $mapping_id ); if ( is_wp_error( $sync_result ) ) { $result['message'] = sprintf( /* translators: 1: mapping title, 2: error message */ __( 'Sync Failed for "%1$s": %2$s', 'robotstxt-documentation-markdown' ), $mapping['title'], $sync_result->get_error_message() ); } else { // Reload mapping to get updated info. $mapping = robotstxt_docmd_get_mapping( $mapping_id ); if ( ! $mapping ) { $result['message'] = __( 'Sync completed but mapping data could not be reloaded.', 'robotstxt-documentation-markdown' ); set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } $result['type'] = 'success'; $result['message'] = sprintf( /* translators: %s: mapping title */ __( 'Sync Successful for "%s"', 'robotstxt-documentation-markdown' ), $mapping['title'] ); $result['details'][] = sprintf( '%s: %s', __( 'Repository', 'robotstxt-documentation-markdown' ), $mapping['repo_owner'] . '/' . $mapping['repo_name'] . '/' . $mapping['file_path'] ); $result['details'][] = sprintf( '%s: %s', __( 'Last Sync', 'robotstxt-documentation-markdown' ), $mapping['last_sync'] ); if ( ! empty( $mapping['target_post_id'] ) ) { $edit_url = get_edit_post_link( $mapping['target_post_id'] ) ?? ''; $result['details'][] = sprintf( '%s: %s', __( 'Target Post', 'robotstxt-documentation-markdown' ), esc_url( $edit_url ), __( 'Edit Post', 'robotstxt-documentation-markdown' ) ); } } set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } /** * Debug: Clear all plugin caches * * Deletes all transients used by the plugin. * * @since 1.0.0 * * @return void */ function robotstxt_docmd_debug_clear_cache() { if ( ! current_user_can( 'edit_pages' ) ) { wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) ); } global $wpdb; $result = array( 'type' => 'error', 'message' => '', 'details' => array(), ); // Delete all plugin transients. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query acceptable for cache clearing operation. $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like( '_transient_robotstxt_docmd_' ) . '%', $wpdb->esc_like( '_transient_timeout_robotstxt_docmd_' ) . '%' ) ); if ( false !== $deleted ) { $result['type'] = 'success'; $result['message'] = sprintf( /* translators: %d: number of cache entries deleted */ __( 'Cache Cleared! Deleted %d cache entries.', 'robotstxt-documentation-markdown' ), $deleted ); } else { $result['message'] = __( 'Failed to clear cache. Please check database permissions.', 'robotstxt-documentation-markdown' ); } set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } /** * Debug: Fix cron jobs * * Reschedules all cron jobs for mappings that have sync enabled. * * @since 1.0.0 * * @return void */ function robotstxt_docmd_debug_fix_crons() { if ( ! current_user_can( 'edit_pages' ) ) { wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) ); } $result = array( 'type' => 'success', 'message' => '', 'details' => array(), ); $mappings = robotstxt_docmd_get_all_mappings(); $fixed = 0; $skipped = 0; if ( empty( $mappings ) ) { $result['type'] = 'warning'; $result['message'] = __( 'No mappings found to fix.', 'robotstxt-documentation-markdown' ); set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } foreach ( $mappings as $mapping ) { // Unschedule existing cron. robotstxt_docmd_unschedule_mapping_sync( $mapping['id'] ); // Reschedule if sync is enabled. if ( $mapping['sync_enabled'] && 'never' !== $mapping['sync_frequency'] ) { robotstxt_docmd_schedule_mapping_sync( $mapping['id'], $mapping['sync_frequency'] ); ++$fixed; $result['details'][] = sprintf( '✓ %s - %s', $mapping['title'], $mapping['sync_frequency'] ); } else { ++$skipped; } } $result['message'] = sprintf( /* translators: 1: number of crons fixed, 2: number of crons skipped */ __( 'Cron Jobs Fixed! Rescheduled %1$d cron jobs, skipped %2$d disabled mappings.', 'robotstxt-documentation-markdown' ), $fixed, $skipped ); set_transient( 'robotstxt_docmd_debug_result', $result, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ); exit; } /** * Check whether the Manager (by ROBOTSTXT) plugin is active * * Updates for this plugin are delivered through the Manager plugin. This * helper detects whether it is installed and active. * * @since 1.2.1 * * @return bool True if Manager (by ROBOTSTXT) is active, false otherwise. */ function robotstxt_docmd_is_manager_active(): bool { return defined( 'ROBOTSTXT_MANAGER_VERSION' ); } /** * Render the Manager (by ROBOTSTXT) recommendation notice * * Shared markup for the plugins list page (dismissible) and the plugin * settings page (not dismissible). Only rendered when Manager is not active. * * @since 1.2.1 * * @param bool $dismissible Whether the notice should be dismissible. * @return void */ function robotstxt_docmd_render_manager_notice( bool $dismissible ): void { echo '

'; echo wp_kses( sprintf( /* translators: %s: Manager (by ROBOTSTXT) plugin URL. */ __( 'To receive automatic updates, the Manager (by ROBOTSTXT) plugin must be installed and active.', 'robotstxt-documentation-markdown' ), esc_url( 'https://www.robotstxt.software/plugins/robotstxt-manager/' ) ), array( 'a' => array( 'href' => true, ), ) ); echo '

'; } /** * Show the Manager (by ROBOTSTXT) notice on the plugins list page * * @since 1.2.1 * * @return void */ function robotstxt_docmd_admin_notice_manager_plugin(): void { global $pagenow; if ( 'plugins.php' !== $pagenow ) { return; } if ( ! current_user_can( 'edit_pages' ) || robotstxt_docmd_is_manager_active() ) { return; } robotstxt_docmd_render_manager_notice( true ); }