Newer
Older
tonitalia-pluginsoci / admin / class-admin.php
<?php
/**
 * Admin UI.
 *
 * @package TonItaliaRegistration
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class TON_Reg_Admin
 */
class TON_Reg_Admin {

	/**
	 * Register hooks.
	 */
	public static function register() {
		add_action( 'admin_menu', array( __CLASS__, 'menu' ) );
		add_action( 'admin_init', array( __CLASS__, 'register_settings' ) );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
		add_action( 'admin_post_ton_reg_save_registration', array( __CLASS__, 'save_registration' ) );
		add_action( 'admin_post_ton_reg_gdpr_export', array( __CLASS__, 'gdpr_export' ) );
		add_action( 'admin_post_ton_reg_gdpr_anonymize', array( __CLASS__, 'gdpr_anonymize' ) );
		add_action( 'admin_post_ton_reg_gdpr_erase', array( __CLASS__, 'gdpr_erase' ) );
		add_action( 'admin_post_ton_reg_upload_document', array( __CLASS__, 'upload_document' ) );
		add_action( 'admin_post_ton_reg_delete_document', array( __CLASS__, 'delete_document' ) );
		add_action( 'admin_post_ton_reg_mailchimp_test', array( __CLASS__, 'mailchimp_test' ) );
		add_action( 'admin_post_ton_reg_mailchimp_retry', array( __CLASS__, 'mailchimp_retry' ) );
		add_action( 'admin_post_ton_reg_save_card_settings', array( __CLASS__, 'save_card_settings' ) );
		add_action( 'admin_post_ton_reg_upload_card_template', array( __CLASS__, 'upload_card_template' ) );
		add_action( 'admin_post_ton_reg_batch_cards', array( __CLASS__, 'batch_cards' ) );
		add_action( 'admin_post_ton_reg_generate_card', array( __CLASS__, 'generate_card' ) );
		add_action( 'admin_post_ton_reg_export_libro_soci', array( __CLASS__, 'export_libro_soci' ) );
	}

	/**
	 * @param string $hook_suffix Current admin page hook.
	 */
	public static function enqueue_assets( $hook_suffix ) {
		$allowed = array(
			'toplevel_page_ton-registrations',
			'ton-registrations_page_ton-reg-settings',
		);
		if ( ! in_array( $hook_suffix, $allowed, true ) ) {
			return;
		}

		wp_enqueue_style(
			'ton-reg-admin',
			TON_REG_PLUGIN_URL . 'assets/css/admin.css',
			array(),
			TON_REG_VERSION
		);
	}

	/**
	 * Admin menu.
	 */
	public static function menu() {
		add_menu_page(
			__( 'TON Iscrizioni', 'ton-italia-registration' ),
			__( 'TON Iscrizioni', 'ton-italia-registration' ),
			'manage_options',
			'ton-registrations',
			array( __CLASS__, 'page_list' ),
			'dashicons-groups',
			58
		);

		add_submenu_page(
			'ton-registrations',
			__( 'Elenco iscrizioni', 'ton-italia-registration' ),
			__( 'Elenco', 'ton-italia-registration' ),
			'manage_options',
			'ton-registrations',
			array( __CLASS__, 'page_list' )
		);

		add_submenu_page(
			'ton-registrations',
			__( 'Impostazioni', 'ton-italia-registration' ),
			__( 'Impostazioni', 'ton-italia-registration' ),
			'manage_options',
			'ton-reg-settings',
			array( __CLASS__, 'page_settings' )
		);
	}

	/**
	 * Register settings.
	 */
	public static function register_settings() {
		$checkboxes = array(
			'ton_reg_captcha_enabled',
			'ton_reg_newsletter_required',
			'ton_reg_delete_users_on_uninstall',
			'ton_reg_mailchimp_enabled',
		);

		$plain_text = array(
			'ton_reg_social_year',
			'ton_reg_renewal_reminders_start_year',
			'ton_reg_membership_fee',
			'ton_reg_beneficiary',
			'ton_reg_iban',
			'ton_reg_admin_email',
			'ton_reg_gdpr_contact',
			'ton_reg_email_admin_subject',
			'ton_reg_email_user_subject',
			'ton_reg_email_renewal_first_subject',
			'ton_reg_email_renewal_second_subject',
			'ton_reg_email_renewal_admin_subject',
			'ton_reg_rate_limit_max',
			'ton_reg_rate_limit_window',
			'ton_reg_privacy_version',
			'ton_reg_mailchimp_tags',
		);

		$skip_auto = array(
			'ton_reg_mailchimp_api_key',
			'ton_reg_mailchimp_list_id',
			'ton_reg_mailchimp_permission_ids',
			'ton_reg_success_page_id',
		);

		register_setting(
			'ton_reg_settings',
			'ton_reg_mailchimp_api_key',
			array(
				'type'              => 'string',
				'sanitize_callback' => array( 'TON_Reg_Mailchimp', 'sanitize_api_key' ),
				'show_in_rest'      => false,
				'default'           => '',
			)
		);

		register_setting(
			'ton_reg_settings',
			'ton_reg_mailchimp_list_id',
			array(
				'type'              => 'string',
				'sanitize_callback' => array( 'TON_Reg_Mailchimp', 'sanitize_list_id' ),
				'show_in_rest'      => false,
				'default'           => '',
			)
		);

		register_setting(
			'ton_reg_settings',
			'ton_reg_success_page_id',
			array(
				'type'              => 'integer',
				'sanitize_callback' => array( __CLASS__, 'sanitize_success_page_id' ),
				'show_in_rest'      => false,
				'default'           => 0,
			)
		);

		$options = array_keys( TON_Reg_Defaults::options() );
		foreach ( $options as $option ) {
			if ( in_array( $option, $skip_auto, true ) ) {
				continue;
			}
			if ( in_array( $option, $checkboxes, true ) ) {
				$sanitize = array( __CLASS__, 'sanitize_checkbox' );
			} elseif ( in_array( $option, $plain_text, true ) ) {
				$sanitize = 'sanitize_text_field';
			} else {
				$sanitize = array( __CLASS__, 'sanitize_setting' );
			}

			register_setting(
				'ton_reg_settings',
				$option,
				array(
					'type'              => 'string',
					'sanitize_callback' => $sanitize,
				)
			);
		}
	}

	/**
	 * @param mixed $value Value.
	 * @return string
	 */
	public static function sanitize_setting( $value ) {
		if ( is_array( $value ) ) {
			return '';
		}
		return wp_kses_post( wp_unslash( (string) $value ) );
	}

	/**
	 * @param string $value Checkbox value.
	 * @return string
	 */
	public static function sanitize_checkbox( $value ) {
		return '1' === (string) $value ? '1' : '0';
	}

	/**
	 * @param mixed $value Page ID.
	 * @return int
	 */
	public static function sanitize_success_page_id( $value ) {
		$page_id = absint( $value );
		if ( ! $page_id ) {
			return 0;
		}

		if ( ! TON_Reg_Registration_Handler::is_publicly_viewable_page( $page_id ) ) {
			return 0;
		}

		return $page_id;
	}

	/**
	 * List page.
	 */
	public static function page_list() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		if ( isset( $_GET['action'], $_GET['id'] ) && 'view' === $_GET['action'] ) {
			self::page_detail( (int) $_GET['id'] );
			return;
		}

		$table = new TON_Reg_Registration_List_Table();
		$table->prepare_items();

		$status = isset( $_GET['status'] ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : '';
		$renewal_unpaid = ! empty( $_GET['renewal_unpaid'] );
		?>
		<div class="wrap ton-reg-admin-list">
			<h1><?php esc_html_e( 'Iscrizioni socio', 'ton-italia-registration' ); ?></h1>
			<?php if ( isset( $_GET['updated'] ) ) : ?>
				<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Salvato.', 'ton-italia-registration' ); ?></p></div>
			<?php endif; ?>
			<ul class="subsubsub ton-reg-admin-list__filters">
				<li><a href="<?php echo esc_url( admin_url( 'admin.php?page=ton-registrations' ) ); ?>"><?php esc_html_e( 'Tutti', 'ton-italia-registration' ); ?></a> |</li>
				<li><a href="<?php echo esc_url( admin_url( 'admin.php?page=ton-registrations&status=pending' ) ); ?>"><?php esc_html_e( 'In attesa', 'ton-italia-registration' ); ?></a> |</li>
				<li><a href="<?php echo esc_url( admin_url( 'admin.php?page=ton-registrations&status=admitted' ) ); ?>"><?php esc_html_e( 'Ammessi', 'ton-italia-registration' ); ?></a> |</li>
				<li><a href="<?php echo esc_url( admin_url( 'admin.php?page=ton-registrations&status=rejected' ) ); ?>"><?php esc_html_e( 'Non ammessi', 'ton-italia-registration' ); ?></a> |</li>
				<li><a href="<?php echo esc_url( admin_url( 'admin.php?page=ton-registrations&status=admitted&renewal_unpaid=1' ) ); ?>"><?php esc_html_e( 'Quota da pagare', 'ton-italia-registration' ); ?></a></li>
			</ul>
			<p class="ton-reg-admin-list__export">
				<a class="button button-secondary" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=ton_reg_export_libro_soci' ), 'ton_reg_export_libro_soci' ) ); ?>">
					<?php esc_html_e( 'Esporta CSV Libro Soci', 'ton-italia-registration' ); ?>
				</a>
				<span class="description"><?php esc_html_e( 'Soci ammessi, esclusi anonimizzati.', 'ton-italia-registration' ); ?></span>
			</p>
			<form method="get" class="ton-reg-list-search">
				<input type="hidden" name="page" value="ton-registrations" />
				<?php if ( $status ) : ?>
					<input type="hidden" name="status" value="<?php echo esc_attr( $status ); ?>" />
				<?php endif; ?>
				<?php if ( $renewal_unpaid ) : ?>
					<input type="hidden" name="renewal_unpaid" value="1" />
				<?php endif; ?>
				<p class="search-box ton-reg-admin-list__search">
					<label class="screen-reader-text" for="ton-reg-search"><?php esc_html_e( 'Cerca', 'ton-italia-registration' ); ?></label>
					<input type="search" id="ton-reg-search" name="s" value="<?php echo isset( $_GET['s'] ) ? esc_attr( sanitize_text_field( wp_unslash( $_GET['s'] ) ) ) : ''; ?>" placeholder="<?php esc_attr_e( 'Cognome, nome, email…', 'ton-italia-registration' ); ?>" />
					<input type="submit" class="button" value="<?php esc_attr_e( 'Cerca', 'ton-italia-registration' ); ?>" />
				</p>
			</form>
			<div class="ton-reg-list-table">
			<?php $table->display(); ?>
			</div>
		</div>
		<?php
	}

