This commit is contained in:
Javier Casares 2026-03-28 15:38:41 +00:00
commit 92d56fe84d
4445 changed files with 992 additions and 529601 deletions

View file

@ -3,8 +3,8 @@
* Plugin Name: Documentation Markdown (by ROBOTSTXT)
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown
* Description: Synchronizes Markdown documentation from GitHub repositories to WordPress pages and posts automatically.
* Version: 1.0.0
* Requires at least: 6.5
* Version: 1.1.0
* Requires at least: 6.7
* Requires PHP: 8.2
* Security: robotstxt@robotstxt.es
* Author: ROBOTSTXT
@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) {
}
// Define plugin constants.
define( 'ROBOTSTXT_DOCMD_VERSION', '1.0.0' );
define( 'ROBOTSTXT_DOCMD_VERSION', '1.1.0' );
define( 'ROBOTSTXT_DOCMD_PLUGIN_FILE', __FILE__ );
define( 'ROBOTSTXT_DOCMD_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'ROBOTSTXT_DOCMD_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
@ -99,13 +99,6 @@ function robotstxt_docmd_deactivate() {
* @return void
*/
function robotstxt_docmd_init() {
// Load textdomain.
load_plugin_textdomain(
'robotstxt-documentation-markdown',
false,
dirname( plugin_basename( __FILE__ ) ) . '/languages'
);
// Register CPT.
robotstxt_docmd_register_post_types();
}
@ -154,7 +147,7 @@ function robotstxt_docmd_register_admin_menu() {
add_menu_page(
__( 'Documentation', 'robotstxt-documentation-markdown' ),
__( 'Documentation', 'robotstxt-documentation-markdown' ),
'manage_options',
'edit_pages',
'robotstxt-docmd-mappings',
'robotstxt_docmd_render_mappings_page',
'dashicons-media-document',
@ -166,7 +159,7 @@ function robotstxt_docmd_register_admin_menu() {
'robotstxt-docmd-mappings',
__( 'Mappings', 'robotstxt-documentation-markdown' ),
__( 'Mappings', 'robotstxt-documentation-markdown' ),
'manage_options',
'edit_pages',
'robotstxt-docmd-mappings',
'robotstxt_docmd_render_mappings_page'
);
@ -176,7 +169,7 @@ function robotstxt_docmd_register_admin_menu() {
'robotstxt-docmd-mappings',
__( 'Discover Files', 'robotstxt-documentation-markdown' ),
__( 'Discover Files', 'robotstxt-documentation-markdown' ),
'manage_options',
'edit_pages',
'robotstxt-docmd-discover',
'robotstxt_docmd_render_discover_page'
);
@ -186,7 +179,7 @@ function robotstxt_docmd_register_admin_menu() {
'robotstxt-docmd-mappings',
__( 'Add New', 'robotstxt-documentation-markdown' ),
__( 'Add New', 'robotstxt-documentation-markdown' ),
'manage_options',
'edit_pages',
'robotstxt-docmd-add-mapping',
'robotstxt_docmd_render_add_mapping_page'
);
@ -196,7 +189,7 @@ function robotstxt_docmd_register_admin_menu() {
'robotstxt-docmd-mappings',
__( 'Settings', 'robotstxt-documentation-markdown' ),
__( 'Settings', 'robotstxt-documentation-markdown' ),
'manage_options',
'edit_pages',
'robotstxt-docmd-settings',
'robotstxt_docmd_render_settings_page'
);
@ -232,14 +225,14 @@ function robotstxt_docmd_enqueue_admin_assets( $hook ) {
* @return void
*/
function robotstxt_docmd_handle_admin_actions() {
if ( ! current_user_can( 'manage_options' ) ) {
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( $_POST['robotstxt_docmd_nonce'] ) ), 'robotstxt_docmd_save_mapping' ) ) {
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.
}
@ -248,7 +241,7 @@ function robotstxt_docmd_handle_admin_actions() {
// 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( $_POST['robotstxt_docmd_nonce'] ) ), 'robotstxt_docmd_save_settings' ) ) {
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.
}
@ -257,7 +250,7 @@ function robotstxt_docmd_handle_admin_actions() {
// 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( $_GET['debug_action'] ) );
$debug_action = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_GET, 'debug_action' ) ) );
if ( 'test_github' === $debug_action ) {
check_admin_referer( 'robotstxt_docmd_debug_test_github' );
@ -272,7 +265,7 @@ function robotstxt_docmd_handle_admin_actions() {
}
if ( 'run_cron' === $debug_action && isset( $_GET['mapping_id'] ) ) {
$mapping_id = absint( wp_unslash( $_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;
@ -300,8 +293,8 @@ function robotstxt_docmd_handle_admin_actions() {
return;
}
$action = sanitize_key( wp_unslash( $_GET['action'] ) );
$mapping_id = absint( wp_unslash( $_GET['mapping_id'] ) );
$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 ) {
@ -332,7 +325,7 @@ function robotstxt_docmd_handle_admin_actions() {
* @return void
*/
function robotstxt_docmd_render_mappings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions.', 'robotstxt-documentation-markdown' ) );
}
@ -380,8 +373,8 @@ function robotstxt_docmd_render_mappings_page() {
</td>
<td>
<?php if ( ! empty( $mapping['target_post_id'] ) ) : ?>
<a href="<?php echo esc_url( get_permalink( $mapping['target_post_id'] ) ); ?>" target="_blank" rel="noopener noreferrer">
<?php echo esc_html( get_the_title( $mapping['target_post_id'] ) ); ?>
<a href="<?php echo esc_url( (string) get_permalink( (int) $mapping['target_post_id'] ) ); ?>" target="_blank" rel="noopener noreferrer">
<?php echo esc_html( (string) get_the_title( (int) $mapping['target_post_id'] ) ); ?>
</a>
<?php else : ?>
<em><?php esc_html_e( 'Not created yet', 'robotstxt-documentation-markdown' ); ?></em>
@ -418,14 +411,15 @@ function robotstxt_docmd_render_mappings_page() {
* @return void
*/
function robotstxt_docmd_render_discover_page() {
if ( ! current_user_can( 'manage_options' ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions.', 'robotstxt-documentation-markdown' ) );
}
// Get settings.
$settings = get_option( 'robotstxt_docmd_settings', array() );
$github_url = $settings['github_url'] ?? '';
$github_token = $settings['github_token'] ?? '';
$raw_settings = get_option( 'robotstxt_docmd_settings', array() );
$settings = is_array( $raw_settings ) ? $raw_settings : array();
$github_url = robotstxt_docmd_input_string( $settings, 'github_url' );
$github_token = robotstxt_docmd_input_string( $settings, 'github_token' );
// Parse GitHub URL.
$repo_data = robotstxt_docmd_parse_github_url( $github_url );
@ -452,11 +446,11 @@ function robotstxt_docmd_render_discover_page() {
// Get branch from query string.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$branch = isset( $_GET['branch'] ) ? sanitize_text_field( wp_unslash( $_GET['branch'] ) ) : 'main';
$branch = sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'branch', 'main' ) ) );
// Check if refresh requested.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$refresh = isset( $_GET['refresh'] ) && '1' === $_GET['refresh'];
$refresh = '1' === robotstxt_docmd_input_string( $_GET, 'refresh' );
if ( $refresh ) {
$cache_key = sprintf( 'robotstxt_docmd_files_%s_%s_%s', $repo_data['owner'], $repo_data['repo'], $branch );
delete_transient( $cache_key );
@ -542,15 +536,14 @@ function robotstxt_docmd_render_discover_page() {
<tbody>
<?php foreach ( $files as $file ) : ?>
<?php
// Skip if file doesn't have required keys.
if ( ! isset( $file['path'] ) || ! isset( $file['size'] ) ) {
continue;
}
$is_mapped = robotstxt_docmd_is_file_mapped( $repo_data['owner'], $repo_data['repo'], $file['path'], $branch );
?>
<tr>
<td><code><?php echo esc_html( $file['path'] ); ?></code></td>
<td><?php echo esc_html( size_format( $file['size'], 2 ) ); ?></td>
<?php
$file_size_str = size_format( $file['size'], 2 );
?>
<td><?php echo esc_html( false !== $file_size_str ? $file_size_str : '0 B' ); ?></td>
<td>
<?php if ( $is_mapped ) : ?>
<span class="status-badge status-mapped"><?php esc_html_e( 'Mapped', 'robotstxt-documentation-markdown' ); ?></span>
@ -599,28 +592,28 @@ function robotstxt_docmd_render_discover_page() {
* @return void
*/
function robotstxt_docmd_render_add_mapping_page() {
if ( ! current_user_can( 'manage_options' ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions.', 'robotstxt-documentation-markdown' ) );
}
// Get mapping ID if editing.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$mapping_id = isset( $_GET['mapping_id'] ) ? absint( wp_unslash( $_GET['mapping_id'] ) ) : 0;
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$mapping_id = robotstxt_docmd_input_int( $_GET, 'mapping_id' );
$mapping = $mapping_id ? robotstxt_docmd_get_mapping( $mapping_id ) : null;
// Pre-fill from discover page or defaults.
$defaults = array(
'title' => '',
// phpcs:disable WordPress.Security.NonceVerification.Recommended
'repo_owner' => isset( $_GET['owner'] ) ? sanitize_text_field( wp_unslash( $_GET['owner'] ) ) : '',
'repo_name' => isset( $_GET['repo'] ) ? sanitize_text_field( wp_unslash( $_GET['repo'] ) ) : '',
'file_path' => isset( $_GET['file_path'] ) ? urldecode( sanitize_text_field( wp_unslash( $_GET['file_path'] ) ) ) : '',
'branch' => isset( $_GET['branch'] ) ? sanitize_text_field( wp_unslash( $_GET['branch'] ) ) : 'main',
'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',
);
@ -641,7 +634,7 @@ function robotstxt_docmd_render_add_mapping_page() {
<form method="post" action="">
<?php wp_nonce_field( 'robotstxt_docmd_save_mapping', 'robotstxt_docmd_nonce' ); ?>
<?php if ( $mapping_id ) : ?>
<input type="hidden" name="mapping_id" value="<?php echo esc_attr( $mapping_id ); ?>">
<input type="hidden" name="mapping_id" value="<?php echo esc_attr( (string) $mapping_id ); ?>">
<?php endif; ?>
<table class="form-table">
@ -727,8 +720,8 @@ function robotstxt_docmd_render_add_mapping_page() {
array(
'name' => 'target_parent',
'show_option_none' => esc_html__( '(no parent)', 'robotstxt-documentation-markdown' ),
'option_none_value' => 0,
'selected' => absint( $data['target_parent'] ?? 0 ),
'option_none_value' => '0',
'selected' => absint( $data['target_parent'] ),
)
);
?>
@ -750,7 +743,7 @@ function robotstxt_docmd_render_add_mapping_page() {
<label>
<?php esc_html_e( 'Order:', 'robotstxt-documentation-markdown' ); ?>
<input type="number" name="target_order" value="<?php echo esc_attr( $data['target_order'] ?? 0 ); ?>" class="small-text" min="0" step="1">
<input type="number" name="target_order" value="<?php echo esc_attr( (string) $data['target_order'] ); ?>" class="small-text" min="0" step="1">
<p class="description"><?php esc_html_e( 'Page order (menu_order). Lower numbers appear first. Default: 0', 'robotstxt-documentation-markdown' ); ?></p>
</label>
</div>
@ -781,7 +774,7 @@ function robotstxt_docmd_render_add_mapping_page() {
foreach ( $all_posts as $post ) {
printf(
'<option value="%d" %s>%s (%s)</option>',
esc_attr( $post->ID ),
esc_attr( (string) $post->ID ),
selected( $data['target_post_id'], $post->ID, false ),
esc_html( $post->post_title ),
esc_html( $post->post_type )
@ -857,36 +850,35 @@ function robotstxt_docmd_render_add_mapping_page() {
* @return void
*/
function robotstxt_docmd_handle_save_mapping() {
if ( ! current_user_can( 'manage_options' ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
return;
}
// Verify nonce.
if ( ! isset( $_POST['robotstxt_docmd_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['robotstxt_docmd_nonce'] ) ), 'robotstxt_docmd_save_mapping' ) ) {
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'robotstxt_docmd_nonce' ) ) ), 'robotstxt_docmd_save_mapping' ) ) {
wp_die( esc_html__( 'Security check failed.', 'robotstxt-documentation-markdown' ) );
}
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$mapping_id = isset( $_POST['mapping_id'] ) ? absint( wp_unslash( $_POST['mapping_id'] ) ) : 0;
$mapping_id = robotstxt_docmd_input_int( $_POST, 'mapping_id' );
$data = array(
'title' => sanitize_text_field( wp_unslash( $_POST['title'] ?? '' ) ),
'repo_owner' => sanitize_text_field( wp_unslash( $_POST['repo_owner'] ?? '' ) ),
'repo_name' => sanitize_text_field( wp_unslash( $_POST['repo_name'] ?? '' ) ),
'file_path' => sanitize_text_field( wp_unslash( $_POST['file_path'] ?? '' ) ),
'branch' => sanitize_text_field( wp_unslash( $_POST['branch'] ?? 'main' ) ),
'target_post_type' => sanitize_key( wp_unslash( $_POST['target_post_type'] ?? 'page' ) ),
'target_author' => absint( wp_unslash( $_POST['target_author'] ?? get_current_user_id() ) ),
'target_parent' => absint( wp_unslash( $_POST['target_parent'] ?? 0 ) ),
'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' => sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_POST, 'target_post_type', 'page' ) ) ),
'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( $_POST['sync_frequency'] ?? 'daily' ) ),
'sync_frequency' => sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_POST, 'sync_frequency', 'daily' ) ) ),
);
$target_type = sanitize_key( wp_unslash( $_POST['target_type'] ?? 'new' ) );
$target_type = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_POST, 'target_type', 'new' ) ) );
if ( 'existing' === $target_type ) {
$data['target_post_id'] = absint( wp_unslash( $_POST['target_post_id'] ?? 0 ) );
$data['target_post_id'] = robotstxt_docmd_input_int( $_POST, 'target_post_id' );
}
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( $mapping_id ) {
$result = robotstxt_docmd_update_mapping( $mapping_id, $data );
@ -915,29 +907,33 @@ function robotstxt_docmd_handle_save_mapping() {
*/
function robotstxt_docmd_render_admin_notices() {
// Check for regular message parameter.
if ( isset( $_GET['message'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$message = sanitize_key( wp_unslash( $_GET['message'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( 'settings_saved' === $message ) {
echo '<div class="notice notice-success is-dismissible"><p>';
esc_html_e( 'Settings saved successfully.', 'robotstxt-documentation-markdown' );
echo '</p></div>';
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$message = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_GET, 'message' ) ) );
if ( 'settings_saved' === $message ) {
echo '<div class="notice notice-success is-dismissible"><p>';
esc_html_e( 'Settings saved successfully.', 'robotstxt-documentation-markdown' );
echo '</p></div>';
}
// Check for debug results.
$debug_result = get_transient( 'robotstxt_docmd_debug_result' );
if ( $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 '<div class="notice ' . esc_attr( $notice_class ) . ' is-dismissible">';
echo '<p><strong>' . wp_kses_post( $debug_result['message'] ) . '</strong></p>';
if ( ! empty( $debug_result['details'] ) ) {
if ( ! empty( $debug_result['details'] ) && is_array( $debug_result['details'] ) ) {
echo '<ul style="list-style: disc; margin-left: 20px;">';
foreach ( $debug_result['details'] as $detail ) {
echo '<li>' . wp_kses_post( $detail ) . '</li>';
if ( is_string( $detail ) ) {
echo '<li>' . wp_kses_post( $detail ) . '</li>';
}
}
echo '</ul>';
}
@ -954,11 +950,12 @@ function robotstxt_docmd_render_admin_notices() {
* @return void
*/
function robotstxt_docmd_render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions.', 'robotstxt-documentation-markdown' ) );
}
$settings = get_option( 'robotstxt_docmd_settings', array() );
$raw_settings = get_option( 'robotstxt_docmd_settings', array() );
$settings = is_array( $raw_settings ) ? $raw_settings : array();
?>
<div class="wrap robotstxt-docmd-settings">
@ -976,7 +973,7 @@ function robotstxt_docmd_render_settings_page() {
<label for="github_url"><?php esc_html_e( 'Repository URL', 'robotstxt-documentation-markdown' ); ?> <span class="required">*</span></label>
</th>
<td>
<input type="url" id="github_url" name="github_url" value="<?php echo esc_attr( $settings['github_url'] ?? '' ); ?>" class="regular-text" required>
<input type="url" id="github_url" name="github_url" value="<?php echo esc_attr( robotstxt_docmd_input_string( $settings, 'github_url' ) ); ?>" class="regular-text" required>
<p class="description"><?php esc_html_e( 'Full GitHub repository URL (e.g., https://github.com/owner/repo)', 'robotstxt-documentation-markdown' ); ?></p>
</td>
</tr>
@ -988,7 +985,7 @@ function robotstxt_docmd_render_settings_page() {
<td>
<input type="password" id="github_token" name="github_token" value="" class="regular-text" placeholder="<?php esc_attr_e( 'Enter new token to update', 'robotstxt-documentation-markdown' ); ?>">
<p class="description"><?php esc_html_e( 'GitHub Personal Access Token with "repo" scope', 'robotstxt-documentation-markdown' ); ?></p>
<?php if ( ! empty( $settings['github_token'] ) ) : ?>
<?php if ( ! empty( robotstxt_docmd_input_string( $settings, 'github_token' ) ) ) : ?>
<p class="description"><strong><?php esc_html_e( 'Token is currently set. Leave blank to keep existing token.', 'robotstxt-documentation-markdown' ); ?></strong></p>
<?php endif; ?>
</td>
@ -1080,7 +1077,9 @@ function robotstxt_docmd_render_settings_page() {
echo '<td>' . esc_html( $mapping['sync_frequency'] ) . '</td>';
echo '<td>';
if ( $timestamp ) {
echo esc_html( gmdate( 'Y-m-d H:i:s', $timestamp + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) );
$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 '<em>' . esc_html__( 'Not scheduled', 'robotstxt-documentation-markdown' ) . '</em>';
}
@ -1134,21 +1133,20 @@ function robotstxt_docmd_render_settings_page() {
* @return void
*/
function robotstxt_docmd_handle_save_settings() {
if ( ! current_user_can( 'manage_options' ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
return;
}
// Verify nonce.
if ( ! isset( $_POST['robotstxt_docmd_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['robotstxt_docmd_nonce'] ) ), 'robotstxt_docmd_save_settings' ) ) {
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'robotstxt_docmd_nonce' ) ) ), 'robotstxt_docmd_save_settings' ) ) {
wp_die( esc_html__( 'Security check failed.', 'robotstxt-documentation-markdown' ) );
}
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$github_url = sanitize_text_field( wp_unslash( $_POST['github_url'] ?? '' ) );
$github_token = sanitize_text_field( wp_unslash( $_POST['github_token'] ?? '' ) );
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$github_url = sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'github_url' ) ) );
$github_token = sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_POST, 'github_token' ) ) );
$settings = get_option( 'robotstxt_docmd_settings', array() );
$raw_settings = get_option( 'robotstxt_docmd_settings', array() );
$settings = is_array( $raw_settings ) ? $raw_settings : array();
$settings['github_url'] = $github_url;
@ -1177,13 +1175,14 @@ function robotstxt_docmd_handle_save_settings() {
* @return void
*/
function robotstxt_docmd_debug_test_github() {
if ( ! current_user_can( 'manage_options' ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) );
}
$settings = get_option( 'robotstxt_docmd_settings', array() );
$repo_url = $settings['github_url'] ?? '';
$token = $settings['github_token'] ?? '';
$raw_settings = get_option( 'robotstxt_docmd_settings', array() );
$settings = is_array( $raw_settings ) ? $raw_settings : array();
$repo_url = robotstxt_docmd_input_string( $settings, 'github_url' );
$token = robotstxt_docmd_input_string( $settings, 'github_token' );
$result = array(
'type' => 'error',
@ -1241,27 +1240,32 @@ function robotstxt_docmd_debug_test_github() {
$response->get_error_message()
);
} elseif ( 200 === $status_code ) {
$body = json_decode( wp_remote_retrieve_body( $response ), true );
$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' ),
$body['full_name'] ?? 'N/A'
$full_name
);
$result['details'][] = sprintf(
'<strong>%s:</strong> %s',
__( 'Name', 'robotstxt-documentation-markdown' ),
$body['name'] ?? 'N/A'
$repo_name
);
$result['details'][] = sprintf(
'<strong>%s:</strong> %s',
__( 'Private', 'robotstxt-documentation-markdown' ),
! empty( $body['private'] ) ? __( 'Yes', 'robotstxt-documentation-markdown' ) : __( 'No', 'robotstxt-documentation-markdown' )
$is_private ? __( 'Yes', 'robotstxt-documentation-markdown' ) : __( 'No', 'robotstxt-documentation-markdown' )
);
$result['details'][] = sprintf(
'<strong>%s:</strong> %s',
__( 'Default Branch', 'robotstxt-documentation-markdown' ),
$body['default_branch'] ?? 'N/A'
$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' );
@ -1290,12 +1294,13 @@ function robotstxt_docmd_debug_test_github() {
* @return void
*/
function robotstxt_docmd_debug_test_token() {
if ( ! current_user_can( 'manage_options' ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) );
}
$settings = get_option( 'robotstxt_docmd_settings', array() );
$token = $settings['github_token'] ?? '';
$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',
@ -1339,11 +1344,13 @@ function robotstxt_docmd_debug_test_token() {
$result['type'] = 'success';
$result['message'] = __( 'Token Valid! Your GitHub token is working correctly.', 'robotstxt-documentation-markdown' );
if ( isset( $body['resources']['core'] ) ) {
$core = $body['resources']['core'];
$remaining = $core['remaining'] ?? 0;
$limit = $core['limit'] ?? 0;
$reset = $core['reset'] ?? 0;
$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(
'<strong>%s:</strong> %d / %d',
@ -1353,7 +1360,9 @@ function robotstxt_docmd_debug_test_token() {
);
if ( $reset > 0 ) {
$reset_time = gmdate( 'Y-m-d H:i:s', $reset + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) );
$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(
'<strong>%s:</strong> %s',
__( 'Resets At', 'robotstxt-documentation-markdown' ),
@ -1391,8 +1400,8 @@ function robotstxt_docmd_debug_test_token() {
* @param int $mapping_id Mapping ID to sync.
* @return void
*/
function robotstxt_docmd_debug_run_cron( $mapping_id ) {
if ( ! current_user_can( 'manage_options' ) ) {
function robotstxt_docmd_debug_run_cron( int $mapping_id ) {
if ( ! current_user_can( 'edit_pages' ) ) {
wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) );
}
@ -1425,6 +1434,13 @@ function robotstxt_docmd_debug_run_cron( $mapping_id ) {
// 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 */
@ -1441,11 +1457,11 @@ function robotstxt_docmd_debug_run_cron( $mapping_id ) {
$result['details'][] = sprintf(
'<strong>%s:</strong> %s',
__( 'Last Sync', 'robotstxt-documentation-markdown' ),
$mapping['last_sync'] ?? 'N/A'
$mapping['last_sync']
);
if ( ! empty( $mapping['target_post_id'] ) ) {
$edit_url = get_edit_post_link( $mapping['target_post_id'] );
$edit_url = get_edit_post_link( $mapping['target_post_id'] ) ?? '';
$result['details'][] = sprintf(
'<strong>%s:</strong> <a href="%s">%s</a>',
__( 'Target Post', 'robotstxt-documentation-markdown' ),
@ -1470,7 +1486,7 @@ function robotstxt_docmd_debug_run_cron( $mapping_id ) {
* @return void
*/
function robotstxt_docmd_debug_clear_cache() {
if ( ! current_user_can( 'manage_options' ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) );
}
@ -1483,6 +1499,7 @@ function robotstxt_docmd_debug_clear_cache() {
);
// 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}
@ -1519,7 +1536,7 @@ function robotstxt_docmd_debug_clear_cache() {
* @return void
*/
function robotstxt_docmd_debug_fix_crons() {
if ( ! current_user_can( 'manage_options' ) ) {
if ( ! current_user_can( 'edit_pages' ) ) {
wp_die( esc_html__( 'Permission denied.', 'robotstxt-documentation-markdown' ) );
}