page_hook = add_management_page( __('Reprint Server', 'reprint'), __('Reprint Server', 'reprint'), 'manage_options', 'reprint-server', [$this, 'render_admin_page'] ); } /** Network credentials are configured only by network administrators. */ public function add_network_admin_menu(): void { $this->page_hook = add_submenu_page( 'settings.php', __('Reprint Server', 'reprint'), __('Reprint Server', 'reprint'), 'manage_network_options', 'reprint-server', [$this, 'render_network_admin_page'] ); } /** Render a network-option form without the site Settings API. */ public function render_network_admin_page(): void { if (!is_multisite() || !current_user_can('manage_network_options')) { return; } $this->render_page(); } /** Validate network capability and nonce before updating the network token. */ public function handle_network_token_save(): void { if (!is_multisite() || !current_user_can('manage_network_options')) { wp_die(esc_html__('You are not allowed to manage this network.', 'reprint')); } check_admin_referer('reprint_server_save_network_token'); $connection_token = isset($_POST[CONNECTION_TOKEN_OPTION]) && is_string($_POST[CONNECTION_TOKEN_OPTION]) ? sanitize_text_field(wp_unslash($_POST[CONNECTION_TOKEN_OPTION])) : ''; $result = change_connection_token($connection_token); if ($result === 'storage_failure') { wp_die(esc_html__('The network connection token could not be saved.', 'reprint')); } $this->redirect_to_page(['reprint_server_notice' => 'network_token_' . $result]); } /** Add the Settings link to the plugin row. */ public function add_settings_link(array $links): array { $url = is_multisite() ? network_admin_url('settings.php?page=reprint-server') : admin_url('tools.php?page=reprint-server'); array_unshift( $links, '' . esc_html__('Settings', 'reprint') . '' ); return $links; } /** Enqueue browser-only behavior on the bundled page. */ public function enqueue_assets(string $hook_suffix): void { if ($this->page_hook === false || $hook_suffix !== $this->page_hook) { return; } wp_enqueue_script( 'reprint-server-admin', plugins_url('wordpress/reprint-server.js', PLUGIN_FILE), ['wp-a11y'], VERSION, true ); wp_enqueue_style( 'reprint-server-admin', plugins_url('wordpress/reprint-server.css', PLUGIN_FILE), [], VERSION ); } /** Apply one push-access change and redirect back to the bundled page. */ public function handle_push_access_save(): void { if (!current_user_can('manage_options')) { wp_die(esc_html__('You are not allowed to manage Reprint Server.', 'reprint')); } check_admin_referer('reprint_server_save_push_access'); $enabled = isset($_POST['reprint_server_push_enabled']); $result = change_push_access($enabled); $redirect_url = add_query_arg( 'reprint_server_notice', $result, admin_url('tools.php?page=reprint-server') ); wp_safe_redirect($redirect_url); exit; } /** Validate capability and nonce, then enroll one pasted public key. */ public function handle_public_key_enroll(): void { $this->require_manage_capability(); check_admin_referer('reprint_server_enroll_public_key'); // assert_valid_public_key() is the real check. The sanitizer only cleans the pasted text. $pasted_key = isset($_POST['reprint_server_public_key']) && is_string($_POST['reprint_server_public_key']) ? sanitize_textarea_field(wp_unslash($_POST['reprint_server_public_key'])) : ''; $result = enroll_public_key($pasted_key); $query = ['reprint_server_notice' => $result === 'saved' ? 'enrolled' : 'enroll_' . $result]; if ($result === 'saved') { $query['reprint_server_key_id'] = (string) get_last_enrolled_key_id(); } $this->redirect_to_page($query); } /** Validate capability and nonce, then remove one enrolled key. */ public function handle_public_key_remove(): void { $this->require_manage_capability(); check_admin_referer('reprint_server_remove_public_key'); $key_id = isset($_POST['reprint_server_key_id']) && is_string($_POST['reprint_server_key_id']) ? sanitize_key(wp_unslash($_POST['reprint_server_key_id'])) : ''; $result = remove_public_key($key_id); $this->redirect_to_page(['reprint_server_notice' => $result === 'saved' ? 'key_removed' : 'remove_' . $result]); } /** Validate capability and nonce, then grant or revoke push for one key. */ public function handle_key_push_access_save(): void { $this->require_manage_capability(); check_admin_referer('reprint_server_save_key_push_access'); $key_id = isset($_POST['reprint_server_key_id']) && is_string($_POST['reprint_server_key_id']) ? sanitize_key(wp_unslash($_POST['reprint_server_key_id'])) : ''; $enabled = isset($_POST['reprint_server_key_push_enabled']); $result = change_key_push_access($key_id, $enabled); $this->redirect_to_page(['reprint_server_notice' => $result === 'saved' ? 'key_push_saved' : 'key_push_' . $result]); } /** * Validate capability and nonce, then clear the option-backed connection token. * * Offered on a key host, where a stored token is never accepted; the plugin * does not delete a credential the administrator set without being asked. */ public function handle_connection_token_remove(): void { $this->require_manage_capability(); check_admin_referer('reprint_server_remove_connection_token'); $result = change_connection_token(''); $this->redirect_to_page([ 'reprint_server_notice' => $result === 'storage_failure' ? 'token_remove_storage_failure' : 'token_removed', ]); } /** Stop with wp_die() unless the current user may manage this site's, or on multisite the network's, options. */ private function require_manage_capability(): void { $capability = is_multisite() ? 'manage_network_options' : 'manage_options'; if (!current_user_can($capability)) { wp_die(esc_html__('You are not allowed to manage Reprint Server.', 'reprint')); } } /** @param array $query */ private function redirect_to_page(array $query): void { $base = is_multisite() ? network_admin_url('settings.php?page=reprint-server') : admin_url('tools.php?page=reprint-server'); wp_safe_redirect(add_query_arg($query, $base)); exit; } /** Render the bundled Tools page. */ public function render_admin_page(): void { if (is_multisite()) { $this->render_network_admin_page(); return; } if (!current_user_can('manage_options')) { return; } $this->render_page(); } /** Site and network pages share authorization, but each copied site needs its own API URL. */ private function render_page(): void { $configuration = get_configuration_state(); $remote_reprint_api_url = home_url('?reprint-api'); $uses_http = wp_parse_url($remote_reprint_api_url, PHP_URL_SCHEME) === 'http'; // Nothing on the page changes which scheme this host accepts. $key_host = $configuration['required_scheme'] === 'key'; $configured = $configuration['is_configured']; $notice = ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The query selects a read-only action result, not a permission change. if (!isset($_GET['settings-updated']) && isset($_GET['reprint_server_notice']) && is_string($_GET['reprint_server_notice'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only fixed action-result names affect the display. $notice = sanitize_key(wp_unslash($_GET['reprint_server_notice'])); } $authorized_key_id = null; // Saved access does not confirm that a tool has connected. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The key id selects a read-only confirmation for a saved key. if ($key_host && $notice === 'enrolled' && isset($_GET['reprint_server_key_id']) && is_string($_GET['reprint_server_key_id'])) { foreach ($configuration['enrolled_keys'] as $entry) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The key id must match the current key list before the form is replaced. if ($entry['key_id'] === sanitize_key(wp_unslash($_GET['reprint_server_key_id']))) { $authorized_key_id = $entry['key_id']; break; } } } $page_url = is_multisite() ? network_admin_url('settings.php?page=reprint-server') : admin_url('tools.php?page=reprint-server'); ?>

render_settings_notices(); ?> render_push_access_notice(); ?>

public-keys.php

secret.php

render_connection_token_form(); ?>

https://your-site.example/?reprint-api

> render_public_keys_section($configuration); ?> render_stored_connection_token_section($configuration); ?>

render_connection_token_form(); ?>

render_push_access_form($configuration); ?>

render_public_keys_section($configuration); ?>

render_connection_token_field(); ?>

public-keys.php ' . esc_html__('override is active.', 'reprint') . ' ' . esc_html__('Edit that file to authorize a tool or remove a Migration Token. This page cannot change its Migration Tokens.', 'reprint') . '

'; } ?>

0 ? gmdate('Y-m-d', $entry['added_at']) : '—'); ?>
sprintf( /* translators: %s: Enrolled Migration Token ID. */ __('Save push access for Migration Token %s', 'reprint'), $entry['key_id'] ), ]); ?>
sprintf( /* translators: %s: Enrolled Migration Token ID. */ __('Remove Migration Token %s', 'reprint'), $entry['key_id'] ), ]); ?>
render_notice('warning', esc_html($unsupported_message)); return; } if ($configuration['managed_push_enabled'] !== null) { ?>

['error', __('That is not a usable Migration Token. Copy the Migration Token from your tool and paste it here.', 'reprint')], 'enroll_no_openssl' => ['error', __('This host cannot read Migration Tokens because the OpenSSL extension is missing. Clients authenticate with the connection token here.', 'reprint')], 'enroll_duplicate' => ['info', __('That Migration Token is already enrolled.', 'reprint')], 'enroll_file_override' => ['error', __('public-keys.php is active. Edit that file to change enrolled Migration Tokens.', 'reprint')], 'enroll_storage_failure' => ['error', __('Failed to save the Migration Token.', 'reprint')], 'enroll_runtime_missing' => ['error', __('The Reprint Server runtime is missing. Run composer install in the plugin directory or reinstall the release package.', 'reprint')], 'key_removed' => ['success', __('Migration Token removed.', 'reprint')], 'remove_unknown' => ['error', __('That Migration Token is not enrolled.', 'reprint')], 'remove_file_override' => ['error', __('public-keys.php is active. Edit that file to change enrolled Migration Tokens.', 'reprint')], 'remove_storage_failure' => ['error', __('Failed to remove the Migration Token.', 'reprint')], 'remove_runtime_missing' => ['error', __('The Reprint Server runtime is missing. Run composer install in the plugin directory or reinstall the release package.', 'reprint')], 'network_token_saved' => ['success', __('Connection token saved.', 'reprint')], 'network_token_unchanged' => ['success', __('Connection token was already up to date.', 'reprint')], 'token_removed' => ['success', __('Connection token removed.', 'reprint')], 'token_remove_storage_failure' => ['error', __('Failed to remove the connection token.', 'reprint')], ]; // A saved push-result URL can be reloaded after changing distributions. if (!defined('REPRINT_PUSH_AVAILABLE') || REPRINT_PUSH_AVAILABLE) { $notices += [ 'saved' => ['success', __('Push access updated.', 'reprint')], 'unchanged' => ['success', __('Push access was already up to date.', 'reprint')], 'unsupported' => ['error', __('Push access requires PHP 7.2 or newer. Downloads remain available.', 'reprint')], 'managed' => ['info', __('Push access is managed by your hosting provider.', 'reprint')], 'not_configured' => ['error', __('Configure a connection token before enabling push access.', 'reprint')], 'storage_failure' => ['error', __('Failed to save push access.', 'reprint')], 'key_push_saved' => ['success', __('Push access for the Migration Token updated.', 'reprint')], 'key_push_unchanged' => ['success', __('Push access for the Migration Token was already up to date.', 'reprint')], 'key_push_unknown' => ['error', __('That Migration Token is not enrolled.', 'reprint')], 'key_push_multisite' => ['info', __('Push is not supported on multisite networks.', 'reprint')], 'key_push_unsupported' => ['error', __('Push access requires PHP 7.2 or newer.', 'reprint')], 'key_push_managed' => ['info', __('Push access is managed by your hosting provider.', 'reprint')], 'key_push_file_override' => ['error', __('public-keys.php is active. Push grants cannot be stored for file-provided Migration Tokens.', 'reprint')], 'key_push_storage_failure' => ['error', __('Failed to save push access for the Migration Token.', 'reprint')], 'key_push_runtime_missing' => ['error', __('The Reprint Server runtime is missing. Run composer install in the plugin directory or reinstall the release package.', 'reprint')], ]; } if (!isset($notices[$result])) { return; } $message = esc_html($notices[$result][1]); $this->render_notice( $notices[$result][0], $message, true ); } /** Render Settings API results with stable native notice markup. */ private function render_settings_notices(): void { foreach (get_settings_errors() as $notice) { $type = $notice['type'] === 'updated' ? 'success' : $notice['type']; $this->render_notice( $type, $notice['message'], true, 'setting-error-' . $notice['code'], true ); } } /** Render one native inline administrator notice. */ private function render_notice( string $type, string $message, bool $dismissible = false, string $id = '', bool $settings_error = false ): void { $allowed_types = ['error', 'success', 'warning', 'info']; if (!in_array($type, $allowed_types, true)) { $type = 'error'; } $classes = 'notice notice-' . $type; if ($settings_error) { $classes .= ' settings-error'; } if ($dismissible) { $classes .= ' is-dismissible'; } $classes .= ' inline'; ?> id="" class="">