Newer
Older
tonitalia-pluginsoci / includes / class-registration-form.php
<?php
/**
 * Registration form shortcode.
 *
 * @package TonItaliaRegistration
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class TON_Reg_Registration_Form
 */
class TON_Reg_Registration_Form {

	/**
	 * Whether the registration form was rendered on the current request.
	 *
	 * @var bool
	 */
	private static $form_rendered = false;

	/**
	 * Register hooks.
	 */
	public static function register() {
		add_shortcode( 'ton_registration_form', array( __CLASS__, 'render_shortcode' ) );
		add_shortcode( 'ton_registration_success', array( __CLASS__, 'render_success_shortcode' ) );
		add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
	}

	/**
	 * Enqueue form assets when the page content includes the shortcode.
	 */
	public static function enqueue_assets() {
		if ( self::page_has_shortcode() ) {
			self::enqueue_form_assets();
		}
	}

	/**
	 * Ensure assets are loaded whenever the form shortcode is actually rendered.
	 */
	public static function ensure_form_assets() {
		self::enqueue_form_assets();
	}

	/**
	 * @return void
	 */
	private static function enqueue_form_assets() {
		static $enqueued = false;
		if ( $enqueued ) {
			return;
		}
		$enqueued = true;

		$form_css_path = TON_REG_PLUGIN_DIR . 'assets/css/form.css';
		$form_js_path  = TON_REG_PLUGIN_DIR . 'assets/js/form.js';

		wp_enqueue_style(
			'ton-reg-form',
			TON_REG_PLUGIN_URL . 'assets/css/form.css',
			array(),
			file_exists( $form_css_path ) ? (string) filemtime( $form_css_path ) : TON_REG_VERSION
		);
		wp_enqueue_script(
			'ton-reg-form',
			TON_REG_PLUGIN_URL . 'assets/js/form.js',
			array(),
			file_exists( $form_js_path ) ? (string) filemtime( $form_js_path ) : TON_REG_VERSION,
			true
		);

		wp_localize_script(
			'ton-reg-form',
			'tonRegForm',
			array(
				'msgRequired'   => __( 'Compila questo campo.', 'ton-italia-registration' ),
				'msgEmail'      => __( 'Inserisci un indirizzo email valido.', 'ton-italia-registration' ),
				'msgCf'         => __( 'Codice fiscale non valido (16 caratteri).', 'ton-italia-registration' ),
				'msgCap'        => __( 'Il CAP deve essere di 5 cifre.', 'ton-italia-registration' ),
				'msgProvincia'  => __( 'Inserisci la sigla provincia (2 lettere).', 'ton-italia-registration' ),
				'msgDate'       => __( 'Inserisci la data nel formato gg-mm-aaaa.', 'ton-italia-registration' ),
				'msgDateFuture' => __( 'La data di dichiarazione non può essere futura.', 'ton-italia-registration' ),
				'msgCheckbox'   => __( 'Devi selezionare questa casella per continuare.', 'ton-italia-registration' ),
				'msgCaptcha'    => __( 'Inserisci la risposta alla verifica anti-spam.', 'ton-italia-registration' ),
			)
		);
	}

	/**
	 * Mark form as rendered and attach footer fallback script.
	 *
	 * @return void
	 */
	private static function mark_form_rendered() {
		if ( self::$form_rendered ) {
			return;
		}

		self::$form_rendered = true;
		self::ensure_form_assets();
		add_action( 'wp_footer', array( __CLASS__, 'print_form_field_normalization_script' ), 20 );
	}

