'forwp-seo-techarticle-goal', self::CONTEXT_BLOCK => 'forwp-seo-techarticle-context', self::ISSUES_BLOCK => 'forwp-seo-techarticle-issues', self::STEPS_BLOCK => 'forwp-seo-techarticle-steps', ]; foreach ( $wrappers as $block_name => $class_name ) { register_block_type( $block_name, [ 'api_version' => 3, 'editor_script' => self::SCRIPT_HANDLE, 'supports' => Release::block_inserter_supports(), 'render_callback' => function ( array $attributes, string $content, \WP_Block $block ) use ( $class_name ): string { return $this->render_wrapper( $class_name, $content, $attributes, 'section', $block ); }, ] ); } // Legacy inner step wrapper — optional inside Steps; still parsed for schema. register_block_type( self::STEP_BLOCK, [ 'api_version' => 3, 'editor_script' => self::SCRIPT_HANDLE, 'supports' => Release::block_inserter_supports(), 'attributes' => [ 'stepNumber' => [ 'type' => 'number', ], ], 'render_callback' => function ( array $attributes, string $content, \WP_Block $block ): string { return $this->render_wrapper( 'forwp-seo-techarticle-step', $content, $attributes, 'div', $block ); }, ] ); register_block_type( self::COMPLETION_BLOCK, [ 'api_version' => 3, 'supports' => Release::block_inserter_supports(), 'attributes' => [ 'perfect' => [ 'type' => 'array', 'default' => [], 'items' => [ 'type' => 'string', ], ], 'optimize' => [ 'type' => 'array', 'default' => [], 'items' => [ 'type' => 'string', ], ], 'next' => [ 'type' => 'array', 'default' => [], 'items' => [ 'type' => 'string', ], ], ], 'render_callback' => [ $this, 'render_completion' ], ] ); } private function render_wrapper( string $class_name, string $content, array $attributes, string $tag, ?\WP_Block $block = null ): string { if ( '' === trim( $content ) && $block instanceof \WP_Block && ! empty( $block->inner_blocks ) ) { $content = $block->inner_blocks->render(); } if ( '' === trim( $content ) ) { return ''; } $classes = [ $class_name ]; if ( ! empty( $attributes['className'] ) && is_string( $attributes['className'] ) ) { foreach ( preg_split( '/\s+/', trim( $attributes['className'] ) ) as $custom_class ) { if ( '' !== $custom_class ) { $classes[] = sanitize_html_class( $custom_class ); } } } $data_attrs = ''; if ( isset( $attributes['stepNumber'] ) && is_numeric( $attributes['stepNumber'] ) ) { $data_attrs = sprintf( ' data-step="%d"', (int) $attributes['stepNumber'] ); } return sprintf( '<%1$s class="%2$s"%3$s>%4$s', $tag, esc_attr( implode( ' ', array_filter( $classes ) ) ), $data_attrs, wp_kses_post( $content ) ); } /** * @param array $attributes */ public function render_completion( array $attributes ): string { $columns = [ 'perfect' => __( 'What you nailed', '4wp-seo-helper' ), 'optimize' => __( 'Level up', '4wp-seo-helper' ), 'next' => __( 'What\'s next', '4wp-seo-helper' ), ]; $html = ''; foreach ( $columns as $key => $title ) { $items = $attributes[ $key ] ?? []; if ( ! is_array( $items ) || empty( $items ) ) { continue; } $list = ''; foreach ( $items as $item ) { if ( ! is_string( $item ) || '' === trim( $item ) ) { continue; } $list .= '
  • ' . esc_html( $item ) . '
  • '; } if ( '' === $list ) { continue; } $html .= sprintf( '

    %2$s

    ', esc_attr( $key ), esc_html( $title ), $list ); } if ( '' === $html ) { return ''; } return sprintf( '
    %s
    ', $html ); } }