admin_url( 'admin-ajax.php' ), 'testNonce' => wp_create_nonce( 'ageonce_test_connection' ), 'testingText' => __( 'Testing connection...', 'age-verification-with-ageonce' ), 'missingFieldsText' => __( 'Enter Client ID and Client Secret first.', 'age-verification-with-ageonce' ), 'genericErrorText' => __( 'Connection test failed. Please try again.', 'age-verification-with-ageonce' ), 'successTitleText' => __( 'Connection verified', 'age-verification-with-ageonce' ), 'successHintText' => __( 'Next: configure rules and run your first checkout/content test below.', 'age-verification-with-ageonce' ), 'errorTitleText' => __( 'Connection failed', 'age-verification-with-ageonce' ), 'saveNonce' => wp_create_nonce( 'ageonce_save_settings' ), 'saveTitleText' => __( 'Settings saved', 'age-verification-with-ageonce' ), 'saveHintText' => __( 'Changes were saved without page reload.', 'age-verification-with-ageonce' ), 'savingText' => __( 'Saving...', 'age-verification-with-ageonce' ), 'selectedCountText' => __( '%d selected', 'age-verification-with-ageonce' ), 'settingsUpdated' => isset( $_GET['settings-updated'] ) && sanitize_text_field( wp_unslash( $_GET['settings-updated'] ) ) === 'true', ) ); } public function clean_admin_notices() { $screen = get_current_screen(); if ( $screen && $screen->id === 'toplevel_page_ageonce-settings' ) { remove_all_actions( 'admin_notices' ); remove_all_actions( 'all_admin_notices' ); } } public function register_menu_page() { add_menu_page( __( 'AgeOnce Settings', 'age-verification-with-ageonce' ), __( 'AgeOnce', 'age-verification-with-ageonce' ), 'manage_options', 'ageonce-settings', array( $this, 'render_settings_page' ), 'dashicons-shield', 56 ); } public function register_settings() { register_setting( 'ageonce_group', 'ageonce_enabled', array( 'type' => 'string', 'default' => 'no', 'sanitize_callback' => function ( $value ) { return in_array( $value, array( 'yes', 'no' ), true ) ? $value : 'no'; }, ) ); register_setting( 'ageonce_group', 'ageonce_client_id', array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field', ) ); register_setting( 'ageonce_group', 'ageonce_client_secret', array( 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field', ) ); register_setting( 'ageonce_group', 'ageonce_rules', array( 'type' => 'array', 'default' => array(), 'sanitize_callback' => array( $this, 'sanitize_rules' ), ) ); register_setting( 'ageonce_group', 'ageonce_registration_enabled', array( 'type' => 'string', 'default' => 'no', 'sanitize_callback' => function ( $value ) { return in_array( $value, array( 'yes', 'no' ), true ) ? $value : 'no'; }, ) ); register_setting( 'ageonce_group', 'ageonce_registration_age', array( 'type' => 'string', 'default' => '18', 'sanitize_callback' => function ( $value ) { $age = (string) absint( $value ); return in_array( $age, array( '16', '18', '21' ), true ) ? $age : '18'; }, ) ); } /** * Sanitize content rules option (nested array). * * @param mixed $value Raw option value. * @return array Sanitized rules. */ public function sanitize_rules( $value ) { if ( ! is_array( $value ) ) { return array(); } $allowed_ages = array( '16', '18', '21' ); $out = array(); foreach ( $value as $pt_raw => $data ) { $pt = sanitize_key( (string) $pt_raw ); if ( $pt === '' || ! post_type_exists( $pt ) || ! is_array( $data ) ) { continue; } $out[ $pt ] = array(); if ( isset( $data['enabled'] ) ) { $out[ $pt ]['enabled'] = ( $data['enabled'] === 'yes' ) ? 'yes' : 'no'; } if ( isset( $data['manual_lock_enabled'] ) ) { $out[ $pt ]['manual_lock_enabled'] = ( $data['manual_lock_enabled'] === 'yes' ) ? 'yes' : 'no'; } if ( $pt === 'product' ) { $out[ $pt ]['gate_product_pages'] = ( isset( $data['gate_product_pages'] ) && $data['gate_product_pages'] === 'yes' ) ? 'yes' : 'no'; } if ( isset( $data['enabled_ages'] ) && is_array( $data['enabled_ages'] ) ) { $clean_ages = array(); foreach ( $data['enabled_ages'] as $a ) { $a = (string) absint( $a ); if ( in_array( $a, $allowed_ages, true ) ) { $clean_ages[] = $a; } } $clean_ages = array_values( array_unique( $clean_ages ) ); if ( ! empty( $clean_ages ) ) { $out[ $pt ]['enabled_ages'] = $clean_ages; } } if ( isset( $data['ages'] ) && is_array( $data['ages'] ) ) { $tax_for_pt = get_object_taxonomies( $pt, 'names' ); $out[ $pt ]['ages'] = array(); foreach ( $data['ages'] as $age_key => $age_block ) { $age_str = (string) absint( $age_key ); if ( ! in_array( $age_str, $allowed_ages, true ) || ! is_array( $age_block ) ) { continue; } if ( empty( $age_block['taxonomies'] ) || ! is_array( $age_block['taxonomies'] ) ) { continue; } $out[ $pt ]['ages'][ $age_str ] = array( 'taxonomies' => array() ); foreach ( $age_block['taxonomies'] as $tax_raw => $term_ids ) { $tax = sanitize_key( (string) $tax_raw ); if ( $tax === '' || ! in_array( $tax, $tax_for_pt, true ) || ! is_array( $term_ids ) ) { continue; } $valid_ids = array(); foreach ( $term_ids as $tid ) { $tid = absint( $tid ); if ( $tid <= 0 ) { continue; } $term = get_term( $tid, $tax ); if ( $term && ! is_wp_error( $term ) ) { $valid_ids[] = $tid; } } $valid_ids = array_values( array_unique( $valid_ids ) ); if ( ! empty( $valid_ids ) ) { $out[ $pt ]['ages'][ $age_str ]['taxonomies'][ $tax ] = $valid_ids; } } } } } return $out; } private function get_formatted_terms( $taxonomy ) { $terms = get_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => false, 'suppress_filter' => true, ) ); if ( empty( $terms ) || is_wp_error( $terms ) ) { return array(); } $term_list = array(); $id_map = array(); foreach ( $terms as $term ) { $id_map[ $term->term_id ] = $term; } foreach ( $terms as $term ) { $name_parts = array( $term->name ); $current_parent = $term->parent; while ( $current_parent && isset( $id_map[ $current_parent ] ) ) { $parent_term = $id_map[ $current_parent ]; array_unshift( $name_parts, $parent_term->name ); $current_parent = $parent_term->parent; } $term_list[ $term->term_id ] = implode( ' > ', $name_parts ); } asort( $term_list ); return $term_list; } private function has_configured_rules( array $rules ) { foreach ( $rules as $data ) { if ( ! is_array( $data ) || ! isset( $data['enabled'] ) || $data['enabled'] !== 'yes' ) { continue; } if ( ! empty( $data['manual_lock_enabled'] ) && $data['manual_lock_enabled'] === 'yes' ) { return true; } if ( ! empty( $data['ages'] ) && is_array( $data['ages'] ) ) { foreach ( $data['ages'] as $age_block ) { if ( ! empty( $age_block['taxonomies'] ) && is_array( $age_block['taxonomies'] ) ) { foreach ( $age_block['taxonomies'] as $term_ids ) { if ( ! empty( $term_ids ) ) { return true; } } } } } } return false; } private function get_connection_signature( $client_id, $client_secret ) { return hash( 'sha256', (string) $client_id . '|' . (string) $client_secret . '|' . AgeOnce_API::get_redirect_uri() ); } private function is_connection_tested( $client_id, $client_secret ) { if ( trim( (string) $client_id ) === '' || trim( (string) $client_secret ) === '' ) { return false; } return get_option( 'ageonce_connection_test_signature', '' ) === $this->get_connection_signature( $client_id, $client_secret ); } private function render_setup_step( $is_done, $label, $description, $link_url = '', $link_label = '', $step_key = '' ) { $classes = 'ageonce-setup-step' . ( $is_done ? ' is-done' : '' ); $icon = $is_done ? 'dashicons-yes' : 'dashicons-marker'; $step_attr = $step_key ? ' data-ageonce-setup-step="' . esc_attr( $step_key ) . '"' : ''; ?>
  • > ↗
  • array( 'product_cat', 'product_tag' ), 'post' => array( 'category', 'post_tag' ), ); return isset( $primary_map[ $post_type ] ) && in_array( $taxonomy, $primary_map[ $post_type ], true ); } private function render_taxonomy_rule_field( $pt_slug, $age, $tax_slug, $tax_obj, array $terms_list, array $selected_terms ) { $selected_count = count( array_unique( array_map( 'absint', $selected_terms ) ) ); $select_id = 'ageonce-select-' . sanitize_html_class( $pt_slug . '-' . $age . '-' . $tax_slug ); ?>
    __( 'You do not have permission to test this connection.', 'age-verification-with-ageonce' ) ), 403 ); } check_ajax_referer( 'ageonce_test_connection', 'nonce' ); $client_id = isset( $_POST['client_id'] ) ? sanitize_text_field( wp_unslash( $_POST['client_id'] ) ) : ''; $client_secret = isset( $_POST['client_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['client_secret'] ) ) : ''; if ( $client_id === '' || $client_secret === '' ) { wp_send_json_error( array( 'message' => __( 'Enter Client ID and Client Secret first.', 'age-verification-with-ageonce' ) ), 400 ); } $response = wp_remote_post( AGEONCE_API_URL . '/oauth/token', array( 'headers' => array( 'Authorization' => 'Basic ' . base64_encode( $client_id . ':' . $client_secret ), 'Content-Type' => 'application/x-www-form-urlencoded', ), 'body' => array( 'grant_type' => 'authorization_code', 'code' => 'ageonce_connection_test', 'redirect_uri' => AgeOnce_API::get_redirect_uri(), 'state' => 'ageonce_connection_test', ), 'timeout' => 15, ) ); if ( is_wp_error( $response ) ) { wp_send_json_error( array( 'message' => sprintf( /* translators: %s: WordPress HTTP API error message. */ __( 'Unable to reach AgeOnce API: %s', 'age-verification-with-ageonce' ), $response->get_error_message() ), ), 500 ); } $response_code = wp_remote_retrieve_response_code( $response ); $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); $error_code = is_array( $data ) && isset( $data['error'] ) ? sanitize_key( (string) $data['error'] ) : ''; if ( in_array( $error_code, array( 'invalid_grant', 'expired_code', 'code_already_used', 'verification_not_found' ), true ) ) { update_option( 'ageonce_client_id', $client_id, false ); update_option( 'ageonce_client_secret', $client_secret, false ); update_option( 'ageonce_connection_test_signature', $this->get_connection_signature( $client_id, $client_secret ), false ); wp_send_json_success( array( 'message' => __( 'Connection looks good. Credentials were saved and the Redirect URL was accepted by AgeOnce.', 'age-verification-with-ageonce' ) ) ); } if ( $error_code === 'invalid_client' ) { delete_option( 'ageonce_connection_test_signature' ); wp_send_json_error( array( 'message' => __( 'Invalid Client ID or Client Secret. Copy fresh credentials from the AgeOnce dashboard.', 'age-verification-with-ageonce' ) ), 401 ); } if ( $error_code === 'invalid_redirect_uri' ) { delete_option( 'ageonce_connection_test_signature' ); wp_send_json_error( array( 'message' => __( 'AgeOnce rejected the Redirect URL. Copy the Redirect URL below into your AgeOnce dashboard and try again.', 'age-verification-with-ageonce' ) ), 400 ); } wp_send_json_error( array( 'message' => sprintf( /* translators: 1: HTTP status code, 2: API error code. */ __( 'Unexpected API response (%1$d%2$s). Check credentials and try again.', 'age-verification-with-ageonce' ), absint( $response_code ), $error_code ? ', ' . $error_code : '' ), ), $response_code ? $response_code : 500 ); } public function ajax_save_settings() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to save settings.', 'age-verification-with-ageonce' ) ), 403 ); } check_ajax_referer( 'ageonce_save_settings', 'nonce' ); $form_data = isset( $_POST['form_data'] ) ? (string) wp_unslash( $_POST['form_data'] ) : ''; if ( $form_data === '' ) { wp_send_json_error( array( 'message' => __( 'No form data received.', 'age-verification-with-ageonce' ) ), 400 ); } $parsed = array(); parse_str( $form_data, $parsed ); $enabled = isset( $parsed['ageonce_enabled'] ) && $parsed['ageonce_enabled'] === 'yes' ? 'yes' : 'no'; $client_id = isset( $parsed['ageonce_client_id'] ) ? sanitize_text_field( (string) $parsed['ageonce_client_id'] ) : ''; $client_secret = isset( $parsed['ageonce_client_secret'] ) ? sanitize_text_field( (string) $parsed['ageonce_client_secret'] ) : ''; $rules = isset( $parsed['ageonce_rules'] ) ? $this->sanitize_rules( $parsed['ageonce_rules'] ) : array(); $registration_enabled = isset( $parsed['ageonce_registration_enabled'] ) && $parsed['ageonce_registration_enabled'] === 'yes' ? 'yes' : 'no'; $registration_age_raw = isset( $parsed['ageonce_registration_age'] ) ? (string) absint( $parsed['ageonce_registration_age'] ) : '18'; $registration_age = in_array( $registration_age_raw, array( '16', '18', '21' ), true ) ? $registration_age_raw : '18'; update_option( 'ageonce_enabled', $enabled, false ); update_option( 'ageonce_client_id', $client_id, false ); update_option( 'ageonce_client_secret', $client_secret, false ); update_option( 'ageonce_rules', $rules, false ); update_option( 'ageonce_registration_enabled', $registration_enabled, false ); update_option( 'ageonce_registration_age', $registration_age, false ); if ( ! $this->is_connection_tested( $client_id, $client_secret ) ) { delete_option( 'ageonce_connection_test_signature' ); } wp_send_json_success( array( 'message' => __( 'Settings saved.', 'age-verification-with-ageonce' ), ) ); } public function render_settings_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $enabled = get_option( 'ageonce_enabled', 'yes' ); $client_id = get_option( 'ageonce_client_id', '' ); $client_secret = get_option( 'ageonce_client_secret', '' ); $saved_rules = (array) get_option( 'ageonce_rules', array() ); $registration_enabled = get_option( 'ageonce_registration_enabled', 'no' ); $registration_age = (string) absint( get_option( 'ageonce_registration_age', '18' ) ); if ( ! in_array( $registration_age, array( '16', '18', '21' ), true ) ) { $registration_age = '18'; } $missing_credentials = ( trim( (string) $client_id ) === '' || trim( (string) $client_secret ) === '' ); $has_rules = $this->has_configured_rules( $saved_rules ); $connection_tested = $this->is_connection_tested( $client_id, $client_secret ); $plugin_base = defined( 'AGEONCE_VERIFICATION_PLUGIN_FILE' ) ? AGEONCE_VERIFICATION_PLUGIN_FILE : dirname( __DIR__ ) . '/ageonce-verification.php'; $onboarding_image_url = plugins_url( 'assets/images/plan.png', $plugin_base ); $has_woocommerce = class_exists( 'WooCommerce' ); $all_post_types = get_post_types( array( 'public' => true ), 'objects' ); unset( $all_post_types['attachment'] ); $ordered_types = array(); $priority_slugs = array( 'product', 'post', 'page' ); foreach ( $priority_slugs as $slug ) { if ( isset( $all_post_types[ $slug ] ) ) { $ordered_types[ $slug ] = $all_post_types[ $slug ]; unset( $all_post_types[ $slug ] ); } } ksort( $all_post_types ); $final_post_types = $ordered_types + $all_post_types; $age_groups = array( '16', '18', '21' ); ?>

      render_setup_step( ! $missing_credentials, __( 'Create account', 'age-verification-with-ageonce' ), __( 'Start free and get OAuth keys.', 'age-verification-with-ageonce' ), '', '', 'account' ); $this->render_setup_step( ! $missing_credentials, __( 'Paste credentials', 'age-verification-with-ageonce' ), __( 'Add Client ID and Secret below.', 'age-verification-with-ageonce' ), '', '', 'credentials' ); $this->render_setup_step( $connection_tested, __( 'Add Redirect URL', 'age-verification-with-ageonce' ), __( 'Copy this callback into AgeOnce.', 'age-verification-with-ageonce' ), '', '', 'redirect' ); $this->render_setup_step( $has_rules, __( 'Protect content', 'age-verification-with-ageonce' ), __( 'Choose products, posts, or pages.', 'age-verification-with-ageonce' ), '', '', 'rules' ); ?>
    <?php esc_attr_e( 'AgeOnce', 'age-verification-with-ageonce' ); ?>

    $pt_obj ) : $taxonomies = get_object_taxonomies( $pt_slug, 'objects' ); $is_enabled = isset( $saved_rules[ $pt_slug ]['enabled'] ) && $saved_rules[ $pt_slug ]['enabled'] === 'yes'; $enabled_ages = isset( $saved_rules[ $pt_slug ]['enabled_ages'] ) ? (array) $saved_rules[ $pt_slug ]['enabled_ages'] : array( '18' ); ?>

    labels->name ); ?>

    ' . esc_html( $age ) . '' ); ?>

    $tax_obj ) : $terms_list = $this->get_formatted_terms( $tax_slug ); if ( empty( $terms_list ) ) { continue; } $selected_terms = isset( $saved_rules[ $pt_slug ]['ages'][ $age ]['taxonomies'][ $tax_slug ] ) ? $saved_rules[ $pt_slug ]['ages'][ $age ]['taxonomies'][ $tax_slug ] : array(); $field = array( 'slug' => $tax_slug, 'object' => $tax_obj, 'terms' => $terms_list, 'selected' => (array) $selected_terms, ); $taxonomy_fields[] = $field; endforeach; foreach ( $taxonomy_fields as $field ) { if ( $this->is_primary_taxonomy( $pt_slug, $field['slug'] ) ) { $primary_taxonomies[] = $field; } else { $advanced_taxonomies[] = $field; } } if ( empty( $primary_taxonomies ) && ! empty( $advanced_taxonomies ) ) { $primary_taxonomies = array_splice( $advanced_taxonomies, 0, 1 ); } ?>
    render_taxonomy_rule_field( $pt_slug, $age, $field['slug'], $field['object'], $field['terms'], $field['selected'] ); } ?>

    render_taxonomy_rule_field( $pt_slug, $age, $field['slug'], $field['object'], $field['terms'], $field['selected'] ); } ?>