'',
'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 '
';
foreach ( $debug_result['details'] as $detail ) {
if ( is_string( $detail ) ) {
echo '- ' . wp_kses_post( $detail ) . '
';
}
}
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 '| ' . esc_html__( 'Mapping', 'robotstxt-documentation-markdown' ) . ' | ';
echo '' . esc_html__( 'Sync Enabled', 'robotstxt-documentation-markdown' ) . ' | ';
echo '' . esc_html__( 'Frequency', 'robotstxt-documentation-markdown' ) . ' | ';
echo '' . esc_html__( 'Next Run', 'robotstxt-documentation-markdown' ) . ' | ';
echo '' . esc_html__( 'Last Sync', 'robotstxt-documentation-markdown' ) . ' | ';
echo '' . esc_html__( 'Actions', 'robotstxt-documentation-markdown' ) . ' | ';
echo '
';
foreach ( $mappings as $mapping ) {
$hook = 'robotstxt_docmd_sync_event';
$args = array( $mapping['id'] );
$timestamp = wp_next_scheduled( $hook, $args );
echo '';
echo '' . esc_html( $mapping['title'] ) . ' ';
echo '' . esc_html( $mapping['repo_owner'] . '/' . $mapping['repo_name'] . '/' . $mapping['file_path'] ) . ' | ';
echo '' . ( $mapping['sync_enabled'] ? '✓ ' . esc_html__( 'Yes', 'robotstxt-documentation-markdown' ) : '✗ ' . esc_html__( 'No', 'robotstxt-documentation-markdown' ) ) . ' | ';
echo '' . esc_html( $mapping['sync_frequency'] ) . ' | ';
echo '';
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 ' | ';
echo '' . ( $mapping['last_sync'] ? esc_html( $mapping['last_sync'] ) : '' . esc_html__( 'Never', 'robotstxt-documentation-markdown' ) . '' ) . ' | ';
echo '';
echo '';
echo esc_html__( 'Run Now', 'robotstxt-documentation-markdown' );
echo '';
echo ' | ';
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 );
}