form_id = (int) $form_id; $this->include_null_status = (bool) $include_null_status; } public function surface() { return class_exists( 'AIEO_Form_Registry' ) ? AIEO_Form_Registry::surface( 'gravity', $this->form_id ) : 'gravity_' . $this->form_id; } public function label() { if ( ! class_exists( 'GFAPI' ) ) { return 'Gravity ' . $this->form_id; } $form = GFAPI::get_form( $this->form_id ); return is_array( $form ) && ! empty( $form['title'] ) ? (string) $form['title'] : 'Gravity ' . $this->form_id; } public function available() { return class_exists( 'AIEO_Form_Insights' ) && AIEO_Form_Insights::available() && (bool) $this->shapes(); } private function shapes() { if ( null === $this->shapes ) { $this->shapes = class_exists( 'AIEO_Form_Insights' ) ? (array) AIEO_Form_Insights::field_shapes( $this->form_id ) : array(); } return $this->shapes; } public function field_catalog() { $out = array(); foreach ( $this->shapes() as $fid => $f ) { $out[ (string) $fid ] = array( 'key' => (string) $fid, 'label' => isset( $f['label'] ) ? (string) $f['label'] : (string) $fid, 'admin_label' => isset( $f['admin_label'] ) ? (string) $f['admin_label'] : '', 'shape' => isset( $f['shape'] ) ? (string) $f['shape'] : '', 'choices' => isset( $f['choices'] ) && is_array( $f['choices'] ) ? $f['choices'] : array(), ); } return $out; } public function harvest( $limit_per_field = 200 ) { global $wpdb; $meta = $wpdb->prefix . 'gf_entry_meta'; $ent = $wpdb->prefix . 'gf_entry'; $seen = array(); $rows = (array) $wpdb->get_results( $wpdb->prepare( "SELECT m.meta_key, m.meta_value, COUNT(*) AS hits, MIN(m.entry_id) AS sample FROM `{$meta}` m JOIN `{$ent}` e ON e.id = m.entry_id WHERE m.form_id = %d AND {$this->status_sql()} AND m.meta_value <> '' GROUP BY m.meta_key, m.meta_value ORDER BY hits DESC", $this->form_id ), ARRAY_A ); $shapes = $this->shapes(); foreach ( $rows as $r ) { $key = (string) $r['meta_key']; $dot = strpos( $key, '.' ); $fid = (string) ( false !== $dot ? substr( $key, 0, $dot ) : $key ); if ( ! isset( $shapes[ $fid ] ) ) { continue; } $parts = ( 'ranking' === ( isset( $shapes[ $fid ]['shape'] ) ? $shapes[ $fid ]['shape'] : '' ) ) ? array_filter( array_map( 'trim', explode( ',', (string) $r['meta_value'] ) ) ) : array( (string) $r['meta_value'] ); foreach ( $parts as $part ) { $label = $this->resolve_token( $shapes[ $fid ], $part ); if ( '' === $label ) { continue; } if ( ! isset( $seen[ $fid ] ) ) { $seen[ $fid ] = array(); } if ( count( $seen[ $fid ] ) >= $limit_per_field && ! isset( $seen[ $fid ][ $label ] ) ) { continue; } if ( isset( $seen[ $fid ][ $label ] ) ) { $seen[ $fid ][ $label ]['hits'] += (int) $r['hits']; } else { $seen[ $fid ][ $label ] = array( 'hits' => (int) $r['hits'], 'sample_entry_id' => (int) $r['sample'] ); } } } return AIEO_Survey_Value_Stage::replace( $this->surface(), $seen ); } public function observed_values( $field_key ) { $out = array(); foreach ( AIEO_Survey_Value_Stage::values( $this->surface(), $field_key ) as $r ) { $out[ (string) $r['observed_value'] ] = (int) $r['hits']; } return $out; } public function entry_ids( array $args = array() ) { global $wpdb; $ent = $wpdb->prefix . 'gf_entry'; $since = isset( $args['since'] ) ? (string) $args['since'] : ''; $after = isset( $args['after_id'] ) ? (int) $args['after_id'] : 0; $limit = isset( $args['limit'] ) ? max( 1, (int) $args['limit'] ) : 500; $sql = "SELECT e.id FROM `{$ent}` e WHERE e.form_id = %d AND {$this->status_sql()} AND e.id > %d"; $vals = array( $this->form_id, $after ); if ( '' !== $since ) { $sql .= ' AND e.date_created >= %s'; $vals[] = $since; } $sql .= ' ORDER BY e.id ASC LIMIT %d'; $vals[] = $limit; return array_map( 'intval', (array) $wpdb->get_col( $wpdb->prepare( $sql, $vals ) ) ); } public function entry( $entry_id ) { global $wpdb; $entry_id = (int) $entry_id; $ent = $wpdb->prefix . 'gf_entry'; $meta = $wpdb->prefix . 'gf_entry_meta'; $head = $wpdb->get_row( $wpdb->prepare( "SELECT e.id, e.date_created, e.created_by FROM `{$ent}` e WHERE e.id = %d AND e.form_id = %d AND {$this->status_sql()}", $entry_id, $this->form_id ), ARRAY_A ); if ( ! $head ) { return null; } $meta_rows = (array) $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM `{$meta}` WHERE entry_id = %d AND meta_value <> ''", $entry_id ), ARRAY_A ); return array( 'rows' => $this->rows_from_meta( $meta_rows ), 'context' => array_merge( array( 'user_id' => (int) $head['created_by'], 'source' => 'gravity', 'created_at' => (string) $head['date_created'], 'status' => 'imported', ), $this->context_from_meta( $meta_rows ) ), ); } private function context_from_meta( array $meta_rows ) { $roles = AIEO_Survey_Mapping::get( $this->surface() )['context']; if ( ! array_filter( $roles ) ) { return array(); } $flat = array(); foreach ( $meta_rows as $r ) { $flat[ (string) $r['meta_key'] ] = (string) $r['meta_value']; } $order_id = ( '' !== $roles['order'] && isset( $flat[ $roles['order'] ] ) ) ? (int) $flat[ $roles['order'] ] : 0; $email = ( '' !== $roles['email'] && isset( $flat[ $roles['email'] ] ) ) ? strtolower( trim( $flat[ $roles['email'] ] ) ) : ''; $customer = ( '' !== $roles['customer'] && isset( $flat[ $roles['customer'] ] ) ) ? (int) $flat[ $roles['customer'] ] : 0; $out = array( 'order_id' => $order_id ); if ( $customer > 0 ) { $out['user_id'] = $customer; } $eponymous = self::resolve_eponymous( $order_id, $email ); if ( $eponymous > 0 ) { $out['eponymous_id'] = $eponymous; } return $out; } private static function resolve_eponymous( $order_id, $email ) { static $memo = array(); $key = $order_id . '|' . $email; if ( isset( $memo[ $key ] ) ) { return $memo[ $key ]; } global $wpdb; $id = 0; $ci = $wpdb->prefix . 'aieo_customer_intelligence'; if ( (string) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $ci ) ) === $ci ) { if ( $order_id > 0 && function_exists( 'wc_get_order' ) ) { $order = wc_get_order( $order_id ); if ( $order ) { $order_email = strtolower( trim( (string) $order->get_billing_email() ) ); if ( '' !== $order_email ) { $id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT eponymous_id FROM `{$ci}` WHERE email = %s LIMIT 1", $order_email ) ); } } } if ( $id <= 0 && '' !== $email ) { $id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT eponymous_id FROM `{$ci}` WHERE email = %s LIMIT 1", $email ) ); } } $memo[ $key ] = $id; return $id; } public function rows_from_meta( array $meta_rows ) { $shapes = $this->shapes(); $surface = $this->surface(); $map = AIEO_Survey_Mapping::get( $surface ); $codes = AIEO_Survey_Mapping::resolved( $surface ); $fixed = AIEO_Survey_Mapping::fixed_choices( $surface ); $out = array(); foreach ( $meta_rows as $r ) { $key = (string) $r['meta_key']; $dot = strpos( $key, '.' ); $fid = (string) ( false !== $dot ? substr( $key, 0, $dot ) : $key ); if ( ! isset( $codes[ $fid ] ) ) { continue; } $question = AIEO_Survey_Blueprint::question( $codes[ $fid ] ); if ( ! $question ) { continue; } if ( ! isset( $shapes[ $fid ] ) ) { if ( 'text' !== $question['answers'] ) { continue; } $txt = trim( (string) $r['meta_value'] ); if ( '' !== $txt ) { $out[] = array( 'field_key' => $question['code'], 'disposition' => 'answered', 'choice_key' => null, 'value_num' => null, 'value_text' => mb_substr( wp_check_invalid_utf8( $txt ), 0, 5000 ), ); } continue; } if ( isset( $fixed[ $fid ] ) && '' !== trim( (string) $r['meta_value'] ) ) { $out[] = array( 'field_key' => $question['code'], 'disposition' => 'answered', 'choice_key' => $fixed[ $fid ], 'value_num' => null, 'value_text' => null, ); continue; } $base = (array) AIEO_Form_Insights::answer_rows( $shapes[ $fid ], $key, (string) $r['meta_value'] ); foreach ( $base as $row ) { $out[] = $this->to_question_row( $row, $question, $shapes[ $fid ], $map['unmapped_policy'] ); } } $present = array(); foreach ( $out as $row ) { $present[ $row['field_key'] ] = true; } foreach ( $codes as $code ) { if ( ! isset( $present[ $code ] ) ) { $out[] = array( 'field_key' => $code, 'disposition' => 'skipped', 'choice_key' => null, 'value_num' => null, 'value_text' => null ); } } return array_values( array_filter( $out ) ); } private function to_question_row( array $row, array $question, array $shape, $policy ) { $out = array( 'field_key' => $question['code'], 'disposition' => isset( $row['disposition'] ) ? $row['disposition'] : 'answered', 'choice_key' => null, 'value_num' => isset( $row['value_num'] ) ? $row['value_num'] : null, 'value_text' => isset( $row['value_text'] ) ? $row['value_text'] : null, ); if ( 'scale' === $question['scored'] ) { $out['choice_key'] = isset( $row['choice_key'] ) ? $row['choice_key'] : null; return $out; } $observed = null !== $out['value_text'] ? (string) $out['value_text'] : (string) ( isset( $row['choice_key'] ) ? $row['choice_key'] : '' ); if ( '' !== trim( $observed ) ) { $label = $this->resolve_token( $shape, $observed ); $code = AIEO_Survey_Choice_Register::code_for( '' !== $label ? $label : $observed ); if ( '' !== $code && ( ! $question['choices'] || isset( $question['choices'][ $code ] ) ) ) { $out['choice_key'] = $code; $out['value_text'] = null; return $out; } if ( 'exclude' === $policy ) { return null; } $out['value_text'] = '' !== $label ? $label : $observed; } return $out; } private function resolve_token( array $shape, $stored ) { $stored = (string) $stored; if ( isset( $shape['choices'] ) && is_array( $shape['choices'] ) && isset( $shape['choices'][ $stored ] ) ) { return (string) $shape['choices'][ $stored ]; } return $stored; } private function status_sql() { return $this->include_null_status ? "( e.status = 'active' OR e.status IS NULL )" : "e.status = 'active'"; } }