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( '', 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( '' . '%3$s' . '' . '', $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