	/**
	 * @param int $id Registration ID.
	 */
	public static function page_detail( $id ) {
		$row = TON_Reg_Database::get( $id );
		if ( ! $row ) {
			echo '<div class="wrap"><p>' . esc_html__( 'Non trovato.', 'ton-italia-registration' ) . '</p></div>';
			return;
		}

		$list_context = self::get_detail_list_context();
		$adjacent     = TON_Reg_Database::get_adjacent_ids( $id, $list_context );
		?>
		<div class="wrap ton-reg-admin-detail">
			<h1><?php printf( esc_html__( 'Iscrizione #%d', 'ton-italia-registration' ), (int) $id ); ?></h1>
			<?php self::render_detail_navigation( $id, $row, $adjacent, $list_context ); ?>

			<?php self::render_admin_notices(); ?>

			<h2><?php esc_html_e( 'Documenti', 'ton-italia-registration' ); ?></h2>
			<p class="description">
				<?php
				printf(
					/* translators: %s: folder path under uploads */
					esc_html__( 'I file vengono salvati in Media, cartella «%s», sottocartella «%s».', 'ton-italia-registration' ),
					esc_html( TON_Reg_Documents::BASE_FOLDER ),
					esc_html( TON_Reg_Documents::member_folder_slug( $row ) )
				);
				?>
			</p>
			<?php foreach ( TON_Reg_Documents::get_types() as $doc_type ) : ?>
				<?php self::render_document_block( $id, $row, $doc_type ); ?>
			<?php endforeach; ?>

			<?php self::render_membership_cards_section( $id, $row ); ?>

			<?php self::render_consents_section( $row ); ?>

			<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
				<input type="hidden" name="action" value="ton_reg_save_registration" />
				<input type="hidden" name="registration_id" value="<?php echo (int) $id; ?>" />
				<?php wp_nonce_field( 'ton_reg_save_' . $id, 'ton_reg_admin_nonce' ); ?>
				<?php self::render_detail_list_context_fields( $list_context ); ?>

				<?php self::render_renewals_section( $id, $row ); ?>

				<table class="form-table">
					<?php
					$fields = array(
						'cognome', 'nome', 'luogo_nascita', 'provincia_nascita', 'data_nascita', 'codice_fiscale',
						'comune_residenza', 'provincia_residenza', 'indirizzo', 'numero_civico', 'cap', 'telefono', 'email',
						'registration_ip', 'user_agent', 'created_at',
					);
					foreach ( $fields as $field ) :
						?>
					<tr>
						<th><?php echo esc_html( $field ); ?></th>
						<td><?php echo esc_html( $row->$field ?? '' ); ?></td>
					</tr>
					<?php endforeach; ?>
					<tr>
						<th><?php esc_html_e( 'Stato', 'ton-italia-registration' ); ?></th>
						<td>
							<select name="status">
								<option value="pending" <?php selected( $row->status, 'pending' ); ?>><?php esc_html_e( 'In attesa', 'ton-italia-registration' ); ?></option>
								<option value="admitted" <?php selected( $row->status, 'admitted' ); ?>><?php esc_html_e( 'Ammesso', 'ton-italia-registration' ); ?></option>
								<option value="rejected" <?php selected( $row->status, 'rejected' ); ?>><?php esc_html_e( 'Non ammesso', 'ton-italia-registration' ); ?></option>
							</select>
						</td>
					</tr>
					<tr>
						<th><?php esc_html_e( 'Data Libro Associati', 'ton-italia-registration' ); ?></th>
						<td><input type="date" name="libro_associati_date" value="<?php echo esc_attr( $row->libro_associati_date ?? '' ); ?>" /></td>
					</tr>
					<tr>
						<th><?php esc_html_e( 'Note admin', 'ton-italia-registration' ); ?></th>
						<td><textarea name="admin_notes" rows="4" class="large-text"><?php echo esc_textarea( $row->admin_notes ?? '' ); ?></textarea></td>
					</tr>
				</table>
				<?php submit_button( __( 'Salva', 'ton-italia-registration' ) ); ?>
			</form>

			<h2><?php esc_html_e( 'GDPR', 'ton-italia-registration' ); ?></h2>
			<p>
				<a class="button" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=ton_reg_gdpr_export&id=' . $id . '&format=json' ), 'ton_reg_gdpr_' . $id ) ); ?>"><?php esc_html_e( 'Esporta JSON', 'ton-italia-registration' ); ?></a>
				<a class="button" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=ton_reg_gdpr_export&id=' . $id . '&format=csv' ), 'ton_reg_gdpr_' . $id ) ); ?>"><?php esc_html_e( 'Esporta CSV', 'ton-italia-registration' ); ?></a>
				<?php if ( ! $row->anonymized_at ) : ?>
				<a class="button" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=ton_reg_gdpr_anonymize&id=' . $id ), 'ton_reg_gdpr_' . $id ) ); ?>" onclick="return confirm('<?php esc_attr_e( 'Anonimizzare i dati?', 'ton-italia-registration' ); ?>');"><?php esc_html_e( 'Anonimizza', 'ton-italia-registration' ); ?></a>
				<?php endif; ?>
				<a class="button button-link-delete" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=ton_reg_gdpr_erase&id=' . $id ), 'ton_reg_gdpr_' . $id ) ); ?>" onclick="return confirm('<?php esc_attr_e( 'Eliminare definitivamente iscrizione e utente WP?', 'ton-italia-registration' ); ?>');"><?php esc_html_e( 'Elimina definitivamente', 'ton-italia-registration' ); ?></a>
			</p>
		</div>
		<?php
	}

	/**
	 * @return array<string,mixed>
	 */
	public static function get_detail_list_context() {
		if ( ! empty( $_POST['ton_reg_list_context'] ) && is_array( $_POST['ton_reg_list_context'] ) ) {
			$source = wp_unslash( $_POST['ton_reg_list_context'] );
		} else {
			$source = wp_unslash( $_GET );
		}

		$orderby = sanitize_text_field( wp_unslash( $source['list_orderby'] ?? $source['orderby'] ?? 'created_at' ) );
		$order   = sanitize_text_field( wp_unslash( $source['list_order'] ?? $source['order'] ?? 'DESC' ) );
		$allowed_orderby = array( 'id', 'created_at', 'cognome', 'nome', 'email', 'status' );

		return array(
			'status'         => sanitize_text_field( wp_unslash( $source['list_status'] ?? $source['status'] ?? '' ) ),
			'search'         => sanitize_text_field( wp_unslash( $source['list_search'] ?? $source['s'] ?? '' ) ),
			'renewal_unpaid' => ! empty( $source['list_renewal_unpaid'] ?? $source['renewal_unpaid'] ?? '' ),
			'renewal_year'   => TON_Reg_Membership_Renewals::current_year(),
			'orderby'        => in_array( $orderby, $allowed_orderby, true ) ? $orderby : 'created_at',
			'order'          => 'ASC' === strtoupper( $order ) ? 'ASC' : 'DESC',
		);
	}

	/**
	 * @param int                  $id      Registration ID.
	 * @param array<string,mixed> $context List context.
	 * @return string
	 */
	public static function detail_url( $id, $context = array() ) {
		$args = array(
			'page'   => 'ton-registrations',
			'action' => 'view',
			'id'     => (int) $id,
		);

		if ( ! empty( $context['status'] ) ) {
			$args['list_status'] = $context['status'];
		}
		if ( ! empty( $context['search'] ) ) {
			$args['list_search'] = $context['search'];
		}
		if ( ! empty( $context['renewal_unpaid'] ) ) {
			$args['list_renewal_unpaid'] = '1';
		}
		if ( ! empty( $context['orderby'] ) && 'created_at' !== $context['orderby'] ) {
			$args['list_orderby'] = $context['orderby'];
		}
		if ( ! empty( $context['order'] ) && 'DESC' !== strtoupper( (string) $context['order'] ) ) {
			$args['list_order'] = $context['order'];
		}

		return add_query_arg( $args, admin_url( 'admin.php' ) );
	}

	/**
	 * @param array<string,mixed> $context List context.
	 * @return string
	 */
	public static function list_url( $context = array() ) {
		$args = array(
			'page' => 'ton-registrations',
		);

		if ( ! empty( $context['status'] ) ) {
			$args['status'] = $context['status'];
		}
		if ( ! empty( $context['search'] ) ) {
			$args['s'] = $context['search'];
		}
		if ( ! empty( $context['renewal_unpaid'] ) ) {
			$args['renewal_unpaid'] = '1';
		}
		if ( ! empty( $context['orderby'] ) && 'created_at' !== $context['orderby'] ) {
			$args['orderby'] = $context['orderby'];
		}
		if ( ! empty( $context['order'] ) && 'DESC' !== strtoupper( (string) $context['order'] ) ) {
			$args['order'] = $context['order'];
		}

		return add_query_arg( $args, admin_url( 'admin.php' ) );
	}

	/**
	 * @param int                  $id      Registration ID.
	 * @param object               $row     Registration row.
	 * @param array{prev:int|null,next:int|null} $adjacent Adjacent IDs.
	 * @param array<string,mixed> $context List context.
	 */
	private static function render_detail_navigation( $id, $row, $adjacent, $context ) {
		$name = trim( ( $row->cognome ?? '' ) . ' ' . ( $row->nome ?? '' ) );
		?>
		<div class="ton-reg-detail-nav">
			<p class="ton-reg-detail-nav__back">
				<a href="<?php echo esc_url( self::list_url( $context ) ); ?>">&larr; <?php esc_html_e( 'Torna all\'elenco', 'ton-italia-registration' ); ?></a>
			</p>
			<div class="ton-reg-detail-nav__pager">
				<?php if ( ! empty( $adjacent['prev'] ) ) : ?>
					<a class="button" href="<?php echo esc_url( self::detail_url( (int) $adjacent['prev'], $context ) ); ?>">&larr; <?php esc_html_e( 'Socio precedente', 'ton-italia-registration' ); ?></a>
				<?php else : ?>
					<span class="button disabled" aria-disabled="true">&larr; <?php esc_html_e( 'Socio precedente', 'ton-italia-registration' ); ?></span>
				<?php endif; ?>
				<span class="ton-reg-detail-nav__current"><?php echo esc_html( $name ); ?></span>
				<?php if ( ! empty( $adjacent['next'] ) ) : ?>
					<a class="button" href="<?php echo esc_url( self::detail_url( (int) $adjacent['next'], $context ) ); ?>"><?php esc_html_e( 'Socio successivo', 'ton-italia-registration' ); ?> &rarr;</a>
				<?php else : ?>
					<span class="button disabled" aria-disabled="true"><?php esc_html_e( 'Socio successivo', 'ton-italia-registration' ); ?> &rarr;</span>
				<?php endif; ?>
			</div>
		</div>
		<?php
	}

