get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $t ) ) !== $t ) { $ok = false; break; } } return $ok; } public static function forms() { if ( ! self::available() ) { return array(); } $forms = GFAPI::get_forms( true ); $out = array(); foreach ( (array) $forms as $f ) { if ( empty( $f['id'] ) ) { continue; } $out[] = array( 'id' => (int) $f['id'], 'title' => (string) ( isset( $f['title'] ) ? $f['title'] : '' ), 'entries' => self::entry_count( (int) $f['id'] ), ); } return $out; } public static function distributions( $form_id, $fresh = false ) { $form_id = (int) $form_id; if ( ! self::available() ) { return new WP_Error( 'aieo_no_gf', __( 'Gravity Forms is not active.', 'ai-eshop-optimizer' ) ); } $cache_key = 'aieo_form_dist_' . self::REPORT_VERSION . '_' . $form_id; if ( ! $fresh ) { $hit = get_transient( $cache_key ); if ( is_array( $hit ) ) { return $hit; } } $form = GFAPI::get_form( $form_id ); if ( ! $form || empty( $form['fields'] ) ) { return new WP_Error( 'aieo_no_form', __( 'That form could not be read.', 'ai-eshop-optimizer' ) ); } $fields = self::report_fields( $form ); $sections = self::section_labels( $form ); if ( ! $fields ) { return array( 'form_id' => $form_id, 'form_title' => (string) ( isset( $form['title'] ) ? $form['title'] : '' ), 'total_entries' => self::entry_count( $form_id ), 'sections' => array(), 'fields' => array(), ); } $tally = self::tally( $form_id, $fields ); $entries = self::entry_count( $form_id ); $out_fields = array(); foreach ( $fields as $fid => $meta ) { $one = self::summarise( $fid, $meta, $tally, $entries ); if ( null !== $one ) { $out_fields[] = $one; } } $report = array( 'form_id' => $form_id, 'form_title' => (string) ( isset( $form['title'] ) ? $form['title'] : '' ), 'total_entries' => $entries, 'sections' => self::group_by_section( $out_fields, $sections ), 'fields' => $out_fields, 'computed_at' => current_time( 'mysql', true ), ); set_transient( $cache_key, $report, self::CACHE_TTL ); return $report; } private static function summarise( $fid, array $meta, array $tally, $entries ) { $shape = $meta['shape']; $counts = isset( $tally['counts'][ $fid ] ) ? $tally['counts'][ $fid ] : array(); $respondents = isset( $tally['respondents'][ $fid ] ) ? (int) $tally['respondents'][ $fid ] : 0; $sub = isset( $tally['sub'][ $fid ] ) ? $tally['sub'][ $fid ] : array(); $ranks = isset( $tally['ranks'][ $fid ] ) ? $tally['ranks'][ $fid ] : array(); $base = array( 'id' => $fid, 'label' => $meta['label'], 'type' => $meta['type'], 'shape' => $shape, 'multi' => (bool) $meta['multi'], 'binary' => (bool) $meta['binary'], 'section_id' => (int) $meta['section_id'], 'reversed' => (bool) $meta['reversed'], 'respondents' => $respondents, 'options' => array(), 'mean' => null, 'n' => 0, 'scale' => $meta['scale'], 'rows' => array(), 'drillable' => true, ); if ( self::SHAPE_RANKING === $shape ) { if ( ! $ranks ) { return null; } $rows = array(); foreach ( $ranks as $value => $agg ) { if ( $agg['n'] < 1 ) { continue; } $rows[] = array( 'label' => self::option_label( $meta, (string) $value ), 'value' => '', 'count' => (int) $agg['n'], 'mean' => round( $agg['sum'] / $agg['n'], 2 ), 'pct' => 0.0, ); } usort( $rows, static function ( $a, $b ) { if ( $a['mean'] === $b['mean'] ) { return 0; } return ( $a['mean'] < $b['mean'] ) ? -1 : 1; } ); $base['options'] = $rows; $base['drillable'] = false; $base['n'] = $respondents; return $base; } if ( self::SHAPE_MATRIX === $shape ) { if ( ! $sub ) { return null; } $rows = array(); foreach ( $meta['inputs'] as $input_id => $row_label ) { if ( empty( $sub[ $input_id ] ) ) { continue; } $rows[] = self::scale_row( $meta, (string) $input_id, $row_label, $sub[ $input_id ] ); } foreach ( $sub as $input_id => $counts_for_row ) { if ( isset( $meta['inputs'][ $input_id ] ) || ! $counts_for_row ) { continue; } $rows[] = self::scale_row( $meta, (string) $input_id, (string) $input_id, $counts_for_row ); } if ( ! $rows ) { return null; } usort( $rows, static function ( $a, $b ) { $am = ( null === $a['mean'] ) ? PHP_INT_MAX : $a['mean']; $bm = ( null === $b['mean'] ) ? PHP_INT_MAX : $b['mean']; if ( $am === $bm ) { return 0; } return ( $am < $bm ) ? -1 : 1; } ); $base['rows'] = $rows; $base['n'] = $respondents; return $base; } if ( ! $counts ) { return null; } if ( $meta['binary'] ) { $respondents = $entries > 0 ? $entries : $respondents; $base['respondents'] = $respondents; } if ( self::SHAPE_SCALE === $shape ) { $order = self::scale_order( $meta, $counts ); $sum = 0.0; $n = 0; $opts = array(); foreach ( $order as $value ) { $c = (int) $counts[ $value ]; $score = self::score_of( $meta, (string) $value ); if ( null !== $score ) { $sum += $score * $c; $n += $c; } $opts[] = array( 'label' => self::option_label( $meta, (string) $value ), 'value' => (string) $value, 'score' => $score, 'count' => $c, 'pct' => $respondents > 0 ? round( 100 * $c / $respondents, 1 ) : 0.0, ); } $base['options'] = ( count( $opts ) > self::MAX_OPTIONS ) ? array() : $opts; $base['n'] = $n; $base['mean'] = $n > 0 ? round( $sum / $n, 2 ) : null; return $base; } arsort( $counts ); $options = array(); $rank = 0; $tail = 0; foreach ( $counts as $value => $n ) { $rank++; if ( $rank > self::MAX_OPTIONS ) { $tail += (int) $n; continue; } $options[] = array( 'label' => self::option_label( $meta, (string) $value ), 'value' => (string) $value, 'count' => (int) $n, 'pct' => $respondents > 0 ? round( 100 * $n / $respondents, 1 ) : 0.0, ); } if ( $tail > 0 ) { $options[] = array( 'label' => __( 'Other answers', 'ai-eshop-optimizer' ), 'value' => '', 'count' => $tail, 'pct' => $respondents > 0 ? round( 100 * $tail / $respondents, 1 ) : 0.0, ); } $base['options'] = $options; $base['n'] = $respondents; return $base; } private static function scale_row( array $meta, $key, $label, array $counts ) { $sum = 0.0; $n = 0; $resp = 0; $opts = array(); foreach ( self::scale_order( $meta, $counts ) as $value ) { $c = (int) $counts[ $value ]; $resp += $c; $score = self::score_of( $meta, (string) $value ); if ( null !== $score ) { $sum += $score * $c; $n += $c; } $opts[] = array( 'label' => self::option_label( $meta, (string) $value ), 'value' => (string) $value, 'score' => $score, 'count' => $c, ); } foreach ( $opts as $i => $o ) { $opts[ $i ]['pct'] = $resp > 0 ? round( 100 * $o['count'] / $resp, 1 ) : 0.0; } return array( 'key' => (string) $key, 'label' => (string) $label, 'respondents' => $resp, 'n' => $n, 'mean' => $n > 0 ? round( $sum / $n, 2 ) : null, 'options' => $opts, ); } private static function scale_order( array $meta, array $counts ) { $order = array(); if ( $meta['choices'] ) { foreach ( array_keys( $meta['choices'] ) as $value ) { if ( isset( $counts[ $value ] ) ) { $order[] = (string) $value; } } } else { $numeric = array_keys( $counts ); usort( $numeric, static function ( $a, $b ) { $an = is_numeric( $a ); $bn = is_numeric( $b ); if ( $an && $bn ) { return ( (float) $a === (float) $b ) ? 0 : ( ( (float) $a < (float) $b ) ? -1 : 1 ); } if ( $an !== $bn ) { return $an ? -1 : 1; } return strcmp( (string) $a, (string) $b ); } ); $order = array_map( 'strval', $numeric ); } foreach ( array_keys( $counts ) as $value ) { if ( ! in_array( (string) $value, $order, true ) ) { $order[] = (string) $value; } } return $order; } private static function group_by_section( array $fields, array $sections ) { $buckets = array(); foreach ( $fields as $f ) { $sid = (int) $f['section_id']; if ( ! isset( $buckets[ $sid ] ) ) { $buckets[ $sid ] = array( 'id' => $sid, 'label' => ( $sid && isset( $sections[ $sid ] ) ) ? $sections[ $sid ] : '', 'fields' => array(), ); } $buckets[ $sid ]['fields'][] = $f['id']; } return array_values( $buckets ); } public static function top_segments( $form_id, $limit = 8 ) { $rep = self::distributions( $form_id ); if ( is_wp_error( $rep ) || empty( $rep['fields'] ) ) { return array(); } $flat = array(); foreach ( $rep['fields'] as $f ) { if ( isset( $f['shape'] ) && self::SHAPE_CHOICE !== $f['shape'] ) { continue; } $choices = count( $f['options'] ); foreach ( $f['options'] as $o ) { if ( $o['count'] <= 0 || '' === $o['value'] ) { continue; } if ( $o['pct'] >= 98.0 ) { continue; } $flat[] = array( 'field' => $f['label'], 'label' => $o['label'], 'pct' => (float) $o['pct'], 'count' => (int) $o['count'], 'choices' => $choices, ); } } usort( $flat, static function ( $a, $b ) { $a_rich = ( $a['choices'] >= 3 ) ? 1 : 0; $b_rich = ( $b['choices'] >= 3 ) ? 1 : 0; if ( $a_rich !== $b_rich ) { return $b_rich - $a_rich; } if ( $a['count'] === $b['count'] ) { return 0; } return ( $b['count'] < $a['count'] ) ? -1 : 1; } ); return array_slice( $flat, 0, max( 1, (int) $limit ) ); } public static function audience_brief( $form_id ) { $segments = self::top_segments( $form_id, 6 ); if ( ! $segments ) { return ''; } $by_field = array(); foreach ( $segments as $s ) { $by_field[ self::prompt_safe( $s['field'] ) ][] = sprintf( '%s (%s%%)', self::prompt_safe( $s['label'] ), $s['pct'] ); } $parts = array(); foreach ( $by_field as $field => $vals ) { $parts[] = $field . ': ' . implode( ', ', array_slice( $vals, 0, 4 ) ); } return implode( '; ', $parts ); } private static function prompt_safe( $s ) { $s = wp_strip_all_tags( (string) $s ); $s = preg_replace( '/[\p{Cc}\p{Cf}]+/u', ' ', $s ); $s = preg_replace( '/\s+/u', ' ', (string) $s ); $s = trim( (string) $s ); if ( mb_strlen( $s ) > self::PROMPT_LABEL_MAX ) { $s = mb_substr( $s, 0, self::PROMPT_LABEL_MAX ) . '…'; } return $s; } public static function field_shapes( $form ) { if ( ! is_array( $form ) ) { if ( ! class_exists( 'GFAPI' ) ) { return array(); } $form = GFAPI::get_form( (int) $form ); } if ( ! is_array( $form ) || empty( $form['fields'] ) ) { return array(); } return self::report_fields( $form ); } public static function answer_rows( array $meta, $meta_key, $raw ) { $raw = trim( (string) $raw ); if ( '' === $raw ) { return array(); } $key = (string) $meta_key; $dot = strpos( $key, '.' ); $field = (string) ( false !== $dot ? substr( $key, 0, $dot ) : $key ); $out = array(); $row = static function ( $choice_key, $value_num, $value_text ) use ( $field ) { if ( null !== $choice_key && strlen( (string) $choice_key ) > 64 ) { $value_text = ( null === $value_text ) ? (string) $choice_key : $value_text; $choice_key = md5( (string) $choice_key ); } return array( 'field_key' => $field, 'disposition' => 'answered', 'choice_key' => $choice_key, 'value_num' => $value_num, 'value_text' => $value_text, ); }; switch ( $meta['shape'] ) { case self::SHAPE_RANKING: $position = 0; foreach ( array_filter( array_map( 'trim', explode( ',', $raw ) ), 'strlen' ) as $value ) { $position++; $out[] = $row( $value, (float) $position, self::option_label( $meta, $value ) ); } return $out; case self::SHAPE_MATRIX: $at = strpos( $raw, ':' ); $column = ( false === $at ) ? $raw : substr( $raw, $at + 1 ); $row_id = ( false === $at ) ? ( isset( $meta['row_tokens'][ $key ] ) ? $meta['row_tokens'][ $key ] : $key ) : substr( $raw, 0, $at ); $out[] = $row( $row_id, self::score_of( $meta, $column ), $column ); return $out; case self::SHAPE_SCALE: $out[] = $row( $raw, self::score_of( $meta, $raw ), self::option_label( $meta, $raw ) ); return $out; case self::SHAPE_CHOICE: foreach ( self::split_values( $raw, $meta ) as $value ) { if ( '' === $value ) { continue; } $out[] = $row( $value, null, self::option_label( $meta, $value ) ); } return $out; } $out[] = $row( null, is_numeric( $raw ) ? (float) $raw : null, $raw ); return $out; } private static function prop( $field, $key, $default = '' ) { if ( is_object( $field ) ) { return isset( $field->$key ) ? $field->$key : $default; } return ( is_array( $field ) && isset( $field[ $key ] ) ) ? $field[ $key ] : $default; } public static function classify( $field ) { $type = strtolower( (string) self::prop( $field, 'type' ) ); $input = strtolower( (string) self::prop( $field, 'inputType' ) ); if ( is_object( $field ) ) { if ( $field instanceof GF_Field_Likert ) { return self::prop( $field, 'gsurveyLikertEnableMultipleRows' ) ? self::SHAPE_MATRIX : self::SHAPE_SCALE; } if ( $field instanceof GF_Field_Rank ) { return self::SHAPE_RANKING; } if ( $field instanceof GF_Field_Rating ) { return self::SHAPE_SCALE; } } $sub = ( 'survey' === $type ) ? $input : $type; switch ( $sub ) { case 'likert': return self::prop( $field, 'gsurveyLikertEnableMultipleRows' ) ? self::SHAPE_MATRIX : self::SHAPE_SCALE; case 'rank': return self::SHAPE_RANKING; case 'rating': case 'number': return self::SHAPE_SCALE; case 'repeater': return self::SHAPE_REPEATER; } if ( in_array( $sub, self::CHOICE_TYPES, true ) ) { return self::SHAPE_CHOICE; } return self::SHAPE_TEXT; } private static function reported_type( $field ) { $type = strtolower( (string) self::prop( $field, 'type' ) ); $input = strtolower( (string) self::prop( $field, 'inputType' ) ); if ( 'survey' === $type && '' !== $input ) { return $input; } return $type; } private static function report_fields( array $form ) { $out = array(); $section = 0; foreach ( (array) $form['fields'] as $field ) { $type = strtolower( (string) self::prop( $field, 'type' ) ); $fid = (int) self::prop( $field, 'id', 0 ); if ( 'section' === $type ) { $section = $fid; continue; } if ( 'page' === $type ) { $section = 0; continue; } if ( $fid <= 0 ) { continue; } $shape = self::classify( $field ); if ( self::SHAPE_TEXT === $shape || self::SHAPE_REPEATER === $shape ) { continue; } $label = (string) self::prop( $field, 'label' ); $raw = self::prop( $field, 'choices', array() ); $choices = array(); $scores = array(); $pos = 0; foreach ( (array) $raw as $c ) { $pos++; $val = (string) ( isset( $c['value'] ) ? $c['value'] : ( isset( $c['text'] ) ? $c['text'] : '' ) ); $txt = (string) ( isset( $c['text'] ) ? $c['text'] : $val ); if ( '' === $val && '' === $txt ) { continue; } $choices[ $val ] = $txt; if ( self::SHAPE_SCALE === $shape || self::SHAPE_MATRIX === $shape ) { $scores[ $val ] = self::choice_score( $c, $val, $pos ); } } $ticks = ( 'checkbox' === $type ) || ( in_array( $type, array( 'multi_choice', 'image_choice' ), true ) && 'checkbox' === strtolower( (string) self::prop( $field, 'inputType' ) ) ); $out[ $fid ] = array( 'label' => ( '' !== $label ) ? $label : sprintf( __( 'Field %d', 'ai-eshop-optimizer' ), $fid ), 'admin_label' => (string) self::prop( $field, 'adminLabel', '' ), 'type' => self::reported_type( $field ), 'shape' => $shape, 'multi' => $ticks || 'multiselect' === $type, 'binary' => ( $ticks && count( $choices ) <= 1 ), 'section_id' => $section, 'reversed' => (bool) self::prop( $field, 'reversed', false ), 'choices' => $choices, 'scores' => $scores, 'inputs' => self::field_inputs( $field, $shape ), 'row_tokens' => self::field_row_tokens( $field, $shape ), 'scale' => self::scale_bounds( $scores ), ); } return $out; } private static function choice_score( array $choice, $value, $position ) { if ( isset( $choice['score'] ) && is_numeric( $choice['score'] ) ) { return (float) $choice['score']; } if ( is_numeric( $value ) ) { return (float) $value; } return (float) $position; } private static function score_of( array $meta, $value ) { if ( isset( $meta['scores'][ $value ] ) ) { return (float) $meta['scores'][ $value ]; } return is_numeric( $value ) ? (float) $value : null; } private static function scale_bounds( array $scores ) { if ( ! $scores ) { return array( 'min' => null, 'max' => null ); } return array( 'min' => (float) min( $scores ), 'max' => (float) max( $scores ) ); } private static function field_inputs( $field, $shape ) { $out = array(); $inputs = self::prop( $field, 'inputs', array() ); foreach ( (array) $inputs as $in ) { $id = (string) ( isset( $in['id'] ) ? $in['id'] : '' ); if ( '' === $id ) { continue; } $out[ $id ] = (string) ( isset( $in['label'] ) && '' !== $in['label'] ? $in['label'] : $id ); } if ( $out || self::SHAPE_MATRIX !== $shape ) { return $out; } $fid = (string) self::prop( $field, 'id', '' ); $rows = (array) self::prop( $field, 'gsurveyLikertRows', array() ); $skip = 0; foreach ( array_values( $rows ) as $i => $row ) { if ( 0 === ( $i + 1 + $skip ) % 10 ) { $skip++; } $out[ $fid . '.' . ( $i + 1 + $skip ) ] = (string) ( isset( $row['text'] ) ? $row['text'] : ( $i + 1 ) ); } return $out; } private static function field_row_tokens( $field, $shape ) { if ( self::SHAPE_MATRIX !== $shape ) { return array(); } $out = array(); foreach ( (array) self::prop( $field, 'inputs', array() ) as $in ) { $id = (string) ( isset( $in['id'] ) ? $in['id'] : '' ); if ( '' === $id || empty( $in['name'] ) ) { continue; } $out[ $id ] = (string) $in['name']; } if ( $out ) { return $out; } $fid = (string) self::prop( $field, 'id', '' ); $rows = array_values( (array) self::prop( $field, 'gsurveyLikertRows', array() ) ); $skip = 0; foreach ( $rows as $i => $row ) { if ( 0 === ( $i + 1 + $skip ) % 10 ) { $skip++; } if ( ! empty( $row['value'] ) ) { $out[ $fid . '.' . ( $i + 1 + $skip ) ] = (string) $row['value']; } } return $out; } private static function section_labels( array $form ) { $out = array(); foreach ( (array) $form['fields'] as $field ) { if ( 'section' !== strtolower( (string) self::prop( $field, 'type' ) ) ) { continue; } $fid = (int) self::prop( $field, 'id', 0 ); if ( $fid > 0 ) { $out[ $fid ] = (string) self::prop( $field, 'label' ); } } return $out; } private static function tally( $form_id, array $fields ) { global $wpdb; $counts = array(); $respondents = array(); $sub = array(); $ranks = array(); $meta_table = self::meta_table(); $entry_table = self::entry_table(); if ( '' === $meta_table || '' === $entry_table ) { return array( 'counts' => $counts, 'respondents' => $respondents, 'sub' => $sub, 'ranks' => $ranks ); } $clauses = array(); $params = array( (int) $form_id ); foreach ( array_keys( $fields ) as $fid ) { $clauses[] = '( m.meta_key = %s OR m.meta_key LIKE %s )'; $params[] = (string) $fid; $params[] = $wpdb->esc_like( $fid . '.' ) . '%'; } if ( ! $clauses ) { return array( 'counts' => $counts, 'respondents' => $respondents, 'sub' => $sub, 'ranks' => $ranks ); } $where = '( ' . implode( ' OR ', $clauses ) . ' )'; $rows = $wpdb->get_results( $wpdb->prepare( "SELECT m.meta_key, m.meta_value, m.entry_id FROM `{$meta_table}` m INNER JOIN `{$entry_table}` e ON e.id = m.entry_id WHERE m.form_id = %d AND e.status = 'active' AND {$where} AND m.meta_value <> ''", $params ), ARRAY_A ); $seen = array(); foreach ( (array) $rows as $r ) { $key = (string) $r['meta_key']; $dot = strpos( $key, '.' ); $fid = (int) ( false !== $dot ? substr( $key, 0, $dot ) : $key ); if ( ! isset( $fields[ $fid ] ) ) { continue; } $meta = $fields[ $fid ]; $eid = (int) $r['entry_id']; $raw = (string) $r['meta_value']; if ( self::SHAPE_RANKING === $meta['shape'] ) { $order = array_values( array_filter( array_map( 'trim', explode( ',', $raw ) ), 'strlen' ) ); foreach ( $order as $i => $value ) { if ( ! isset( $ranks[ $fid ][ $value ] ) ) { $ranks[ $fid ][ $value ] = array( 'sum' => 0.0, 'n' => 0 ); } $ranks[ $fid ][ $value ]['sum'] += ( $i + 1 ); $ranks[ $fid ][ $value ]['n']++; } if ( $order ) { $seen[ $fid ][ $eid ] = true; } continue; } foreach ( self::split_values( $raw, $meta ) as $val ) { if ( '' === $val ) { continue; } if ( ! isset( $counts[ $fid ][ $val ] ) ) { $counts[ $fid ][ $val ] = 0; } $counts[ $fid ][ $val ]++; if ( false !== $dot ) { if ( ! isset( $sub[ $fid ][ $key ][ $val ] ) ) { $sub[ $fid ][ $key ][ $val ] = 0; } $sub[ $fid ][ $key ][ $val ]++; } $seen[ $fid ][ $eid ] = true; } } foreach ( $seen as $fid => $entries ) { $respondents[ $fid ] = count( $entries ); } return array( 'counts' => $counts, 'respondents' => $respondents, 'sub' => $sub, 'ranks' => $ranks ); } private static function split_values( $raw, array $field ) { $raw = trim( $raw ); if ( '' === $raw ) { return array(); } if ( self::SHAPE_MATRIX === $field['shape'] ) { $at = strpos( $raw, ':' ); return array( false === $at ? $raw : substr( $raw, $at + 1 ) ); } if ( 'multiselect' === $field['type'] && '[' === substr( $raw, 0, 1 ) ) { $decoded = json_decode( $raw, true ); if ( is_array( $decoded ) ) { return array_map( 'strval', $decoded ); } } return array( $raw ); } private static function option_label( array $field, $value ) { if ( isset( $field['choices'][ $value ] ) && '' !== $field['choices'][ $value ] ) { return (string) $field['choices'][ $value ]; } if ( ! empty( $field['binary'] ) ) { $first = reset( $field['choices'] ); if ( is_string( $first ) && '' !== $first ) { return $first; } return (string) $field['label']; } foreach ( $field['choices'] as $v => $txt ) { if ( (string) $txt === $value ) { return (string) $txt; } unset( $v ); } return $value; } private static function entry_count( $form_id ) { global $wpdb; $t = self::entry_table(); if ( '' === $t ) { return 0; } return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM `{$t}` WHERE form_id = %d AND status = 'active'", (int) $form_id ) ); } private static function entry_table() { if ( class_exists( 'GFFormsModel' ) && method_exists( 'GFFormsModel', 'get_entry_table_name' ) ) { return (string) GFFormsModel::get_entry_table_name(); } global $wpdb; return $wpdb->prefix . 'gf_entry'; } private static function meta_table() { if ( class_exists( 'GFFormsModel' ) && method_exists( 'GFFormsModel', 'get_entry_meta_table_name' ) ) { return (string) GFFormsModel::get_entry_meta_table_name(); } global $wpdb; return $wpdb->prefix . 'gf_entry_meta'; } public static function respondents( $form_id, $field_id, $value, $limit = 50, $offset = 0 ) { global $wpdb; $form_id = (int) $form_id; if ( ! self::available() ) { return new WP_Error( 'aieo_no_gf', __( 'Gravity Forms is not active.', 'ai-eshop-optimizer' ) ); } $meta_table = self::meta_table(); $entry_table = self::entry_table(); if ( '' === $meta_table || '' === $entry_table ) { return new WP_Error( 'aieo_no_tables', __( 'Gravity Forms tables are unavailable.', 'ai-eshop-optimizer' ) ); } $field_id = preg_replace( '/[^0-9.]/', '', (string) $field_id ); if ( '' === $field_id ) { return new WP_Error( 'aieo_bad_field', __( 'Unknown field.', 'ai-eshop-optimizer' ) ); } $value = (string) $value; $json_like = '%' . $wpdb->esc_like( '"' . $value . '"' ) . '%'; $matrix_like = '%' . $wpdb->esc_like( ':' . $value ); $ids = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT m.entry_id FROM `{$meta_table}` m JOIN `{$entry_table}` e ON e.id = m.entry_id WHERE m.form_id = %d AND e.status = 'active' AND ( m.meta_key = %s OR m.meta_key LIKE %s ) AND ( m.meta_value = %s OR m.meta_value LIKE %s OR m.meta_value LIKE %s ) ORDER BY m.entry_id DESC", $form_id, $field_id, $wpdb->esc_like( $field_id . '.' ) . '%', $value, $json_like, $matrix_like ) ); $ids = array_map( 'intval', (array) $ids ); $total = count( $ids ); if ( ! $total ) { return array( 'total' => 0, 'rows' => array(), 'known' => 0, 'emails' => array() ); } $form = class_exists( 'GFAPI' ) ? GFAPI::get_form( $form_id ) : null; $email_keys = array(); $name_keys = array(); if ( is_array( $form ) && ! empty( $form['fields'] ) ) { foreach ( $form['fields'] as $f ) { $type = is_object( $f ) ? (string) ( $f->type ?? '' ) : (string) ( $f['type'] ?? '' ); $fid = is_object( $f ) ? (string) ( $f->id ?? '' ) : (string) ( $f['id'] ?? '' ); if ( '' === $fid ) { continue; } if ( 'email' === $type ) { $email_keys[] = $fid; } elseif ( 'name' === $type ) { $name_keys[] = $fid . '.3'; $name_keys[] = $fid . '.6'; } } } $in = implode( ',', array_map( 'intval', $ids ) ); $email_of = array(); if ( $email_keys ) { $ph = implode( ',', array_fill( 0, count( $email_keys ), '%s' ) ); $rows_e = $wpdb->get_results( $wpdb->prepare( "SELECT entry_id, meta_value FROM `{$meta_table}` WHERE entry_id IN ({$in}) AND meta_key IN ({$ph}) AND meta_value <> ''", $email_keys ), ARRAY_A ); foreach ( (array) $rows_e as $r ) { $addr = strtolower( trim( (string) $r['meta_value'] ) ); if ( is_email( $addr ) && ! isset( $email_of[ (int) $r['entry_id'] ] ) ) { $email_of[ (int) $r['entry_id'] ] = $addr; } } } $name_of = array(); if ( $name_keys ) { $ph = implode( ',', array_fill( 0, count( $name_keys ), '%s' ) ); $rows_n = $wpdb->get_results( $wpdb->prepare( "SELECT entry_id, meta_key, meta_value FROM `{$meta_table}` WHERE entry_id IN ({$in}) AND meta_key IN ({$ph}) AND meta_value <> '' ORDER BY meta_key", $name_keys ), ARRAY_A ); foreach ( (array) $rows_n as $r ) { $eid = (int) $r['entry_id']; $name_of[ $eid ] = trim( ( isset( $name_of[ $eid ] ) ? $name_of[ $eid ] . ' ' : '' ) . trim( (string) $r['meta_value'] ) ); } } $known = 0; $ci = $wpdb->prefix . 'aieo_customer_intelligence'; if ( $email_of && (string) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $ci ) ) === $ci ) { $ph = implode( ',', array_fill( 0, count( $email_of ), '%s' ) ); $known = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT email) FROM `{$ci}` WHERE email IN ({$ph})", array_values( $email_of ) ) ); } if ( $limit < 1 ) { return array( 'total' => $total, 'rows' => array(), 'known' => $known, 'emails' => array_values( $email_of ) ); } $page_ids = array_slice( $ids, max( 0, (int) $offset ), (int) $limit ); $dates = array(); if ( $page_ids ) { $pin = implode( ',', array_map( 'intval', $page_ids ) ); foreach ( (array) $wpdb->get_results( "SELECT id, date_created FROM `{$entry_table}` WHERE id IN ({$pin})", ARRAY_A ) as $d ) { $dates[ (int) $d['id'] ] = (string) $d['date_created']; } } $rows = array(); foreach ( $page_ids as $eid ) { $rows[] = array( 'entry_id' => (int) $eid, 'date' => isset( $dates[ $eid ] ) ? $dates[ $eid ] : '', 'name' => isset( $name_of[ $eid ] ) ? $name_of[ $eid ] : '', 'email' => isset( $email_of[ $eid ] ) ? $email_of[ $eid ] : '', ); } return array( 'total' => $total, 'rows' => $rows, 'known' => $known, 'emails' => array_values( $email_of ) ); } public static function flush( $form_id ) { delete_transient( 'aieo_form_dist_' . self::REPORT_VERSION . '_' . (int) $form_id ); delete_transient( 'aieo_form_dist_' . (int) $form_id ); } }