get_var( "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ('wpcf7_contact_form','wpforms') AND post_status NOT IN ('trash','auto-draft') LIMIT 1" ) ) { // phpcs:ignore WordPress.DB return true; } return Import_Sources::has_sources(); } /** * Posts of a type (read directly, the source plugin may be inactive). * * @param string $type Post type. * @return \WP_Post[] */ private static function posts( $type ) { global $wpdb; $ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status NOT IN ('trash','auto-draft') ORDER BY ID ASC", $type ) ); // phpcs:ignore WordPress.DB return array_filter( array_map( 'get_post', $ids ) ); } /* ------------------------------------------------------------------ * Contact Form 7 * ---------------------------------------------------------------- */ /** * Convert a CF7 form. * * @param \WP_Post $post Form post. * @return array|null */ public static function from_cf7( $post ) { $markup = (string) get_post_meta( $post->ID, '_form', true ); if ( '' === $markup ) { // Older versions keep everything in post_content (form first). $parts = explode( "\n1\n", (string) $post->post_content ); $markup = $parts[0]; } $mail = (array) get_post_meta( $post->ID, '_mail', true ); $fields = array(); $skipped = array(); $submit = ''; $ids = array(); // Acceptance tags have content: [acceptance name] text [/acceptance]. $markup = preg_replace_callback( '/\[acceptance\s+([\w-]+)[^\]]*\](.*?)\[\/acceptance\]/s', static function ( $m ) { return '[acceptance ' . $m[1] . ' __label="' . base64_encode( trim( $m[2] ) ) . '"]'; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode }, $markup ); if ( ! preg_match_all( '/\[([a-z_]+)(\*?)(?:\s+([^\]]*))?\]/i', $markup, $tags, PREG_SET_ORDER | PREG_OFFSET_CAPTURE ) ) { $tags = array(); } $last_end = 0; foreach ( $tags as $t ) { $tag = strtolower( $t[1][0] ); $required = '*' === $t[2][0]; $rest = isset( $t[3] ) ? trim( $t[3][0] ) : ''; $start = $t[0][1]; $before = substr( $markup, $last_end, $start - $last_end ); $last_end = $start + strlen( $t[0][0] ); if ( 'submit' === $tag ) { if ( preg_match( '/"([^"]*)"/', $rest, $m ) ) { $submit = $m[1]; } continue; } // Name = first word, then options and quoted values. $name = strtok( $rest, " \t" ); $options = array(); $values = array(); if ( preg_match_all( '/"([^"]*)"|(\S+)/', substr( $rest, strlen( (string) $name ) ), $mm, PREG_SET_ORDER ) ) { foreach ( $mm as $x ) { if ( isset( $x[2] ) && '' !== $x[2] ) { $options[] = $x[2]; } else { $values[] = $x[1]; } } } $label = self::cf7_label( $before, $markup, $start, (string) $name ); $map = array( 'text' => 'text', 'email' => 'email', 'tel' => 'tel', 'url' => 'url', 'number' => 'number', 'range' => 'number', 'date' => 'date', 'textarea' => 'textarea', 'select' => 'select', 'checkbox' => 'checkbox', 'radio' => 'radio', 'acceptance' => 'consent', 'file' => 'file', ); if ( ! isset( $map[ $tag ] ) ) { if ( ! in_array( $tag, array( 'response', 'recaptcha', 'cf7sr-simple-recaptcha' ), true ) ) { $skipped[] = $tag . ( $name ? ' (' . $name . ')' : '' ); } continue; } $type = $map[ $tag ]; // A CF7 radio group always has one option selected, so it is effectively required. $required = $required || 'radio' === $tag; if ( 'select' === $type && in_array( 'multiple', $options, true ) ) { $type = 'checkbox'; } if ( 'checkbox' === $type && in_array( 'exclusive', $options, true ) ) { $type = 'radio'; } $field = array( 'id' => self::unique_id( (string) $name, $ids ), 'type' => $type, 'label' => $label, 'required' => $required, 'placeholder' => '', 'help' => '', 'width' => 'full', 'options' => array(), ); if ( Fields::has_options( $type ) ) { $field['options'] = array_values( array_map( 'wp_strip_all_tags', $values ) ); } elseif ( in_array( 'placeholder', $options, true ) || in_array( 'watermark', $options, true ) ) { $field['placeholder'] = isset( $values[0] ) ? $values[0] : ''; } if ( 'consent' === $type ) { $field['required'] = true; foreach ( $options as $o ) { if ( 0 === strpos( $o, '__label=' ) ) { $field['label'] = wp_kses( base64_decode( trim( substr( $o, 8 ), '"' ) ), Fields::consent_kses() ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode } } if ( '' === trim( wp_strip_all_tags( $field['label'] ) ) ) { $field['label'] = __( 'I agree that my details will be stored to process my request. More information in the privacy policy.', 'alpeg-forms' ); } } if ( 'file' === $type ) { $field['accept'] = 'documents'; $field['extensions'] = ''; $field['max_size'] = 1; $field['max_files'] = 1; foreach ( $options as $o ) { if ( preg_match( '/^limit:(\d+)(mb|kb|b)?$/i', $o, $m ) ) { $bytes = (int) $m[1] * ( 'mb' === strtolower( $m[2] ?? '' ) ? MB_IN_BYTES : ( 'kb' === strtolower( $m[2] ?? '' ) ? KB_IN_BYTES : 1 ) ); $field['max_size'] = max( 1, (int) ceil( $bytes / MB_IN_BYTES ) ); } if ( preg_match( '/^filetypes:(.+)$/i', $o, $m ) ) { $field['accept'] = 'custom'; $field['extensions'] = implode( ', ', array_filter( array_map( 'trim', explode( '|', str_replace( '.', '', $m[1] ) ) ) ) ); } } if ( ! preg_grep( '/^limit:/i', $options ) ) { $field['max_size'] = 1; // CF7 default limit is 1 MB. } } $fields[] = $field; } $recipient = isset( $mail['recipient'] ) ? (string) $mail['recipient'] : ''; $notify = ( false !== strpos( $recipient, '[' ) ) ? '' : $recipient; return self::result( 'cf7', 'Contact Form 7', (int) $post->ID, $post->post_title, $fields, $skipped, array( 'submitLabel' => $submit, 'notifyEmail' => $notify, 'notifySubject' => '', ) ); } /** * Determine the label of a CF7 tag from the surrounding markup. * * @param string $before Markup between the previous tag and this one. * @param string $markup Whole markup. * @param int $start Offset of the tag. * @param string $name Tag name. * @return string */ private static function cf7_label( $before, $markup, $start, $name ) { // Inside ... [tag] ... ? $open = strripos( substr( $markup, 0, $start ), '' ) ) { $inside = substr( $markup, $open, $start - $open ); $text = trim( wp_strip_all_tags( preg_replace( '/^]*>/i', '', $inside ) ) ); if ( '' !== $text ) { return self::clean_label( $text ); } } $text = trim( wp_strip_all_tags( preg_replace( '/<\/label>/i', "\n", $before ) ) ); if ( '' !== $text ) { $lines = array_filter( array_map( 'trim', preg_split( '/\r\n|\r|\n/', $text ) ) ); $line = end( $lines ); if ( $line && mb_strlen( $line ) <= 80 ) { return self::clean_label( $line ); } } return self::clean_label( ucfirst( str_replace( array( 'your-', '-', '_' ), array( '', ' ', ' ' ), $name ) ) ); } /** * Clean a label (remove "(required)", asterisks, trailing colon). * * @param string $label Label. * @return string */ private static function clean_label( $label ) { $label = preg_replace( '/\s*\((required|erforderlich|pflicht|pflichtfeld)\)\s*/i', ' ', $label ); $label = trim( preg_replace( '/[\s*:]+$/u', '', trim( $label ) ) ); return sanitize_text_field( $label ); } /* ------------------------------------------------------------------ * WPForms * ---------------------------------------------------------------- */ /** * Convert a WPForms form. * * @param \WP_Post $post Form post. * @return array|null */ public static function from_wpforms( $post ) { $data = json_decode( (string) $post->post_content, true ); if ( ! is_array( $data ) ) { return null; } $settings = isset( $data['settings'] ) && is_array( $data['settings'] ) ? $data['settings'] : array(); $fields = array(); $skipped = array(); $ids = array(); $first = ''; foreach ( (array) ( isset( $data['fields'] ) ? $data['fields'] : array() ) as $f ) { if ( ! is_array( $f ) || empty( $f['type'] ) ) { continue; } $type = $f['type']; $label = isset( $f['label'] ) ? sanitize_text_field( $f['label'] ) : ''; if ( 'pagebreak' === $type ) { $position = isset( $f['position'] ) ? $f['position'] : ''; if ( 'top' === $position ) { $first = isset( $f['title'] ) ? sanitize_text_field( $f['title'] ) : ''; } elseif ( 'bottom' !== $position ) { $fields[] = array( 'id' => self::unique_id( 'step', $ids ), 'type' => 'step', 'label' => isset( $f['title'] ) && '' !== $f['title'] ? sanitize_text_field( $f['title'] ) : __( 'Next step', 'alpeg-forms' ), 'required' => false, 'options' => array(), ); } continue; } $map = array( 'name' => 'text', 'text' => 'text', 'email' => 'email', 'textarea' => 'textarea', 'select' => 'select', 'radio' => 'radio', 'checkbox' => 'checkbox', 'number' => 'number', 'number-slider' => 'number', 'phone' => 'tel', 'url' => 'url', 'date-time' => 'date', 'file-upload' => 'file', 'gdpr-checkbox' => 'consent', ); if ( ! isset( $map[ $type ] ) ) { if ( ! in_array( $type, array( 'divider', 'html', 'content', 'captcha', 'hidden' ), true ) ) { $skipped[] = $type . ( $label ? ' (' . $label . ')' : '' ); } continue; } $new = $map[ $type ]; if ( 'date-time' === $type && isset( $f['format'] ) && 'time' === $f['format'] ) { $new = 'text'; } $field = array( 'id' => self::unique_id( '' !== $label ? $label : $new, $ids ), 'type' => $new, 'label' => '' !== $label ? $label : ucfirst( $new ), 'required' => ! empty( $f['required'] ), 'placeholder' => isset( $f['placeholder'] ) ? sanitize_text_field( $f['placeholder'] ) : '', 'help' => isset( $f['description'] ) ? sanitize_text_field( wp_strip_all_tags( $f['description'] ) ) : '', 'width' => 'full', 'options' => array(), ); if ( Fields::has_options( $new ) && ! empty( $f['choices'] ) && is_array( $f['choices'] ) ) { foreach ( $f['choices'] as $choice ) { if ( isset( $choice['label'] ) && '' !== trim( $choice['label'] ) ) { $field['options'][] = sanitize_text_field( $choice['label'] ); } } } if ( 'consent' === $new ) { $field['required'] = true; $choice = ! empty( $f['choices'] ) && is_array( $f['choices'] ) ? reset( $f['choices'] ) : array(); $field['label'] = ! empty( $choice['label'] ) ? wp_kses( $choice['label'], Fields::consent_kses() ) : __( 'I agree that my details will be stored to process my request. More information in the privacy policy.', 'alpeg-forms' ); } if ( 'file' === $new ) { $exts = isset( $f['extensions'] ) ? trim( (string) $f['extensions'] ) : ''; $field['accept'] = '' !== $exts ? 'custom' : 'both'; $field['extensions'] = str_replace( array( ',', '.' ), array( ', ', '' ), $exts ); $field['max_size'] = max( 1, (int) ( isset( $f['max_size'] ) && '' !== $f['max_size'] ? $f['max_size'] : 5 ) ); $field['max_files'] = max( 1, min( Files::MAX_FILES, (int) ( isset( $f['max_file_number'] ) ? $f['max_file_number'] : 1 ) ) ); } $fields[] = $field; } $notify = ''; $subject = ''; if ( ! empty( $settings['notifications'] ) && is_array( $settings['notifications'] ) ) { $n = reset( $settings['notifications'] ); $email = isset( $n['email'] ) ? (string) $n['email'] : ''; $notify = false !== strpos( $email, '{' ) ? '' : $email; $subject = isset( $n['subject'] ) && false === strpos( (string) $n['subject'], '{' ) ? sanitize_text_field( $n['subject'] ) : ''; } $success = ''; if ( ! empty( $settings['confirmations'] ) && is_array( $settings['confirmations'] ) ) { $c = reset( $settings['confirmations'] ); $success = isset( $c['message'] ) ? trim( wp_strip_all_tags( $c['message'] ) ) : ''; } $title = ! empty( $settings['form_title'] ) ? $settings['form_title'] : $post->post_title; return self::result( 'wpforms', 'WPForms', (int) $post->ID, $post->post_title, $fields, $skipped, array( 'formTitle' => sanitize_text_field( $title ), 'submitLabel' => isset( $settings['submit_text'] ) ? sanitize_text_field( $settings['submit_text'] ) : '', 'notifyEmail' => $notify, 'notifySubject' => $subject, 'successMessage' => $success, 'firstStepTitle' => $first, ) ); } /* ------------------------------------------------------------------ * Shared * ---------------------------------------------------------------- */ /** * Build the result record. * * @param string $source Source key (cf7, wpforms, ninja, gravity, formidable, fluent). * @param string $name Plugin name. * @param int $id Form ID in the source plugin. * @param string $title Form title in the source plugin. * @param array[] $fields Converted fields. * @param string[] $skipped Skipped fields. * @param array $attrs Additional block attributes. * @return array */ public static function result( $source, $name, $id, $title, array $fields, array $skipped, array $attrs ) { $title = ! empty( $attrs['formTitle'] ) ? $attrs['formTitle'] : ( '' !== (string) $title ? $title : $name . ' #' . $id ); $attrs = array_merge( array_filter( $attrs, 'strlen' ), array( 'formTitle' => $title, 'fields' => $fields, ) ); return array( 'source' => $source, 'plugin' => $name, 'id' => (int) $id, 'title' => $title, 'attributes' => $attrs, 'skipped' => $skipped, 'pages' => self::pages_using( $source, (int) $id ), 'imported' => self::imported_pattern( $source . ':' . $id ), ); } /** * Unique, readable field ID. * * @param string $base Base (name or label). * @param array $taken IDs used so far (by reference). * @return string */ public static function unique_id( $base, array &$taken ) { $id = substr( sanitize_key( str_replace( ' ', '_', remove_accents( strtolower( $base ) ) ) ), 0, 36 ); $id = '' !== $id ? $id : 'field'; $try = $id; $n = 2; while ( isset( $taken[ $try ] ) ) { $try = $id . '_' . $n++; } $taken[ $try ] = true; return $try; } /** * Pages and posts that embed the source form. * * @param string $source Source key. * @param int $id Form ID in the source plugin. * @return array[] ID, title. */ private static function pages_using( $source, $id ) { global $wpdb; $likes = array(); switch ( $source ) { case 'cf7': $likes[] = '%[contact-form-7%id="' . $id . '"%'; $likes[] = '%[contact-form-7%id=' . $id . ' %'; $hash = (string) get_post_meta( $id, '_hash', true ); if ( '' !== $hash ) { $likes[] = '%[contact-form-7%id="' . substr( $hash, 0, 7 ) . '"%'; } $likes[] = '%wp:contact-form-7/contact-form-selector {"id":' . $id . '%'; break; case 'wpforms': $likes[] = '%[wpforms%id="' . $id . '"%'; $likes[] = '%wp:wpforms/form-selector {%"formId":"' . $id . '"%'; break; case 'ninja': $likes[] = '%[ninja_form%id=' . $id . ']%'; $likes[] = '%[ninja_form%id="' . $id . '"%'; $likes[] = '%[ninja_form%id=' . $id . ' %'; $likes[] = '%wp:ninja-forms/form {%"formID":' . $id . '%'; $likes[] = '%wp:ninja-forms/form {%"formID":"' . $id . '"%'; break; case 'gravity': $likes[] = '%[gravityform%id="' . $id . '"%'; $likes[] = '%[gravityform%id=' . $id . ' %'; $likes[] = '%[gravityform%id=' . $id . ']%'; $likes[] = '%wp:gravityforms/form {%"formId":"' . $id . '"%'; break; case 'formidable': $likes[] = '%[formidable%id=' . $id . ']%'; $likes[] = '%[formidable%id="' . $id . '"%'; $likes[] = '%[formidable%id=' . $id . ' %'; $likes[] = '%wp:formidable/simple-form {%"formId":"' . $id . '"%'; break; case 'fluent': $likes[] = '%[fluentform%id="' . $id . '"%'; $likes[] = '%[fluentform%id=' . $id . ']%'; $likes[] = '%[fluentform%id=' . $id . ' %'; $likes[] = '%wp:fluentfom/guten-block {%"formId":"' . $id . '"%'; break; } if ( ! $likes ) { return array(); } $where = implode( ' OR ', array_fill( 0, count( $likes ), 'post_content LIKE %s' ) ); $rows = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_status IN ('publish','draft','private','future') AND post_type NOT IN ('revision','wp_block','wpcf7_contact_form','wpforms') AND ({$where}) LIMIT 20", $likes ) ); // phpcs:ignore WordPress.DB return array_map( static function ( $r ) { return array( 'id' => (int) $r->ID, 'title' => '' !== $r->post_title ? $r->post_title : '#' . $r->ID, ); }, (array) $rows ); } /** * Already imported? Returns the pattern ID. * * @param string $key source:id. * @return int */ private static function imported_pattern( $key ) { $found = get_posts( array( 'post_type' => 'wp_block', 'post_status' => 'any', 'meta_key' => self::META_SOURCE, // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_value' => $key, // phpcs:ignore WordPress.DB.SlowDBQuery 'fields' => 'ids', 'posts_per_page' => 1, ) ); return $found ? (int) $found[0] : 0; } /** * Create an ALPEG Forms template (unsynced pattern) from an import result. * * @param array $item From find(). * @return int|\WP_Error Pattern ID. */ public static function create_template( array $item ) { $attrs = $item['attributes']; if ( empty( $attrs['formId'] ) ) { // Same form ID as imported old submissions, so both show up together. $attrs['formId'] = self::form_id_for( $item['source'], $item['id'] ); } $block = ''; $id = wp_insert_post( array( 'post_type' => 'wp_block', 'post_status' => 'publish', /* translators: 1: form title, 2: plugin name */ 'post_title' => sprintf( __( '%1$s (from %2$s)', 'alpeg-forms' ), $item['title'], $item['plugin'] ), 'post_content' => wp_slash( $block ), ), true ); if ( is_wp_error( $id ) ) { return $id; } update_post_meta( $id, 'wp_pattern_sync_status', 'unsynced' ); update_post_meta( $id, self::META_SOURCE, $item['source'] . ':' . $item['id'] ); return (int) $id; } /** * ALPEG form ID used for a form taken over from another plugin. * * @param string $source Source key. * @param int $id Form ID in the source plugin. * @return string */ public static function form_id_for( $source, $id ) { return sanitize_key( $source . '_' . (int) $id ); } /** * Handle the import form (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' ); $wanted = isset( $_POST['af_import'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['af_import'] ) ) : array(); $done = array(); $errors = array(); foreach ( self::find() as $item ) { $key = $item['source'] . ':' . $item['id']; if ( ! in_array( $key, $wanted, true ) || $item['imported'] ) { continue; } $r = self::create_template( $item ); if ( is_wp_error( $r ) ) { $errors[] = $item['title'] . ': ' . $r->get_error_message(); } else { $done[] = $item['title']; } } set_transient( 'alpeg_forms_import_notice_' . get_current_user_id(), array( 'done' => $done, 'errors' => $errors, ), 60 ); wp_safe_redirect( admin_url( 'admin.php?page=alpeg-forms-import' ) ); exit; } /** * Render the import page. */ public static function render() { $items = self::find(); $notice = get_transient( 'alpeg_forms_import_notice_' . get_current_user_id() ); delete_transient( 'alpeg_forms_import_notice_' . get_current_user_id() ); ?> –'; } foreach ( $item['pages'] as $i => $page ) { echo ( $i ? ', ' : '' ) . '' . esc_html( $page['title'] ) . ''; } ?> 0 ) ) ) { submit_button( __( 'Import selected forms', 'alpeg-forms' ) ); } else { echo '' . esc_html__( 'All forms have been imported.', 'alpeg-forms' ) . ''; } ?>
' . esc_html__( 'All forms have been imported.', 'alpeg-forms' ) . '