	/**
	 * Inline fallback so field normalization works even if external JS is blocked or cached stale.
	 *
	 * @return void
	 */
	public static function print_form_field_normalization_script() {
		if ( ! self::$form_rendered ) {
			return;
		}
		?>
		<script id="ton-reg-form-normalize">
		(function () {
			function padPart(part) {
				var num = parseInt(part, 10);
				return part.length === 1 && num >= 1 && num <= 9 ? '0' + part : part;
			}

			function normalizeDate(value) {
				var trimmed = (value || '').trim().replace(/[./]/g, '-').replace(/\s+/g, '-').replace(/-+/g, '-');
				var match = trimmed.match(/^(\d{1,2})-(\d{1,2})-(\d{4})$/);
				return match ? padPart(match[1]) + '-' + padPart(match[2]) + '-' + match[3] : trimmed;
			}

			function normalizeCf(value) {
				return (value || '').trim().toUpperCase().replace(/\s+/g, '');
			}

			function normalizeProv(value) {
				return (value || '').trim().toUpperCase();
			}

			function normalizeInput(input) {
				if (!input || !input.name) {
					return;
				}

				if ('data_nascita' === input.name || 'data_dichiarazione' === input.name) {
					input.value = normalizeDate(input.value);
					return;
				}

				if ('codice_fiscale' === input.name) {
					input.value = normalizeCf(input.value);
					return;
				}

				if ('provincia_nascita' === input.name || 'provincia_residenza' === input.name) {
					input.value = normalizeProv(input.value);
				}
			}

			function bindForm(form) {
				if (!form || '1' === form.getAttribute('data-ton-reg-normalize')) {
					return;
				}

				form.setAttribute('data-ton-reg-normalize', '1');

				form.addEventListener('input', function (event) {
					var input = event.target;
					if (!input.classList || !input.classList.contains('ton-reg-form__input')) {
						return;
					}

					if ('codice_fiscale' === input.name && input.value) {
						input.value = normalizeCf(input.value);
					}

					if (('data_nascita' === input.name || 'data_dichiarazione' === input.name) && input.value) {
						input.value = input.value.replace(/[./]/g, '-');
					}

					if (('provincia_nascita' === input.name || 'provincia_residenza' === input.name) && input.value) {
						input.value = normalizeProv(input.value);
					}
				});

				form.addEventListener('focusout', function (event) {
					var input = event.target;
					if (!input.classList || !input.classList.contains('ton-reg-form__input')) {
						return;
					}

					normalizeInput(input);
				}, true);
			}

			function init() {
				document.querySelectorAll('.ton-reg-form__body').forEach(bindForm);
				if (typeof window.tonRegFormInit === 'function') {
					window.tonRegFormInit();
				}
			}

			if (document.readyState === 'loading') {
				document.addEventListener('DOMContentLoaded', init);
			} else {
				init();
			}
		})();
		</script>
		<?php
	}

	/**
	 * @return bool
	 */
	private static function page_has_shortcode() {
		global $post;
		if ( $post instanceof WP_Post ) {
			if ( self::content_has_shortcode( $post->post_content, 'ton_registration_form' ) ) {
				return true;
			}
			if ( self::content_has_shortcode( $post->post_content, 'ton_registration_success' ) ) {
				return true;
			}
		}

		return isset( $_GET['ton_reg'] ) && in_array( $_GET['ton_reg'], array( 'success', 'error' ), true );
	}

	/**
	 * @param string $content Post content.
	 * @param string $tag     Shortcode tag.
	 * @return bool
	 */
	private static function content_has_shortcode( $content, $tag ) {
		if ( ! is_string( $content ) || '' === $content ) {
			return false;
		}

		if ( has_shortcode( $content, $tag ) ) {
			return true;
		}

		if ( false !== strpos( $content, '[' . $tag ) ) {
			return true;
		}

		if ( function_exists( 'parse_blocks' ) && false !== strpos( $content, '<!-- wp:' ) ) {
			return self::blocks_contain_shortcode( parse_blocks( $content ), $tag );
		}

		return false;
	}

