form_id = (string) $form_id; $this->title = (string) $title; } /** * Plugin slug as used by the platform. * * @return string */ abstract protected function plugin(); /** * Fields of the form: list of { name, label, type }. * * @return array */ abstract protected function fields(); /** * Count, oldest and newest. * * @return array */ abstract protected function stats(); /** {@inheritDoc} */ public function key() { return 'form_entries:' . $this->plugin() . ':' . $this->form_id; } /** {@inheritDoc} */ public function type() { return 'form_entries'; } /** {@inheritDoc} */ public function label() { /* translators: %s: form title. */ return sprintf( __( 'Form entries: %s', 'hypamoon-connect' ), $this->title ); } /** {@inheritDoc} */ public function personal_data() { return true; } /** {@inheritDoc} */ public function date_field() { return 'date'; } /** {@inheritDoc} */ public function form() { return array( 'plugin' => $this->plugin(), 'id' => $this->form_id, 'title' => $this->title, ); } /** {@inheritDoc} */ public function inventory() { $item = $this->item( $this->stats() ); $item['form'] = $this->form(); $item['fields'] = $this->fields(); return $item; } /** * Add a value under a unique label. * * @param array $fields Fields, by reference. * @param string $label Label. * @param mixed $value Value. */ protected static function put( &$fields, $label, $value ) { $label = trim( wp_strip_all_tags( (string) $label ) ); if ( '' === $label || Hypamoon_Connect_Record::is_secret_key( $label ) ) { return; } $name = $label; $i = 2; while ( array_key_exists( $name, $fields ) ) { $name = $label . ' (' . $i . ')'; ++$i; } $fields[ $name ] = Hypamoon_Connect_Record::plain( $value ); } } /** * Contact Form 7 entries stored by Flamingo. */ class Hypamoon_Connect_Cf7_Source extends Hypamoon_Connect_Form_Source { /** * Flamingo channel term taxonomy id. * * @var int */ private $term_taxonomy_id; /** * Contact Form 7 form post id, or 0. * * @var int */ private $form_post_id; /** * Constructor. * * @param string $form_id Form id. * @param string $title Title. * @param int $term_taxonomy_id Channel term taxonomy id. * @param int $form_post_id CF7 form post id or 0. */ public function __construct( $form_id, $title, $term_taxonomy_id, $form_post_id ) { parent::__construct( $form_id, $title ); $this->term_taxonomy_id = (int) $term_taxonomy_id; $this->form_post_id = (int) $form_post_id; } /** * Find all Flamingo channels of Contact Form 7. * * @return Hypamoon_Connect_Cf7_Source[] */ public static function discover() { $sources = array(); if ( ! post_type_exists( 'flamingo_inbound' ) || ! taxonomy_exists( 'flamingo_inbound_channel' ) ) { return $sources; } $parent = get_term_by( 'slug', 'contact-form-7', 'flamingo_inbound_channel' ); $args = array( 'taxonomy' => 'flamingo_inbound_channel', 'hide_empty' => false, ); if ( $parent ) { $args['parent'] = (int) $parent->term_id; } $terms = get_terms( $args ); if ( ! is_array( $terms ) ) { return $sources; } foreach ( $terms as $term ) { $form_post_id = 0; $forms = get_posts( array( 'post_type' => 'wpcf7_contact_form', 'name' => $term->slug, 'posts_per_page' => 1, 'fields' => 'ids', 'post_status' => 'any', ) ); if ( ! empty( $forms ) ) { $form_post_id = (int) $forms[0]; } $form_id = $form_post_id ? (string) $form_post_id : 'channel-' . $term->term_id; $sources[] = new self( $form_id, $term->name, (int) $term->term_taxonomy_id, $form_post_id ); } return $sources; } /** {@inheritDoc} */ protected function plugin() { return 'contact_form_7'; } /** {@inheritDoc} */ protected function fields() { $fields = array(); if ( ! $this->form_post_id || ! class_exists( 'WPCF7_ContactForm' ) ) { return $fields; } $form = WPCF7_ContactForm::get_instance( $this->form_post_id ); if ( ! $form ) { return $fields; } foreach ( $form->scan_form_tags() as $tag ) { if ( empty( $tag->name ) || in_array( $tag->basetype, array( 'submit', 'quiz', 'captchar', 'captchac' ), true ) || Hypamoon_Connect_Record::is_secret_key( $tag->name ) ) { continue; } $fields[] = array( 'name' => (string) $tag->name, 'label' => (string) $tag->name, 'type' => (string) $tag->basetype, ); } return $fields; } /** {@inheritDoc} */ protected function stats() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery $row = $wpdb->get_row( $wpdb->prepare( "SELECT COUNT(*) AS total, MIN(p.post_date_gmt) AS oldest, MAX(p.post_date_gmt) AS newest FROM {$wpdb->posts} p INNER JOIN {$wpdb->term_relationships} r ON r.object_id = p.ID WHERE r.term_taxonomy_id = %d AND p.post_type = 'flamingo_inbound' AND p.post_status = 'publish'", $this->term_taxonomy_id ), ARRAY_A ); return array( 'count' => isset( $row['total'] ) ? (int) $row['total'] : 0, 'oldest' => empty( $row['oldest'] ) ? null : Hypamoon_Connect_Record::iso( $row['oldest'] ), 'newest' => empty( $row['newest'] ) ? null : Hypamoon_Connect_Record::iso( $row['newest'] ), ); } /** {@inheritDoc} */ public function fetch( $sub, $cursor, $limit, $rule ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery $ids = $wpdb->get_col( $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p INNER JOIN {$wpdb->term_relationships} r ON r.object_id = p.ID WHERE r.term_taxonomy_id = %d AND p.post_type = 'flamingo_inbound' AND p.post_status = 'publish' AND p.ID > %d ORDER BY p.ID ASC LIMIT %d", $this->term_taxonomy_id, (int) $cursor, $limit + 1 ) ); $ids = array_map( 'intval', (array) $ids ); return $this->items_from_ids( array_slice( $ids, 0, $limit ), array( $this, 'build' ), count( $ids ) <= $limit ); } /** * Build an entry record. * * @param int $id Flamingo message id. * @return array|null */ public function build( $id ) { $post = get_post( $id ); if ( ! $post ) { return null; } $names = get_post_meta( $post->ID, '_fields', true ); $fields = array(); foreach ( array_keys( is_array( $names ) ? $names : array() ) as $name ) { self::put( $fields, $name, get_post_meta( $post->ID, '_field_' . $name, true ) ); } $meta = get_post_meta( $post->ID, '_meta', true ); $record = array( 'wp_id' => (int) $post->ID, 'date' => Hypamoon_Connect_Record::iso( $post->post_date_gmt, $post->post_date ), 'fields' => (object) $fields, ); if ( is_array( $meta ) && ! empty( $meta['url'] ) ) { $record['page_url'] = esc_url_raw( (string) $meta['url'] ); } return $record; } } /** * WPForms entries (stored by WPForms Pro in its own table). */ class Hypamoon_Connect_Wpforms_Source extends Hypamoon_Connect_Form_Source { /** * Whether WPForms stores entries on this site. * * @return bool */ public static function has_entries_table() { global $wpdb; $table = esc_sql( $wpdb->prefix . 'wpforms_entries' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery return $table === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) ); } /** * Find all WPForms forms. * * @return Hypamoon_Connect_Wpforms_Source[] */ public static function discover() { $sources = array(); $forms = get_posts( array( 'post_type' => 'wpforms', 'post_status' => array( 'publish', 'draft', 'private' ), 'posts_per_page' => -1, 'orderby' => 'ID', 'order' => 'ASC', ) ); foreach ( $forms as $form ) { $sources[] = new self( (string) $form->ID, $form->post_title ); } return $sources; } /** {@inheritDoc} */ protected function plugin() { return 'wpforms'; } /** * Form definition fields keyed by field id. * * @return array */ private function definition() { $post = get_post( (int) $this->form_id ); if ( ! $post ) { return array(); } $data = json_decode( $post->post_content, true ); return ( is_array( $data ) && isset( $data['fields'] ) && is_array( $data['fields'] ) ) ? $data['fields'] : array(); } /** {@inheritDoc} */ protected function fields() { $fields = array(); foreach ( $this->definition() as $field ) { $type = isset( $field['type'] ) ? (string) $field['type'] : ''; if ( in_array( $type, array( 'password', 'captcha', 'pagebreak', 'divider', 'html', 'content' ), true ) ) { continue; } $label = isset( $field['label'] ) ? (string) $field['label'] : ''; if ( Hypamoon_Connect_Record::is_secret_key( $label ) ) { continue; } $fields[] = array( 'name' => $label, 'label' => $label, 'type' => $type, ); } return $fields; } /** {@inheritDoc} */ protected function stats() { global $wpdb; $table = esc_sql( $wpdb->prefix . 'wpforms_entries' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $row = $wpdb->get_row( $wpdb->prepare( "SELECT COUNT(*) AS total, MIN(date) AS oldest, MAX(date) AS newest FROM {$table} WHERE form_id = %d", (int) $this->form_id ), ARRAY_A ); return array( 'count' => isset( $row['total'] ) ? (int) $row['total'] : 0, 'oldest' => empty( $row['oldest'] ) ? null : Hypamoon_Connect_Record::iso( $row['oldest'] ), 'newest' => empty( $row['newest'] ) ? null : Hypamoon_Connect_Record::iso( $row['newest'] ), ); } /** {@inheritDoc} */ public function fetch( $sub, $cursor, $limit, $rule ) { global $wpdb; $table = esc_sql( $wpdb->prefix . 'wpforms_entries' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $rows = $wpdb->get_results( $wpdb->prepare( "SELECT entry_id, status, fields, date FROM {$table} WHERE form_id = %d AND entry_id > %d ORDER BY entry_id ASC LIMIT %d", (int) $this->form_id, (int) $cursor, $limit + 1 ), ARRAY_A ); $rows = (array) $rows; $done = count( $rows ) <= $limit; $rows = array_slice( $rows, 0, $limit ); $definition = $this->definition(); $items = array(); foreach ( $rows as $row ) { $values = json_decode( (string) $row['fields'], true ); $fields = array(); foreach ( is_array( $values ) ? $values : array() as $field_id => $value ) { $type = isset( $value['type'] ) ? (string) $value['type'] : ( isset( $definition[ $field_id ]['type'] ) ? (string) $definition[ $field_id ]['type'] : '' ); if ( 'password' === $type ) { continue; } $label = isset( $value['name'] ) && '' !== $value['name'] ? $value['name'] : ( isset( $definition[ $field_id ]['label'] ) ? $definition[ $field_id ]['label'] : (string) $field_id ); self::put( $fields, $label, isset( $value['value'] ) ? $value['value'] : '' ); } $record = array( 'wp_id' => (int) $row['entry_id'], 'date' => Hypamoon_Connect_Record::iso( $row['date'] ), 'fields' => (object) $fields, 'status' => (string) $row['status'], ); $items[] = array( 'cursor' => (int) $row['entry_id'], 'records' => array( $record ), ); } return array( 'items' => $items, 'done' => $done, ); } } /** * Gravity Forms entries through GFAPI. */ class Hypamoon_Connect_Gravity_Source extends Hypamoon_Connect_Form_Source { /** * Find all Gravity Forms forms. * * @return Hypamoon_Connect_Gravity_Source[] */ public static function discover() { $sources = array(); if ( ! class_exists( 'GFAPI' ) ) { return $sources; } $forms = GFAPI::get_forms( null, false ); foreach ( (array) $forms as $form ) { if ( isset( $form['id'] ) ) { $sources[] = new self( (string) $form['id'], isset( $form['title'] ) ? $form['title'] : (string) $form['id'] ); } } return $sources; } /** {@inheritDoc} */ protected function plugin() { return 'gravity_forms'; } /** * Usable fields of the form. * * @return array */ private function form_fields() { $form = GFAPI::get_form( (int) $this->form_id ); $out = array(); if ( ! $form || empty( $form['fields'] ) ) { return $out; } foreach ( $form['fields'] as $field ) { $type = isset( $field->type ) ? (string) $field->type : ''; if ( in_array( $type, array( 'password', 'html', 'section', 'page', 'captcha', 'creditcard' ), true ) ) { continue; } $out[] = $field; } return $out; } /** {@inheritDoc} */ protected function fields() { $fields = array(); foreach ( $this->form_fields() as $field ) { $fields[] = array( 'name' => (string) $field->label, 'label' => (string) $field->label, 'type' => (string) $field->type, ); } return $fields; } /** * Active entry ids, ascending. * * @return int[] */ private function ids() { $ids = GFAPI::get_entry_ids( (int) $this->form_id, array( 'status' => 'active' ), array( 'key' => 'id', 'direction' => 'ASC', 'is_numeric' => true, ), array( 'offset' => 0, 'page_size' => 1000000, ) ); return is_array( $ids ) ? array_map( 'intval', $ids ) : array(); } /** {@inheritDoc} */ protected function stats() { $ids = $this->ids(); $oldest = null; $newest = null; if ( ! empty( $ids ) ) { $first = GFAPI::get_entry( min( $ids ) ); $last = GFAPI::get_entry( max( $ids ) ); $oldest = is_array( $first ) ? Hypamoon_Connect_Record::iso( $first['date_created'] ) : null; $newest = is_array( $last ) ? Hypamoon_Connect_Record::iso( $last['date_created'] ) : null; } return array( 'count' => count( $ids ), 'oldest' => $oldest, 'newest' => $newest, ); } /** {@inheritDoc} */ public function fetch( $sub, $cursor, $limit, $rule ) { $slice = $this->slice_ids( $this->ids(), $cursor, $limit ); return $this->items_from_ids( $slice['ids'], array( $this, 'build' ), $slice['done'] ); } /** * Build an entry record. * * @param int $id Entry id. * @return array|null */ public function build( $id ) { $entry = GFAPI::get_entry( $id ); if ( ! is_array( $entry ) ) { return null; } $fields = array(); foreach ( $this->form_fields() as $field ) { $inputs = method_exists( $field, 'get_entry_inputs' ) ? $field->get_entry_inputs() : null; if ( is_array( $inputs ) && ! empty( $inputs ) ) { $parts = array(); foreach ( $inputs as $input ) { $value = isset( $entry[ (string) $input['id'] ] ) ? $entry[ (string) $input['id'] ] : ''; if ( '' !== $value && null !== $value ) { $parts[ isset( $input['label'] ) && '' !== $input['label'] ? (string) $input['label'] : (string) $input['id'] ] = $value; } } self::put( $fields, $field->label, $parts ); } else { $value = isset( $entry[ (string) $field->id ] ) ? $entry[ (string) $field->id ] : ''; self::put( $fields, $field->label, $value ); } } $record = array( 'wp_id' => (int) $entry['id'], 'date' => Hypamoon_Connect_Record::iso( $entry['date_created'] ), 'fields' => (object) $fields, 'status' => isset( $entry['status'] ) ? (string) $entry['status'] : '', ); if ( ! empty( $entry['source_url'] ) ) { $record['page_url'] = esc_url_raw( (string) $entry['source_url'] ); } return $record; } }