'professional-application', 'student' => 'student-application', ); const SHORTCODE = 'aieo_pro_signup'; const ACTION = 'aieo_pro_application'; const RESULT = 'aieo_pro_result_'; const APPROVED = 'approved'; const DECLINED = 'declined'; public static function register() { static $done = false; if ( $done ) { return; } $done = true; add_shortcode( self::SHORTCODE, array( __CLASS__, 'shortcode' ) ); add_action( 'admin_post_' . self::ACTION, array( __CLASS__, 'decide' ) ); } private static function setting( $key, $default = '' ) { return class_exists( 'AIEO_DMM_Settings' ) ? AIEO_DMM_Settings::get( $key, $default ) : $default; } public static function provider() { $chosen = (string) self::setting( 'pro_signup_form', '' ); if ( in_array( $chosen, array( 'aieo', 'gravity' ), true ) ) { return $chosen; } return (int) self::setting( 'gf_registration_form_id', 0 ) > 0 && function_exists( 'gravity_form' ) ? 'gravity' : 'aieo'; } public static function role( $kind ) { if ( ! isset( self::KINDS[ $kind ] ) ) { return ''; } $enabled = array_values( array_filter( array_map( 'strval', (array) self::setting( 'enabled_roles', array() ) ), array( 'AIEO_Safe_Roles', 'is_safe' ) ) ); $chosen = sanitize_key( (string) self::setting( 'pro_signup_role_' . $kind, '' ) ); if ( '' !== $chosen ) { return in_array( $chosen, $enabled, true ) ? $chosen : ''; } foreach ( $enabled as $slug ) { if ( ( false !== strpos( $slug, 'student' ) ) === ( 'student' === $kind ) ) { return $slug; } } return ''; } public static function kind_of_form( $form_id ) { $slug = (string) get_post_field( 'post_name', (int) $form_id ); $kind = array_search( $slug, self::KINDS, true ); return false === $kind ? '' : (string) $kind; } public static function form_id( $kind, $create = true ) { if ( ! isset( self::KINDS[ $kind ] ) || ! class_exists( 'AIEO_Form_CPT' ) ) { return 0; } $slug = self::KINDS[ $kind ]; $posts = get_posts( array( 'post_type' => AIEO_Form_CPT::POST_TYPE, 'name' => $slug, 'post_status' => array( 'publish', 'draft', 'pending', 'private', 'future' ), 'numberposts' => 1, 'no_found_rows' => true, 'suppress_filters' => true, ) ); if ( $posts ) { return 'publish' === $posts[0]->post_status ? (int) $posts[0]->ID : 0; } $premade = $create && class_exists( 'AIEO_Form_Premade' ) ? AIEO_Form_Premade::get( $slug ) : null; if ( ! $premade ) { return 0; } $lock = 'aieo_pro_signup_making_' . $kind; if ( ! add_option( $lock, time(), '', false ) ) { if ( time() - (int) get_option( $lock, 0 ) < MINUTE_IN_SECONDS ) { return 0; } update_option( $lock, time(), false ); } $id = wp_insert_post( array( 'post_type' => AIEO_Form_CPT::POST_TYPE, 'post_status' => 'publish', 'post_title' => (string) $premade['title'], 'post_name' => $slug, ), true ); if ( ! is_wp_error( $id ) && $id ) { AIEO_Form_CPT::save_definition( (int) $id, (array) $premade['fields'], (array) $premade['settings'], ! empty( $premade['notify'] ) ); } delete_option( $lock ); return is_wp_error( $id ) ? 0 : (int) $id; } private static function manager() { return is_user_logged_in() && current_user_can( defined( 'AIEO_CAPABILITY' ) ? AIEO_CAPABILITY : 'manage_options' ); } public static function settings_url() { return admin_url( 'admin.php?page=ai-eshop-dmm&dmm_tab=user_vitals#user-vitals-professional-signup-gravity-forms' ); } public static function render( $kind ) { $kind = isset( self::KINDS[ $kind ] ) ? $kind : 'professional'; $role = self::role( $kind ); if ( '' === $role ) { return self::manager() ? self::note( __( 'Visitors see no application form here yet: choose the role an approved applicant gets.', 'ai-eshop-optimizer' ), true ) : ''; } $user = wp_get_current_user(); if ( $user->ID && in_array( $role, (array) $user->roles, true ) ) { return self::note( 'student' === $kind ? __( 'You have a student account.', 'ai-eshop-optimizer' ) : __( 'You have a professional account.', 'ai-eshop-optimizer' ) ); } $form_id = self::form_id( $kind ); if ( ! $form_id || ! class_exists( 'AIEO_Form_Render' ) ) { return ''; } if ( $user->ID && self::waiting( $form_id, (int) $user->ID ) ) { return self::note( __( 'We have your application and will e-mail you when it is reviewed.', 'ai-eshop-optimizer' ) ); } $supports = class_exists( 'WP_Block_Supports' ) ? WP_Block_Supports::$block_to_render : null; if ( class_exists( 'WP_Block_Supports' ) ) { WP_Block_Supports::$block_to_render = null; } $html = AIEO_Form_Render::block( array( 'variant' => 'panel', 'form' => $form_id ) ); if ( class_exists( 'WP_Block_Supports' ) ) { WP_Block_Supports::$block_to_render = $supports; } return $html; } private static function note( $text, $settings = false ) { $link = $settings ? ' ' . esc_html__( 'Open the settings', 'ai-eshop-optimizer' ) . '' : ''; return '

' . esc_html( $text ) . $link . '

'; } private static function waiting( $form_id, $user_id ) { global $wpdb; return (bool) $wpdb->get_var( $wpdb->prepare( 'SELECT 1 FROM %i WHERE form_id = %d AND user_id = %d AND status = %s LIMIT 1', AIEO_Form_CPT::entries_table(), (int) $form_id, (int) $user_id, 'new' ) ); } public static function signup( $kind ) { if ( 'gravity' === self::provider() ) { $inner = shortcode_exists( 'membership_gf' ) ? (string) do_shortcode( '[membership_gf]' ) : ''; } else { $inner = self::render( $kind ); } if ( '' === $inner ) { return ''; } if ( ! is_user_logged_in() && defined( 'AIEO_DIR' ) ) { $dir = AIEO_DIR . 'includes/social-login/'; if ( ! class_exists( 'AIEO_SSO_Buttons_Data' ) && is_readable( $dir . 'class-aieo-sso-buttons-data.php' ) ) { require_once $dir . 'class-aieo-sso-buttons-data.php'; } if ( ! class_exists( 'AIEO_SSO_Buttons' ) && is_readable( $dir . 'class-aieo-sso-buttons.php' ) ) { require_once $dir . 'class-aieo-sso-buttons.php'; } if ( class_exists( 'AIEO_SSO_Buttons' ) ) { $inner .= AIEO_SSO_Buttons::render( 'register', array( 'intro' => __( 'Or sign in via', 'ai-eshop-optimizer' ) ) ); } } return $inner; } public static function allowed_html() { $svg = array( 'width' => true, 'height' => true, 'viewbox' => true, 'fill' => true, 'aria-hidden' => true, 'focusable' => true ); return ( class_exists( 'AIEO_Form_Render' ) ? AIEO_Form_Render::allowed_html() : array() ) + array( 'svg' => $svg, 'path' => array( 'd' => true, 'fill' => true ), 'rect' => array( 'x' => true, 'y' => true, 'width' => true, 'height' => true, 'fill' => true ), ); } public static function shortcode( $atts ) { $atts = shortcode_atts( array( 'kind' => 'professional' ), (array) $atts, self::SHORTCODE ); $html = self::signup( sanitize_key( (string) $atts['kind'] ) ); return '' === $html ? '' : '
' . $html . '
'; } public static function statuses( array $entry_ids ) { $ids = array_values( array_filter( array_map( 'intval', $entry_ids ) ) ); if ( ! $ids ) { return array(); } global $wpdb; $rows = (array) $wpdb->get_results( $wpdb->prepare( 'SELECT id, status FROM %i WHERE id BETWEEN %d AND %d', AIEO_Form_CPT::entries_table(), min( $ids ), max( $ids ) ), ARRAY_A ); $want = array_flip( $ids ); $out = array(); foreach ( $rows as $r ) { if ( isset( $want[ (int) $r['id'] ] ) ) { $out[ (int) $r['id'] ] = (string) $r['status']; } } return $out; } public static function waiting_counts() { global $wpdb; $out = array(); foreach ( array_keys( self::KINDS ) as $kind ) { $form_id = self::form_id( $kind, false ); if ( ! $form_id ) { continue; } $count = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE form_id = %d AND status = %s', AIEO_Form_CPT::entries_table(), $form_id, 'new' ) ); $out[ $kind ] = array( 'form_id' => $form_id, 'count' => $count ); } return $out; } private static function decision_url( $entry_id, $decision ) { return wp_nonce_url( add_query_arg( array( 'action' => self::ACTION, 'entry' => (int) $entry_id, 'decision' => $decision ), admin_url( 'admin-post.php' ) ), self::ACTION ); } public static function decision_cell( $entry_id, $status ) { $approve = \Rooster\Admin\Ui::button( __( 'Approve', 'ai-eshop-optimizer' ), array( 'href' => self::decision_url( $entry_id, 'approve' ), 'variant' => 'primary', 'size' => 'sm' ) ); if ( self::APPROVED === $status ) { return esc_html__( 'Approved', 'ai-eshop-optimizer' ); } if ( self::DECLINED === $status ) { return esc_html__( 'Declined', 'ai-eshop-optimizer' ) . ' ' . $approve; } return $approve . ' ' . \Rooster\Admin\Ui::button( __( 'Decline', 'ai-eshop-optimizer' ), array( 'href' => self::decision_url( $entry_id, 'decline' ), 'variant' => 'outline', 'size' => 'sm' ) ); } public static function result_notice() { $code = (string) get_transient( self::RESULT . get_current_user_id() ); if ( '' !== $code ) { delete_transient( self::RESULT . get_current_user_id() ); } $map = array( self::APPROVED => array( __( 'Approved: the account has its role, and the applicant has been e-mailed.', 'ai-eshop-optimizer' ), 'success' ), self::DECLINED => array( __( 'Declined. Nothing was sent to the applicant, and the application can still be approved.', 'ai-eshop-optimizer' ), 'info' ), 'norole' => array( __( 'Not approved: choose the role an approved applicant gets in the settings first.', 'ai-eshop-optimizer' ), 'warning' ), 'noemail' => array( __( 'Not approved: the application has no valid e-mail address.', 'ai-eshop-optimizer' ), 'warning' ), 'error' => array( __( 'Not approved: the account could not be made.', 'ai-eshop-optimizer' ), 'destructive' ), 'forbidden' => array( __( 'Not approved: your role may not give this role. An administrator decides who may, under Customer Profiling, Roles.', 'ai-eshop-optimizer' ), 'warning' ), 'account' => array( __( 'Not approved: the application’s e-mail address belongs to a staff account, whose roles only an administrator changes.', 'ai-eshop-optimizer' ), 'warning' ), ); return isset( $map[ $code ] ) && class_exists( 'AIEO_DMM_UI' ) ? AIEO_DMM_UI::notice( esc_html( $map[ $code ][0] ), $map[ $code ][1] ) : ''; } public static function decide() { check_admin_referer( self::ACTION ); if ( ! current_user_can( defined( 'AIEO_CAPABILITY' ) ? AIEO_CAPABILITY : 'manage_options' ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to decide applications.', 'ai-eshop-optimizer' ), 403 ); } $entry = isset( $_GET['entry'] ) ? absint( wp_unslash( $_GET['entry'] ) ) : 0; $decision = isset( $_GET['decision'] ) ? sanitize_key( wp_unslash( $_GET['decision'] ) ) : ''; if ( 'approve' === $decision ) { $r = self::approve( $entry ); } elseif ( 'decline' === $decision ) { $r = self::decline( $entry ); } else { $r = new WP_Error( 'aieo_pro_error', 'No decision.' ); } $code = is_wp_error( $r ) ? str_replace( 'aieo_pro_', '', $r->get_error_code() ) : ( 'approve' === $decision ? self::APPROVED : self::DECLINED ); $row = self::entry( $entry ); $back = $row ? get_edit_post_link( (int) $row['form_id'], 'url' ) : ''; set_transient( self::RESULT . get_current_user_id(), $code, MINUTE_IN_SECONDS ); wp_safe_redirect( $back ? $back : admin_url() ); exit; } private static function entry( $entry_id ) { global $wpdb; $row = $wpdb->get_row( $wpdb->prepare( 'SELECT id, form_id, user_id, status FROM %i WHERE id = %d', AIEO_Form_CPT::entries_table(), (int) $entry_id ), ARRAY_A ); return is_array( $row ) ? $row : null; } private static function answers( $entry_id ) { $out = array(); foreach ( AIEO_Form_Entries::entry_answers( (int) $entry_id ) as $v ) { if ( 'answered' === (string) $v['disposition'] ) { $out[ (string) $v['field_key'] ] = trim( (string) $v['value_text'] ); } } return $out; } private static function mark( $entry_id, $status, $user_id = 0 ) { global $wpdb; $data = array( 'status' => $status ); if ( $user_id ) { $data['user_id'] = (int) $user_id; } $wpdb->update( AIEO_Form_CPT::entries_table(), $data, array( 'id' => (int) $entry_id ) ); } public static function approve( $entry_id ) { $row = self::entry( $entry_id ); $kind = $row ? self::kind_of_form( (int) $row['form_id'] ) : ''; if ( '' === $kind ) { return new WP_Error( 'aieo_pro_error', 'Not an application.' ); } $role = self::role( $kind ); if ( '' === $role || ! get_role( $role ) ) { return new WP_Error( 'aieo_pro_norole', 'No role for ' . $kind . '.' ); } $actor = get_current_user_id(); if ( $actor && ! AIEO_User_Roles::may_assign( $role ) ) { return new WP_Error( 'aieo_pro_forbidden', 'Not a role this user gives.' ); } $answers = self::answers( $entry_id ); $email = sanitize_email( $answers['email'] ?? '' ); $user = (int) $row['user_id'] > 0 ? get_userdata( (int) $row['user_id'] ) : false; if ( ! $user && is_email( $email ) ) { $user = get_user_by( 'email', $email ); } $new = false; if ( ! $user ) { if ( ! is_email( $email ) ) { return new WP_Error( 'aieo_pro_noemail', 'No e-mail.' ); } $id = self::create_account( $email, $answers['first_name'] ?? '', $answers['last_name'] ?? '' ); if ( is_wp_error( $id ) ) { return new WP_Error( 'aieo_pro_error', $id->get_error_message() ); } $user = get_userdata( $id ); $new = true; } if ( ! $user ) { return new WP_Error( 'aieo_pro_error', 'No account.' ); } if ( $actor && ! $new && true !== AIEO_User_Roles::check( (int) $user->ID, $role ) ) { return new WP_Error( 'aieo_pro_account', 'An account this user does not manage.' ); } $before = AIEO_Safe_Roles::$source; AIEO_Safe_Roles::$source = 'signup'; $user->add_role( $role ); AIEO_Safe_Roles::$source = $before; self::mark( $entry_id, self::APPROVED, (int) $user->ID ); self::mail_approved( $user, $kind, $new ); do_action( 'aieo_pro_application_approved', (int) $entry_id, (int) $user->ID, $kind, $new ); return true; } public static function decline( $entry_id ) { $row = self::entry( $entry_id ); if ( ! $row || '' === self::kind_of_form( (int) $row['form_id'] ) ) { return new WP_Error( 'aieo_pro_error', 'Not an application.' ); } self::mark( $entry_id, self::DECLINED ); do_action( 'aieo_pro_application_declined', (int) $entry_id ); return true; } private static function create_account( $email, $first, $last ) { $pass = wp_generate_password( 24, true, true ); if ( function_exists( 'wc_create_new_customer' ) ) { add_filter( 'woocommerce_email_enabled_customer_new_account', '__return_false', 999 ); $id = wc_create_new_customer( $email, '', $pass, array( 'first_name' => sanitize_text_field( $first ), 'last_name' => sanitize_text_field( $last ) ) ); remove_filter( 'woocommerce_email_enabled_customer_new_account', '__return_false', 999 ); return $id; } $base = sanitize_user( (string) strstr( $email, '@', true ), true ); $base = '' !== $base ? $base : 'customer'; $login = $base; for ( $i = 2; username_exists( $login ); $i++ ) { $login = $base . $i; } return wp_insert_user( array( 'user_login' => $login, 'user_email' => $email, 'user_pass' => $pass, 'first_name' => sanitize_text_field( $first ), 'last_name' => sanitize_text_field( $last ), 'role' => (string) get_option( 'default_role', 'subscriber' ), ) ); } private static function set_password_url( WP_User $user ) { $key = get_password_reset_key( $user ); if ( is_wp_error( $key ) ) { return wp_lostpassword_url(); } if ( function_exists( 'wc_get_endpoint_url' ) && function_exists( 'wc_get_page_permalink' ) ) { return add_query_arg( array( 'key' => $key, 'id' => $user->ID ), wc_get_endpoint_url( 'lost-password', '', wc_get_page_permalink( 'myaccount' ) ) ); } return network_site_url( 'wp-login.php?action=rp&key=' . $key . '&login=' . rawurlencode( $user->user_login ), 'login' ); } private static function mail_approved( WP_User $user, $kind, $new ) { $site = wp_specialchars_decode( (string) get_bloginfo( 'name' ), ENT_QUOTES ); $name = '' !== trim( (string) $user->first_name ) ? (string) $user->first_name : (string) $user->display_name; $subject = sprintf( 'student' === $kind ? __( 'Your student account at %s is approved', 'ai-eshop-optimizer' ) : __( 'Your professional account at %s is approved', 'ai-eshop-optimizer' ), $site ); $lines = array( sprintf( __( 'Hello %s,', 'ai-eshop-optimizer' ), $name ), '' ); $lines[] = 'student' === $kind ? __( 'Your application is approved: your account now has student access, and you see your prices when you are signed in.', 'ai-eshop-optimizer' ) : __( 'Your application is approved: your account now has professional access, and you see your prices when you are signed in.', 'ai-eshop-optimizer' ); $lines[] = ''; if ( $new ) { $lines[] = __( 'An account was made for you with this e-mail address. To sign in, set its password here:', 'ai-eshop-optimizer' ); $lines[] = self::set_password_url( $user ); $lines[] = __( 'The link works for 24 hours. After that, use "Lost your password?" on the sign-in page.', 'ai-eshop-optimizer' ); } else { $lines[] = __( 'Sign in with your usual details:', 'ai-eshop-optimizer' ); $lines[] = function_exists( 'wc_get_page_permalink' ) ? wc_get_page_permalink( 'myaccount' ) : wp_login_url(); } $lines[] = ''; $lines[] = $site; wp_mail( $user->user_email, $subject, implode( "\n", $lines ) ); } } }