<?php /** * Contact form handler for /instantfilter/contact/. * * @package InstantStack */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Allowed subject values => email label. * * @return array<string,string> */ function it_contact_subject_options(): array { return array( 'pre_sales' => __( 'Pre-sales question', 'instantstack' ), 'technical' => __( 'Technical support', 'instantstack' ), 'billing' => __( 'Billing & licensing', 'instantstack' ), 'partnership' => __( 'Partnership', 'instantstack' ), 'other' => __( 'Other', 'instantstack' ), ); } /** * Whether the current request is the contact page. */ function it_is_contact_page(): bool { return is_page() && 'contact' === get_queried_object()->post_name && it_is_instantfilter_context(); } /** * Process form submission on POST. */ function it_contact_form_process(): void { if ( ! it_is_contact_page() || 'POST' !== ( $_SERVER['REQUEST_METHOD'] ?? '' ) ) { return; } if ( empty( $_POST['it_contact_form'] ) ) { return; } $redirect = get_permalink(); if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'it_contact_form' ) ) { wp_safe_redirect( add_query_arg( 'contact', 'error', $redirect ) ); exit; } // Honeypot  bots fill hidden fields. if ( ! empty( $_POST['it_contact_website'] ) ) { wp_safe_redirect( add_query_arg( 'contact', 'sent', $redirect ) ); exit; } $ip_hash = md5( sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? 'unknown' ) ) ); $rate_key = 'it_contact_rate_' . $ip_hash; if ( get_transient( $rate_key ) ) { wp_safe_redirect( add_query_arg( 'contact', 'rate', $redirect ) ); exit; } $name = isset( $_POST['it_contact_name'] ) ? sanitize_text_field( wp_unslash( $_POST['it_contact_name'] ) ) : ''; $email = isset( $_POST['it_contact_email'] ) ? sanitize_email( wp_unslash( $_POST['it_contact_email'] ) ) : ''; $subject = isset( $_POST['it_contact_subject'] ) ? sanitize_key( wp_unslash( $_POST['it_contact_subject'] ) ) : ''; $license = isset( $_POST['it_contact_license'] ) ? sanitize_email( wp_unslash( $_POST['it_contact_license'] ) ) : ''; $site_url = isset( $_POST['it_contact_site'] ) ? esc_url_raw( wp_unslash( $_POST['it_contact_site'] ) ) : ''; $message = isset( $_POST['it_contact_message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['it_contact_message'] ) ) : ''; $consent = ! empty( $_POST['it_contact_consent'] ); $subjects = it_contact_subject_options(); $errors = array(); if ( '' === $name || strlen( $name ) < 2 ) { $errors[] = 'name'; } if ( ! is_email( $email ) ) { $errors[] = 'email'; } if ( ! isset( $subjects[ $subject ] ) ) { $errors[] = 'subject'; } if ( strlen( $message ) < 20 ) { $errors[] = 'message'; } if ( ! $consent ) { $errors[] = 'consent'; } if ( $license && ! is_email( $license ) ) { $errors[] = 'license'; } if ( $site_url && ! wp_http_validate_url( $site_url ) ) { $errors[] = 'site'; } if ( $errors ) { set_transient( 'it_contact_form_' . $ip_hash, array( 'errors' => $errors, 'values' => array( 'name' => $name, 'email' => $email, 'subject' => $subject, 'license' => $license, 'site' => $site_url, 'message' => $message, ), ), 300 ); wp_safe_redirect( add_query_arg( 'contact', 'invalid', $redirect ) ); exit; } $subject_label = $subjects[ $subject ]; $mail_subject = sprintf( '[InstantFilter Contact] %s  %s', $subject_label, $name ); $body_lines = array( 'Subject: ' . $subject_label, 'Name: ' . $name, 'Email: ' . $email, ); if ( $license ) { $body_lines[] = 'License email: ' . $license; } if ( $site_url ) { $body_lines[] = 'Site URL: ' . $site_url; } $body_lines[] = ''; $body_lines[] = 'Message:'; $body_lines[] = $message; $body_lines[] = ''; $body_lines[] = '---'; $body_lines[] = 'Sent from: ' . home_url( '/instantfilter/contact/' ); $body_lines[] = 'IP: ' . sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) ); $headers = array( 'Content-Type: text/plain; charset=UTF-8', 'Reply-To: ' . $name . ' <' . $email . '>', ); $sent = wp_mail( 'tom@instantstack.co', $mail_subject, implode( "\n", $body_lines ), $headers ); if ( ! $sent ) { wp_safe_redirect( add_query_arg( 'contact', 'error', $redirect ) ); exit; } set_transient( $rate_key, 1, MINUTE_IN_SECONDS ); delete_transient( 'it_contact_form_' . $ip_hash ); wp_safe_redirect( add_query_arg( 'contact', 'sent', $redirect ) ); exit; } add_action( 'template_redirect', 'it_contact_form_process' ); /** * Flash data after redirect. * * @return array{errors?:array<int,string>,values?:array<string,string>}|null */ function it_contact_form_flash(): ?array { $ip_hash = md5( sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? 'unknown' ) ) ); $data = get_transient( 'it_contact_form_' . $ip_hash ); return is_array( $data ) ? $data : null; } /** * Status notice HTML for the contact page. */ function it_contact_form_notice_html(): string { if ( ! isset( $_GET['contact'] ) ) { return ''; } $status = sanitize_key( wp_unslash( $_GET['contact'] ) ); switch ( $status ) { case 'sent': return '<div class="is-form-notice is-form-notice--success" role="status">' . esc_html__( 'Thanks  your message was sent. We usually reply within one business day (Central European Time).', 'instantstack' ) . '</div>'; case 'rate': return '<div class="is-form-notice is-form-notice--warning" role="status">' . esc_html__( 'Please wait a moment before sending another message.', 'instantstack' ) . '</div>'; case 'invalid': return '<div class="is-form-notice is-form-notice--error" role="alert">' . esc_html__( 'Some fields need attention. Check the highlighted items below.', 'instantstack' ) . '</div>'; case 'error': default: return '<div class="is-form-notice is-form-notice--error" role="alert">' . esc_html__( 'Something went wrong while sending your message. Please try again or email tom@instantstack.co directly.', 'instantstack' ) . '</div>'; } } /** * Render the contact form. */ function it_contact_form_html(): string { $flash = it_contact_form_flash(); $errors = isset( $flash['errors'] ) && is_array( $flash['errors'] ) ? $flash['errors'] : array(); $values = isset( $flash['values'] ) && is_array( $flash['values'] ) ? $flash['values'] : array(); $subjects = it_contact_subject_options(); $val = static function ( string $key ) use ( $values ): string { return isset( $values[ $key ] ) ? (string) $values[ $key ] : ''; }; $err = static function ( string $key ) use ( $errors ): string { return in_array( $key, $errors, true ) ? ' is-form-field--error' : ''; }; ob_start(); ?> <form class="is-contact-form" method="post" action="<?php echo esc_url( get_permalink() ); ?>" novalidate> <input type="hidden" name="it_contact_form" value="1" /> <?php wp_nonce_field( 'it_contact_form' ); ?> <div class="is-form-field<?php echo esc_attr( $err( 'name' ) ); ?>"> <label class="is-form-label" for="it_contact_name"><?php esc_html_e( 'Your name', 'instantstack' ); ?> <span aria-hidden="true">*</span></label> <input class="is-form-input" type="text" id="it_contact_name" name="it_contact_name" value="<?php echo esc_attr( $val( 'name' ) ); ?>" required autocomplete="name" /> </div> <div class="is-form-field<?php echo esc_attr( $err( 'email' ) ); ?>"> <label class="is-form-label" for="it_contact_email"><?php esc_html_e( 'Email address', 'instantstack' ); ?> <span aria-hidden="true">*</span></label> <input class="is-form-input" type="email" id="it_contact_email" name="it_contact_email" value="<?php echo esc_attr( $val( 'email' ) ); ?>" required autocomplete="email" /> </div> <div class="is-form-field<?php echo esc_attr( $err( 'subject' ) ); ?>"> <label class="is-form-label" for="it_contact_subject"><?php esc_html_e( 'Topic', 'instantstack' ); ?> <span aria-hidden="true">*</span></label> <select class="is-form-select" id="it_contact_subject" name="it_contact_subject" required> <option value="" disabled <?php selected( '', $val( 'subject' ) ); ?>><?php esc_html_e( 'Select a topic& ', 'instantstack' ); ?></option> <?php foreach ( $subjects as $key => $label ) : ?> <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $val( 'subject' ), $key ); ?>><?php echo esc_html( $label ); ?></option> <?php endforeach; ?> </select> </div> <div class="is-form-row"> <div class="is-form-field<?php echo esc_attr( $err( 'license' ) ); ?>"> <label class="is-form-label" for="it_contact_license"><?php esc_html_e( 'License email', 'instantstack' ); ?> <span class="is-form-label__hint"><?php esc_html_e( '(optional)', 'instantstack' ); ?></span></label> <input class="is-form-input" type="email" id="it_contact_license" name="it_contact_license" value="<?php echo esc_attr( $val( 'license' ) ); ?>" autocomplete="off" placeholder="you@company.com" /> </div> <div class="is-form-field<?php echo esc_attr( $err( 'site' ) ); ?>"> <label class="is-form-label" for="it_contact_site"><?php esc_html_e( 'Site URL', 'instantstack' ); ?> <span class="is-form-label__hint"><?php esc_html_e( '(optional)', 'instantstack' ); ?></span></label> <input class="is-form-input" type="url" id="it_contact_site" name="it_contact_site" value="<?php echo esc_attr( $val( 'site' ) ); ?>" placeholder="https://yourshop.com" /> </div> </div> <div class="is-form-field<?php echo esc_attr( $err( 'message' ) ); ?>"> <label class="is-form-label" for="it_contact_message"><?php esc_html_e( 'Message', 'instantstack' ); ?> <span aria-hidden="true">*</span></label> <textarea class="is-form-textarea" id="it_contact_message" name="it_contact_message" rows="6" required><?php echo esc_textarea( $val( 'message' ) ); ?></textarea> <p class="is-form-hint"><?php esc_html_e( 'Include your InstantFilter and WooCommerce version numbers for technical questions.', 'instantstack' ); ?></p> </div> <div class="is-form-field is-form-field--honeypot" aria-hidden="true"> <label class="is-form-label" for="it_contact_website"><?php esc_html_e( 'Website', 'instantstack' ); ?></label> <input class="is-form-input" type="text" id="it_contact_website" name="it_contact_website" tabindex="-1" autocomplete="off" /> </div> <div class="is-form-field is-form-field--checkbox<?php echo esc_attr( $err( 'consent' ) ); ?>"> <label class="is-form-checkbox"> <input type="checkbox" name="it_contact_consent" value="1" required /> <span><?php printf( /* translators: %s: privacy policy URL */ wp_kses_post( __( 'I agree that InstantStack may process this message to respond to my request. See our <a href="%s">Privacy Policy</a>.', 'instantstack' ) ), esc_url( home_url( '/privacy/' ) ) ); ?></span> </label> </div> <div class="is-form-actions"> <button type="submit" class="is-btn-primary"><?php esc_html_e( 'Send message', 'instantstack' ); ?></button> </div> </form> <?php return (string) ob_get_clean(); }