	/**
	 * @param array<string,mixed> $context List context.
	 */
	private static function render_detail_list_context_fields( $context ) {
		$fields = array(
			'list_status'         => $context['status'] ?? '',
			'list_search'         => $context['search'] ?? '',
			'list_renewal_unpaid' => ! empty( $context['renewal_unpaid'] ) ? '1' : '',
			'list_orderby'        => $context['orderby'] ?? 'created_at',
			'list_order'          => $context['order'] ?? 'DESC',
		);

		foreach ( $fields as $key => $value ) {
			if ( '' === (string) $value ) {
				continue;
			}
			printf(
				'<input type="hidden" name="ton_reg_list_context[%s]" value="%s" />',
				esc_attr( $key ),
				esc_attr( (string) $value )
			);
		}
	}

	/**
	 * @param int    $id  Registration ID.
	 * @param object $row Registration row.
	 */
	private static function render_renewals_section( $id, $row ) {
		if ( 'admitted' !== ( $row->status ?? '' ) || ! empty( $row->anonymized_at ) ) {
			return;
		}

		$year_range = TON_Reg_Membership_Renewals::get_checkbox_year_range( $row );
		$records    = TON_Reg_Membership_Renewals::get_by_registration( $id );
		$paid_map   = array();

		foreach ( $records as $record ) {
			if ( TON_Reg_Membership_Renewals::STATUS_PAID === (string) $record->status ) {
				$paid_map[ (int) $record->year ] = true;
			}
		}
		?>
		<h2><?php esc_html_e( 'Quote associative annuali', 'ton-italia-registration' ); ?></h2>
		<p class="description"><?php esc_html_e( 'Segna manualmente il pagamento della quota per ogni anno solare (1 gennaio – 31 dicembre). Viene mostrato solo l\'anno in corso e gli anni precedenti dall\'iscrizione.', 'ton-italia-registration' ); ?></p>
		<table class="form-table ton-reg-renewals">
			<?php for ( $year = $year_range['start']; $year <= $year_range['end']; $year++ ) : ?>
			<tr>
				<th scope="row">
					<?php
					printf(
						/* translators: %d: calendar year */
						esc_html__( 'Quota %d', 'ton-italia-registration' ),
						(int) $year
					);
					?>
				</th>
				<td>
					<label>
						<input type="checkbox" name="ton_reg_renewal_paid[<?php echo (int) $year; ?>]" value="1" <?php checked( ! empty( $paid_map[ $year ] ) ); ?> />
						<?php esc_html_e( 'Pagata', 'ton-italia-registration' ); ?>
					</label>
				</td>
			</tr>
			<?php endfor; ?>
		</table>
		<?php
	}

	/**
	 * @param int    $id  Registration ID.
	 * @param object $row Registration row before save.
	 */
	private static function save_renewal_checkboxes( $id, $row ) {
		$new_status = sanitize_text_field( wp_unslash( $_POST['status'] ?? '' ) );
		if ( 'admitted' !== $new_status ) {
			return;
		}

		$year_range = TON_Reg_Membership_Renewals::get_checkbox_year_range( $row );
		$posted     = isset( $_POST['ton_reg_renewal_paid'] ) && is_array( $_POST['ton_reg_renewal_paid'] )
			? wp_unslash( $_POST['ton_reg_renewal_paid'] )
			: array();
		$user_id    = get_current_user_id();

		for ( $year = $year_range['start']; $year <= $year_range['end']; $year++ ) {
			$paid = ! empty( $posted[ $year ] );
			TON_Reg_Membership_Renewals::set_paid( $id, $year, $paid, $user_id );
		}
	}

	/**
	 * @param object $row Registration row.
	 */
	private static function render_consents_section( $row ) {
		?>
		<h2><?php esc_html_e( 'Consensi e dichiarazioni', 'ton-italia-registration' ); ?></h2>
		<p class="description"><?php esc_html_e( 'Il richiedente ha espresso i consensi spuntando le caselle nel modulo pubblico (sostitutivo della firma).', 'ton-italia-registration' ); ?></p>
		<table class="form-table ton-reg-consents">
			<tr>
				<th scope="row"><?php esc_html_e( 'Dichiarazione statuto e regolamenti', 'ton-italia-registration' ); ?></th>
				<td>
					<?php self::render_consent_status( ! empty( $row->consenso_statuto ) ); ?>
					<?php if ( ! empty( $row->consent_statuto_at ) ) : ?>
						<p class="description"><?php echo esc_html( self::format_consent_datetime( $row->consent_statuto_at ) ); ?></p>
					<?php endif; ?>
				</td>
			</tr>
			<tr>
				<th scope="row"><?php esc_html_e( 'Privacy e trattamento dati personali', 'ton-italia-registration' ); ?></th>
				<td>
					<?php self::render_consent_status( ! empty( $row->consenso_privacy ) ); ?>
					<?php if ( ! empty( $row->consent_privacy_at ) ) : ?>
						<p class="description"><?php echo esc_html( self::format_consent_datetime( $row->consent_privacy_at ) ); ?></p>
					<?php endif; ?>
					<?php if ( ! empty( $row->privacy_text_version ) ) : ?>
						<p class="description">
							<?php
							printf(
								/* translators: %s: privacy text version id */
								esc_html__( 'Versione informativa al momento del consenso: %s', 'ton-italia-registration' ),
								esc_html( $row->privacy_text_version )
							);
							?>
						</p>
					<?php endif; ?>
				</td>
			</tr>
			<tr>
				<th scope="row"><?php esc_html_e( 'Consenso newsletter', 'ton-italia-registration' ); ?></th>
				<td>
					<?php if ( ! empty( $row->consenso_newsletter ) ) : ?>
						<?php self::render_consent_status( true ); ?>
						<?php self::render_mailchimp_sync_status( $row ); ?>
					<?php else : ?>
						<span class="ton-reg-consent ton-reg-consent--neutral"><?php esc_html_e( 'Non espresso / non richiesto', 'ton-italia-registration' ); ?></span>
					<?php endif; ?>
				</td>
			</tr>
			<tr>
				<th scope="row"><?php esc_html_e( 'Bonifico effettuato (dichiarato)', 'ton-italia-registration' ); ?></th>
				<td>
					<?php self::render_consent_status( ! empty( $row->bonifico_effettuato ), false ); ?>
				</td>
			</tr>
			<tr>
				<th scope="row"><?php esc_html_e( 'Luogo e data dichiarazione', 'ton-italia-registration' ); ?></th>
				<td>
					<?php
					echo esc_html(
						trim(
							( $row->luogo_dichiarazione ?? '' ) . ( ! empty( $row->data_dichiarazione ) ? ', ' . self::format_date_display( $row->data_dichiarazione ) : '' )
						)
					);
					?>
				</td>
			</tr>
		</table>
		<?php
	}

	/**
	 * @param bool $accepted Whether consent was given.
	 * @param bool $is_consent Whether this is a legal consent (vs operational flag).
	 */
	private static function render_consent_status( $accepted, $is_consent = true ) {
		if ( $accepted ) {
			$class = 'ton-reg-consent ton-reg-consent--yes';
			$text  = $is_consent
				? __( 'Sì — accettato tramite checkbox nel form', 'ton-italia-registration' )
				: __( 'Sì — indicato tramite checkbox nel form', 'ton-italia-registration' );
		} else {
			$class = 'ton-reg-consent ton-reg-consent--no';
			$text  = $is_consent
				? __( 'No — non risulta accettato', 'ton-italia-registration' )
				: __( 'No — non indicato', 'ton-italia-registration' );
		}
		echo '<span class="' . esc_attr( $class ) . '">' . esc_html( $text ) . '</span>';
	}

	/**
	 * @param object $row Registration row.
	 */
	private static function render_mailchimp_sync_status( $row ) {
		$status = isset( $row->mailchimp_status ) ? (string) $row->mailchimp_status : '';

		if ( '' === $status ) {
			if ( TON_Reg_Mailchimp::is_configured() ) {
				echo '<p class="description">' . esc_html__( 'Mailchimp: in attesa di sincronizzazione.', 'ton-italia-registration' ) . '</p>';
			}
			return;
		}

		$labels = array(
			'pending' => __( 'Mailchimp: sincronizzazione in corso', 'ton-italia-registration' ),
			'synced'  => __( 'Mailchimp: iscritto correttamente', 'ton-italia-registration' ),
			'error'   => __( 'Mailchimp: errore di sincronizzazione', 'ton-italia-registration' ),
			'skipped' => __( 'Mailchimp: non sincronizzato (integrazione disattiva)', 'ton-italia-registration' ),
		);

		$label = $labels[ $status ] ?? $status;
		echo '<p class="description"><strong>' . esc_html( $label ) . '</strong></p>';

		if ( ! empty( $row->mailchimp_synced_at ) && 'synced' === $status ) {
			echo '<p class="description">' . esc_html( self::format_consent_datetime( $row->mailchimp_synced_at ) ) . '</p>';
		}

		if ( ! empty( $row->mailchimp_error ) && 'error' === $status ) {
			echo '<p class="description">' . esc_html( $row->mailchimp_error ) . '</p>';
		}

		if ( TON_Reg_Mailchimp::is_configured() && in_array( $status, array( 'error', 'pending' ), true ) ) {
			$retry_url = wp_nonce_url(
				admin_url( 'admin-post.php?action=ton_reg_mailchimp_retry&id=' . (int) $row->id ),
				'ton_reg_mc_retry_' . (int) $row->id
			);
			echo '<p><a class="button button-small" href="' . esc_url( $retry_url ) . '">' . esc_html__( 'Riprova iscrizione Mailchimp', 'ton-italia-registration' ) . '</a></p>';
		}
	}

	/**
	 * @param string $datetime UTC datetime from DB.
	 * @return string
	 */
	private static function format_consent_datetime( $datetime ) {
		$ts = strtotime( $datetime . ' UTC' );
		if ( ! $ts ) {
			return $datetime;
		}
		return sprintf(
			/* translators: %s: formatted date and time */
			__( 'Registrato il %s', 'ton-italia-registration' ),
			wp_date( 'd/m/Y H:i', $ts )
		);
	}

	/**
	 * @param string $date Date Y-m-d.
	 * @return string
	 */
	private static function format_date_display( $date ) {
		$ts = strtotime( $date );
		return $ts ? wp_date( 'd/m/Y', $ts ) : $date;
	}

