get_var( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'flamingo_inbound' AND post_status = 'publish' LIMIT 1" ) ) { // phpcs:ignore WordPress.DB return true; } $table = $wpdb->prefix . 'wpforms_entries'; return Import_Sources::table_exists( $table ) && (bool) $wpdb->get_var( $wpdb->prepare( "SELECT entry_id FROM %i WHERE status NOT IN ('spam','trash','partial','abandoned') LIMIT 1", $table ) ); // phpcs:ignore WordPress.DB } /* ------------------------------------------------------------------ * Flamingo * ---------------------------------------------------------------- */ /** * Flamingo messages grouped by channel (one channel per CF7 form). * * @return array[] */ private static function flamingo_groups() { global $wpdb; $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB "SELECT t.term_id, t.slug, t.name, COUNT(p.ID) AS total FROM {$wpdb->posts} p JOIN {$wpdb->term_relationships} r ON r.object_id = p.ID JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = r.term_taxonomy_id AND tt.taxonomy = 'flamingo_inbound_channel' JOIN {$wpdb->terms} t ON t.term_id = tt.term_id WHERE p.post_type = 'flamingo_inbound' AND p.post_status = 'publish' GROUP BY t.term_id, t.slug, t.name" ); if ( ! $rows ) { return array(); } $done = Entries::import_refs( 'flamingo:' ); $out = array(); foreach ( $rows as $row ) { $form = self::cf7_by_slug( $row->slug ); $ids = self::flamingo_ids( (int) $row->term_id ); $out[] = array( 'key' => 'flamingo:' . (int) $row->term_id, 'plugin' => 'Flamingo (Contact Form 7)', 'title' => $form ? $form->post_title : $row->name, 'form_id' => $form ? Importer::form_id_for( 'cf7', $form->ID ) : sanitize_key( 'flamingo_' . $row->term_id ), 'total' => count( $ids ), 'imported' => count( array_intersect_key( $done, array_flip( array_map( static function ( $id ) { return 'flamingo:' . $id; }, $ids ) ) ) ), 'too_old' => count( self::flamingo_ids( (int) $row->term_id, true ) ), ); } return $out; } /** * Message IDs of a Flamingo channel. * * @param int $term_id Channel term. * @param bool $old true: only messages older than the retention period, false: only newer ones. * @return int[] */ private static function flamingo_ids( $term_id, $old = false ) { global $wpdb; $cutoff = self::cutoff(); if ( ! $cutoff && $old ) { return array(); } $date = $cutoff ? gmdate( 'Y-m-d H:i:s', $cutoff ) : '0000-00-00 00:00:00'; if ( $old ) { $sql = $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->term_relationships} r ON r.object_id = p.ID JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = r.term_taxonomy_id AND tt.taxonomy = 'flamingo_inbound_channel' WHERE p.post_type = 'flamingo_inbound' AND p.post_status = 'publish' AND tt.term_id = %d AND p.post_date_gmt < %s ORDER BY p.ID ASC", $term_id, $date ); } else { $sql = $wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p JOIN {$wpdb->term_relationships} r ON r.object_id = p.ID JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = r.term_taxonomy_id AND tt.taxonomy = 'flamingo_inbound_channel' WHERE p.post_type = 'flamingo_inbound' AND p.post_status = 'publish' AND tt.term_id = %d AND p.post_date_gmt >= %s ORDER BY p.ID ASC", $term_id, $date ); } return array_map( 'intval', $wpdb->get_col( $sql ) ); // phpcs:ignore WordPress.DB -- Both branches bind all values; core table names only, fresh source rows required. } /** * CF7 form by its slug (Flamingo names its channels after the form). * * @param string $slug Slug. * @return \WP_Post|null */ private static function cf7_by_slug( $slug ) { global $wpdb; $id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'wpcf7_contact_form' AND post_name = %s LIMIT 1", $slug ) ); // phpcs:ignore WordPress.DB return $id ? get_post( (int) $id ) : null; } /** * Copy Flamingo messages of one channel. * * @param array $group Group from groups(). * @param int $limit Max. number to copy. * @return int Copied. */ private static function import_flamingo( array $group, $limit ) { $term_id = (int) substr( $group['key'], strlen( 'flamingo:' ) ); $done = Entries::import_refs( 'flamingo:' ); $copied = 0; foreach ( self::flamingo_ids( $term_id ) as $id ) { if ( $copied >= $limit ) { break; } $ref = 'flamingo:' . $id; if ( isset( $done[ $ref ] ) ) { continue; } $post = get_post( $id ); if ( ! $post ) { continue; } $keys = array_keys( (array) get_post_meta( $id, '_fields', true ) ); $data = array(); foreach ( $keys as $key ) { $value = get_post_meta( $id, sanitize_key( '_field_' . $key ), true ); if ( is_array( $value ) ) { $value = implode( ', ', array_map( 'strval', array_filter( $value, 'is_scalar' ) ) ); } $data[] = self::value( (string) $key, self::label_from_name( (string) $key ), (string) $value ); } $meta = (array) get_post_meta( $id, '_meta', true ); $saved = self::store( $group, $ref, $data, (string) get_post_meta( $id, '_from_email', true ), isset( $meta['remote_ip'] ) ? (string) $meta['remote_ip'] : '', isset( $meta['user_agent'] ) ? (string) $meta['user_agent'] : '', isset( $meta['url'] ) ? (string) $meta['url'] : '', $post->post_date_gmt, (bool) get_post_meta( $id, '_consent', true ) ); if ( $saved ) { ++$copied; } else { ++self::$failed; break; } } return $copied; } /* ------------------------------------------------------------------ * WPForms (entries are stored by WPForms Pro) * ---------------------------------------------------------------- */ /** * WPForms entries grouped by form. * * @return array[] */ private static function wpforms_groups() { global $wpdb; $table = $wpdb->prefix . 'wpforms_entries'; if ( ! Import_Sources::table_exists( $table ) ) { return array(); } $rows = $wpdb->get_results( $wpdb->prepare( "SELECT form_id, COUNT(*) AS total FROM %i WHERE status NOT IN ('spam','trash','partial','abandoned') GROUP BY form_id", $table ) ); // phpcs:ignore WordPress.DB if ( ! $rows ) { return array(); } $out = array(); foreach ( $rows as $row ) { $form_id = (int) $row->form_id; $date = self::cutoff() ? gmdate( 'Y-m-d H:i:s', self::cutoff() ) : '0000-00-00 00:00:00'; $ids = $wpdb->get_col( $wpdb->prepare( "SELECT entry_id FROM %i WHERE form_id = %d AND status NOT IN ('spam','trash','partial','abandoned') AND date >= %s", $table, $form_id, $date ) ); // phpcs:ignore WordPress.DB $done = Entries::import_refs( 'wpforms:' ); $post = get_post( $form_id ); $out[] = array( 'key' => 'wpforms:' . $form_id, 'plugin' => 'WPForms', 'title' => $post ? $post->post_title : 'WPForms #' . $form_id, 'form_id' => Importer::form_id_for( 'wpforms', $form_id ), 'total' => count( $ids ), 'imported' => count( array_intersect_key( $done, array_flip( array_map( static function ( $id ) { return 'wpforms:' . $id; }, $ids ) ) ) ), 'too_old' => (int) $row->total - count( $ids ), ); } return $out; } /** * Copy WPForms entries of one form. * * @param array $group Group from groups(). * @param int $limit Max. number to copy. * @return int Copied. */ private static function import_wpforms( array $group, $limit ) { global $wpdb; $table = $wpdb->prefix . 'wpforms_entries'; $form_id = (int) substr( $group['key'], strlen( 'wpforms:' ) ); $done = Entries::import_refs( 'wpforms:' ); $cutoff = self::cutoff(); $date = $cutoff ? gmdate( 'Y-m-d H:i:s', $cutoff ) : '0000-00-00 00:00:00'; $rows = $wpdb->get_results( $wpdb->prepare( "SELECT entry_id, fields, date, ip_address, user_agent FROM %i WHERE form_id = %d AND status NOT IN ('spam','trash','partial','abandoned') AND date >= %s ORDER BY entry_id ASC", $table, $form_id, $date ) ); // phpcs:ignore WordPress.DB $copied = 0; foreach ( (array) $rows as $row ) { if ( $copied >= $limit ) { break; } $ref = 'wpforms:' . (int) $row->entry_id; if ( isset( $done[ $ref ] ) ) { continue; } $fields = json_decode( (string) $row->fields, true ); $data = array(); $email = ''; $consent = false; $map = array( 'email' => 'email', 'textarea' => 'textarea', 'phone' => 'tel', 'url' => 'url', 'number' => 'number', 'gdpr-checkbox' => 'consent', ); foreach ( (array) $fields as $f ) { if ( ! is_array( $f ) || ! isset( $f['value'] ) ) { continue; } $type = isset( $f['type'] ) && isset( $map[ $f['type'] ] ) ? $map[ $f['type'] ] : 'text'; $label = isset( $f['name'] ) && '' !== $f['name'] ? (string) $f['name'] : 'Field ' . ( isset( $f['id'] ) ? $f['id'] : '' ); $value = is_scalar( $f['value'] ) ? (string) $f['value'] : ''; if ( 'consent' === $type && self::consent_given( $f['value'] ) ) { $consent = true; } $data[] = array( 'id' => sanitize_key( 'f' . ( isset( $f['id'] ) ? $f['id'] : count( $data ) ) ), 'label' => sanitize_text_field( $label ), 'type' => $type, 'value' => sanitize_textarea_field( $value ), ); if ( '' === $email && 'email' === $type && is_email( $value ) ) { $email = $value; } } if ( self::store( $group, $ref, $data, $email, (string) $row->ip_address, (string) $row->user_agent, '', (string) $row->date, $consent ) ) { ++$copied; } else { ++self::$failed; break; } } return $copied; } /* ------------------------------------------------------------------ * Shared * ---------------------------------------------------------------- */ /** * Run the import for the selected groups. * * @param string[] $keys Group keys. * @return array copied, old, remaining. */ public static function run( array $keys ) { self::$failed = 0; $copied = 0; $old = 0; $left = self::BATCH; foreach ( self::groups() as $group ) { if ( ! in_array( $group['key'], $keys, true ) || $left <= 0 ) { continue; } $n = 0 === strpos( $group['key'], 'flamingo:' ) ? self::import_flamingo( $group, $left ) : self::import_wpforms( $group, $left ); $copied += $n; $old += $group['too_old']; $left -= $n; if ( self::$failed ) { break; } } $remaining = 0; foreach ( self::groups() as $group ) { if ( in_array( $group['key'], $keys, true ) ) { $remaining += max( 0, $group['total'] - $group['imported'] ); } } return array( 'copied' => $copied, 'old' => $old, 'remaining' => $remaining, 'failed' => self::$failed, ); } /** * Only an explicit checkbox value establishes imported consent. * Unknown labels or structures are preserved as data, never treated as proof. * * @param mixed $value Source value. * @return bool */ private static function consent_given( $value ) { return is_scalar( $value ) && in_array( strtolower( trim( (string) $value ) ), array( '1', 'true', 'yes', 'on', 'checked' ), true ); } /** * Store one copied submission. * * @param array $group Group. * @param string $ref Import reference. * @param array $data Field values. * @param string $email Sender email. * @param string $ip IP address. * @param string $agent User agent. * @param string $url Page URL. * @param string $date Date (GMT). * @param bool $consent Consent given. * @return bool Whether the copy was stored. */ private static function store( array $group, $ref, array $data, $email, $ip, $agent, $url, $date, $consent ) { if ( '' === $email ) { foreach ( $data as $d ) { if ( is_email( $d['value'] ) ) { $email = $d['value']; break; } } } $mode = alpeg_forms_setting( 'ip_mode', 'anonymized' ); $ip = 'none' === $mode || ! filter_var( $ip, FILTER_VALIDATE_IP ) ? '' : ( 'full' === $mode ? $ip : (string) wp_privacy_anonymize_ip( $ip ) ); $time = strtotime( $date . ' UTC' ); return false !== Entries::insert( array( 'form_id' => $group['form_id'], 'form_title' => sanitize_text_field( $group['title'] ), 'page_url' => esc_url_raw( $url ), 'data' => $data, 'email' => is_email( $email ) ? $email : '', 'ip' => $ip, 'user_agent' => substr( sanitize_text_field( $agent ), 0, 255 ), 'consent' => $consent ? 1 : 0, 'status' => 'read', 'import_ref' => $ref, 'created_at' => $time ? gmdate( 'Y-m-d H:i:s', $time ) : current_time( 'mysql', true ), ) ); } /** * One stored value. * * @param string $id Field name. * @param string $label Label. * @param string $value Value. * @return array */ private static function value( $id, $label, $value ) { $value = sanitize_textarea_field( $value ); $type = is_email( $value ) ? 'email' : ( false !== strpos( $value, "\n" ) || strlen( $value ) > 80 ? 'textarea' : 'text' ); return array( 'id' => substr( sanitize_key( $id ), 0, 40 ), 'label' => sanitize_text_field( $label ), 'type' => $type, 'value' => $value, ); } /** * Readable label from a CF7 field name ("your-name" → "Name"). * * @param string $name Field name. * @return string */ public static function label_from_name( $name ) { $label = preg_replace( '/^(your|ihre?|dein[e]?)[-_]/i', '', $name ); $label = trim( str_replace( array( '-', '_' ), ' ', (string) $label ) ); $known = array( 'email' => __( 'Email', 'alpeg-forms' ), 'e mail' => __( 'Email', 'alpeg-forms' ), 'name' => __( 'Name', 'alpeg-forms' ), 'message' => __( 'Message', 'alpeg-forms' ), 'subject' => __( 'Subject', 'alpeg-forms' ), 'phone' => __( 'Phone', 'alpeg-forms' ), 'tel' => __( 'Phone', 'alpeg-forms' ), ); $lower = strtolower( $label ); return isset( $known[ $lower ] ) ? $known[ $lower ] : ( '' !== $label ? ucfirst( $label ) : $name ); } /** * Submissions older than the retention period are not copied (they would be deleted right away). * * @return int Timestamp or 0 (no retention period). */ private static function cutoff() { $days = (int) alpeg_forms_setting( 'retention_days', 0 ); return $days > 0 ? time() - $days * DAY_IN_SECONDS : 0; } /** * Handle the request (admin-post). */ public static function handle() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You are not allowed to do this.', 'alpeg-forms' ), 403 ); } check_admin_referer( 'alpeg_forms_import_entries' ); $keys = isset( $_POST['af_entries'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['af_entries'] ) ) : array(); $r = self::run( $keys ); set_transient( 'alpeg_forms_entries_notice_' . get_current_user_id(), $r, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=alpeg-forms-import#entries' ) ); exit; } /** * Section on the import page. */ public static function render() { $groups = self::groups(); if ( ! $groups ) { return; } $notice = get_transient( 'alpeg_forms_entries_notice_' . get_current_user_id() ); delete_transient( 'alpeg_forms_entries_notice_' . get_current_user_id() ); $days = (int) alpeg_forms_setting( 'retention_days', 0 ); ?>