0 ? add_query_arg( 'form', $form_id, $url ) : $url;
}
/**
* Adds "Get started" to the plugin's row on the Plugins screen.
*
* @param array $links Action links.
* @return array
*/
public static function action_links( array $links ): array {
if ( current_user_can( self::capability() ) ) {
$links = array( 'tflexforms-get-started' => sprintf( '%s', esc_url( self::url() ), esc_html__( 'Get started', 'flexsolutions-form-builder' ) ) ) + $links;
}
return $links;
}
/**
* Loads the guide's styles and copy button on its page.
*
* @param string $hook_suffix Current admin page.
*/
public static function enqueue( string $hook_suffix ): void {
if ( Form_Post_Type::POST_TYPE . '_page_' . self::PAGE !== $hook_suffix ) {
return;
}
wp_enqueue_style( 'tflexforms-admin', plugins_url( 'assets/css/admin.css', PLUGIN_FILE ), array(), VERSION );
wp_enqueue_style( 'tflexforms-onboarding', plugins_url( 'assets/css/onboarding.css', PLUGIN_FILE ), array( 'tflexforms-admin' ), VERSION );
wp_enqueue_script( 'tflexforms-onboarding', plugins_url( 'assets/js/onboarding.js', PLUGIN_FILE ), array(), VERSION, array( 'in_footer' => true ) );
wp_localize_script(
'tflexforms-onboarding',
'tflexformsOnboarding',
array(
'copied' => __( 'Shortcode copied.', 'flexsolutions-form-builder' ),
'failed' => __( 'Copying failed. Select the shortcode and copy it yourself.', 'flexsolutions-form-builder' ),
)
);
}
/**
* Where the site stands, for one form (the chosen one, else the most
* recently changed published form, else the newest draft).
*
* @param int $form_id Chosen form ID, or 0.
* @return array{form:?\WP_Post,forms:\WP_Post[],published:bool,embeds:\WP_Post[],live:bool,entry:?object,spam:?object,complete:bool}
*/
public static function state( int $form_id = 0 ): array {
$forms = get_posts(
array(
'post_type' => Form_Post_Type::POST_TYPE,
'post_status' => array( 'publish', 'draft', 'pending', 'private', 'future' ),
'numberposts' => 50,
'orderby' => 'modified',
'order' => 'DESC',
)
);
$forms = array_values( array_filter( $forms, static fn( \WP_Post $post ): bool => current_user_can( 'edit_post', $post->ID ) ) );
$form = null;
foreach ( $forms as $post ) {
if ( $post->ID === $form_id ) {
$form = $post;
}
}
if ( null === $form ) {
$published = array_values( array_filter( $forms, static fn( \WP_Post $post ): bool => 'publish' === $post->post_status ) );
$form = $published[0] ?? $forms[0] ?? null;
}
$published = null !== $form && 'publish' === $form->post_status;
$embeds = null !== $form ? self::embeds( $form->ID ) : array();
$live = array() !== array_filter( $embeds, static fn( \WP_Post $post ): bool => 'publish' === $post->post_status );
$entry = null;
$spam = null;
if ( $published ) {
$entry = Entry_Store::query(
array(
'form_id' => $form->ID,
'per_page' => 1,
)
)['items'][0] ?? null;
if ( null === $entry ) {
$spam = Entry_Store::query(
array(
'form_id' => $form->ID,
'status' => Entry_Store::SPAM,
'per_page' => 1,
)
)['items'][0] ?? null;
}
}
return array(
'form' => $form,
'forms' => $forms,
'published' => $published,
'embeds' => $embeds,
'live' => $live,
'entry' => $entry,
'spam' => $spam,
'complete' => $published && null !== $entry,
);
}
/**
* Posts and pages whose content shows a form, through the block or the
* shortcode (published, scheduled, private, pending and drafts).
*
* @param int $form_id Form ID.
* @return \WP_Post[]
*/
public static function embeds( int $form_id ): array {
global $wpdb;
$types = array_values( array_diff( get_post_types( array( 'public' => true ) ), array( 'attachment' ) ) );
if ( array() === $types ) {
return array();
}
// A LIKE pre-filter finds both "[tflexforms" and "wp:flexsolutions-form-builder/form"; the
// exact form is checked below. Never cached: the guide reads it live.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$ids = $wpdb->get_col(
$wpdb->prepare(
'SELECT ID FROM %i WHERE post_status IN ( %s, %s, %s, %s, %s ) AND post_type IN ( ' . implode( ', ', array_fill( 0, count( $types ), '%s' ) ) . ' ) AND ( post_content LIKE %s OR post_content LIKE %s ) ORDER BY post_modified DESC LIMIT 100', // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Only placeholders are added.
array_merge(
array( $wpdb->posts, 'publish', 'future', 'private', 'pending', 'draft' ),
$types,
array( '%[' . $wpdb->esc_like( Shortcode::TAG ) . '%', '%' . $wpdb->esc_like( 'wp:flexsolutions-form-builder/form' ) . '%' )
)
)
);
$found = array();
foreach ( (array) $ids as $id ) {
$post = get_post( (int) $id );
if ( $post instanceof \WP_Post && in_array( $form_id, self::forms_in( $post->post_content ), true ) ) {
$found[] = $post;
}
if ( count( $found ) >= 5 ) {
break;
}
}
return $found;
}
/**
* IDs of the forms a post's content shows.
*
* @param string $content Post content.
* @return int[]
*/
public static function forms_in( string $content ): array {
$ids = array();
if ( preg_match_all( '/' . get_shortcode_regex( array( Shortcode::TAG ) ) . '/', $content, $matches, PREG_SET_ORDER ) ) {
foreach ( $matches as $match ) {
if ( '[' === $match[1] && ']' === $match[6] ) {
continue; // [[tflexforms]] is an escaped, printed shortcode.
}
$atts = shortcode_parse_atts( $match[3] );
$ids[] = is_array( $atts ) ? absint( $atts['id'] ?? 0 ) : 0;
}
}
if ( has_blocks( $content ) ) {
$walk = static function ( array $blocks ) use ( &$walk, &$ids ): void {
foreach ( $blocks as $block ) {
if ( 'flexsolutions-form-builder/form' === $block['blockName'] ) {
$ids[] = absint( $block['attrs']['formId'] ?? 0 );
}
$walk( $block['innerBlocks'] );
}
};
$walk( parse_blocks( $content ) );
}
return array_values( array_unique( array_filter( $ids ) ) );
}
/**
* Creates a draft page holding the form's block and opens it in the editor.
*/
public static function create_page(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Checked by check_admin_referer() with this ID.
$form_id = isset( $_POST['form'] ) ? absint( wp_unslash( $_POST['form'] ) ) : 0;
check_admin_referer( self::CREATE_PAGE . '_' . $form_id );
$form = get_post( $form_id );
if ( ! $form instanceof \WP_Post || Form_Post_Type::POST_TYPE !== $form->post_type ) {
wp_die( esc_html__( 'Form not found.', 'flexsolutions-form-builder' ), '', array( 'response' => 404 ) );
}
if ( ! current_user_can( 'edit_pages' ) || ! current_user_can( 'edit_post', $form_id ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to create pages.', 'flexsolutions-form-builder' ), '', array( 'response' => 403 ) );
}
$page_id = wp_insert_post(
array(
'post_type' => 'page',
'post_status' => 'draft',
'post_title' => $form->post_title,
'post_content' => serialize_block(
array(
'blockName' => 'flexsolutions-form-builder/form',
'attrs' => array( 'formId' => $form_id ),
'innerBlocks' => array(),
'innerHTML' => '',
'innerContent' => array(),
)
),
),
true
);
if ( is_wp_error( $page_id ) ) {
wp_die( esc_html( $page_id->get_error_message() ), '', array( 'response' => 500 ) );
}
wp_safe_redirect( (string) get_edit_post_link( $page_id, 'raw' ) );
exit;
}
/**
* Hides the guide's notices for the current user.
*/
public static function dismiss(): void {
check_admin_referer( self::DISMISS );
if ( ! current_user_can( self::capability() ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to do that.', 'flexsolutions-form-builder' ), '', array( 'response' => 403 ) );
}
update_user_meta( get_current_user_id(), self::DISMISSED_META, 1 );
$back = wp_get_referer();
wp_safe_redirect( false !== $back ? $back : admin_url( 'edit.php?post_type=' . Form_Post_Type::POST_TYPE ) );
exit;
}
/**
* On the forms list and the form editor, names the next step until the
* site has a published form with an entry or the user hides the guide.
*/
public static function notice(): void {
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( null === $screen || Form_Post_Type::POST_TYPE !== $screen->post_type || ! in_array( $screen->base, array( 'edit', 'post' ), true ) || 'add' === $screen->action ) {
return;
}
if ( ! current_user_can( self::capability() ) || get_user_meta( get_current_user_id(), self::DISMISSED_META, true ) ) {
return;
}
$post = 'post' === $screen->base ? get_post() : null;
$state = self::state( null !== $post ? $post->ID : 0 );
if ( $state['complete'] ) {
return;
}
if ( null === $state['form'] ) {
$text = __( 'New to Flexsolutions Form Builder? The Get Started guide takes you from a template to your first entry in three steps.', 'flexsolutions-form-builder' );
} elseif ( ! $state['published'] ) {
$text = __( 'Next step: publish your form, then the Get Started guide shows you how to put it on a page.', 'flexsolutions-form-builder' );
} else {
$text = __( 'Your form is published. Next step: put it on a page and send a test entry.', 'flexsolutions-form-builder' );
}
$dismiss = wp_nonce_url( admin_url( 'admin-post.php?action=' . self::DISMISS ), self::DISMISS );
?>
$state['published'],
2 => $state['live'],
3 => null !== $state['entry'],
);
$done = count( array_filter( $steps ) );
?>
$state See state().
*/
private static function step_form( array $state ): void {
$form = $state['form'];
if ( null === $form ) {
echo '' . esc_html__( 'Pick a template to start from. Each one comes with its fields, the notification email and the messages visitors see, and you can change all of it.', 'flexsolutions-form-builder' ) . '
';
echo '
';
echo '';
foreach ( $state['embeds'] as $post ) {
$edit = get_edit_post_link( $post->ID );
echo '- ';
echo '' . esc_html( self::title( $post ) ) . ' ';
echo '(' . esc_html( self::status_label( $post ) ) . ') ';
if ( null !== $edit ) {
echo '' . esc_html__( 'Edit', 'flexsolutions-form-builder' ) . '';
}
echo '
';
}
echo '
';
if ( ! $state['live'] ) {
echo '' . esc_html__( 'Publish the page when you are happy with it so visitors can see the form. You can send a test from its preview already.', 'flexsolutions-form-builder' ) . '
';
}
}
/**
* Step 3: where to send the test, where the email goes, and the entry once
* it arrives.
*
* @param array $state See state().
*/
private static function step_test( array $state ): void {
$form = $state['form'];
if ( ! $state['published'] ) {
echo '' . esc_html__( 'Once your form is published and on a page, send it once yourself, as a visitor would, to see the entry arrive.', 'flexsolutions-form-builder' ) . '
';
return;
}
$entry = $state['entry'];
if ( null !== $entry ) {
echo '';
echo esc_html(
sprintf(
/* translators: %s: date and time the entry was received. */
__( 'An entry arrived on %s.', 'flexsolutions-form-builder' ),
wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) strtotime( $entry->created_at . ' UTC' ) )
)
);
echo ' ';
echo esc_html( $entry->mail_sent ? __( 'Its notification email was sent.', 'flexsolutions-form-builder' ) : __( 'Its notification email was not sent.', 'flexsolutions-form-builder' ) );
echo '
';
if ( current_user_can( 'read_tflexforms_entries' ) ) {
echo '' . esc_html__( 'View the entry', 'flexsolutions-form-builder' ) . '
';
}
if ( ! $entry->mail_sent ) {
self::mail_help();
}
return;
}
$targets = array();
foreach ( $state['embeds'] as $post ) {
$url = 'publish' === $post->post_status || 'private' === $post->post_status ? get_permalink( $post ) : get_preview_post_link( $post );
if ( is_string( $url ) && '' !== $url ) {
$targets[] = array( $post, $url );
}
}
if ( array() === $targets ) {
echo '' . esc_html__( 'Open the page where you added the form, fill it in as a visitor would and send it. This page then shows the entry.', 'flexsolutions-form-builder' ) . '
';
} else {
echo '' . esc_html__( 'Open the page, fill in the form as a visitor would and send it. Then come back: this page shows the entry.', 'flexsolutions-form-builder' ) . '
';
echo '';
}
$recipient = (string) ( Form::from_post( $form )->mail['recipient'] ?? '' );
echo '';
echo esc_html(
sprintf(
/* translators: %s: email address(es) notifications go to. */
__( 'The notification email goes to %s.', 'flexsolutions-form-builder' ),
'' !== $recipient ? $recipient : (string) get_option( 'admin_email' )
)
);
echo '
';
if ( null !== $state['spam'] ) {
echo '';
esc_html_e( 'A submission arrived but was marked as spam, so no email was sent. Submissions sent within a few seconds of opening the page, or with links and blocked words, can look like spam. Open it under Entries → Spam and mark it "Not spam", or send another test more slowly.', 'flexsolutions-form-builder' );
echo '
';
}
echo '' . esc_html__( 'Check again', 'flexsolutions-form-builder' ) . '
';
}
/**
* Points to the Diagnostics test email when a notification failed.
*/
private static function mail_help(): void {
echo '';
esc_html_e( 'The entry is stored, but the email did not go out. Many hosts need an SMTP plugin to send mail reliably.', 'flexsolutions-form-builder' );
if ( current_user_can( 'manage_options' ) ) {
echo ' ' . esc_html__( 'Send a test email from Diagnostics', 'flexsolutions-form-builder' ) . '';
}
echo '
';
}
/**
* Opens one step.
*
* @param int $number Step number.
* @param string $title Step title.
* @param bool $done Whether the step is done.
*/
private static function open_step( int $number, string $title, bool $done ): void {
$id = 'tflexforms-onboarding-step-' . $number;
?>
';
}
/**
* A post's title, or a placeholder for an untitled one.
*
* @param \WP_Post $post Post.
*/
private static function title( \WP_Post $post ): string {
$title = html_entity_decode( get_the_title( $post ), ENT_QUOTES | ENT_HTML5, 'UTF-8' );
return '' !== $title ? $title : __( '(no title)', 'flexsolutions-form-builder' );
}
/**
* A post's status in words.
*
* @param \WP_Post $post Post.
*/
private static function status_label( \WP_Post $post ): string {
$status = get_post_status_object( $post->post_status );
return null !== $status ? (string) $status->label : $post->post_status;
}
/**
* Number of forms on the site, in any status but trash and auto-draft.
*/
private static function form_count(): int {
$counts = (array) wp_count_posts( Form_Post_Type::POST_TYPE );
unset( $counts['trash'], $counts['auto-draft'] );
return (int) array_sum( $counts );
}
/**
* Capability needed to use the guide: creating forms.
*/
public static function capability(): string {
$type = get_post_type_object( Form_Post_Type::POST_TYPE );
return null !== $type ? (string) $type->cap->create_posts : 'edit_tflexforms_forms';
}
}