registry = $registry; } /** * Handle the AJAX refresh request. * * Security: nonce-gated via wp-nonce, capability gated to manage_options. * Response format: JSON { success: bool, data: { count: int, fetched_at: int, message: string } } * * @side-effect Writes HTTP GET request, database options, transient cache. * @return void (echoes JSON) */ public function handle(): void { // Nonce check. if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'prautoblogger_refresh_models' ) ) { wp_send_json_error( [ 'message' => __( 'Security check failed. Please refresh the page and try again.', 'prautoblogger' ), ], 403 ); } // Capability check. if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Insufficient permissions. Only administrators can refresh the model list.', 'prautoblogger' ), ], 403 ); } // Perform the refresh (force=true bypasses the idempotency window). $result = $this->registry->refresh( true ); $count = (int) ( $result['count'] ?? 0 ); $fetched_at = (int) ( $result['fetched_at'] ?? 0 ); $mins_ago = $fetched_at > 0 ? intval( ( time() - $fetched_at ) / 60 ) : 0; wp_send_json_success( [ 'count' => $count, 'fetched_at' => $fetched_at, 'message' => sprintf( __( 'Model registry refreshed: %d models loaded, last updated %s', 'prautoblogger' ), $count, 0 === $mins_ago ? __( 'just now', 'prautoblogger' ) : sprintf( __( '%d minutes ago', 'prautoblogger' ), $mins_ago ) ), ] ); } }