	/**
	 * Admin notices for document upload / errors.
	 */
	private static function render_admin_notices() {
		if ( isset( $_GET['doc_ok'] ) ) {
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Documento caricato.', 'ton-italia-registration' ) . '</p></div>';
		}
		if ( isset( $_GET['doc_deleted'] ) ) {
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Documento eliminato.', 'ton-italia-registration' ) . '</p></div>';
		}
		if ( ! empty( $_GET['doc_error'] ) ) {
			$msg = sanitize_text_field( wp_unslash( rawurldecode( $_GET['doc_error'] ) ) );
			echo '<div class="notice notice-error is-dismissible"><p>' . esc_html( $msg ) . '</p></div>';
		}
		if ( isset( $_GET['mc_ok'] ) ) {
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Sincronizzazione Mailchimp completata.', 'ton-italia-registration' ) . '</p></div>';
		}
		if ( ! empty( $_GET['mc_error'] ) ) {
			$msg = sanitize_text_field( wp_unslash( rawurldecode( $_GET['mc_error'] ) ) );
			echo '<div class="notice notice-error is-dismissible"><p>' . esc_html( $msg ) . '</p></div>';
		}
		if ( isset( $_GET['card_generated'] ) ) {
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Tessera generata.', 'ton-italia-registration' ) . '</p></div>';
		}
		if ( ! empty( $_GET['card_error'] ) ) {
			$msg = sanitize_text_field( wp_unslash( rawurldecode( $_GET['card_error'] ) ) );
			echo '<div class="notice notice-error is-dismissible"><p>' . esc_html( $msg ) . '</p></div>';
		}
	}

	/**
	 * @param int    $id   Registration ID.
	 * @param object $row  Registration row.
	 * @param string $type Document type.
	 */
	private static function render_document_block( $id, $row, $type ) {
		$column = TON_Reg_Documents::get_column( $type );
		$file   = $column ? TON_Reg_Documents::get_file_info( (int) ( $row->$column ?? 0 ) ) : null;
		?>
		<div class="ton-reg-doc-block">
			<h3><?php echo esc_html( TON_Reg_Documents::type_label( $type ) ); ?></h3>
			<?php if ( $file ) : ?>
				<p class="ton-reg-doc-block__file">
					<a href="<?php echo esc_url( $file['url'] ); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html( $file['name'] ); ?></a>
					<a class="button button-small" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=ton_reg_delete_document&registration_id=' . $id . '&document_type=' . $type ), 'ton_reg_del_doc_' . $id . '_' . $type ) ); ?>" onclick="return confirm('<?php esc_attr_e( 'Eliminare questo documento?', 'ton-italia-registration' ); ?>');"><?php esc_html_e( 'Elimina', 'ton-italia-registration' ); ?></a>
				</p>
			<?php else : ?>
				<p class="description"><?php esc_html_e( 'Nessun file caricato.', 'ton-italia-registration' ); ?></p>
			<?php endif; ?>
			<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" enctype="multipart/form-data" class="ton-reg-doc-block__upload">
				<input type="hidden" name="action" value="ton_reg_upload_document" />
				<input type="hidden" name="registration_id" value="<?php echo (int) $id; ?>" />
				<input type="hidden" name="document_type" value="<?php echo esc_attr( $type ); ?>" />
				<?php wp_nonce_field( 'ton_reg_doc_' . $id . '_' . $type, 'ton_reg_doc_nonce' ); ?>
				<input type="file" name="ton_reg_document" accept=".pdf,.jpg,.jpeg,.png,application/pdf,image/jpeg,image/png" <?php echo $file ? '' : 'required'; ?> />
				<?php submit_button( $file ? __( 'Sostituisci file', 'ton-italia-registration' ) : __( 'Carica file', 'ton-italia-registration' ), 'secondary', 'submit', false ); ?>
			</form>
		</div>
		<?php
	}

