v1.0.0
This commit is contained in:
parent
ac2c7a8014
commit
f7702f2872
25 changed files with 6453 additions and 0 deletions
733
admin/class-robotstxt-og-admin-settings.php
Normal file
733
admin/class-robotstxt-og-admin-settings.php
Normal file
|
|
@ -0,0 +1,733 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin Settings Class
|
||||
*
|
||||
* Handles administration interface and settings management.
|
||||
*
|
||||
* @package ROBOTSTXT_OG
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Robotstxt_OG_Admin_Settings
|
||||
*
|
||||
* Manages plugin settings page and administrative functions.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Robotstxt_OG_Admin_Settings {
|
||||
|
||||
/**
|
||||
* Image resolver instance.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var Robotstxt_OG_Image_Resolver
|
||||
*/
|
||||
private Robotstxt_OG_Image_Resolver $resolver;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param Robotstxt_OG_Image_Resolver $resolver Image resolver instance.
|
||||
*/
|
||||
public function __construct( Robotstxt_OG_Image_Resolver $resolver ) {
|
||||
$this->resolver = $resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize admin hooks.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init(): void {
|
||||
add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
|
||||
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
||||
add_action( 'admin_init', array( $this, 'handle_clear_cache' ) );
|
||||
add_action( 'admin_init', array( $this, 'handle_resolve_all' ) );
|
||||
add_action( 'admin_init', array( $this, 'handle_clear_single' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clear single post cache action.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_clear_single(): void {
|
||||
// Check if this is a single clear request.
|
||||
$post_id_raw = filter_input( INPUT_GET, 'robotstxt_og_clear_single', FILTER_SANITIZE_NUMBER_INT );
|
||||
if ( null === $post_id_raw ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = absint( $post_id_raw );
|
||||
|
||||
if ( $post_id <= 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify nonce.
|
||||
$nonce_raw = filter_input( INPUT_GET, '_wpnonce', FILTER_SANITIZE_SPECIAL_CHARS );
|
||||
$nonce = $nonce_raw ? sanitize_text_field( wp_unslash( $nonce_raw ) ) : '';
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce, 'robotstxt_og_clear_single_' . $post_id ) ) {
|
||||
wp_die( esc_html__( 'Security check failed', 'robotstxt-og' ) );
|
||||
}
|
||||
|
||||
// Check permissions.
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-og' ) );
|
||||
}
|
||||
|
||||
// Clear single post cache.
|
||||
$this->resolver->clear_cache( $post_id );
|
||||
|
||||
// Add admin notice.
|
||||
add_settings_error(
|
||||
'robotstxt_og_messages',
|
||||
'robotstxt_og_single_cleared',
|
||||
/* translators: %d: post ID */
|
||||
sprintf( __( 'Successfully cleared cached fallback URL for post #%d.', 'robotstxt-og' ), $post_id ),
|
||||
'success'
|
||||
);
|
||||
|
||||
// Redirect back to diagnostics tab.
|
||||
$page_url = admin_url( 'options-general.php?page=robotstxt-og-settings' );
|
||||
wp_safe_redirect(
|
||||
add_query_arg( 'tab', 'diagnostics', remove_query_arg( array( 'robotstxt_og_clear_single', '_wpnonce' ), $page_url ) )
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings page to WordPress admin menu.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_settings_page(): void {
|
||||
add_options_page(
|
||||
__( 'OpenGraph Settings', 'robotstxt-og' ),
|
||||
__( 'OpenGraph', 'robotstxt-og' ),
|
||||
'manage_options',
|
||||
'robotstxt-og-settings',
|
||||
array( $this, 'render_settings_page' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register plugin settings.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_settings(): void {
|
||||
// Register settings.
|
||||
register_setting(
|
||||
'robotstxt_og_settings',
|
||||
'robotstxt_og_fallback_image',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( $this, 'sanitize_image_url' ),
|
||||
'default' => '',
|
||||
)
|
||||
);
|
||||
|
||||
register_setting(
|
||||
'robotstxt_og_settings',
|
||||
'robotstxt_og_homepage_image',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( $this, 'sanitize_image_url' ),
|
||||
'default' => '',
|
||||
)
|
||||
);
|
||||
|
||||
register_setting(
|
||||
'robotstxt_og_settings',
|
||||
'robotstxt_og_enable_facebook',
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
'default' => true,
|
||||
)
|
||||
);
|
||||
|
||||
register_setting(
|
||||
'robotstxt_og_settings',
|
||||
'robotstxt_og_enable_twitter',
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
'default' => true,
|
||||
)
|
||||
);
|
||||
|
||||
register_setting(
|
||||
'robotstxt_og_settings',
|
||||
'robotstxt_og_twitter_card_type',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_key',
|
||||
'default' => 'summary_large_image',
|
||||
)
|
||||
);
|
||||
|
||||
register_setting(
|
||||
'robotstxt_og_settings',
|
||||
'robotstxt_og_twitter_site',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'default' => '',
|
||||
)
|
||||
);
|
||||
|
||||
register_setting(
|
||||
'robotstxt_og_settings',
|
||||
'robotstxt_og_delete_data_on_uninstall',
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
'default' => false,
|
||||
)
|
||||
);
|
||||
|
||||
// --- General Settings section ---
|
||||
add_settings_section(
|
||||
'robotstxt_og_main_section',
|
||||
__( 'General Settings', 'robotstxt-og' ),
|
||||
array( $this, 'render_main_section' ),
|
||||
'robotstxt-og-settings'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'robotstxt_og_fallback_image',
|
||||
__( 'Global Fallback Image', 'robotstxt-og' ),
|
||||
array( $this, 'render_fallback_image_field' ),
|
||||
'robotstxt-og-settings',
|
||||
'robotstxt_og_main_section'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'robotstxt_og_homepage_image',
|
||||
__( 'Homepage Image', 'robotstxt-og' ),
|
||||
array( $this, 'render_homepage_image_field' ),
|
||||
'robotstxt-og-settings',
|
||||
'robotstxt_og_main_section'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'robotstxt_og_delete_data_on_uninstall',
|
||||
__( 'Data Management', 'robotstxt-og' ),
|
||||
array( $this, 'render_delete_data_field' ),
|
||||
'robotstxt-og-settings',
|
||||
'robotstxt_og_main_section'
|
||||
);
|
||||
|
||||
// --- Social Media Tags section ---
|
||||
add_settings_section(
|
||||
'robotstxt_og_social_section',
|
||||
__( 'Social Media Tags', 'robotstxt-og' ),
|
||||
array( $this, 'render_social_section' ),
|
||||
'robotstxt-og-settings'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'robotstxt_og_enable_facebook',
|
||||
__( 'Facebook / OG Tags', 'robotstxt-og' ),
|
||||
array( $this, 'render_enable_facebook_field' ),
|
||||
'robotstxt-og-settings',
|
||||
'robotstxt_og_social_section'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'robotstxt_og_enable_twitter',
|
||||
__( 'Twitter Card Tags', 'robotstxt-og' ),
|
||||
array( $this, 'render_enable_twitter_field' ),
|
||||
'robotstxt-og-settings',
|
||||
'robotstxt_og_social_section'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'robotstxt_og_twitter_card_type',
|
||||
__( 'Twitter Card Type', 'robotstxt-og' ),
|
||||
array( $this, 'render_twitter_card_type_field' ),
|
||||
'robotstxt-og-settings',
|
||||
'robotstxt_og_social_section'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'robotstxt_og_twitter_site',
|
||||
__( 'Twitter/X Site Handle', 'robotstxt-og' ),
|
||||
array( $this, 'render_twitter_site_field' ),
|
||||
'robotstxt-og-settings',
|
||||
'robotstxt_og_social_section'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize and validate an image URL for use as an OG image.
|
||||
*
|
||||
* Validates URL format and checks compatibility (JPEG/PNG). For AVIF/WebP
|
||||
* and other incompatible formats, attempts to find a JPEG/PNG alternative.
|
||||
* Returns empty string and adds a settings error if the URL is invalid or
|
||||
* no compatible version can be found.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param string $raw Raw value from the settings form.
|
||||
* @return string Validated, compatible image URL or empty string.
|
||||
*/
|
||||
public function sanitize_image_url( string $raw ): string {
|
||||
$url = esc_url_raw( trim( $raw ) );
|
||||
|
||||
if ( empty( $url ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
|
||||
add_settings_error(
|
||||
'robotstxt_og_messages',
|
||||
'robotstxt_og_invalid_url',
|
||||
__( 'The image URL is not valid. Please enter a full URL starting with https://.', 'robotstxt-og' ),
|
||||
'error'
|
||||
);
|
||||
return '';
|
||||
}
|
||||
|
||||
$compatible = $this->resolver->ensure_compatible_format( $url );
|
||||
|
||||
if ( empty( $compatible ) ) {
|
||||
add_settings_error(
|
||||
'robotstxt_og_messages',
|
||||
'robotstxt_og_incompatible_format',
|
||||
__( 'The image URL is not in a compatible format (JPEG or PNG), and no compatible alternative could be found.', 'robotstxt-og' ),
|
||||
'error'
|
||||
);
|
||||
return '';
|
||||
}
|
||||
|
||||
return $compatible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render main settings section description.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_main_section(): void {
|
||||
echo '<p>';
|
||||
esc_html_e( 'Configure OpenGraph image fallback behavior.', 'robotstxt-og' );
|
||||
echo '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render fallback image field.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_fallback_image_field(): void {
|
||||
$image_url = (string) get_option( 'robotstxt_og_fallback_image', '' );
|
||||
?>
|
||||
<input
|
||||
type="url"
|
||||
id="robotstxt_og_fallback_image"
|
||||
name="robotstxt_og_fallback_image"
|
||||
value="<?php echo esc_url( $image_url ); ?>"
|
||||
class="large-text"
|
||||
placeholder="https://example.com/fallback.jpg"
|
||||
/>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Used as Open Graph image when a post has no featured image or when no compatible format (JPEG/PNG) is found. Must be a direct URL to a JPEG or PNG file.', 'robotstxt-og' ); ?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render homepage image field.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_homepage_image_field(): void {
|
||||
$image_url = (string) get_option( 'robotstxt_og_homepage_image', '' );
|
||||
?>
|
||||
<input
|
||||
type="url"
|
||||
id="robotstxt_og_homepage_image"
|
||||
name="robotstxt_og_homepage_image"
|
||||
value="<?php echo esc_url( $image_url ); ?>"
|
||||
class="large-text"
|
||||
placeholder="https://example.com/homepage.jpg"
|
||||
/>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Dedicated Open Graph image for the site homepage. Falls back to the Global Fallback Image if not set. Must be a direct URL to a JPEG or PNG file.', 'robotstxt-og' ); ?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render delete data checkbox field.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_delete_data_field(): void {
|
||||
$delete_data = get_option( 'robotstxt_og_delete_data_on_uninstall', false );
|
||||
?>
|
||||
<label>
|
||||
<input type="checkbox" name="robotstxt_og_delete_data_on_uninstall" value="1" <?php checked( $delete_data, true ); ?> />
|
||||
<?php esc_html_e( 'Delete all plugin data when uninstalling', 'robotstxt-og' ); ?>
|
||||
</label>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Warning: This will permanently delete all cached fallback URLs and plugin settings. This action cannot be undone.', 'robotstxt-og' ); ?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render social media tags section description.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_social_section(): void {
|
||||
echo '<p>';
|
||||
esc_html_e( 'Choose which social media meta tag groups to output and configure their behavior.', 'robotstxt-og' );
|
||||
echo '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render enable Facebook/OG tags checkbox.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_enable_facebook_field(): void {
|
||||
$enabled = get_option( 'robotstxt_og_enable_facebook', true );
|
||||
?>
|
||||
<label>
|
||||
<input type="checkbox" name="robotstxt_og_enable_facebook" value="1" <?php checked( $enabled, true ); ?> />
|
||||
<?php esc_html_e( 'Enable Facebook / Open Graph image tags', 'robotstxt-og' ); ?>
|
||||
</label>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Outputs og:title, og:type, og:url, og:description, og:site_name, og:locale, og:image, og:image:width, og:image:height, og:image:type, and og:image:alt meta tags.', 'robotstxt-og' ); ?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render enable Twitter Card tags checkbox.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_enable_twitter_field(): void {
|
||||
$enabled = get_option( 'robotstxt_og_enable_twitter', true );
|
||||
?>
|
||||
<label>
|
||||
<input type="checkbox" name="robotstxt_og_enable_twitter" value="1" <?php checked( $enabled, true ); ?> />
|
||||
<?php esc_html_e( 'Enable Twitter Card image tags', 'robotstxt-og' ); ?>
|
||||
</label>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Outputs twitter:card, twitter:site, and twitter:image meta tags.', 'robotstxt-og' ); ?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Twitter card type select field.
|
||||
*
|
||||
* @since 1.1.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_twitter_card_type_field(): void {
|
||||
$card_type = get_option( 'robotstxt_og_twitter_card_type', 'summary_large_image' );
|
||||
$options = array(
|
||||
'summary_large_image' => __( 'Summary with large image', 'robotstxt-og' ),
|
||||
'summary' => __( 'Summary (small image)', 'robotstxt-og' ),
|
||||
);
|
||||
?>
|
||||
<select name="robotstxt_og_twitter_card_type" id="robotstxt_og_twitter_card_type">
|
||||
<?php foreach ( $options as $value => $label ) : ?>
|
||||
<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $card_type, $value ); ?>>
|
||||
<?php echo esc_html( $label ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Controls the twitter:card meta tag value. "Summary with large image" is recommended for most sites.', 'robotstxt-og' ); ?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Twitter/X site handle field.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_twitter_site_field(): void {
|
||||
$handle = (string) get_option( 'robotstxt_og_twitter_site', '' );
|
||||
?>
|
||||
<input
|
||||
type="text"
|
||||
id="robotstxt_og_twitter_site"
|
||||
name="robotstxt_og_twitter_site"
|
||||
value="<?php echo esc_attr( $handle ); ?>"
|
||||
placeholder="@example"
|
||||
class="regular-text"
|
||||
/>
|
||||
<p class="description">
|
||||
<?php esc_html_e( "Your site's Twitter/X account handle (e.g. @example). Used for the twitter:site meta tag.", 'robotstxt-og' ); ?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render settings page.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_settings_page(): void {
|
||||
// Check user capabilities.
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-og' ) );
|
||||
}
|
||||
|
||||
// Include view file.
|
||||
require_once ROBOTSTXT_OG_PATH . 'admin/views/settings-page.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clear cache action.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_clear_cache(): void {
|
||||
// Check if this is a cache clear request.
|
||||
$clear_cache = filter_input( INPUT_GET, 'robotstxt_og_clear_cache', FILTER_SANITIZE_SPECIAL_CHARS );
|
||||
if ( null === $clear_cache ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify nonce.
|
||||
$nonce_raw = filter_input( INPUT_GET, '_wpnonce', FILTER_SANITIZE_SPECIAL_CHARS );
|
||||
$nonce = $nonce_raw ? sanitize_text_field( wp_unslash( $nonce_raw ) ) : '';
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce, 'robotstxt_og_clear_cache' ) ) {
|
||||
wp_die( esc_html__( 'Security check failed', 'robotstxt-og' ) );
|
||||
}
|
||||
|
||||
// Check permissions.
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-og' ) );
|
||||
}
|
||||
|
||||
// Clear all caches.
|
||||
$count = $this->clear_all_caches();
|
||||
|
||||
// Add admin notice.
|
||||
add_settings_error(
|
||||
'robotstxt_og_messages',
|
||||
'robotstxt_og_cache_cleared',
|
||||
/* translators: %d: number of cleared cache entries */
|
||||
sprintf( __( 'Successfully cleared %d cached fallback URLs.', 'robotstxt-og' ), $count ),
|
||||
'success'
|
||||
);
|
||||
|
||||
// Redirect back to tools tab.
|
||||
$page_url = admin_url( 'options-general.php?page=robotstxt-og-settings' );
|
||||
wp_safe_redirect(
|
||||
add_query_arg( 'tab', 'tools', remove_query_arg( array( 'robotstxt_og_clear_cache', '_wpnonce' ), $page_url ) )
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle resolve all images action.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_resolve_all(): void {
|
||||
// Check if this is a resolve all request.
|
||||
$resolve_all = filter_input( INPUT_GET, 'robotstxt_og_resolve_all', FILTER_SANITIZE_SPECIAL_CHARS );
|
||||
if ( null === $resolve_all ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify nonce.
|
||||
$nonce_raw = filter_input( INPUT_GET, '_wpnonce', FILTER_SANITIZE_SPECIAL_CHARS );
|
||||
$nonce = $nonce_raw ? sanitize_text_field( wp_unslash( $nonce_raw ) ) : '';
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce, 'robotstxt_og_resolve_all' ) ) {
|
||||
wp_die( esc_html__( 'Security check failed', 'robotstxt-og' ) );
|
||||
}
|
||||
|
||||
// Check permissions.
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-og' ) );
|
||||
}
|
||||
|
||||
// Resolve all images.
|
||||
$result = $this->resolve_all_images();
|
||||
|
||||
// Add admin notice.
|
||||
add_settings_error(
|
||||
'robotstxt_og_messages',
|
||||
'robotstxt_og_images_resolved',
|
||||
/* translators: 1: number of successful resolutions 2: number of failed resolutions */
|
||||
sprintf( __( 'Batch resolution complete. Success: %1$d, Failed: %2$d', 'robotstxt-og' ), $result['success'], $result['failed'] ),
|
||||
$result['failed'] > 0 ? 'warning' : 'success'
|
||||
);
|
||||
|
||||
// Redirect back to tools tab.
|
||||
$page_url = admin_url( 'options-general.php?page=robotstxt-og-settings' );
|
||||
wp_safe_redirect(
|
||||
add_query_arg( 'tab', 'tools', remove_query_arg( array( 'robotstxt_og_resolve_all', '_wpnonce' ), $page_url ) )
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached fallback URLs.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return int Number of entries deleted.
|
||||
*/
|
||||
private function clear_all_caches(): int {
|
||||
global $wpdb;
|
||||
|
||||
// Bulk-delete all cache entries by meta key. No WP API performs this
|
||||
// in a single query while also returning the affected row count.
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$result = $wpdb->delete(
|
||||
$wpdb->postmeta,
|
||||
array( 'meta_key' => '_og_image_fallback_url' ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
array( '%s' )
|
||||
);
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
|
||||
return $result ? (int) $result : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve all images in batch.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array{success: int, failed: int} Result counts.
|
||||
*/
|
||||
private function resolve_all_images(): array {
|
||||
// Query all posts with featured images.
|
||||
$posts = get_posts(
|
||||
array(
|
||||
'post_type' => 'any',
|
||||
'post_status' => 'any',
|
||||
'posts_per_page' => -1,
|
||||
'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
array(
|
||||
'key' => '_thumbnail_id',
|
||||
'compare' => 'EXISTS',
|
||||
),
|
||||
),
|
||||
'fields' => 'ids',
|
||||
)
|
||||
);
|
||||
|
||||
$success = 0;
|
||||
$failed = 0;
|
||||
|
||||
foreach ( $posts as $post_id ) {
|
||||
// Clear cache first.
|
||||
$this->resolver->clear_cache( $post_id );
|
||||
|
||||
// Resolve image.
|
||||
$url = $this->resolver->resolve_image( $post_id );
|
||||
|
||||
if ( ! empty( $url ) ) {
|
||||
++$success;
|
||||
} else {
|
||||
++$failed;
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => $success,
|
||||
'failed' => $failed,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin assets.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $hook Current admin page hook.
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_admin_assets( string $hook ): void {
|
||||
// Only on our settings page.
|
||||
if ( 'settings_page_robotstxt-og-settings' !== $hook ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enqueue custom admin JS.
|
||||
wp_enqueue_script(
|
||||
'robotstxt-og-admin',
|
||||
ROBOTSTXT_OG_URL . 'assets/admin.js',
|
||||
array( 'jquery' ),
|
||||
ROBOTSTXT_OG_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
// Pass translated strings to JavaScript.
|
||||
wp_localize_script(
|
||||
'robotstxt-og-admin',
|
||||
'robotstxt_og_admin',
|
||||
array(
|
||||
'confirm_clear_cache' => __( 'Are you sure you want to clear all cached fallback URLs? This action cannot be undone.', 'robotstxt-og' ),
|
||||
'confirm_resolve_all' => __( 'Are you sure you want to re-resolve all images? This may take some time on large sites.', 'robotstxt-og' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Enqueue custom admin CSS.
|
||||
wp_enqueue_style(
|
||||
'robotstxt-og-admin',
|
||||
ROBOTSTXT_OG_URL . 'assets/admin.css',
|
||||
array(),
|
||||
ROBOTSTXT_OG_VERSION
|
||||
);
|
||||
}
|
||||
}
|
||||
389
admin/views/settings-page.php
Normal file
389
admin/views/settings-page.php
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin Settings Page View
|
||||
*
|
||||
* Template for the OpenGraph settings page with tab navigation.
|
||||
*
|
||||
* @package ROBOTSTXT_OG
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$active_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'settings';
|
||||
$valid_tabs = array( 'settings', 'tools', 'diagnostics' );
|
||||
|
||||
if ( ! in_array( $active_tab, $valid_tabs, true ) ) {
|
||||
$active_tab = 'settings';
|
||||
}
|
||||
|
||||
$page_url = admin_url( 'options-general.php?page=robotstxt-og-settings' );
|
||||
?>
|
||||
|
||||
<div class="wrap">
|
||||
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
||||
|
||||
<?php settings_errors( 'robotstxt_og_messages' ); ?>
|
||||
|
||||
<nav class="nav-tab-wrapper" aria-label="<?php esc_attr_e( 'Settings tabs', 'robotstxt-og' ); ?>">
|
||||
<a href="<?php echo esc_url( add_query_arg( 'tab', 'settings', $page_url ) ); ?>"
|
||||
class="nav-tab <?php echo 'settings' === $active_tab ? 'nav-tab-active' : ''; ?>">
|
||||
<?php esc_html_e( 'Settings', 'robotstxt-og' ); ?>
|
||||
</a>
|
||||
<a href="<?php echo esc_url( add_query_arg( 'tab', 'tools', $page_url ) ); ?>"
|
||||
class="nav-tab <?php echo 'tools' === $active_tab ? 'nav-tab-active' : ''; ?>">
|
||||
<?php esc_html_e( 'Tools', 'robotstxt-og' ); ?>
|
||||
</a>
|
||||
<a href="<?php echo esc_url( add_query_arg( 'tab', 'diagnostics', $page_url ) ); ?>"
|
||||
class="nav-tab <?php echo 'diagnostics' === $active_tab ? 'nav-tab-active' : ''; ?>">
|
||||
<?php esc_html_e( 'Diagnostics', 'robotstxt-og' ); ?>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="tab-content" style="margin-top: 1em;">
|
||||
|
||||
<?php if ( 'settings' === $active_tab ) : ?>
|
||||
|
||||
<form method="post" action="options.php">
|
||||
<?php
|
||||
settings_fields( 'robotstxt_og_settings' );
|
||||
do_settings_sections( 'robotstxt-og-settings' );
|
||||
submit_button( __( 'Save Settings', 'robotstxt-og' ) );
|
||||
?>
|
||||
</form>
|
||||
|
||||
<?php elseif ( 'tools' === $active_tab ) : ?>
|
||||
|
||||
<h2><?php esc_html_e( 'Cache Management', 'robotstxt-og' ); ?></h2>
|
||||
|
||||
<p><?php esc_html_e( 'Use these tools to manage cached fallback image URLs.', 'robotstxt-og' ); ?></p>
|
||||
|
||||
<table class="form-table" role="presentation">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e( 'Clear All Caches', 'robotstxt-og' ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<a href="
|
||||
<?php
|
||||
echo esc_url(
|
||||
wp_nonce_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
'robotstxt_og_clear_cache' => '1',
|
||||
'tab' => 'tools',
|
||||
),
|
||||
$page_url
|
||||
),
|
||||
'robotstxt_og_clear_cache'
|
||||
)
|
||||
);
|
||||
?>
|
||||
" class="button">
|
||||
<?php esc_html_e( 'Clear All Cached URLs', 'robotstxt-og' ); ?>
|
||||
</a>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Delete all cached fallback URLs. Images will be re-resolved on next page view.', 'robotstxt-og' ); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php esc_html_e( 'Re-resolve All Images', 'robotstxt-og' ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<a href="
|
||||
<?php
|
||||
echo esc_url(
|
||||
wp_nonce_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
'robotstxt_og_resolve_all' => '1',
|
||||
'tab' => 'tools',
|
||||
),
|
||||
$page_url
|
||||
),
|
||||
'robotstxt_og_resolve_all'
|
||||
)
|
||||
);
|
||||
?>
|
||||
" class="button">
|
||||
<?php esc_html_e( 'Re-resolve All Images Now', 'robotstxt-og' ); ?>
|
||||
</a>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Clear cache and immediately re-resolve all posts with featured images. May take time on large sites.', 'robotstxt-og' ); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 style="margin-top: 2em;"><?php esc_html_e( 'Plugin Information', 'robotstxt-og' ); ?></h2>
|
||||
|
||||
<table class="form-table" role="presentation">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Version', 'robotstxt-og' ); ?></th>
|
||||
<td><code><?php echo esc_html( ROBOTSTXT_OG_VERSION ); ?></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Documentation', 'robotstxt-og' ); ?></th>
|
||||
<td>
|
||||
<a href="https://git.robotstxt.es/ROBOTSTXT/robotstxt-og" target="_blank" rel="noopener noreferrer">
|
||||
<?php esc_html_e( 'View Documentation', 'robotstxt-og' ); ?>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Support', 'robotstxt-og' ); ?></th>
|
||||
<td>
|
||||
<a href="https://git.robotstxt.es/ROBOTSTXT/robotstxt-og/issues" target="_blank" rel="noopener noreferrer">
|
||||
<?php esc_html_e( 'Report an Issue', 'robotstxt-og' ); ?>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php elseif ( 'diagnostics' === $active_tab ) : ?>
|
||||
|
||||
<?php
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
// Diagnostics page requires live data — caching would show stale counts.
|
||||
$total_cached = (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = %s",
|
||||
'_og_image_fallback_url'
|
||||
)
|
||||
);
|
||||
|
||||
$total_with_thumbnail = (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = %s",
|
||||
'_thumbnail_id'
|
||||
)
|
||||
);
|
||||
|
||||
$posts_per_page = 20;
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$current_page = isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1;
|
||||
$offset = ( $current_page - 1 ) * $posts_per_page;
|
||||
$total_pages = (int) ceil( $total_cached / $posts_per_page );
|
||||
|
||||
$cached_entries = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT pm.post_id, pm.meta_value as fallback_url, p.post_title, p.post_type
|
||||
FROM {$wpdb->postmeta} pm
|
||||
INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
||||
WHERE pm.meta_key = %s
|
||||
ORDER BY pm.post_id DESC
|
||||
LIMIT %d OFFSET %d",
|
||||
'_og_image_fallback_url',
|
||||
$posts_per_page,
|
||||
$offset
|
||||
)
|
||||
);
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
?>
|
||||
|
||||
<h2><?php esc_html_e( 'Statistics', 'robotstxt-og' ); ?></h2>
|
||||
|
||||
<table class="widefat striped" style="max-width: 480px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th><?php esc_html_e( 'Posts with featured images', 'robotstxt-og' ); ?></th>
|
||||
<td><?php echo absint( $total_with_thumbnail ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php esc_html_e( 'Posts with cached fallback URLs', 'robotstxt-og' ); ?></th>
|
||||
<td><?php echo absint( $total_cached ); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><?php esc_html_e( 'Coverage', 'robotstxt-og' ); ?></th>
|
||||
<td>
|
||||
<?php
|
||||
if ( $total_with_thumbnail > 0 ) {
|
||||
echo esc_html( number_format_i18n( ( $total_cached / $total_with_thumbnail ) * 100, 1 ) ) . '%';
|
||||
} else {
|
||||
esc_html_e( 'N/A', 'robotstxt-og' );
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 style="margin-top: 2em;"><?php esc_html_e( 'Test URL', 'robotstxt-og' ); ?></h2>
|
||||
|
||||
<p><?php esc_html_e( 'Test whether a URL is reachable via HTTP HEAD request.', 'robotstxt-og' ); ?></p>
|
||||
|
||||
<form method="get" action="<?php echo esc_url( $page_url ); ?>">
|
||||
<input type="hidden" name="page" value="robotstxt-og-settings" />
|
||||
<input type="hidden" name="tab" value="diagnostics" />
|
||||
<?php wp_nonce_field( 'robotstxt_og_test_url', '_wpnonce', false ); ?>
|
||||
<table class="form-table" role="presentation">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="test_url"><?php esc_html_e( 'URL to test', 'robotstxt-og' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<?php
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$test_url_raw = isset( $_GET['test_url'] ) ? esc_url_raw( wp_unslash( $_GET['test_url'] ) ) : '';
|
||||
?>
|
||||
<input
|
||||
type="url"
|
||||
id="test_url"
|
||||
name="test_url"
|
||||
value="<?php echo esc_attr( $test_url_raw ); ?>"
|
||||
class="regular-text"
|
||||
placeholder="https://example.com/image.jpg"
|
||||
/>
|
||||
<?php submit_button( __( 'Test URL', 'robotstxt-og' ), 'secondary', 'submit', false ); ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<?php
|
||||
if ( ! empty( $test_url_raw ) ) {
|
||||
$nonce_raw = filter_input( INPUT_GET, '_wpnonce', FILTER_SANITIZE_SPECIAL_CHARS );
|
||||
$nonce = $nonce_raw ? sanitize_text_field( wp_unslash( $nonce_raw ) ) : '';
|
||||
|
||||
if ( wp_verify_nonce( $nonce, 'robotstxt_og_test_url' ) ) {
|
||||
$response = wp_remote_head(
|
||||
$test_url_raw,
|
||||
array(
|
||||
'timeout' => 5,
|
||||
'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ),
|
||||
)
|
||||
);
|
||||
|
||||
echo '<h3>' . esc_html__( 'Test Result', 'robotstxt-og' ) . '</h3>';
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
echo '<div class="notice notice-error inline"><p><strong>';
|
||||
esc_html_e( 'Error:', 'robotstxt-og' );
|
||||
echo '</strong> ' . esc_html( $response->get_error_message() ) . '</p></div>';
|
||||
} else {
|
||||
$http_code = (int) wp_remote_retrieve_response_code( $response );
|
||||
$http_message = wp_remote_retrieve_response_message( $response );
|
||||
$notice_type = ( $http_code >= 200 && $http_code < 300 ) ? 'success' : 'error';
|
||||
|
||||
echo '<div class="notice notice-' . esc_attr( $notice_type ) . ' inline"><p>';
|
||||
echo '<strong>' . esc_html__( 'HTTP Status:', 'robotstxt-og' ) . '</strong> ';
|
||||
echo esc_html( $http_code . ' ' . $http_message ) . '</p></div>';
|
||||
|
||||
$headers = wp_remote_retrieve_headers( $response );
|
||||
if ( ! empty( $headers ) ) {
|
||||
echo '<table class="widefat striped" style="max-width:600px;margin-top:10px;">';
|
||||
echo '<thead><tr><th>' . esc_html__( 'Header', 'robotstxt-og' ) . '</th>';
|
||||
echo '<th>' . esc_html__( 'Value', 'robotstxt-og' ) . '</th></tr></thead><tbody>';
|
||||
foreach ( $headers as $header_key => $header_value ) {
|
||||
echo '<tr><td><code>' . esc_html( $header_key ) . '</code></td>';
|
||||
echo '<td>' . esc_html( is_array( $header_value ) ? implode( ', ', $header_value ) : $header_value ) . '</td></tr>';
|
||||
}
|
||||
echo '</tbody></table>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<h2 style="margin-top:2em;">
|
||||
<?php
|
||||
/* translators: %d: total entries */
|
||||
printf( esc_html__( 'Cached Fallback URLs (%d)', 'robotstxt-og' ), absint( $total_cached ) );
|
||||
?>
|
||||
</h2>
|
||||
|
||||
<?php if ( empty( $cached_entries ) ) : ?>
|
||||
<p><?php esc_html_e( 'No cached fallback URLs found.', 'robotstxt-og' ); ?></p>
|
||||
<?php else : ?>
|
||||
|
||||
<table class="widefat striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e( 'Post ID', 'robotstxt-og' ); ?></th>
|
||||
<th><?php esc_html_e( 'Title', 'robotstxt-og' ); ?></th>
|
||||
<th><?php esc_html_e( 'Type', 'robotstxt-og' ); ?></th>
|
||||
<th><?php esc_html_e( 'Cached Fallback URL', 'robotstxt-og' ); ?></th>
|
||||
<th><?php esc_html_e( 'Actions', 'robotstxt-og' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ( $cached_entries as $cached_entry ) : ?>
|
||||
<tr>
|
||||
<td><?php echo absint( $cached_entry->post_id ); ?></td>
|
||||
<td>
|
||||
<a href="<?php echo esc_url( get_edit_post_link( $cached_entry->post_id ) ); ?>">
|
||||
<?php echo esc_html( $cached_entry->post_title ); ?>
|
||||
</a>
|
||||
</td>
|
||||
<td><code><?php echo esc_html( $cached_entry->post_type ); ?></code></td>
|
||||
<td style="word-break:break-all;">
|
||||
<a href="<?php echo esc_url( $cached_entry->fallback_url ); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo esc_html( $cached_entry->fallback_url ); ?>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="
|
||||
<?php
|
||||
echo esc_url(
|
||||
wp_nonce_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
'robotstxt_og_clear_single' => $cached_entry->post_id,
|
||||
'tab' => 'diagnostics',
|
||||
),
|
||||
$page_url
|
||||
),
|
||||
'robotstxt_og_clear_single_' . $cached_entry->post_id
|
||||
)
|
||||
);
|
||||
?>
|
||||
" class="button button-small">
|
||||
<?php esc_html_e( 'Clear', 'robotstxt-og' ); ?>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if ( $total_pages > 1 ) : ?>
|
||||
<div class="tablenav bottom">
|
||||
<div class="tablenav-pages">
|
||||
<?php
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo paginate_links(
|
||||
array(
|
||||
'base' => add_query_arg( 'paged', '%#%' ),
|
||||
'format' => '',
|
||||
'current' => $current_page,
|
||||
'total' => $total_pages,
|
||||
'prev_text' => '«',
|
||||
'next_text' => '»',
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php // phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound ?>
|
||||
Loading…
Reference in a new issue