v0.1.0
This commit is contained in:
commit
bc1cb5e00a
32 changed files with 4254 additions and 0 deletions
157
admin/class-robotstxt-manager-admin.php
Normal file
157
admin/class-robotstxt-manager-admin.php
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin menu and main catalog page.
|
||||
*
|
||||
* @package Robotstxt_Manager
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Robotstxt_Manager_Admin
|
||||
*
|
||||
* Adds the top-level "ROBOTSTXT Plugins" menu and renders the catalog table
|
||||
* that lists every plugin from the remote Plugins Core install alongside
|
||||
* its local install/active/update state.
|
||||
*/
|
||||
class Robotstxt_Manager_Admin {
|
||||
|
||||
/**
|
||||
* Admin page and menu slug.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PAGE_SLUG = 'robotstxt-manager';
|
||||
|
||||
/**
|
||||
* Registers all hooks via the loader.
|
||||
*
|
||||
* @param Robotstxt_Manager_Loader $loader The plugin hook loader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register( Robotstxt_Manager_Loader $loader ): void {
|
||||
$loader->add_action( 'admin_menu', $this, 'add_menu' );
|
||||
$loader->add_action( 'admin_post_robotstxt_manager_refresh_catalog', $this, 'handle_refresh' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the top-level admin menu.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_menu(): void {
|
||||
add_menu_page(
|
||||
esc_html__( 'Manager (by ROBOTSTXT) — Plugins', 'robotstxt-manager' ),
|
||||
esc_html__( 'ROBOTSTXT', 'robotstxt-manager' ),
|
||||
'manage_options',
|
||||
self::PAGE_SLUG,
|
||||
array( $this, 'render_page' ),
|
||||
'dashicons-screenoptions',
|
||||
71
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the main catalog page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_page(): void {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-manager' ) );
|
||||
}
|
||||
|
||||
$client = Robotstxt_Manager_Core_Client::from_options();
|
||||
$catalog = $client->get_catalog();
|
||||
$local = $this->resolve_local_state( $catalog );
|
||||
|
||||
require_once ROBOTSTXT_MANAGER_DIR . 'admin/views/page-catalog.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the "Refresh catalog" admin-post action.
|
||||
*
|
||||
* Clears the cached catalog response and redirects back to the page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_refresh(): void {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'Insufficient permissions.', 'robotstxt-manager' ) );
|
||||
}
|
||||
|
||||
check_admin_referer( 'robotstxt_manager_refresh_catalog' );
|
||||
|
||||
$client = Robotstxt_Manager_Core_Client::from_options();
|
||||
$client->clear_catalog_cache();
|
||||
|
||||
$redirect = add_query_arg(
|
||||
array(
|
||||
'page' => self::PAGE_SLUG,
|
||||
'refreshed' => '1',
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
|
||||
wp_safe_redirect( $redirect );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves local install state for every catalog entry.
|
||||
*
|
||||
* Reads the local plugin list once and matches by slug. Does not perform
|
||||
* any network calls.
|
||||
*
|
||||
* @param list<array<string,mixed>> $catalog Catalog entries from Core.
|
||||
*
|
||||
* @return array<string, array{installed:bool, active:bool, version:string}> Keyed by slug.
|
||||
*/
|
||||
private function resolve_local_state( array $catalog ): array {
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$all_plugins = get_plugins();
|
||||
$local = array();
|
||||
|
||||
// Build a slug → file map from local installs.
|
||||
$slug_to_file = array();
|
||||
foreach ( $all_plugins as $file => $data ) {
|
||||
$slug = dirname( $file );
|
||||
if ( '.' === $slug ) {
|
||||
$slug = basename( $file, '.php' );
|
||||
}
|
||||
$slug_to_file[ $slug ] = $file;
|
||||
}
|
||||
|
||||
foreach ( $catalog as $entry ) {
|
||||
$raw_slug = $entry['slug'] ?? '';
|
||||
$slug = is_string( $raw_slug ) ? $raw_slug : '';
|
||||
if ( '' === $slug ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file = $slug_to_file[ $slug ] ?? null;
|
||||
$version = '';
|
||||
|
||||
if ( null !== $file && isset( $all_plugins[ $file ]['Version'] ) ) {
|
||||
$raw_ver = $all_plugins[ $file ]['Version'];
|
||||
$version = is_string( $raw_ver ) ? $raw_ver : '';
|
||||
}
|
||||
|
||||
$active = null !== $file && is_string( $file ) && is_plugin_active( $file );
|
||||
|
||||
$local[ $slug ] = array(
|
||||
'installed' => null !== $file,
|
||||
'active' => $active,
|
||||
'version' => $version,
|
||||
);
|
||||
}
|
||||
|
||||
return $local;
|
||||
}
|
||||
}
|
||||
402
admin/class-robotstxt-manager-settings.php
Normal file
402
admin/class-robotstxt-manager-settings.php
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin settings page: API key, store URL, cache TTL, connection test.
|
||||
*
|
||||
* @package Robotstxt_Manager
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Robotstxt_Manager_Settings
|
||||
*
|
||||
* Registers the plugin settings page and all setting fields using the
|
||||
* WordPress Settings API. The API key is stored encrypted via
|
||||
* Robotstxt_Manager_Encryption and displayed masked in the UI. Provides an
|
||||
* AJAX handler for the "Test connection" button.
|
||||
*/
|
||||
class Robotstxt_Manager_Settings {
|
||||
|
||||
/**
|
||||
* Admin page and menu slug.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PAGE_SLUG = 'robotstxt-manager-settings';
|
||||
|
||||
/**
|
||||
* Settings API option group.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const OPTION_GROUP = 'robotstxt_manager_settings_group';
|
||||
|
||||
/**
|
||||
* Registers all hooks via the loader.
|
||||
*
|
||||
* @param Robotstxt_Manager_Loader $loader The plugin hook loader.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register( Robotstxt_Manager_Loader $loader ): void {
|
||||
$loader->add_action( 'admin_menu', $this, 'add_settings_page' );
|
||||
$loader->add_action( 'admin_init', $this, 'register_settings' );
|
||||
$loader->add_action( 'admin_enqueue_scripts', $this, 'enqueue_scripts' );
|
||||
$loader->add_action( 'wp_ajax_robotstxt_manager_test_connection', $this, 'handle_test_connection' );
|
||||
$loader->add_action( 'wp_ajax_robotstxt_manager_delete_key', $this, 'handle_delete_key' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Settings submenu under the Manager top-level menu.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_settings_page(): void {
|
||||
add_submenu_page(
|
||||
Robotstxt_Manager_Admin::PAGE_SLUG,
|
||||
esc_html__( 'Manager (by ROBOTSTXT) — Settings', 'robotstxt-manager' ),
|
||||
esc_html__( 'Settings', 'robotstxt-manager' ),
|
||||
'manage_options',
|
||||
self::PAGE_SLUG,
|
||||
array( $this, 'render_page' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers settings, sections, and fields with the Settings API.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_settings(): void {
|
||||
add_settings_section(
|
||||
'robotstxt_manager_connection_section',
|
||||
esc_html__( 'Connection', 'robotstxt-manager' ),
|
||||
'__return_false',
|
||||
self::PAGE_SLUG
|
||||
);
|
||||
|
||||
register_setting(
|
||||
self::OPTION_GROUP,
|
||||
'robotstxt_manager_store_url',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'esc_url_raw',
|
||||
'default' => 'https://plugins.robotstxt.es',
|
||||
)
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'robotstxt_manager_store_url',
|
||||
esc_html__( 'Store URL', 'robotstxt-manager' ),
|
||||
array( $this, 'render_field_store_url' ),
|
||||
self::PAGE_SLUG,
|
||||
'robotstxt_manager_connection_section'
|
||||
);
|
||||
|
||||
register_setting(
|
||||
self::OPTION_GROUP,
|
||||
'robotstxt_manager_api_key',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( $this, 'sanitize_api_key' ),
|
||||
'default' => '',
|
||||
)
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'robotstxt_manager_api_key',
|
||||
esc_html__( 'API Key', 'robotstxt-manager' ),
|
||||
array( $this, 'render_field_api_key' ),
|
||||
self::PAGE_SLUG,
|
||||
'robotstxt_manager_connection_section'
|
||||
);
|
||||
|
||||
add_settings_section(
|
||||
'robotstxt_manager_cache_section',
|
||||
esc_html__( 'Cache', 'robotstxt-manager' ),
|
||||
'__return_false',
|
||||
self::PAGE_SLUG
|
||||
);
|
||||
|
||||
register_setting(
|
||||
self::OPTION_GROUP,
|
||||
'robotstxt_manager_cache_ttl_minutes',
|
||||
array(
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => array( $this, 'sanitize_cache_ttl' ),
|
||||
'default' => 60,
|
||||
)
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'robotstxt_manager_cache_ttl_minutes',
|
||||
esc_html__( 'Catalog Cache (minutes)', 'robotstxt-manager' ),
|
||||
array( $this, 'render_field_cache_ttl' ),
|
||||
self::PAGE_SLUG,
|
||||
'robotstxt_manager_cache_section'
|
||||
);
|
||||
|
||||
register_setting(
|
||||
self::OPTION_GROUP,
|
||||
'robotstxt_manager_delete_data_on_uninstall',
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
'default' => false,
|
||||
)
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'robotstxt_manager_delete_data_on_uninstall',
|
||||
esc_html__( 'Data on Uninstall', 'robotstxt-manager' ),
|
||||
array( $this, 'render_field_delete_on_uninstall' ),
|
||||
self::PAGE_SLUG,
|
||||
'robotstxt_manager_cache_section'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the settings page JS on the correct screen.
|
||||
*
|
||||
* @param string $hook_suffix The current admin page hook suffix.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_scripts( string $hook_suffix ): void {
|
||||
if ( ! str_contains( $hook_suffix, self::PAGE_SLUG ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script(
|
||||
'robotstxt-manager-settings',
|
||||
ROBOTSTXT_MANAGER_URL . 'admin/js/robotstxt-manager-settings.js',
|
||||
array( 'jquery' ),
|
||||
ROBOTSTXT_MANAGER_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'robotstxt-manager-settings',
|
||||
'RobotstxtManagerSettings',
|
||||
array(
|
||||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||||
'nonce' => wp_create_nonce( 'robotstxt_manager_test_connection' ),
|
||||
'deleteNonce' => wp_create_nonce( 'robotstxt_manager_delete_key' ),
|
||||
'i18n' => array(
|
||||
'testing' => __( 'Testing…', 'robotstxt-manager' ),
|
||||
'testLabel' => __( 'Test connection', 'robotstxt-manager' ),
|
||||
'deleting' => __( 'Deleting…', 'robotstxt-manager' ),
|
||||
'deleteLabel' => __( 'Delete API key', 'robotstxt-manager' ),
|
||||
'deleteConfirm' => __( 'Delete the stored API key? The catalog and subscription data will stop working until a new key is entered.', 'robotstxt-manager' ),
|
||||
'deleted' => __( 'API key deleted. Save changes to persist.', 'robotstxt-manager' ),
|
||||
'ajaxError' => __( 'An unexpected error occurred.', 'robotstxt-manager' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the settings page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_page(): void {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-manager' ) );
|
||||
}
|
||||
|
||||
require_once ROBOTSTXT_MANAGER_DIR . 'admin/views/page-settings.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the Store URL field.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_field_store_url(): void {
|
||||
$raw = get_option( 'robotstxt_manager_store_url', 'https://plugins.robotstxt.es' );
|
||||
$value = is_string( $raw ) ? $raw : 'https://plugins.robotstxt.es';
|
||||
|
||||
printf(
|
||||
'<input type="url" id="robotstxt_manager_store_url" name="robotstxt_manager_store_url" value="%s" class="regular-text" maxlength="500" placeholder="https://plugins.robotstxt.es" />',
|
||||
esc_attr( $value )
|
||||
);
|
||||
echo '<p class="description">' . esc_html__( 'Base URL of the remote Plugins Core installation that this site will pull the plugin catalog from.', 'robotstxt-manager' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the API key field. Always empty on render — the stored value
|
||||
* is never sent back to the browser. The masked last 4 chars of the
|
||||
* stored key are shown as a hint.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_field_api_key(): void {
|
||||
$stored = get_option( 'robotstxt_manager_api_key', '' );
|
||||
$has_key = is_string( $stored ) && '' !== $stored;
|
||||
$last4 = '';
|
||||
|
||||
if ( $has_key ) {
|
||||
$plain = Robotstxt_Manager_Encryption::decrypt( is_string( $stored ) ? $stored : '' );
|
||||
// Only show last-4 if decryption produced a plausible key (36-char UUID).
|
||||
if ( '' !== $plain && 36 === strlen( $plain ) ) {
|
||||
$last4 = substr( $plain, -4 );
|
||||
}
|
||||
}
|
||||
|
||||
echo '<input type="password" id="robotstxt_manager_api_key" name="robotstxt_manager_api_key" value="" class="regular-text" autocomplete="new-password" />';
|
||||
|
||||
if ( $has_key ) {
|
||||
echo '<p class="description">';
|
||||
if ( '' !== $last4 ) {
|
||||
echo wp_kses_post(
|
||||
sprintf(
|
||||
/* translators: %s: last 4 characters of the stored API key. */
|
||||
__( 'A key is stored (last 4 characters: <code>%s</code>). Leave blank to keep the existing key; enter a new value to replace it.', 'robotstxt-manager' ),
|
||||
esc_html( $last4 )
|
||||
)
|
||||
);
|
||||
} else {
|
||||
esc_html_e( 'A key is stored but could not be decoded. You can replace it by entering a new value above, or delete it with the button below.', 'robotstxt-manager' );
|
||||
}
|
||||
echo '</p>';
|
||||
} else {
|
||||
echo '<p class="description">' . esc_html__( 'Account-level API key issued by the ROBOTSTXT store. Required to authenticate catalog and subscription requests. The value is encrypted before storage.', 'robotstxt-manager' ) . '</p>';
|
||||
}
|
||||
|
||||
// Action buttons.
|
||||
echo '<p>';
|
||||
|
||||
// Test connection: disabled when no key is stored.
|
||||
$test_disabled = $has_key ? '' : ' disabled';
|
||||
printf( '<button type="button" id="robotstxt-manager-test-connection" class="button"%s>%s</button>', esc_attr( $test_disabled ), esc_html__( 'Test connection', 'robotstxt-manager' ) );
|
||||
|
||||
// Delete stored key: only shown when a key exists.
|
||||
if ( $has_key ) {
|
||||
echo ' <button type="button" id="robotstxt-manager-delete-key" class="button button-link-delete">' . esc_html__( 'Delete API key', 'robotstxt-manager' ) . '</button>';
|
||||
}
|
||||
|
||||
echo '<span id="robotstxt-manager-test-result" style="margin-left:8px"></span>';
|
||||
echo '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the cache TTL field.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_field_cache_ttl(): void {
|
||||
$raw = get_option( 'robotstxt_manager_cache_ttl_minutes', 60 );
|
||||
$value = is_numeric( $raw ) ? (int) $raw : 60;
|
||||
|
||||
printf(
|
||||
'<input type="number" id="robotstxt_manager_cache_ttl_minutes" name="robotstxt_manager_cache_ttl_minutes" value="%d" class="small-text" min="1" max="1440" />',
|
||||
absint( $value )
|
||||
);
|
||||
echo '<p class="description">' . esc_html__( 'How long the catalog response from Core is cached in a transient. Default: 60 minutes. Lower values refresh more often at the cost of more requests to Core. Maximum: 1440 (24 hours).', 'robotstxt-manager' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the delete-on-uninstall checkbox.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_field_delete_on_uninstall(): void {
|
||||
$value = (bool) get_option( 'robotstxt_manager_delete_data_on_uninstall', false );
|
||||
echo '<label>';
|
||||
printf(
|
||||
'<input type="checkbox" id="robotstxt_manager_delete_data_on_uninstall" name="robotstxt_manager_delete_data_on_uninstall" value="1"%s />',
|
||||
checked( $value, true, false )
|
||||
);
|
||||
echo ' ' . esc_html__( 'Delete all plugin data when the plugin is uninstalled.', 'robotstxt-manager' );
|
||||
echo '</label>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the API key field.
|
||||
*
|
||||
* Empty input preserves the existing encrypted value; non-empty input is
|
||||
* encrypted before storage. The catalog transient is cleared whenever
|
||||
* the key changes so the next page load fetches fresh data.
|
||||
*
|
||||
* @param mixed $value Raw submitted value.
|
||||
*
|
||||
* @return string Encrypted key, or existing value if input is empty.
|
||||
*/
|
||||
public function sanitize_api_key( mixed $value ): string {
|
||||
$plain = sanitize_text_field( is_string( $value ) ? $value : '' );
|
||||
|
||||
if ( '' === $plain ) {
|
||||
$raw = get_option( 'robotstxt_manager_api_key', '' );
|
||||
return is_string( $raw ) ? $raw : '';
|
||||
}
|
||||
|
||||
delete_transient( 'robotstxt_manager_catalog' );
|
||||
|
||||
return Robotstxt_Manager_Encryption::encrypt( $plain );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the cache TTL value.
|
||||
*
|
||||
* @param mixed $value Raw submitted value.
|
||||
*
|
||||
* @return int Clamped to 1-1440 minutes.
|
||||
*/
|
||||
public function sanitize_cache_ttl( mixed $value ): int {
|
||||
return max( 1, min( 1440, absint( is_scalar( $value ) ? $value : 0 ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the "Test connection" AJAX request.
|
||||
*
|
||||
* Reads the current Store URL + API key from options, attempts a GET
|
||||
* /plugins against Core, and returns success/failure with a message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_test_connection(): void {
|
||||
check_ajax_referer( 'robotstxt_manager_test_connection', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'robotstxt-manager' ) ) );
|
||||
}
|
||||
|
||||
$client = Robotstxt_Manager_Core_Client::from_options();
|
||||
$result = $client->test_connection();
|
||||
|
||||
if ( $result['ok'] ) {
|
||||
wp_send_json_success( array( 'message' => $result['message'] ) );
|
||||
}
|
||||
|
||||
wp_send_json_error( array( 'message' => $result['message'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the "Delete API key" AJAX request.
|
||||
*
|
||||
* Removes the encrypted API key from wp_options. The change takes effect
|
||||
* immediately — subsequent catalog fetches and connection tests will fail
|
||||
* until a new key is entered and saved.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_delete_key(): void {
|
||||
check_ajax_referer( 'robotstxt_manager_delete_key', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'robotstxt-manager' ) ) );
|
||||
}
|
||||
|
||||
delete_option( 'robotstxt_manager_api_key' );
|
||||
delete_transient( 'robotstxt_manager_catalog' );
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'message' => __( 'API key deleted. Save changes to persist.', 'robotstxt-manager' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
89
admin/js/robotstxt-manager-settings.js
Normal file
89
admin/js/robotstxt-manager-settings.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Settings page JS — "Test connection" and "Delete API key" handlers.
|
||||
*
|
||||
* @global RobotstxtManagerSettings
|
||||
* @global jQuery
|
||||
* @package Robotstxt_Manager
|
||||
*/
|
||||
|
||||
( function ( $ ) {
|
||||
'use strict';
|
||||
|
||||
// Test connection.
|
||||
$( document ).on(
|
||||
'click',
|
||||
'#robotstxt-manager-test-connection',
|
||||
function () {
|
||||
var $btn = $( this );
|
||||
var $result = $( '#robotstxt-manager-test-result' );
|
||||
|
||||
$btn.prop( 'disabled', true ).text( RobotstxtManagerSettings.i18n.testing );
|
||||
$result.text( '' ).css( 'color', '' );
|
||||
|
||||
$.post(
|
||||
RobotstxtManagerSettings.ajaxUrl,
|
||||
{
|
||||
action: 'robotstxt_manager_test_connection',
|
||||
nonce: RobotstxtManagerSettings.nonce,
|
||||
},
|
||||
function ( response ) {
|
||||
if ( response.success ) {
|
||||
$result.text( response.data.message ).css( 'color', '#00a32a' );
|
||||
} else {
|
||||
$result.text( response.data.message ).css( 'color', '#d63638' );
|
||||
}
|
||||
}
|
||||
).fail(
|
||||
function () {
|
||||
$result.text( RobotstxtManagerSettings.i18n.ajaxError ).css( 'color', '#d63638' );
|
||||
}
|
||||
).always(
|
||||
function () {
|
||||
$btn.prop( 'disabled', false ).text( RobotstxtManagerSettings.i18n.testLabel );
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Delete API key.
|
||||
$( document ).on(
|
||||
'click',
|
||||
'#robotstxt-manager-delete-key',
|
||||
function () {
|
||||
var $btn = $( this );
|
||||
var $result = $( '#robotstxt-manager-test-result' );
|
||||
|
||||
if ( ! window.confirm( RobotstxtManagerSettings.i18n.deleteConfirm ) ) { /* eslint-disable-line no-alert */
|
||||
return;
|
||||
}
|
||||
|
||||
$btn.prop( 'disabled', true ).text( RobotstxtManagerSettings.i18n.deleting );
|
||||
$result.text( '' ).css( 'color', '' );
|
||||
|
||||
$.post(
|
||||
RobotstxtManagerSettings.ajaxUrl,
|
||||
{
|
||||
action: 'robotstxt_manager_delete_key',
|
||||
nonce: RobotstxtManagerSettings.deleteNonce,
|
||||
},
|
||||
function ( response ) {
|
||||
if ( response.success ) {
|
||||
$result.text( RobotstxtManagerSettings.i18n.deleted ).css( 'color', '#00a32a' );
|
||||
// Hide the delete button + test button; update description.
|
||||
$btn.hide();
|
||||
$( '#robotstxt-manager-test-connection' ).prop( 'disabled', true );
|
||||
$( '#robotstxt_manager_api_key' ).next( '.description' ).html( RobotstxtManagerSettings.i18n.deleted );
|
||||
} else {
|
||||
$result.text( response.data.message ).css( 'color', '#d63638' );
|
||||
$btn.prop( 'disabled', false ).text( RobotstxtManagerSettings.i18n.deleteLabel );
|
||||
}
|
||||
}
|
||||
).fail(
|
||||
function () {
|
||||
$result.text( RobotstxtManagerSettings.i18n.ajaxError ).css( 'color', '#d63638' );
|
||||
$btn.prop( 'disabled', false ).text( RobotstxtManagerSettings.i18n.deleteLabel );
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}( jQuery ) );
|
||||
240
admin/views/page-catalog.php
Normal file
240
admin/views/page-catalog.php
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin page view: ROBOTSTXT Plugins catalog.
|
||||
*
|
||||
* Available variables:
|
||||
* $catalog list<array<string,mixed>> Catalog entries from Core.
|
||||
* $local array<string, array{installed:bool, active:bool, version:string}>
|
||||
* Local install state, keyed by slug.
|
||||
*
|
||||
* @package Robotstxt_Manager
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Variables provided by Robotstxt_Manager_Admin::render_page().
|
||||
*
|
||||
* @var list<array<string,mixed>> $catalog
|
||||
* @var array<string, array{installed:bool, active:bool, version:string}> $local
|
||||
*/
|
||||
|
||||
$client_configured = Robotstxt_Manager_Core_Client::from_options()->is_configured();
|
||||
$refresh_url = add_query_arg(
|
||||
array(
|
||||
'action' => 'robotstxt_manager_refresh_catalog',
|
||||
),
|
||||
admin_url( 'admin-post.php' )
|
||||
);
|
||||
$refresh_url = wp_nonce_url( $refresh_url, 'robotstxt_manager_refresh_catalog' );
|
||||
$refreshed = isset( $_GET['refreshed'] ) && '1' === $_GET['refreshed']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
$local_wp_version = (string) get_bloginfo( 'version' );
|
||||
$local_php_version = (string) PHP_VERSION;
|
||||
$compat_warnings = 0;
|
||||
?>
|
||||
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'Manager (by ROBOTSTXT) — Plugins', 'robotstxt-manager' ); ?></h1>
|
||||
|
||||
<?php if ( $refreshed ) : ?>
|
||||
<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Catalog refreshed.', 'robotstxt-manager' ); ?></p></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ( ! $client_configured ) : ?>
|
||||
<div class="notice notice-error">
|
||||
<p>
|
||||
<?php
|
||||
echo wp_kses_post(
|
||||
sprintf(
|
||||
/* translators: %s: settings URL. */
|
||||
__( 'ROBOTSTXT Manager is not configured yet. <a href="%s">Set the Store URL and API key</a> to see your plugin catalog.', 'robotstxt-manager' ),
|
||||
esc_url( admin_url( 'admin.php?page=' . Robotstxt_Manager_Settings::PAGE_SLUG ) )
|
||||
)
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php elseif ( empty( $catalog ) ) : ?>
|
||||
<div class="notice notice-warning">
|
||||
<p>
|
||||
<?php esc_html_e( 'No plugins were returned by the ROBOTSTXT store. Check the Store URL and API key on the Settings page, or click Refresh to try again.', 'robotstxt-manager' ); ?>
|
||||
</p>
|
||||
</div>
|
||||
<p><a class="button" href="<?php echo esc_url( $refresh_url ); ?>"><?php esc_html_e( 'Refresh catalog', 'robotstxt-manager' ); ?></a></p>
|
||||
<?php else : ?>
|
||||
<p>
|
||||
<a class="button" href="<?php echo esc_url( $refresh_url ); ?>"><?php esc_html_e( 'Refresh catalog', 'robotstxt-manager' ); ?></a>
|
||||
</p>
|
||||
|
||||
<table class="wp-list-table widefat striped robotstxt-manager-catalog">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><?php esc_html_e( 'Plugin', 'robotstxt-manager' ); ?></th>
|
||||
<th scope="col"><?php esc_html_e( 'Type', 'robotstxt-manager' ); ?></th>
|
||||
<th scope="col"><?php esc_html_e( 'Price', 'robotstxt-manager' ); ?></th>
|
||||
<th scope="col"><?php esc_html_e( 'Requires WP', 'robotstxt-manager' ); ?></th>
|
||||
<th scope="col"><?php esc_html_e( 'Requires PHP', 'robotstxt-manager' ); ?></th>
|
||||
<th scope="col"><?php esc_html_e( 'Local state', 'robotstxt-manager' ); ?></th>
|
||||
<th scope="col"><?php esc_html_e( 'Action', 'robotstxt-manager' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ( $catalog as $entry ) : ?>
|
||||
<?php
|
||||
$raw_slug = $entry['slug'] ?? '';
|
||||
$raw_name = $entry['name'] ?? '';
|
||||
$raw_kind = $entry['type'] ?? 'free';
|
||||
$raw_cost = $entry['price_annual'] ?? 0;
|
||||
$raw_ver = $entry['current_version'] ?? '';
|
||||
$raw_url = $entry['page_url'] ?? '';
|
||||
$raw_rwp = $entry['requires_wp'] ?? '';
|
||||
$raw_rphp = $entry['requires_php'] ?? '';
|
||||
|
||||
$slug = is_string( $raw_slug ) ? $raw_slug : '';
|
||||
$name = is_string( $raw_name ) ? $raw_name : $slug;
|
||||
$kind = is_string( $raw_kind ) ? $raw_kind : 'free';
|
||||
$cost = is_numeric( $raw_cost ) ? (float) ( $raw_cost + 0.0 ) : 0.0;
|
||||
$remote_v = is_string( $raw_ver ) ? $raw_ver : '';
|
||||
$page_url = is_string( $raw_url ) ? $raw_url : '';
|
||||
$req_wp = is_string( $raw_rwp ) ? $raw_rwp : '';
|
||||
$req_php = is_string( $raw_rphp ) ? $raw_rphp : '';
|
||||
|
||||
// Compatibility checks.
|
||||
$wp_ok = '' === $req_wp || version_compare( $local_wp_version, $req_wp, '>=' );
|
||||
$php_ok = '' === $req_php || version_compare( $local_php_version, $req_php, '>=' );
|
||||
|
||||
if ( ! $wp_ok || ! $php_ok ) {
|
||||
++$compat_warnings;
|
||||
}
|
||||
|
||||
$raw_state = $local[ $slug ] ?? array(
|
||||
'installed' => false,
|
||||
'active' => false,
|
||||
'version' => '',
|
||||
);
|
||||
$l_installed = (bool) ( $raw_state['installed'] ?? false );
|
||||
$l_active = (bool) ( $raw_state['active'] ?? false );
|
||||
$raw_lv = $raw_state['version'] ?? '';
|
||||
$l_version = is_string( $raw_lv ) ? $raw_lv : '';
|
||||
|
||||
$update_available = $l_installed && '' !== $remote_v && '' !== $l_version
|
||||
&& version_compare( $l_version, $remote_v, '<' );
|
||||
|
||||
$row_class = ( ! $wp_ok || ! $php_ok ) ? ' robotstxt-manager-row--incompatible' : '';
|
||||
?>
|
||||
<tr class="<?php echo esc_attr( ltrim( $row_class ) ); ?>">
|
||||
<td>
|
||||
<strong><?php echo esc_html( $name ); ?></strong>
|
||||
<?php if ( '' !== $remote_v ) : ?>
|
||||
<br /><span class="description"><?php echo esc_html( sprintf( 'v%s', $remote_v ) ); ?></span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo esc_html( 'premium' === $kind ? __( 'Premium', 'robotstxt-manager' ) : __( 'Free', 'robotstxt-manager' ) ); ?></td>
|
||||
<td>
|
||||
<?php
|
||||
if ( $cost > 0.0 ) {
|
||||
echo esc_html( sprintf( '€%s / year', number_format_i18n( $cost, 0 ) ) );
|
||||
} else {
|
||||
echo '—';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ( '' !== $req_wp ) : ?>
|
||||
<span class="<?php echo $wp_ok ? 'robotstxt-manager-compat--ok' : 'robotstxt-manager-compat--fail'; ?>">
|
||||
<?php echo esc_html( $req_wp ); ?>
|
||||
<?php if ( ! $wp_ok ) : ?>
|
||||
<span class="robotstxt-manager-compat-warning" title="<?php esc_attr_e( 'Your site runs WordPress', 'robotstxt-manager' ); ?> <?php echo esc_attr( $local_wp_version ); ?>"> ⚠</span>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php else : ?>
|
||||
—
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ( '' !== $req_php ) : ?>
|
||||
<span class="<?php echo $php_ok ? 'robotstxt-manager-compat--ok' : 'robotstxt-manager-compat--fail'; ?>">
|
||||
<?php echo esc_html( $req_php ); ?>
|
||||
<?php if ( ! $php_ok ) : ?>
|
||||
<span class="robotstxt-manager-compat-warning" title="<?php esc_attr_e( 'Your server runs PHP', 'robotstxt-manager' ); ?> <?php echo esc_attr( $local_php_version ); ?>"> ⚠</span>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php else : ?>
|
||||
—
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
if ( ! $l_installed ) {
|
||||
echo '<span class="robotstxt-manager-state robotstxt-manager-state--not-installed">' . esc_html__( 'Not installed', 'robotstxt-manager' ) . '</span>';
|
||||
} elseif ( ! $l_active ) {
|
||||
echo '<span class="robotstxt-manager-state robotstxt-manager-state--inactive">' . esc_html__( 'Installed (inactive)', 'robotstxt-manager' ) . '</span>';
|
||||
} elseif ( $update_available ) {
|
||||
echo '<span class="robotstxt-manager-state robotstxt-manager-state--update">' . esc_html(
|
||||
sprintf(
|
||||
/* translators: 1: installed version, 2: available version. */
|
||||
__( 'Update available (v%1$s → v%2$s)', 'robotstxt-manager' ),
|
||||
$l_version,
|
||||
$remote_v
|
||||
)
|
||||
) . '</span>';
|
||||
} else {
|
||||
echo '<span class="robotstxt-manager-state robotstxt-manager-state--ok">' . esc_html__( 'Up to date', 'robotstxt-manager' ) . '</span>';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ( ! $l_installed ) : ?>
|
||||
<?php if ( ! $wp_ok || ! $php_ok ) : ?>
|
||||
<span class="description"><?php esc_html_e( 'Incompatible', 'robotstxt-manager' ); ?></span>
|
||||
<?php elseif ( $cost > 0.0 && '' !== $page_url ) : ?>
|
||||
<a class="button button-primary" href="<?php echo esc_url( $page_url ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Buy', 'robotstxt-manager' ); ?></a>
|
||||
<?php elseif ( $cost <= 0.0 ) : ?>
|
||||
<button type="button" class="button button-secondary robotstxt-manager-action-install" data-slug="<?php echo esc_attr( $slug ); ?>" disabled><?php esc_html_e( 'Install (soon)', 'robotstxt-manager' ); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php elseif ( $update_available ) : ?>
|
||||
<button type="button" class="button button-secondary robotstxt-manager-action-update" data-slug="<?php echo esc_attr( $slug ); ?>" disabled><?php esc_html_e( 'Update (soon)', 'robotstxt-manager' ); ?></button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if ( $compat_warnings > 0 ) : ?>
|
||||
<div class="notice notice-warning inline" style="margin-top:1em">
|
||||
<p>
|
||||
<?php
|
||||
echo esc_html(
|
||||
sprintf(
|
||||
/* translators: %d: number of incompatible plugins. */
|
||||
_n( '%d plugin in the catalog is not compatible with this site\'s WordPress or PHP version.', '%d plugins in the catalog are not compatible with this site\'s WordPress or PHP version.', $compat_warnings, 'robotstxt-manager' ),
|
||||
$compat_warnings
|
||||
)
|
||||
);
|
||||
?>
|
||||
<?php
|
||||
echo esc_html(
|
||||
sprintf(
|
||||
/* translators: 1: local WP version, 2: local PHP version. */
|
||||
__( 'This site runs WordPress %1$s on PHP %2$s.', 'robotstxt-manager' ),
|
||||
$local_wp_version,
|
||||
$local_php_version
|
||||
)
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.robotstxt-manager-compat--ok { color: #00a32a; }
|
||||
.robotstxt-manager-compat--fail { color: #d63638; font-weight: 600; }
|
||||
.robotstxt-manager-compat-warning { cursor: help; }
|
||||
.robotstxt-manager-row--incompatible { background-color: #fef7f0 !important; }
|
||||
</style>
|
||||
21
admin/views/page-settings.php
Normal file
21
admin/views/page-settings.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
/**
|
||||
* Admin page view: ROBOTSTXT Manager settings.
|
||||
*
|
||||
* @package Robotstxt_Manager
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'Manager (by ROBOTSTXT) — Settings', 'robotstxt-manager' ); ?></h1>
|
||||
<form method="post" action="<?php echo esc_url( admin_url( 'options.php' ) ); ?>">
|
||||
<?php
|
||||
settings_fields( Robotstxt_Manager_Settings::OPTION_GROUP );
|
||||
do_settings_sections( Robotstxt_Manager_Settings::PAGE_SLUG );
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
</div>
|
||||
Loading…
Reference in a new issue