true, 'strategy' => 'defer' ) );
wp_register_style( 'funnelfork-runner', FUNNELFORK_URL . 'build/runner.css', array(), $asset['version'] );
}
/**
* [funnelfork id="123"]
*
* @param array|string $atts Shortcode attributes.
*/
public static function shortcode( $atts ): string {
$atts = shortcode_atts( array( 'id' => 0 ), $atts, 'funnelfork' );
return self::render( (int) $atts['id'] );
}
/**
* Renders a scan.
*
* @param int $scan_id Scan ID.
* @param bool $preview Allow drafts (for editors previewing).
*/
public static function render( int $scan_id, bool $preview = false ): string {
$post = get_post( $scan_id );
if ( ! $post || Post_Type::POST_TYPE !== $post->post_type ) {
return '';
}
if ( 'publish' !== $post->post_status && ! ( $preview || current_user_can( 'edit_post', $scan_id ) ) ) {
return '';
}
if ( ! wp_script_is( 'funnelfork-runner', 'registered' ) ) {
self::register_assets();
}
wp_enqueue_script( 'funnelfork-runner' );
wp_enqueue_style( 'funnelfork-runner' );
$graph = funnelfork_get_graph( $scan_id );
$payload = self::public_payload( $scan_id, $graph );
$endpoints = '';
foreach ( $graph['nodes'] as $node ) {
if ( 'end' === $node['type'] ) {
$endpoints .= sprintf(
'%s',
esc_attr( $node['id'] ),
self::endpoint_html( $node, $scan_id )
);
}
}
/**
* Filters the attributes of the scan's wrapper element, for example to add
* a class or a style attribute with --funnelfork-* custom properties.
*
* @param array $attrs Attribute name => value. Values are escaped on output.
* @param int $scan_id Scan ID.
* @param array $graph Graph.
*/
$attrs = (array) apply_filters(
'funnelfork_wrapper_attributes',
array(
'class' => 'funnelfork',
'data-funnelfork' => $scan_id,
'data-transition' => $graph['settings']['transition'],
),
$scan_id,
$graph
);
// The runner needs these two.
$attrs['data-funnelfork'] = $scan_id;
$attrs['class'] = trim( 'funnelfork ' . preg_replace( '/(^|\s)funnelfork(\s|$)/', ' ', (string) ( $attrs['class'] ?? '' ) ) );
$attr_html = '';
foreach ( $attrs as $name => $value ) {
$name = preg_replace( '/[^a-z0-9_:.-]/', '', strtolower( (string) $name ) );
if ( '' !== $name && null !== $value && false !== $value ) {
$attr_html .= sprintf( ' %s="%s"', $name, esc_attr( (string) $value ) );
}
}
$html = sprintf(
'
',
$attr_html,
wp_json_encode( $payload, JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE ),
$endpoints,
esc_html__( 'This quickscan needs JavaScript.', 'funnelfork' )
);
/**
* Filters the complete scan HTML.
*
* @param string $html HTML.
* @param int $scan_id Scan ID.
* @param array $graph Graph.
*/
return (string) apply_filters( 'funnelfork_render', $html, $scan_id, $graph );
}
/**
* What the browser needs to run the scan: no positions, no admin-only data.
*
* @param int $scan_id Scan ID.
* @param array $graph Sanitized graph.
*/
private static function public_payload( int $scan_id, array $graph ): array {
// Add-on data stays on the server; add-ons add what the browser needs
// with the funnelfork_public_payload filter.
$nodes = array();
foreach ( $graph['nodes'] as $node ) {
$data = $node['data'];
unset( $data['addons'] );
if ( 'end' === $node['type'] ) {
unset( $data['content'], $data['cta']['addons'] ); // Content is rendered server-side in a .
}
if ( 'question' === $node['type'] ) {
foreach ( $data['answers'] as $i => $answer ) {
unset( $data['answers'][ $i ]['addons'] );
}
}
$nodes[ $node['id'] ] = array(
'type' => $node['type'],
'data' => $data,
);
}
$edges = array();
foreach ( $graph['edges'] as $edge ) {
$edges[ $edge['source'] . '|' . $edge['sourceHandle'] ] = $edge['target'];
}
$payload = array(
'id' => $scan_id,
'title' => get_the_title( $scan_id ),
'settings' => array_diff_key( $graph['settings'], array( 'addons' => true ) ),
'labels' => $graph['labels'],
'nodes' => $nodes,
'edges' => $edges,
'order' => Graph::question_order( $graph ),
'i18n' => array(
'back' => __( 'Back', 'funnelfork' ),
'restart' => __( 'Start over', 'funnelfork' ),
'progress' => __( 'Progress', 'funnelfork' ),
/* translators: %d: question number. */
'questionN' => __( 'Question %d', 'funnelfork' ),
'yourAdvice' => __( 'Your advice', 'funnelfork' ),
'yourAnswers' => __( 'Your answers', 'funnelfork' ),
/* translators: %d: total score. */
'yourScore' => __( 'Your score: %d', 'funnelfork' ),
'start' => __( 'Start', 'funnelfork' ),
),
);
/**
* Filters the data sent to the browser.
*
* @param array $payload Payload.
* @param int $scan_id Scan ID.
* @param array $graph Graph.
*/
return (array) apply_filters( 'funnelfork_public_payload', $payload, $scan_id, $graph );
}
/**
* HTML of an endpoint's own content (the part below the title).
*
* @param array $node Endpoint node.
* @param int $scan_id Scan ID.
*/
private static function endpoint_html( array $node, int $scan_id ): string {
$html = '';
if ( '' !== trim( $node['data']['content'] ) ) {
$html = '' . wpautop( wp_kses_post( $node['data']['content'] ) ) . '
';
}
/**
* Filters the endpoint HTML. Add-ons render other CTA types here
* (for example a Gravity Forms form).
*
* @param string $html HTML.
* @param array $node Endpoint node.
* @param int $scan_id Scan ID.
*/
return (string) apply_filters( 'funnelfork_endpoint_html', $html, $node, $scan_id );
}
/**
* URL of the preview page of a scan.
*
* @param int $scan_id Scan ID.
*/
public static function preview_url( int $scan_id ): string {
return add_query_arg(
array(
'funnelfork_preview' => $scan_id,
'_wpnonce' => wp_create_nonce( 'funnelfork_preview_' . $scan_id ),
),
home_url( '/' )
);
}
/**
* Renders the preview page for editors: the scan inside the theme's styles.
*/
public static function maybe_render_preview(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Verified below.
$scan_id = isset( $_GET['funnelfork_preview'] ) ? absint( $_GET['funnelfork_preview'] ) : 0;
if ( ! $scan_id ) {
return;
}
$nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
if ( ! wp_verify_nonce( $nonce, 'funnelfork_preview_' . $scan_id ) || ! current_user_can( 'edit_post', $scan_id ) ) {
wp_die( esc_html__( 'You are not allowed to preview this quickscan.', 'funnelfork' ), '', array( 'response' => 403 ) );
}
$content = self::render( $scan_id, true );
nocache_headers();
?>
>
>