v1.2.0
This commit is contained in:
parent
693e5ae2e6
commit
7d3fc1969d
52 changed files with 1776 additions and 295 deletions
|
|
@ -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.1.1
|
||||
* Requires at least: 6.7
|
||||
* Version: 1.2.0
|
||||
* Requires at least: 6.8
|
||||
* Requires PHP: 8.0
|
||||
* Security: robotstxt@robotstxt.es
|
||||
* Author: ROBOTSTXT
|
||||
|
|
@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
}
|
||||
|
||||
// Define plugin constants.
|
||||
define( 'ROBOTSTXT_DOCMD_VERSION', '1.1.1' );
|
||||
define( 'ROBOTSTXT_DOCMD_VERSION', '1.2.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__ ) );
|
||||
|
|
@ -41,6 +41,7 @@ if ( file_exists( ROBOTSTXT_DOCMD_PLUGIN_DIR . 'vendor/autoload.php' ) ) {
|
|||
require_once ROBOTSTXT_DOCMD_PLUGIN_DIR . 'robotstxt-documentation-markdown-functions.php';
|
||||
require_once ROBOTSTXT_DOCMD_PLUGIN_DIR . 'robotstxt-documentation-markdown-github.php';
|
||||
require_once ROBOTSTXT_DOCMD_PLUGIN_DIR . 'robotstxt-documentation-markdown-map.php';
|
||||
require_once ROBOTSTXT_DOCMD_PLUGIN_DIR . 'robotstxt-documentation-markdown-content.php';
|
||||
require_once ROBOTSTXT_DOCMD_PLUGIN_DIR . 'robotstxt-documentation-markdown-cron.php';
|
||||
|
||||
// Activation/Deactivation hooks.
|
||||
|
|
@ -254,35 +255,34 @@ function robotstxt_docmd_handle_admin_actions() {
|
|||
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' ) ) );
|
||||
|
||||
if ( 'test_github' === $debug_action ) {
|
||||
check_admin_referer( 'robotstxt_docmd_debug_test_github' );
|
||||
robotstxt_docmd_debug_test_github();
|
||||
return;
|
||||
}
|
||||
switch ( $debug_action ) {
|
||||
case 'test_github':
|
||||
check_admin_referer( 'robotstxt_docmd_debug_test_github' );
|
||||
robotstxt_docmd_debug_test_github();
|
||||
return;
|
||||
|
||||
if ( 'test_token' === $debug_action ) {
|
||||
check_admin_referer( 'robotstxt_docmd_debug_test_token' );
|
||||
robotstxt_docmd_debug_test_token();
|
||||
return;
|
||||
}
|
||||
case 'test_token':
|
||||
check_admin_referer( 'robotstxt_docmd_debug_test_token' );
|
||||
robotstxt_docmd_debug_test_token();
|
||||
return;
|
||||
|
||||
if ( 'run_cron' === $debug_action && 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 '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;
|
||||
|
||||
if ( 'clear_cache' === $debug_action ) {
|
||||
check_admin_referer( 'robotstxt_docmd_debug_clear_cache' );
|
||||
robotstxt_docmd_debug_clear_cache();
|
||||
return;
|
||||
}
|
||||
case 'clear_cache':
|
||||
check_admin_referer( 'robotstxt_docmd_debug_clear_cache' );
|
||||
robotstxt_docmd_debug_clear_cache();
|
||||
return;
|
||||
|
||||
if ( 'fix_crons' === $debug_action ) {
|
||||
check_admin_referer( 'robotstxt_docmd_debug_fix_crons' );
|
||||
robotstxt_docmd_debug_fix_crons();
|
||||
return;
|
||||
case 'fix_crons':
|
||||
check_admin_referer( 'robotstxt_docmd_debug_fix_crons' );
|
||||
robotstxt_docmd_debug_fix_crons();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -374,11 +374,23 @@ function robotstxt_docmd_render_mappings_page() {
|
|||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ( ! empty( $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'] ) ); ?>
|
||||
<?php
|
||||
if ( ! empty( $mapping['target_post_id'] ) ) :
|
||||
$target_id = (int) $mapping['target_post_id'];
|
||||
$permalink = get_permalink( $target_id );
|
||||
$uri = get_page_uri( $target_id );
|
||||
$post_type = get_post_type( $target_id );
|
||||
?>
|
||||
<a href="<?php echo esc_url( (string) $permalink ); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo esc_html( (string) get_the_title( $target_id ) ); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<br><small>
|
||||
<code><?php echo esc_html( '/' . ( is_string( $uri ) ? $uri : '' ) ); ?></code>
|
||||
<span class="description"><?php echo esc_html( is_string( $post_type ) ? $post_type : '' ); ?> #<?php echo esc_html( (string) $target_id ); ?></span>
|
||||
</small>
|
||||
<?php
|
||||
else :
|
||||
?>
|
||||
<em><?php esc_html_e( 'Not created yet', 'robotstxt-documentation-markdown' ); ?></em>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
|
|
@ -392,9 +404,9 @@ function robotstxt_docmd_render_mappings_page() {
|
|||
<a href="<?php echo esc_url( admin_url( 'admin.php?page=robotstxt-docmd-add-mapping&mapping_id=' . $mapping['id'] ) ); ?>" class="button button-small">
|
||||
<?php esc_html_e( 'Edit', 'robotstxt-documentation-markdown' ); ?>
|
||||
</a>
|
||||
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=robotstxt-docmd-mappings&action=delete&mapping_id=' . $mapping['id'] ), 'robotstxt_docmd_delete_' . $mapping['id'] ) ); ?>" class="button button-small button-link-delete" onclick="return confirm('<?php echo esc_js( __( 'Are you sure?', 'robotstxt-documentation-markdown' ) ); ?>');">
|
||||
<?php esc_html_e( 'Delete', 'robotstxt-documentation-markdown' ); ?>
|
||||
</a>
|
||||
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin.php?page=robotstxt-docmd-mappings&action=delete&mapping_id=' . $mapping['id'] ), 'robotstxt_docmd_delete_' . $mapping['id'] ) ); ?>" class="button button-small button-link-delete robotstxt-docmd-confirm" data-confirm="<?php esc_attr_e( 'Are you sure?', 'robotstxt-documentation-markdown' ); ?>">
|
||||
<?php esc_html_e( 'Delete', 'robotstxt-documentation-markdown' ); ?>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
|
|
@ -402,6 +414,19 @@ function robotstxt_docmd_render_mappings_page() {
|
|||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<script>
|
||||
// Delegated confirmation for delete actions (replaces inline onclick).
|
||||
(function() {
|
||||
var links = document.querySelectorAll('.robotstxt-docmd-confirm');
|
||||
links.forEach(function(link) {
|
||||
link.addEventListener('click', function(e) {
|
||||
if (!confirm(link.getAttribute('data-confirm'))) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
|
@ -450,12 +475,20 @@ function robotstxt_docmd_render_discover_page() {
|
|||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$branch = sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'branch', 'main' ) ) );
|
||||
|
||||
// Check if refresh requested.
|
||||
// Cache key for this repository + branch.
|
||||
$cache_key = sprintf( 'robotstxt_docmd_files_%s_%s_%s', $repo_data['owner'], $repo_data['repo'], $branch );
|
||||
|
||||
// Detect cache state BEFORE the fetch below repopulates it, so the
|
||||
// "cached vs fresh" indicator reflects where the displayed data came from.
|
||||
$was_cached = false !== get_transient( $cache_key );
|
||||
|
||||
// Check if refresh requested (CSRF-protected; forces a fresh GitHub API call).
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$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 );
|
||||
check_admin_referer( 'robotstxt_docmd_discover_refresh' );
|
||||
delete_transient( $cache_key );
|
||||
$was_cached = false;
|
||||
}
|
||||
|
||||
// Get files from GitHub.
|
||||
|
|
@ -476,6 +509,7 @@ function robotstxt_docmd_render_discover_page() {
|
|||
|
||||
<form method="get" style="margin-bottom: 20px;">
|
||||
<input type="hidden" name="page" value="robotstxt-docmd-discover">
|
||||
<?php wp_nonce_field( 'robotstxt_docmd_discover_refresh', '_wpnonce', false ); ?>
|
||||
<label>
|
||||
<?php esc_html_e( 'Branch:', 'robotstxt-documentation-markdown' ); ?>
|
||||
<input type="text" name="branch" value="<?php echo esc_attr( $branch ); ?>" class="regular-text">
|
||||
|
|
@ -508,9 +542,7 @@ function robotstxt_docmd_render_discover_page() {
|
|||
?>
|
||||
<br>
|
||||
<?php
|
||||
$cache_key = sprintf( 'robotstxt_docmd_files_%s_%s_%s', $repo_data['owner'], $repo_data['repo'], $branch );
|
||||
$cached_value = get_transient( $cache_key );
|
||||
if ( false !== $cached_value ) {
|
||||
if ( $was_cached ) {
|
||||
echo '<span style="color: green;">✓ ' . esc_html__( 'Using cached data (24 hour cache)', 'robotstxt-documentation-markdown' ) . '</span>';
|
||||
} else {
|
||||
echo '<span style="color: orange;">⟲ ' . esc_html__( 'Fetching fresh data from GitHub API', 'robotstxt-documentation-markdown' ) . '</span>';
|
||||
|
|
@ -761,25 +793,50 @@ function robotstxt_docmd_render_add_mapping_page() {
|
|||
<label>
|
||||
<?php esc_html_e( 'Select Page/Post:', 'robotstxt-documentation-markdown' ); ?>
|
||||
<?php
|
||||
$all_posts = get_posts(
|
||||
// Only query public, sync-relevant post types (excludes the
|
||||
// internal robotstxt_map CPT and attachments).
|
||||
$selectable_types = get_post_types( array( 'public' => true ), 'names' );
|
||||
unset( $selectable_types['attachment'] );
|
||||
$existing_posts = get_posts(
|
||||
array(
|
||||
'post_type' => 'any',
|
||||
'posts_per_page' => 100,
|
||||
'post_type' => array_values( $selectable_types ),
|
||||
// phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page -- Deliberate cap for the admin "existing content" dropdown; query uses no_found_rows for performance.
|
||||
'posts_per_page' => 200,
|
||||
'orderby' => 'title',
|
||||
'order' => 'ASC',
|
||||
'no_found_rows' => true,
|
||||
'ignore_sticky_posts' => true,
|
||||
)
|
||||
);
|
||||
|
||||
// Build a path-based label per post so nested and duplicate
|
||||
// pages are distinguishable, then sort by path to group the tree.
|
||||
$options = array();
|
||||
foreach ( $existing_posts as $existing ) {
|
||||
$uri = get_page_uri( $existing );
|
||||
$path = ( is_string( $uri ) && '' !== $uri ) ? $uri : $existing->post_name;
|
||||
$depth = substr_count( $path, '/' );
|
||||
$label = str_repeat( '— ', $depth )
|
||||
. $existing->post_title
|
||||
. ' — /' . $path
|
||||
. ' (#' . (string) $existing->ID . ', ' . $existing->post_type . ')';
|
||||
|
||||
$options[ $path . "\0" . (string) $existing->ID ] = array(
|
||||
'id' => $existing->ID,
|
||||
'label' => $label,
|
||||
);
|
||||
}
|
||||
ksort( $options );
|
||||
?>
|
||||
<select name="target_post_id">
|
||||
<option value="0"><?php esc_html_e( 'Select a page...', 'robotstxt-documentation-markdown' ); ?></option>
|
||||
<?php
|
||||
foreach ( $all_posts as $post ) {
|
||||
foreach ( $options as $option ) {
|
||||
printf(
|
||||
'<option value="%d" %s>%s (%s)</option>',
|
||||
esc_attr( (string) $post->ID ),
|
||||
selected( $data['target_post_id'], $post->ID, false ),
|
||||
esc_html( $post->post_title ),
|
||||
esc_html( $post->post_type )
|
||||
'<option value="%d" %s>%s</option>',
|
||||
esc_attr( (string) $option['id'] ),
|
||||
selected( $data['target_post_id'], $option['id'], false ),
|
||||
esc_html( $option['label'] )
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
|
@ -863,13 +920,20 @@ function robotstxt_docmd_handle_save_mapping() {
|
|||
|
||||
$mapping_id = robotstxt_docmd_input_int( $_POST, 'mapping_id' );
|
||||
|
||||
// Validate target post type against registered public post types; fall back to 'page'.
|
||||
$target_post_type = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_POST, 'target_post_type', 'page' ) ) );
|
||||
$public_post_types = get_post_types( array( 'public' => 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' => sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_POST, 'target_post_type', 'page' ) ) ),
|
||||
'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' ),
|
||||
|
|
|
|||
Loading…
Reference in a new issue