	/**
	 * Upload member document.
	 */
	public static function upload_document() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Non autorizzato.', 'ton-italia-registration' ) );
		}

		$id   = isset( $_POST['registration_id'] ) ? (int) $_POST['registration_id'] : 0;
		$type = isset( $_POST['document_type'] ) ? sanitize_text_field( wp_unslash( $_POST['document_type'] ) ) : '';

		check_admin_referer( 'ton_reg_doc_' . $id . '_' . $type, 'ton_reg_doc_nonce' );

		$file = isset( $_FILES['ton_reg_document'] ) ? $_FILES['ton_reg_document'] : null;
		$result = TON_Reg_Documents::upload( $id, $type, $file );

		$redirect = admin_url( 'admin.php?page=ton-registrations&action=view&id=' . $id );

		if ( is_wp_error( $result ) ) {
			wp_safe_redirect(
				add_query_arg(
					array( 'doc_error' => rawurlencode( $result->get_error_message() ) ),
					$redirect
				)
			);
			exit;
		}

		wp_safe_redirect( add_query_arg( 'doc_ok', '1', $redirect ) );
		exit;
	}

	/**
	 * Delete member document.
	 */
	public static function delete_document() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Non autorizzato.', 'ton-italia-registration' ) );
		}

		$id   = isset( $_GET['registration_id'] ) ? (int) $_GET['registration_id'] : 0;
		$type = isset( $_GET['document_type'] ) ? sanitize_text_field( wp_unslash( $_GET['document_type'] ) ) : '';

		check_admin_referer( 'ton_reg_del_doc_' . $id . '_' . $type );

		TON_Reg_Documents::delete_document( $id, $type );

		wp_safe_redirect(
			add_query_arg(
				'doc_deleted',
				'1',
				admin_url( 'admin.php?page=ton-registrations&action=view&id=' . $id )
			)
		);
		exit;
	}

	/**
	 * Settings page.
	 */
	public static function page_settings() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		?>
		<div class="wrap">
			<h1><?php esc_html_e( 'Impostazioni TON Iscrizioni', 'ton-italia-registration' ); ?></h1>

			<?php if ( isset( $_GET['mc_test_ok'] ) ) : ?>
				<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Connessione Mailchimp riuscita.', 'ton-italia-registration' ); ?></p></div>
			<?php endif; ?>
			<?php if ( ! empty( $_GET['mc_test_error'] ) ) : ?>
				<div class="notice notice-error is-dismissible"><p><?php echo esc_html( sanitize_text_field( wp_unslash( rawurldecode( $_GET['mc_test_error'] ) ) ) ); ?></p></div>
			<?php endif; ?>
			<?php if ( isset( $_GET['card_settings_saved'] ) ) : ?>
				<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Impostazioni tessera salvate.', 'ton-italia-registration' ); ?></p></div>
			<?php endif; ?>
			<?php if ( isset( $_GET['card_template_ok'] ) ) : ?>
				<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Template PDF caricato.', 'ton-italia-registration' ); ?></p></div>
			<?php endif; ?>
			<?php if ( ! empty( $_GET['card_batch_done'] ) ) : ?>
				<div class="notice notice-success is-dismissible"><p><?php echo esc_html( self::format_batch_report_message() ); ?></p></div>
			<?php endif; ?>
			<?php if ( ! empty( $_GET['card_error'] ) ) : ?>
				<div class="notice notice-error is-dismissible"><p><?php echo esc_html( sanitize_text_field( wp_unslash( rawurldecode( $_GET['card_error'] ) ) ) ); ?></p></div>
			<?php endif; ?>

			<div class="ton-reg-shortcode-box">
				<h2 class="ton-reg-shortcode-box__title"><?php esc_html_e( 'Profilo socio e tessera', 'ton-italia-registration' ); ?></h2>
				<p class="ton-reg-shortcode-box__text">
					<?php esc_html_e( 'Ogni socio ha un account WordPress (ruolo Subscriber). Dopo l’accesso può aprire il profilo nativo per consultare lo stato iscrizione e scaricare la tessera PDF.', 'ton-italia-registration' ); ?>
				</p>
				<p class="ton-reg-shortcode-box__code">
					<a href="<?php echo esc_url( TON_Reg_Profile::profile_url() ); ?>"><?php echo esc_html( TON_Reg_Profile::profile_url() ); ?></a>
				</p>
				<p class="description ton-reg-shortcode-box__hint">
					<?php esc_html_e( 'Consigliato: aggiungi al menu del sito una voce «Il mio profilo» con URL personalizzato sopra (visibile solo agli utenti loggati).', 'ton-italia-registration' ); ?>
				</p>
				<p class="description ton-reg-shortcode-box__hint">
					<?php esc_html_e( 'Lo shortcode [ton_registration_profile] resta disponibile per retrocompatibilità, ma il profilo WordPress è l’approccio consigliato.', 'ton-italia-registration' ); ?>
				</p>
			</div>

			<div class="ton-reg-shortcode-box">
				<h2 class="ton-reg-shortcode-box__title"><?php esc_html_e( 'Pagina di conferma iscrizione', 'ton-italia-registration' ); ?></h2>
				<p class="ton-reg-shortcode-box__text">
					<?php esc_html_e( 'Crea una pagina pubblica (es. «Iscrizione completata»), incolla lo shortcode sotto e selezionala in «Pagina di conferma» più avanti. Dopo un invio riuscito l’utente verrà reindirizzato lì.', 'ton-italia-registration' ); ?>
				</p>
				<p class="ton-reg-shortcode-box__code"><code>[ton_registration_success]</code></p>
			</div>

			<div class="ton-reg-shortcode-box">
				<h2 class="ton-reg-shortcode-box__title"><?php esc_html_e( 'Inserire il form in una pagina', 'ton-italia-registration' ); ?></h2>
				<p class="ton-reg-shortcode-box__text">
					<?php esc_html_e( 'Crea o modifica una pagina WordPress (es. «Iscrizione socio»), poi incolla lo shortcode nel contenuto — in un blocco «Shortcode» dell’editor o direttamente nel testo. Pubblica la pagina: il modulo di iscrizione apparirà automaticamente.', 'ton-italia-registration' ); ?>
				</p>
				<p class="ton-reg-shortcode-box__code"><code>[ton_registration_form]</code></p>
				<p class="description ton-reg-shortcode-box__hint">
					<?php esc_html_e( 'Opzionale: aggiungi una classe CSS al contenitore, ad esempio', 'ton-italia-registration' ); ?>
					<code>[ton_registration_form class="mia-classe"]</code>
				</p>
			</div>

			<form method="post" action="options.php">
				<?php
				settings_fields( 'ton_reg_settings' );
				$textareas = array(
					'ton_reg_success_message'       => __( 'Messaggio successo', 'ton-italia-registration' ),
					'ton_reg_payment_instructions' => __( 'Istruzioni bonifico', 'ton-italia-registration' ),
					'ton_reg_statuto_declaration'  => __( 'Dichiarazione statuto (checkbox 1)', 'ton-italia-registration' ),
					'ton_reg_privacy_notice'        => __( 'Informativa privacy', 'ton-italia-registration' ),
					'ton_reg_newsletter_label'      => __( 'Label consenso newsletter', 'ton-italia-registration' ),
					'ton_reg_minor_notice'          => __( 'Avviso minorenni', 'ton-italia-registration' ),
					'ton_reg_email_admin_body'      => __( 'Email admin (testo)', 'ton-italia-registration' ),
					'ton_reg_email_user_body'       => __( 'Email utente (testo)', 'ton-italia-registration' ),
					'ton_reg_email_renewal_first_body'  => __( 'Email rinnovo — primo promemoria (1 gen)', 'ton-italia-registration' ),
					'ton_reg_email_renewal_second_body' => __( 'Email rinnovo — secondo promemoria (25 gen)', 'ton-italia-registration' ),
					'ton_reg_email_renewal_admin_body'  => __( 'Email admin rinnovi (riepilogo)', 'ton-italia-registration' ),
				);
				$textarea_hints = array(
					'ton_reg_success_message' => __( 'Testo mostrato dallo shortcode [ton_registration_success] dopo l\'invio. HTML consentito (es. &lt;p&gt;...&lt;/p&gt;).', 'ton-italia-registration' ),
				);
				?>
				<table class="form-table">
					<tr>
						<th><label for="ton_reg_success_page_id"><?php esc_html_e( 'Pagina di conferma', 'ton-italia-registration' ); ?></label></th>
						<td>
							<?php
							wp_dropdown_pages(
								array(
									'name'              => 'ton_reg_success_page_id',
									'id'                => 'ton_reg_success_page_id',
									'selected'          => (int) get_option( 'ton_reg_success_page_id', 0 ),
									'show_option_none'  => __( '— Stessa pagina del form (fallback) —', 'ton-italia-registration' ),
									'option_none_value' => '0',
									'post_status'       => array( 'publish' ),
								)
							);
							?>
							<p class="description"><?php esc_html_e( 'Pagina pubblica con [ton_registration_success]. Obbligatoria per il redirect dedicato.', 'ton-italia-registration' ); ?></p>
						</td>
					</tr>
					<tr><th><?php esc_html_e( 'Anno sociale', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_social_year" value="<?php echo esc_attr( get_option( 'ton_reg_social_year' ) ); ?>" /></td></tr>
					<tr>
						<th><?php esc_html_e( 'Anno inizio promemoria rinnovo', 'ton-italia-registration' ); ?></th>
						<td>
							<input name="ton_reg_renewal_reminders_start_year" type="number" min="2000" max="2100" value="<?php echo esc_attr( get_option( 'ton_reg_renewal_reminders_start_year', '2027' ) ); ?>" />
							<p class="description"><?php esc_html_e( 'Nessuna email automatica di rinnovo viene inviata prima del 1 gennaio di questo anno (es. 2027 = primi invii il 1/1/2027).', 'ton-italia-registration' ); ?></p>
						</td>
					</tr>
					<tr><th><?php esc_html_e( 'Quota €', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_membership_fee" value="<?php echo esc_attr( get_option( 'ton_reg_membership_fee' ) ); ?>" /></td></tr>
					<tr><th><?php esc_html_e( 'Beneficiario', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_beneficiary" class="regular-text" value="<?php echo esc_attr( get_option( 'ton_reg_beneficiary' ) ); ?>" /></td></tr>
					<tr><th>IBAN</th><td><input name="ton_reg_iban" class="regular-text" value="<?php echo esc_attr( get_option( 'ton_reg_iban' ) ); ?>" /></td></tr>
					<tr><th><?php esc_html_e( 'Email admin', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_admin_email" type="email" class="regular-text" value="<?php echo esc_attr( get_option( 'ton_reg_admin_email' ) ); ?>" /></td></tr>
					<tr><th><?php esc_html_e( 'Contatto GDPR', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_gdpr_contact" type="email" class="regular-text" value="<?php echo esc_attr( get_option( 'ton_reg_gdpr_contact' ) ); ?>" /></td></tr>
					<tr><th><?php esc_html_e( 'CAPTCHA attivo', 'ton-italia-registration' ); ?></th><td><input type="hidden" name="ton_reg_captcha_enabled" value="0" /><input type="checkbox" name="ton_reg_captcha_enabled" value="1" <?php checked( get_option( 'ton_reg_captcha_enabled' ), '1' ); ?> /></td></tr>
					<tr><th><?php esc_html_e( 'Newsletter obbligatoria', 'ton-italia-registration' ); ?></th><td><input type="hidden" name="ton_reg_newsletter_required" value="0" /><input type="checkbox" name="ton_reg_newsletter_required" value="1" <?php checked( get_option( 'ton_reg_newsletter_required' ), '1' ); ?> /></td></tr>
					<tr><th><?php esc_html_e( 'Elimina utenti alla disinstallazione', 'ton-italia-registration' ); ?></th><td><input type="hidden" name="ton_reg_delete_users_on_uninstall" value="0" /><input type="checkbox" name="ton_reg_delete_users_on_uninstall" value="1" <?php checked( get_option( 'ton_reg_delete_users_on_uninstall' ), '1' ); ?> /></td></tr>
				</table>

				<h2><?php esc_html_e( 'Integrazione Mailchimp', 'ton-italia-registration' ); ?></h2>
				<p class="description"><?php esc_html_e( 'Iscrive automaticamente a Mailchimp i richiedenti che spuntano il consenso newsletter. L\'iscrizione socio non viene mai bloccata da errori Mailchimp.', 'ton-italia-registration' ); ?></p>
				<table class="form-table">
					<tr>
						<th><?php esc_html_e( 'Integrazione attiva', 'ton-italia-registration' ); ?></th>
						<td><input type="hidden" name="ton_reg_mailchimp_enabled" value="0" /><input type="checkbox" name="ton_reg_mailchimp_enabled" value="1" <?php checked( get_option( 'ton_reg_mailchimp_enabled' ), '1' ); ?> /></td>
					</tr>
					<tr>
						<th><label for="ton_reg_mailchimp_api_key"><?php esc_html_e( 'API key', 'ton-italia-registration' ); ?></label></th>
						<td>
							<input type="password" id="ton_reg_mailchimp_api_key" name="ton_reg_mailchimp_api_key" class="regular-text" value="" autocomplete="new-password" placeholder="<?php echo esc_attr( get_option( 'ton_reg_mailchimp_api_key' ) ? '••••••••••••' : '' ); ?>" />
							<p class="description"><?php esc_html_e( 'Mailchimp → Account → Extras → API keys. Lasciare vuoto per non modificare la chiave salvata.', 'ton-italia-registration' ); ?></p>
						</td>
					</tr>
					<tr>
						<th><label for="ton_reg_mailchimp_list_id"><?php esc_html_e( 'List ID (Audience)', 'ton-italia-registration' ); ?></label></th>
						<td>
							<input type="text" id="ton_reg_mailchimp_list_id" name="ton_reg_mailchimp_list_id" class="regular-text" value="<?php echo esc_attr( get_option( 'ton_reg_mailchimp_list_id' ) ); ?>" />
							<p class="description"><?php esc_html_e( 'Audience → Settings → Audience name and defaults → Audience ID.', 'ton-italia-registration' ); ?></p>
						</td>
					</tr>
					<tr>
						<th><label for="ton_reg_mailchimp_tags"><?php esc_html_e( 'Tag', 'ton-italia-registration' ); ?></label></th>
						<td>
							<input type="text" id="ton_reg_mailchimp_tags" name="ton_reg_mailchimp_tags" class="regular-text" value="<?php echo esc_attr( get_option( 'ton_reg_mailchimp_tags', 'Socio TON ITALIA' ) ); ?>" />
							<p class="description"><?php esc_html_e( 'Separati da virgola (max 5).', 'ton-italia-registration' ); ?></p>
						</td>
					</tr>
				</table>

				<table class="form-table">
					<tr><th><?php esc_html_e( 'Rate limit (max tentativi)', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_rate_limit_max" type="number" min="1" value="<?php echo esc_attr( get_option( 'ton_reg_rate_limit_max', '5' ) ); ?>" /></td></tr>
					<tr><th><?php esc_html_e( 'Finestra rate limit (secondi)', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_rate_limit_window" type="number" min="60" value="<?php echo esc_attr( get_option( 'ton_reg_rate_limit_window', '900' ) ); ?>" /></td></tr>
					<?php foreach ( $textareas as $key => $label ) : ?>
					<tr>
						<th><?php echo esc_html( $label ); ?></th>
						<td>
							<textarea name="<?php echo esc_attr( $key ); ?>" rows="6" class="large-text"><?php echo esc_textarea( get_option( $key, TON_Reg_Defaults::options()[ $key ] ?? '' ) ); ?></textarea>
							<?php if ( isset( $textarea_hints[ $key ] ) ) : ?>
								<p class="description"><?php echo esc_html( $textarea_hints[ $key ] ); ?></p>
							<?php endif; ?>
						</td>
					</tr>
					<?php endforeach; ?>
					<tr><th><?php esc_html_e( 'Oggetto email admin', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_email_admin_subject" class="large-text" value="<?php echo esc_attr( get_option( 'ton_reg_email_admin_subject' ) ); ?>" /></td></tr>
					<tr><th><?php esc_html_e( 'Oggetto email utente', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_email_user_subject" class="large-text" value="<?php echo esc_attr( get_option( 'ton_reg_email_user_subject' ) ); ?>" /></td></tr>
					<tr><th><?php esc_html_e( 'Oggetto primo promemoria rinnovo', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_email_renewal_first_subject" class="large-text" value="<?php echo esc_attr( get_option( 'ton_reg_email_renewal_first_subject', TON_Reg_Defaults::options()['ton_reg_email_renewal_first_subject'] ?? '' ) ); ?>" /></td></tr>
					<tr><th><?php esc_html_e( 'Oggetto secondo promemoria rinnovo', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_email_renewal_second_subject" class="large-text" value="<?php echo esc_attr( get_option( 'ton_reg_email_renewal_second_subject', TON_Reg_Defaults::options()['ton_reg_email_renewal_second_subject'] ?? '' ) ); ?>" /></td></tr>
					<tr><th><?php esc_html_e( 'Oggetto email admin rinnovi', 'ton-italia-registration' ); ?></th><td><input name="ton_reg_email_renewal_admin_subject" class="large-text" value="<?php echo esc_attr( get_option( 'ton_reg_email_renewal_admin_subject', TON_Reg_Defaults::options()['ton_reg_email_renewal_admin_subject'] ?? '' ) ); ?>" /></td></tr>
				</table>
				<p class="description"><?php esc_html_e( 'Placeholder email iscrizione: {nome}, {cognome}, {email}, {codice_fiscale}, {ip}, {login_url}, {profile_url}, {gdpr_contact}, {admin_url}', 'ton-italia-registration' ); ?></p>
				<p class="description"><?php esc_html_e( 'Placeholder email rinnovo: {nome}, {cognome}, {email}, {anno}, {quota}, {beneficiario}, {iban}, {payment_instructions}, {profile_url}, {gdpr_contact}. Admin rinnovi: {lista_soci}, {numero_soci}, {anno}, {admin_url}.', 'ton-italia-registration' ); ?></p>
				<p class="description"><?php esc_html_e( 'I promemoria rinnovo vengono inviati automaticamente il 1 gennaio (primo promemoria) e il 25 gennaio (sollecito) agli ammessi senza quota pagata per l\'anno in corso.', 'ton-italia-registration' ); ?></p>
				<?php submit_button(); ?>
			</form>

			<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="ton-reg-mailchimp-test-form">
				<input type="hidden" name="action" value="ton_reg_mailchimp_test" />
				<?php wp_nonce_field( 'ton_reg_mc_test', 'ton_reg_mc_test_nonce' ); ?>
				<h2><?php esc_html_e( 'Test connessione Mailchimp', 'ton-italia-registration' ); ?></h2>
				<p class="description"><?php esc_html_e( 'Verifica API key e List ID senza salvare le altre impostazioni. Lascia vuota la API key per usare quella già salvata.', 'ton-italia-registration' ); ?></p>
				<table class="form-table">
					<tr>
						<th><label for="ton_reg_mailchimp_test_api_key"><?php esc_html_e( 'API key (test)', 'ton-italia-registration' ); ?></label></th>
						<td>
							<input type="password" id="ton_reg_mailchimp_test_api_key" name="ton_reg_mailchimp_api_key" class="regular-text" value="" autocomplete="new-password" />
						</td>
					</tr>
					<tr>
						<th><label for="ton_reg_mailchimp_test_list_id"><?php esc_html_e( 'List ID (test)', 'ton-italia-registration' ); ?></label></th>
						<td>
							<input type="text" id="ton_reg_mailchimp_test_list_id" name="ton_reg_mailchimp_list_id" class="regular-text" value="<?php echo esc_attr( get_option( 'ton_reg_mailchimp_list_id' ) ); ?>" />
						</td>
					</tr>
				</table>
				<?php submit_button( __( 'Test connessione Mailchimp', 'ton-italia-registration' ), 'secondary', 'submit', false ); ?>
			</form>

			<?php self::render_card_settings_section(); ?>
		</div>
		<?php
	}

	/**
	 * Save registration admin fields.
	 */
	public static function save_registration() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Non autorizzato.', 'ton-italia-registration' ) );
		}

		$id = isset( $_POST['registration_id'] ) ? (int) $_POST['registration_id'] : 0;
		check_admin_referer( 'ton_reg_save_' . $id, 'ton_reg_admin_nonce' );

		$row = TON_Reg_Database::get( $id );
		if ( ! $row ) {
			wp_die( esc_html__( 'Non trovato.', 'ton-italia-registration' ) );
		}

		$old_status = (string) $row->status;
		$new_status = sanitize_text_field( wp_unslash( $_POST['status'] ?? 'pending' ) );

		TON_Reg_Database::update(
			$id,
			array(
				'status'               => $new_status,
				'libro_associati_date' => sanitize_text_field( wp_unslash( $_POST['libro_associati_date'] ?? '' ) ) ?: null,
				'admin_notes'          => sanitize_textarea_field( wp_unslash( $_POST['admin_notes'] ?? '' ) ),
			)
		);

		$redirect_args = array( 'updated' => '1' );
		$card_result   = TON_Reg_Membership_Card::maybe_generate_on_admission( $id, $old_status, $new_status );
		if ( is_wp_error( $card_result ) ) {
			$redirect_args['card_error'] = rawurlencode( $card_result->get_error_message() );
		} elseif ( null !== $card_result ) {
			$redirect_args['card_generated'] = '1';
		}

		self::save_renewal_checkboxes( $id, $row );

		wp_safe_redirect(
			add_query_arg(
				$redirect_args,
				self::detail_url( $id, self::get_detail_list_context() )
			)
		);
		exit;
	}

	/**
	 * Export admitted members as Libro Soci CSV.
	 */
	public static function export_libro_soci() {
		if ( ! current_user_can( 'manage_options' ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'ton_reg_export_libro_soci' ) ) {
			wp_die( esc_html__( 'Non autorizzato.', 'ton-italia-registration' ), 403 );
		}

		TON_Reg_Members_Export::stream_libro_soci_csv();
	}

	/**
	 * GDPR export download.
	 */
	public static function gdpr_export() {
		$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		if ( ! current_user_can( 'manage_options' ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'ton_reg_gdpr_' . $id ) ) {
			wp_die( esc_html__( 'Non autorizzato.', 'ton-italia-registration' ) );
		}

		$data = TON_Reg_Gdpr::export( $id );
		if ( ! $data ) {
			wp_die( esc_html__( 'Non trovato.', 'ton-italia-registration' ) );
		}

		$format = isset( $_GET['format'] ) ? sanitize_text_field( wp_unslash( $_GET['format'] ) ) : 'json';

		if ( 'csv' === $format ) {
			header( 'Content-Type: text/csv; charset=utf-8' );
			header( 'Content-Disposition: attachment; filename=ton-registration-' . $id . '.csv' );
			$out = fopen( 'php://output', 'w' );
			foreach ( $data as $key => $value ) {
				if ( is_array( $value ) ) {
					$value = wp_json_encode( $value, JSON_UNESCAPED_UNICODE );
				}
				fputcsv( $out, array( (string) $key, (string) $value ) );
			}
			fclose( $out );
			exit;
		}

		header( 'Content-Type: application/json; charset=utf-8' );
		header( 'Content-Disposition: attachment; filename=ton-registration-' . $id . '.json' );
		echo wp_json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE );
		exit;
	}

	/**
	 * GDPR anonymize.
	 */
	public static function gdpr_anonymize() {
		$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		if ( ! current_user_can( 'manage_options' ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'ton_reg_gdpr_' . $id ) ) {
			wp_die( esc_html__( 'Non autorizzato.', 'ton-italia-registration' ) );
		}

		TON_Reg_Gdpr::anonymize( $id, 'admin action' );
		wp_safe_redirect( admin_url( 'admin.php?page=ton-registrations&action=view&id=' . $id . '&updated=1' ) );
		exit;
	}

	/**
	 * GDPR erase.
	 */
	public static function gdpr_erase() {
		$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;
		if ( ! current_user_can( 'manage_options' ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'ton_reg_gdpr_' . $id ) ) {
			wp_die( esc_html__( 'Non autorizzato.', 'ton-italia-registration' ) );
		}

		TON_Reg_Gdpr::erase( $id, 'admin action' );
		wp_safe_redirect( admin_url( 'admin.php?page=ton-registrations&updated=1' ) );
		exit;
	}

	/**
	 * Test Mailchimp API connection from settings.
	 */
	public static function mailchimp_test() {
		$redirect = admin_url( 'admin.php?page=ton-reg-settings' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_safe_redirect(
				add_query_arg(
					'mc_test_error',
					rawurlencode( __( 'Non autorizzato.', 'ton-italia-registration' ) ),
					$redirect
				)
			);
			exit;
		}

		$nonce = isset( $_POST['ton_reg_mc_test_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['ton_reg_mc_test_nonce'] ) ) : '';
		if ( ! wp_verify_nonce( $nonce, 'ton_reg_mc_test' ) ) {
			wp_safe_redirect(
				add_query_arg(
					'mc_test_error',
					rawurlencode( __( 'Sessione scaduta. Ricarica la pagina e riprova.', 'ton-italia-registration' ) ),
					$redirect
				)
			);
			exit;
		}

		$api_key_post = isset( $_POST['ton_reg_mailchimp_api_key'] ) ? sanitize_text_field( wp_unslash( $_POST['ton_reg_mailchimp_api_key'] ) ) : '';
		$list_id_post = isset( $_POST['ton_reg_mailchimp_list_id'] ) ? sanitize_text_field( wp_unslash( $_POST['ton_reg_mailchimp_list_id'] ) ) : '';

		if ( ! class_exists( 'TON_Reg_Mailchimp' ) ) {
			wp_safe_redirect(
				add_query_arg(
					'mc_test_error',
					rawurlencode( __( 'Modulo Mailchimp non caricato. Reinstalla o aggiorna il plugin.', 'ton-italia-registration' ) ),
					$redirect
				)
			);
			exit;
		}

		$result = TON_Reg_Mailchimp::test_connection( $api_key_post, $list_id_post );

		if ( is_wp_error( $result ) ) {
			wp_safe_redirect(
				add_query_arg(
					'mc_test_error',
					rawurlencode( $result->get_error_message() ),
					$redirect
				)
			);
			exit;
		}

		wp_safe_redirect( add_query_arg( 'mc_test_ok', '1', $redirect ) );
		exit;
	}

	/**
	 * Retry Mailchimp sync for a single registration.
	 */
	public static function mailchimp_retry() {
		$id = isset( $_GET['id'] ) ? (int) $_GET['id'] : 0;

		if ( ! current_user_can( 'manage_options' ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'ton_reg_mc_retry_' . $id ) ) {
			wp_die( esc_html__( 'Non autorizzato.', 'ton-italia-registration' ) );
		}

		$row = TON_Reg_Database::get( $id );
		if ( ! $row || empty( $row->consenso_newsletter ) ) {
			wp_safe_redirect(
				add_query_arg(
					'mc_error',
					rawurlencode( __( 'Consenso newsletter assente.', 'ton-italia-registration' ) ),
					admin_url( 'admin.php?page=ton-registrations&action=view&id=' . $id )
				)
			);
			exit;
		}

		if ( ! TON_Reg_Mailchimp::is_configured() ) {
			wp_safe_redirect(
				add_query_arg(
					'mc_error',
					rawurlencode( __( 'Integrazione Mailchimp non configurata.', 'ton-italia-registration' ) ),
					admin_url( 'admin.php?page=ton-registrations&action=view&id=' . $id )
				)
			);
			exit;
		}

		$ok = TON_Reg_Mailchimp::sync_registration( $id );
		$redirect = admin_url( 'admin.php?page=ton-registrations&action=view&id=' . $id );

		if ( $ok ) {
			wp_safe_redirect( add_query_arg( 'mc_ok', '1', $redirect ) );
			exit;
		}

		$row = TON_Reg_Database::get( $id );
		$error = ! empty( $row->mailchimp_error ) ? $row->mailchimp_error : __( 'Sincronizzazione Mailchimp non riuscita.', 'ton-italia-registration' );

		wp_safe_redirect(
			add_query_arg(
				'mc_error',
				rawurlencode( $error ),
				$redirect
			)
		);
		exit;
	}

	/**
	 * Membership cards section on registration detail.
	 *
	 * @param int    $id  Registration ID.
	 * @param object $row Registration row.
	 */
	private static function render_membership_cards_section( $id, $row ) {
		$cards       = TON_Reg_Membership_Cards::get_by_registration( $id );
		$active_year = TON_Reg_Membership_Card::get_active_year();
		?>
		<h2><?php esc_html_e( 'Tessere generate', 'ton-italia-registration' ); ?></h2>
		<?php if ( empty( $cards ) ) : ?>
			<p class="description"><?php esc_html_e( 'Nessuna tessera generata.', 'ton-italia-registration' ); ?></p>
		<?php else : ?>
			<table class="widefat striped ton-reg-cards-table">
				<thead>
					<tr>
						<th><?php esc_html_e( 'Anno', 'ton-italia-registration' ); ?></th>
						<th><?php esc_html_e( 'Data', 'ton-italia-registration' ); ?></th>
						<th><?php esc_html_e( 'Origine', 'ton-italia-registration' ); ?></th>
						<th><?php esc_html_e( 'Download', 'ton-italia-registration' ); ?></th>
					</tr>
				</thead>
				<tbody>
					<?php foreach ( $cards as $card ) : ?>
					<tr>
						<td><?php echo esc_html( (string) $card->year ); ?></td>
						<td><?php echo esc_html( mysql2date( 'd/m/Y H:i', $card->created_at ) ); ?></td>
						<td><?php echo esc_html( TON_Reg_Membership_Card::context_label( (string) $card->generation_context ) ); ?></td>
						<td>
							<a href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'ton_reg_download_card', 'card_id' => (int) $card->id ), admin_url( 'admin-post.php' ) ), 'ton_reg_download_card_' . (int) $card->id, 'ton_reg_card_nonce' ) ); ?>">
								<?php esc_html_e( 'Scarica PDF', 'ton-italia-registration' ); ?>
							</a>
						</td>
					</tr>
					<?php endforeach; ?>
				</tbody>
			</table>
		<?php endif; ?>

		<?php if ( 'admitted' === $row->status && empty( $row->anonymized_at ) ) : ?>
			<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="margin-top:1em;">
				<input type="hidden" name="action" value="ton_reg_generate_card" />
				<input type="hidden" name="registration_id" value="<?php echo (int) $id; ?>" />
				<input type="hidden" name="year" value="<?php echo (int) $active_year; ?>" />
				<input type="hidden" name="force_regenerate" value="1" />
				<?php wp_nonce_field( 'ton_reg_generate_card_' . $id, 'ton_reg_card_admin_nonce' ); ?>
				<?php
				submit_button(
					sprintf(
						/* translators: %d: year */
						__( 'Genera / rigenera tessera %d', 'ton-italia-registration' ),
						(int) $active_year
					),
					'secondary',
					'submit',
					false
				);
				?>
			</form>
		<?php endif; ?>
		<?php
	}

	/**
	 * Card settings section on settings page.
	 */
	private static function render_card_settings_section() {
		if ( ! TON_Reg_Membership_Card::is_available() ) {
			echo '<div class="notice notice-error"><p>' . esc_html__( 'Libreria PDF (FPDI) non trovata in vendor/.', 'ton-italia-registration' ) . '</p></div>';
			return;
		}

		$active_year = TON_Reg_Membership_Card::get_active_year();
		$config      = TON_Reg_Membership_Card::get_template_config( $active_year );
		$template_id = (int) ( $config['template_attachment_id'] ?? 0 );
		$template_url = $template_id ? wp_get_attachment_url( $template_id ) : '';
		$admitted_count = TON_Reg_Database::count(
			array(
				'status'             => 'admitted',
				'exclude_anonymized' => true,
			)
		);
		?>
		<hr />
		<h2><?php esc_html_e( 'Tessera socio (PDF)', 'ton-italia-registration' ); ?></h2>
		<p class="description"><?php esc_html_e( 'Carica un template PDF annuale e configura la posizione del nome. La tessera viene generata automaticamente quando un iscritto passa ad «Ammesso».', 'ton-italia-registration' ); ?></p>

		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
			<input type="hidden" name="action" value="ton_reg_save_card_settings" />
			<?php wp_nonce_field( 'ton_reg_save_card_settings', 'ton_reg_card_settings_nonce' ); ?>
			<table class="form-table">
				<tr>
					<th><label for="ton_reg_card_active_year"><?php esc_html_e( 'Anno attivo', 'ton-italia-registration' ); ?></label></th>
					<td><input type="number" id="ton_reg_card_active_year" name="active_year" min="2000" max="2100" value="<?php echo (int) $active_year; ?>" /></td>
				</tr>
				<tr>
					<th><label for="ton_reg_card_name_x"><?php esc_html_e( 'Posizione nome X (mm)', 'ton-italia-registration' ); ?></label></th>
					<td><input type="number" step="0.1" id="ton_reg_card_name_x" name="name_x" value="<?php echo esc_attr( (string) $config['name_x'] ); ?>" /></td>
				</tr>
				<tr>
					<th><label for="ton_reg_card_name_y"><?php esc_html_e( 'Posizione nome Y (mm)', 'ton-italia-registration' ); ?></label></th>
					<td><input type="number" step="0.1" id="ton_reg_card_name_y" name="name_y" value="<?php echo esc_attr( (string) $config['name_y'] ); ?>" /></td>
				</tr>
				<tr>
					<th><label for="ton_reg_card_font_family"><?php esc_html_e( 'Font', 'ton-italia-registration' ); ?></label></th>
					<td>
						<select id="ton_reg_card_font_family" name="font_family">
							<?php foreach ( array( 'Helvetica', 'Times', 'Courier', 'Arial' ) as $font ) : ?>
								<option value="<?php echo esc_attr( $font ); ?>" <?php selected( $config['font_family'], $font ); ?>><?php echo esc_html( $font ); ?></option>
							<?php endforeach; ?>
						</select>
					</td>
				</tr>
				<tr>
					<th><label for="ton_reg_card_font_style"><?php esc_html_e( 'Stile font', 'ton-italia-registration' ); ?></label></th>
					<td>
						<select id="ton_reg_card_font_style" name="font_style">
							<?php
							$styles = array( '' => __( 'Normale', 'ton-italia-registration' ), 'B' => __( 'Grassetto', 'ton-italia-registration' ), 'I' => __( 'Corsivo', 'ton-italia-registration' ), 'BI' => __( 'Grassetto corsivo', 'ton-italia-registration' ) );
							foreach ( $styles as $value => $label ) :
								?>
								<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $config['font_style'], $value ); ?>><?php echo esc_html( $label ); ?></option>
							<?php endforeach; ?>
						</select>
					</td>
				</tr>
				<tr>
					<th><label for="ton_reg_card_font_size"><?php esc_html_e( 'Dimensione font (pt)', 'ton-italia-registration' ); ?></label></th>
					<td><input type="number" step="0.5" min="6" max="72" id="ton_reg_card_font_size" name="font_size" value="<?php echo esc_attr( (string) $config['font_size'] ); ?>" /></td>
				</tr>
				<tr>
					<th><label for="ton_reg_card_text_color"><?php esc_html_e( 'Colore testo', 'ton-italia-registration' ); ?></label></th>
					<td><input type="text" id="ton_reg_card_text_color" name="text_color" value="<?php echo esc_attr( (string) $config['text_color'] ); ?>" placeholder="#000000" /></td>
				</tr>
				<tr>
					<th><?php esc_html_e( 'Nome in maiuscolo', 'ton-italia-registration' ); ?></th>
					<td><input type="hidden" name="uppercase" value="0" /><input type="checkbox" name="uppercase" value="1" <?php checked( ! empty( $config['uppercase'] ) ); ?> /></td>
				</tr>
				<tr>
					<th><label for="ton_reg_card_align"><?php esc_html_e( 'Allineamento', 'ton-italia-registration' ); ?></label></th>
					<td>
						<select id="ton_reg_card_align" name="align">
							<option value="L" <?php selected( $config['align'], 'L' ); ?>><?php esc_html_e( 'Sinistra', 'ton-italia-registration' ); ?></option>
							<option value="C" <?php selected( $config['align'], 'C' ); ?>><?php esc_html_e( 'Centro', 'ton-italia-registration' ); ?></option>
							<option value="R" <?php selected( $config['align'], 'R' ); ?>><?php esc_html_e( 'Destra', 'ton-italia-registration' ); ?></option>
						</select>
					</td>
				</tr>
				<tr>
					<th><label for="ton_reg_card_cell_width"><?php esc_html_e( 'Larghezza cella (mm)', 'ton-italia-registration' ); ?></label></th>
					<td>
						<input type="number" step="0.1" min="0" id="ton_reg_card_cell_width" name="cell_width" value="<?php echo esc_attr( (string) $config['cell_width'] ); ?>" />
						<p class="description"><?php esc_html_e( 'Se il nome completo supera questa larghezza, Nome e Cognome vengono stampati su due righe.', 'ton-italia-registration' ); ?></p>
					</td>
				</tr>
			</table>
			<?php if ( $template_url ) : ?>
				<p class="description">
					<?php esc_html_e( 'Template corrente:', 'ton-italia-registration' ); ?>
					<a href="<?php echo esc_url( $template_url ); ?>" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Apri PDF', 'ton-italia-registration' ); ?></a>
				</p>
			<?php endif; ?>
			<?php submit_button( __( 'Salva impostazioni tessera', 'ton-italia-registration' ) ); ?>
		</form>

		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" enctype="multipart/form-data" style="margin-top:1.5em;">
			<input type="hidden" name="action" value="ton_reg_upload_card_template" />
			<input type="hidden" name="active_year" value="<?php echo (int) $active_year; ?>" />
			<?php wp_nonce_field( 'ton_reg_upload_card_template', 'ton_reg_card_template_nonce' ); ?>
			<h3><?php esc_html_e( 'Template PDF', 'ton-italia-registration' ); ?></h3>
			<p class="description"><?php esc_html_e( 'Carica il PDF per l\'anno attivo. Lascia l\'area del nome vuota nel template.', 'ton-italia-registration' ); ?></p>
			<p><input type="file" name="card_template" accept="application/pdf,.pdf" required /></p>
			<?php submit_button( __( 'Carica template PDF', 'ton-italia-registration' ), 'secondary', 'submit', false ); ?>
		</form>

		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="margin-top:1.5em;" onsubmit="return confirm('<?php echo esc_js( sprintf( __( 'Generare le tessere %d per tutti i soci ammessi (%d)?', 'ton-italia-registration' ), (int) $active_year, (int) $admitted_count ) ); ?>');">
			<input type="hidden" name="action" value="ton_reg_batch_cards" />
			<input type="hidden" name="year" value="<?php echo (int) $active_year; ?>" />
			<input type="hidden" name="offset" value="0" />
			<?php wp_nonce_field( 'ton_reg_batch_cards', 'ton_reg_batch_cards_nonce' ); ?>
			<h3><?php esc_html_e( 'Generazione batch', 'ton-italia-registration' ); ?></h3>
			<p class="description">
				<?php
				printf(
					/* translators: 1: year, 2: count */
					esc_html__( 'Genera tessere %1$d per tutti i soci ammessi (%2$d). Salta chi ha già la tessera per quell\'anno.', 'ton-italia-registration' ),
					(int) $active_year,
					(int) $admitted_count
				);
				?>
			</p>
			<p>
				<label>
					<input type="checkbox" name="force_regenerate" value="1" />
					<?php esc_html_e( 'Sovrascrivi tessere già generate per questo anno', 'ton-italia-registration' ); ?>
				</label>
			</p>
			<?php submit_button( __( 'Genera tessere nuovo anno', 'ton-italia-registration' ), 'primary', 'submit', false ); ?>
		</form>
		<?php
	}

	/**
	 * @return string
	 */
	private static function format_batch_report_message() {
		$generated = isset( $_GET['batch_generated'] ) ? (int) $_GET['batch_generated'] : 0;
		$skipped   = isset( $_GET['batch_skipped'] ) ? (int) $_GET['batch_skipped'] : 0;
		$errors    = isset( $_GET['batch_errors'] ) ? (int) $_GET['batch_errors'] : 0;

		return sprintf(
			/* translators: 1: generated, 2: skipped, 3: errors */
			__( 'Batch completato: %1$d generate, %2$d saltate, %3$d errori.', 'ton-italia-registration' ),
			$generated,
			$skipped,
			$errors
		);
	}

	/**
	 * Save card layout settings.
	 */
	public static function save_card_settings() {
		TON_Reg_Membership_Card::assert_can_manage_cards();
		check_admin_referer( 'ton_reg_save_card_settings', 'ton_reg_card_settings_nonce' );

		$year = isset( $_POST['active_year'] ) ? (int) $_POST['active_year'] : TON_Reg_Membership_Card::get_active_year();
		TON_Reg_Membership_Card::set_active_year( $year );

		$current = TON_Reg_Membership_Card::get_template_config( $year );

		TON_Reg_Membership_Card::save_template_config(
			$year,
			array(
				'template_attachment_id' => (int) ( $current['template_attachment_id'] ?? 0 ),
				'name_x'                 => isset( $_POST['name_x'] ) ? (float) wp_unslash( $_POST['name_x'] ) : 42.5,
				'name_y'                 => isset( $_POST['name_y'] ) ? (float) wp_unslash( $_POST['name_y'] ) : 118.0,
				'font_family'            => sanitize_text_field( wp_unslash( $_POST['font_family'] ?? 'Helvetica' ) ),
				'font_style'             => sanitize_text_field( wp_unslash( $_POST['font_style'] ?? '' ) ),
				'font_size'              => isset( $_POST['font_size'] ) ? (float) wp_unslash( $_POST['font_size'] ) : 14,
				'text_color'             => sanitize_text_field( wp_unslash( $_POST['text_color'] ?? '#000000' ) ),
				'uppercase'              => ! empty( $_POST['uppercase'] ),
				'align'                  => sanitize_text_field( wp_unslash( $_POST['align'] ?? 'C' ) ),
				'cell_width'             => isset( $_POST['cell_width'] ) ? (float) wp_unslash( $_POST['cell_width'] ) : 80,
			)
		);

		wp_safe_redirect( admin_url( 'admin.php?page=ton-reg-settings&card_settings_saved=1' ) );
		exit;
	}

	/**
	 * Upload card template PDF for active year.
	 */
	public static function upload_card_template() {
		TON_Reg_Membership_Card::assert_can_manage_cards();
		check_admin_referer( 'ton_reg_upload_card_template', 'ton_reg_card_template_nonce' );

		$year = isset( $_POST['active_year'] ) ? (int) $_POST['active_year'] : TON_Reg_Membership_Card::get_active_year();
		$file = isset( $_FILES['card_template'] ) ? $_FILES['card_template'] : null;

		$attachment_id = TON_Reg_Membership_Card::upload_template_pdf( $file, $year );

		if ( is_wp_error( $attachment_id ) ) {
			wp_safe_redirect(
				add_query_arg(
					'card_error',
					rawurlencode( $attachment_id->get_error_message() ),
					admin_url( 'admin.php?page=ton-reg-settings' )
				)
			);
			exit;
		}

		update_post_meta( $attachment_id, '_ton_reg_card_template_year', $year );

		$current = TON_Reg_Membership_Card::get_template_config( $year );
		$old_id  = (int) ( $current['template_attachment_id'] ?? 0 );
		if ( $old_id && $old_id !== (int) $attachment_id ) {
			wp_delete_attachment( $old_id, true );
		}

		TON_Reg_Membership_Card::save_template_config(
			$year,
			array(
				'template_attachment_id' => (int) $attachment_id,
			)
		);

		wp_safe_redirect( admin_url( 'admin.php?page=ton-reg-settings&card_template_ok=1' ) );
		exit;
	}

	/**
	 * Chunked batch card generation.
	 */
	public static function batch_cards() {
		TON_Reg_Membership_Card::assert_can_manage_cards();
		check_admin_referer( 'ton_reg_batch_cards', 'ton_reg_batch_cards_nonce' );

		$year             = isset( $_POST['year'] ) ? (int) $_POST['year'] : TON_Reg_Membership_Card::get_active_year();
		$offset           = isset( $_POST['offset'] ) ? (int) $_POST['offset'] : 0;
		$force_regenerate = ! empty( $_POST['force_regenerate'] );
		$chunk_size       = 20;

		$generated = isset( $_POST['batch_generated'] ) ? (int) $_POST['batch_generated'] : 0;
		$skipped   = isset( $_POST['batch_skipped'] ) ? (int) $_POST['batch_skipped'] : 0;
		$errors    = isset( $_POST['batch_errors'] ) ? (int) $_POST['batch_errors'] : 0;

		$rows = TON_Reg_Database::query(
			array(
				'status'             => 'admitted',
				'exclude_anonymized' => true,
				'limit'              => $chunk_size,
				'offset'             => $offset,
				'orderby'            => 'id',
				'order'              => 'ASC',
			)
		);

		foreach ( $rows as $row ) {
			$existing = TON_Reg_Membership_Cards::get_by_registration_year( (int) $row->id, $year );
			if ( $existing && ! $force_regenerate ) {
				++$skipped;
				continue;
			}

			$result = TON_Reg_Membership_Card::generate_for_registration(
				(int) $row->id,
				$year,
				array(
					'context'          => TON_Reg_Membership_Card::CONTEXT_BATCH,
					'force_regenerate' => $force_regenerate,
				)
			);

			if ( is_wp_error( $result ) ) {
				++$errors;
			} else {
				++$generated;
			}
		}

		$processed = count( $rows );
		if ( $processed < $chunk_size ) {
			wp_safe_redirect(
				add_query_arg(
					array(
						'card_batch_done'  => '1',
						'batch_generated'  => $generated,
						'batch_skipped'    => $skipped,
						'batch_errors'     => $errors,
					),
					admin_url( 'admin.php?page=ton-reg-settings' )
				)
			);
			exit;
		}

		?>
		<!DOCTYPE html>
		<html><head><meta charset="utf-8"><title><?php esc_html_e( 'Generazione tessere…', 'ton-italia-registration' ); ?></title></head>
		<body onload="document.getElementById('ton-reg-batch-form').submit();">
		<p><?php esc_html_e( 'Generazione tessere in corso…', 'ton-italia-registration' ); ?></p>
		<form id="ton-reg-batch-form" method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
			<input type="hidden" name="action" value="ton_reg_batch_cards" />
			<input type="hidden" name="year" value="<?php echo (int) $year; ?>" />
			<input type="hidden" name="offset" value="<?php echo (int) ( $offset + $chunk_size ); ?>" />
			<input type="hidden" name="batch_generated" value="<?php echo (int) $generated; ?>" />
			<input type="hidden" name="batch_skipped" value="<?php echo (int) $skipped; ?>" />
			<input type="hidden" name="batch_errors" value="<?php echo (int) $errors; ?>" />
			<?php if ( $force_regenerate ) : ?>
				<input type="hidden" name="force_regenerate" value="1" />
			<?php endif; ?>
			<?php wp_nonce_field( 'ton_reg_batch_cards', 'ton_reg_batch_cards_nonce' ); ?>
		</form>
		</body></html>
		<?php
		exit;
	}

	/**
	 * Generate or regenerate a single membership card.
	 */
	public static function generate_card() {
		TON_Reg_Membership_Card::assert_can_manage_cards();

		$id   = isset( $_POST['registration_id'] ) ? (int) $_POST['registration_id'] : 0;
		$year = isset( $_POST['year'] ) ? (int) $_POST['year'] : TON_Reg_Membership_Card::get_active_year();

		check_admin_referer( 'ton_reg_generate_card_' . $id, 'ton_reg_card_admin_nonce' );

		$result = TON_Reg_Membership_Card::generate_for_registration(
			$id,
			$year,
			array(
				'context'          => TON_Reg_Membership_Card::CONTEXT_MANUAL,
				'force_regenerate' => ! empty( $_POST['force_regenerate'] ),
			)
		);

		$redirect = admin_url( 'admin.php?page=ton-registrations&action=view&id=' . $id );

		if ( is_wp_error( $result ) ) {
			wp_safe_redirect( add_query_arg( 'card_error', rawurlencode( $result->get_error_message() ), $redirect ) );
			exit;
		}

		wp_safe_redirect( add_query_arg( 'card_generated', '1', $redirect ) );
		exit;
	}
}