	/**
	 * @param array<int,array<string,mixed>> $blocks Parsed blocks.
	 * @param string                         $tag    Shortcode tag.
	 * @return bool
	 */
	private static function blocks_contain_shortcode( $blocks, $tag ) {
		foreach ( $blocks as $block ) {
			$block_name = $block['blockName'] ?? '';
			$inner_html = $block['innerHTML'] ?? '';

			if ( 'core/shortcode' === $block_name && false !== strpos( $inner_html, '[' . $tag ) ) {
				return true;
			}

			if ( is_string( $inner_html ) && has_shortcode( $inner_html, $tag ) ) {
				return true;
			}

			if ( ! empty( $block['innerBlocks'] ) && self::blocks_contain_shortcode( $block['innerBlocks'], $tag ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * HTML shown after a successful registration.
	 *
	 * @return string
	 */
	public static function get_success_message_html() {
		$message = get_option( 'ton_reg_success_message', false );
		if ( false === $message || '' === trim( wp_strip_all_tags( (string) $message ) ) ) {
			$defaults = TON_Reg_Defaults::options();
			$message  = $defaults['ton_reg_success_message'];
		}

		return (string) $message;
	}

	/**
	 * @param array<string,string> $atts Attributes.
	 * @return string
	 */
	public static function render_shortcode( $atts ) {
		self::ensure_form_assets();

		$atts = shortcode_atts(
			array( 'class' => '' ),
			$atts,
			'ton_registration_form'
		);

		// Fallback: messaggio sulla stessa pagina del form se non è configurata una pagina di conferma.
		if (
			! (int) get_option( 'ton_reg_success_page_id', 0 )
			&& isset( $_GET['ton_reg'] )
			&& 'success' === $_GET['ton_reg']
		) {
			return self::render_success_message( $atts['class'] );
		}

		$token     = TON_Reg_Captcha::create_token();
		$challenge = TON_Reg_Captcha::generate_challenge( $token );

		ob_start();
		self::render_form( $atts['class'], $token, $challenge );
		return ob_get_clean();
	}

	/**
	 * @param array<string,string> $atts Attributes.
	 * @return string
	 */
	public static function render_success_shortcode( $atts ) {
		$atts = shortcode_atts(
			array( 'class' => '' ),
			$atts,
			'ton_registration_success'
		);

		if ( ! isset( $_GET['ton_reg'] ) || 'success' !== $_GET['ton_reg'] ) {
			return '';
		}

		return self::render_success_message( $atts['class'] );
	}

	/**
	 * @param string $extra_class Optional CSS class.
	 * @return string
	 */
	public static function render_success_message( $extra_class = '' ) {
		$html  = self::get_success_message_html();
		$class = 'ton-reg-form ton-reg-form--success' . ( $extra_class ? ' ' . esc_attr( $extra_class ) : '' );

		return '<div class="' . esc_attr( trim( $class ) ) . '" id="ton-reg-success" role="status" aria-live="polite">' . wp_kses_post( $html ) . '</div>';
	}

	/**
	 * @param string $type success|error.
	 * @return string
	 */
	private static function render_message( $type ) {
		if ( 'success' === $type ) {
			return self::render_success_message();
		}
		$msg = isset( $_GET['ton_msg'] ) ? sanitize_text_field( wp_unslash( rawurldecode( $_GET['ton_msg'] ) ) ) : '';
		return '<div class="ton-reg-form__alert ton-reg-form__alert--error" role="alert"><p>' . esc_html( $msg ) . '</p></div>';
	}

	/**
	 * @param string               $extra_class Extra CSS class.
	 * @param string               $token       Form token.
	 * @param array<string,mixed>  $challenge   Captcha challenge.
	 */
	private static function render_form( $extra_class, $token, $challenge ) {
		self::mark_form_rendered();

		$payment = self::replace_payment_placeholders( get_option( 'ton_reg_payment_instructions', '' ) );
		$wrapper_class = 'ton-reg-form' . ( $extra_class ? ' ' . esc_attr( $extra_class ) : '' );
		?>
		<div class="<?php echo esc_attr( $wrapper_class ); ?>">
		<?php if ( isset( $_GET['ton_reg'] ) && 'error' === $_GET['ton_reg'] ) : ?>
			<?php echo self::render_message( 'error' ); ?>
		<?php endif; ?>
		<form class="ton-reg-form__body" method="post" action="<?php echo esc_url( TON_Reg_Registration_Handler::get_form_page_action_url() ); ?>">
			<input type="hidden" name="ton_reg_frontend" value="1" />
			<?php
			$form_page_id = TON_Reg_Registration_Handler::get_form_page_id();
			if ( $form_page_id ) :
				?>
			<input type="hidden" name="ton_reg_page_id" value="<?php echo (int) $form_page_id; ?>" />
			<input type="hidden" name="ton_reg_redirect" value="<?php echo esc_url( get_permalink( $form_page_id ) ); ?>" />
			<?php endif; ?>
			<?php wp_nonce_field( TON_Reg_Captcha::NONCE_ACTION, 'ton_reg_nonce' ); ?>
			<input type="hidden" name="<?php echo esc_attr( TON_Reg_Captcha::FIELD_TOKEN ); ?>" value="<?php echo esc_attr( $token ); ?>" />

			<p class="ton-reg-form__intro"><?php esc_html_e( 'Domanda di ammissione a socio e contestuale tesseramento', 'ton-italia-registration' ); ?></p>

			<fieldset class="ton-reg-form__fieldset ton-reg-form__payment">
				<legend><?php esc_html_e( 'Quota associativa', 'ton-italia-registration' ); ?></legend>
				<div class="ton-reg-form__richtext"><?php echo wp_kses_post( $payment ); ?></div>
				<label class="ton-reg-form__checkbox">
					<input type="checkbox" name="bonifico_effettuato" value="1" />
					<span><?php esc_html_e( 'Ho effettuato il bonifico (o provvederò a breve)', 'ton-italia-registration' ); ?></span>
				</label>
			</fieldset>

			<fieldset class="ton-reg-form__fieldset">
				<legend><?php esc_html_e( 'Dati anagrafici', 'ton-italia-registration' ); ?></legend>
				<div class="ton-reg-form__grid">
					<?php self::field_text( 'cognome', __( 'Cognome', 'ton-italia-registration' ), true ); ?>
					<?php self::field_text( 'nome', __( 'Nome', 'ton-italia-registration' ), true ); ?>
					<?php self::field_text( 'luogo_nascita', __( 'Nato/a a', 'ton-italia-registration' ), true ); ?>
					<?php self::field_text( 'provincia_nascita', __( 'Prov.', 'ton-italia-registration' ), true, '2', 'text', '', '[A-Za-z]{2}' ); ?>
					<?php self::field_text( 'data_nascita', __( 'Data di nascita', 'ton-italia-registration' ), true, '10', 'text', '', '[0-9]{1,2}[-/.][0-9]{1,2}[-/.][0-9]{4}' ); ?>
					<?php self::field_text( 'codice_fiscale', __( 'Codice fiscale', 'ton-italia-registration' ), true, '16', 'text', '', '[A-Za-z]{6}[0-9]{2}[A-Za-z][0-9]{2}[A-Za-z][0-9]{3}[A-Za-z]' ); ?>
					<?php self::field_text( 'comune_residenza', __( 'Residente in', 'ton-italia-registration' ), true ); ?>
					<?php self::field_text( 'provincia_residenza', __( 'Prov.', 'ton-italia-registration' ), true, '2', 'text', '', '[A-Za-z]{2}' ); ?>
					<?php self::field_text( 'indirizzo', __( 'Via/Piazza', 'ton-italia-registration' ), true ); ?>
					<?php self::field_text( 'numero_civico', __( 'n.', 'ton-italia-registration' ), true, '10' ); ?>
					<?php self::field_text( 'cap', __( 'CAP', 'ton-italia-registration' ), true, '5', 'text', '', '[0-9]{5}' ); ?>
					<?php self::field_text( 'telefono', __( 'Telefono / Cellulare', 'ton-italia-registration' ), true, '', 'tel' ); ?>
					<?php self::field_text( 'email', __( 'Email', 'ton-italia-registration' ), true, '', 'email' ); ?>
				</div>
			</fieldset>

			<fieldset class="ton-reg-form__fieldset">
				<legend><?php esc_html_e( 'Dichiarazione', 'ton-italia-registration' ); ?></legend>
				<p class="ton-reg-form__declaration"><?php echo esc_html( get_option( 'ton_reg_statuto_declaration', '' ) ); ?></p>
				<div class="ton-reg-form__grid ton-reg-form__grid--narrow">
					<?php self::field_text( 'luogo_dichiarazione', __( 'Luogo', 'ton-italia-registration' ), true ); ?>
					<?php self::field_text( 'data_dichiarazione', __( 'Data', 'ton-italia-registration' ), true, '10', 'text', '', '[0-9]{1,2}[-/.][0-9]{1,2}[-/.][0-9]{4}' ); ?>
				</div>
				<label class="ton-reg-form__checkbox ton-reg-form__checkbox--required">
					<input type="checkbox" name="accettazione_statuto" value="1" required />
					<span><?php esc_html_e( 'Dichiaro di aver letto e accettato quanto sopra (sostitutivo della firma).', 'ton-italia-registration' ); ?></span>
				</label>
				<p class="ton-reg-form__minor"><?php echo esc_html( get_option( 'ton_reg_minor_notice', '' ) ); ?></p>
			</fieldset>

			<fieldset class="ton-reg-form__fieldset">
				<legend><?php esc_html_e( 'Privacy', 'ton-italia-registration' ); ?></legend>
				<div class="ton-reg-form__richtext"><?php echo wp_kses_post( get_option( 'ton_reg_privacy_notice', '' ) ); ?></div>
				<label class="ton-reg-form__checkbox ton-reg-form__checkbox--required">
					<input type="checkbox" name="consenso_privacy" value="1" required />
					<span><?php esc_html_e( 'Acconsento al trattamento dei dati personali come da informativa (sostitutivo della firma per consenso).', 'ton-italia-registration' ); ?></span>
				</label>
				<label class="ton-reg-form__checkbox<?php echo '1' === get_option( 'ton_reg_newsletter_required', '0' ) ? ' ton-reg-form__checkbox--required' : ''; ?>">
					<input type="checkbox" name="consenso_newsletter" value="1" <?php echo '1' === get_option( 'ton_reg_newsletter_required', '0' ) ? 'required' : ''; ?> />
					<span><?php echo esc_html( get_option( 'ton_reg_newsletter_label', '' ) ); ?></span>
				</label>
			</fieldset>

			<?php if ( TON_Reg_Captcha::is_enabled() ) : ?>
			<fieldset class="ton-reg-form__fieldset ton-reg-form__captcha">
				<legend><?php esc_html_e( 'Verifica anti-spam', 'ton-italia-registration' ); ?></legend>
				<label class="ton-reg-form__honeypot" aria-hidden="true">
					<?php esc_html_e( 'Lascia vuoto questo campo', 'ton-italia-registration' ); ?>
					<input type="text" name="<?php echo esc_attr( TON_Reg_Captcha::FIELD_HONEYPOT ); ?>" value="" tabindex="-1" autocomplete="off" />
				</label>
				<p class="ton-reg-form__field">
					<label for="ton_reg_captcha_answer"><?php echo esc_html( $challenge['question'] ); ?> <span class="required">*</span></label>
					<input type="number" id="ton_reg_captcha_answer" class="ton-reg-form__input" name="<?php echo esc_attr( TON_Reg_Captcha::FIELD_MATH ); ?>" required inputmode="numeric" placeholder="<?php esc_attr_e( 'Inserisci il risultato', 'ton-italia-registration' ); ?>" />
				</p>
			</fieldset>
			<?php endif; ?>

			<p class="ton-reg-form__submit">
				<button type="submit" class="ton-reg-form__button"><?php esc_html_e( 'Invia domanda di iscrizione', 'ton-italia-registration' ); ?></button>
			</p>
		</form>
		</div>
		<?php
	}

	/**
	 * @param string $name        Field name.
	 * @param string $label       Label.
	 * @param bool   $required    Required.
	 * @param string $maxlength   Max length.
	 * @param string $type        Input type.
	 * @param string $placeholder Placeholder override.
	 * @param string $pattern     HTML pattern.
	 */
	private static function field_text( $name, $label, $required = false, $maxlength = '', $type = 'text', $placeholder = '', $pattern = '' ) {
		$id          = 'ton_reg_' . $name;
		$holders     = self::placeholders();
		$placeholder = '' !== $placeholder ? $placeholder : ( $holders[ $name ] ?? '' );
		?>
		<p class="ton-reg-form__field" data-field="<?php echo esc_attr( $name ); ?>">
			<label for="<?php echo esc_attr( $id ); ?>"><?php echo esc_html( $label ); ?><?php if ( $required ) : ?> <span class="required">*</span><?php endif; ?></label>
			<input
				class="ton-reg-form__input"
				type="<?php echo esc_attr( $type ); ?>"
				id="<?php echo esc_attr( $id ); ?>"
				name="<?php echo esc_attr( $name ); ?>"
				value="<?php echo esc_attr( self::old( $name ) ); ?>"
				<?php echo $required ? 'required' : ''; ?>
				<?php echo $maxlength ? 'maxlength="' . esc_attr( $maxlength ) . '"' : ''; ?>
				<?php echo $placeholder ? 'placeholder="' . esc_attr( $placeholder ) . '"' : ''; ?>
				<?php echo $pattern ? 'pattern="' . esc_attr( $pattern ) . '"' : ''; ?>
				<?php echo 'email' === $type ? 'autocomplete="email"' : ''; ?>
			/>
			<span class="ton-reg-form__error" role="alert" hidden></span>
		</p>
		<?php
	}

	/**
	 * @return array<string, string>
	 */
	private static function placeholders() {
		return array(
			'cognome'              => __( 'Inserisci il cognome', 'ton-italia-registration' ),
			'nome'                 => __( 'Inserisci il nome', 'ton-italia-registration' ),
			'luogo_nascita'        => __( 'Comune o stato di nascita', 'ton-italia-registration' ),
			'provincia_nascita'    => __( 'Sigla provincia, es. VR', 'ton-italia-registration' ),
			'data_nascita'         => __( 'gg-mm-aaaa', 'ton-italia-registration' ),
			'codice_fiscale'       => __( '16 caratteri alfanumerici', 'ton-italia-registration' ),
			'comune_residenza'     => __( 'Comune di residenza', 'ton-italia-registration' ),
			'provincia_residenza'  => __( 'Sigla provincia, es. MI', 'ton-italia-registration' ),
			'indirizzo'            => __( 'Via o piazza', 'ton-italia-registration' ),
			'numero_civico'        => __( 'Numero civico', 'ton-italia-registration' ),
			'cap'                  => __( '5 cifre', 'ton-italia-registration' ),
			'telefono'             => __( 'Telefono o cellulare', 'ton-italia-registration' ),
			'email'                => __( 'nome@esempio.it', 'ton-italia-registration' ),
			'luogo_dichiarazione'  => __( 'Comune della dichiarazione', 'ton-italia-registration' ),
			'data_dichiarazione'   => __( 'gg-mm-aaaa', 'ton-italia-registration' ),
		);
	}

	/**
	 * @param string $key Field key.
	 * @return string
	 */
	private static function old( $key ) {
		// No session storage for security; empty on fresh load.
		return '';
	}

	/**
	 * @param string $html HTML template.
	 * @return string
	 */
	private static function replace_payment_placeholders( $html ) {
		return strtr(
			$html,
			array(
				'{quota}'        => esc_html( get_option( 'ton_reg_membership_fee', '10,00' ) ),
				'{anno}'         => esc_html( get_option( 'ton_reg_social_year', '2026' ) ),
				'{beneficiario}' => esc_html( get_option( 'ton_reg_beneficiary', '' ) ),
				'{iban}'         => esc_html( get_option( 'ton_reg_iban', '' ) ),
				'{nome}'         => '',
				'{cognome}'      => '',
			)
		);
	}
}