'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 ) );
}
}
}