| @@ -4,8 +4,9 @@ | ||
| 4 | 4 | namespace Jet_Form_Builder\Blocks; |
| 5 | 5 | |
| 6 | 6 | use Jet_Form_Builder\Blocks\Types\Base; |
| 7 | 7 | use Jet_Form_Builder\Form_Manager; |
| 8 | +use JFB_Modules\Post_Type\Module as Post_Type_Module; | |
| 8 | 9 | |
| 9 | 10 | // If this file is called directly, abort. |
| 10 | 11 | if ( ! defined( 'WPINC' ) ) { |
| 11 | 12 | die; |
| @@ -17,8 +18,11 @@ | ||
| 17 | 18 | * @package Jet_Form_Builder\Blocks |
| 18 | 19 | */ |
| 19 | 20 | class Block_Helper { |
| 20 | 21 | |
| 22 | + const PREVIEW_NONCE_FIELD = 'jfb_preview_nonce'; | |
| 23 | + const PREVIEW_NONCE_ACTION = 'jfb-preview-form'; | |
| 24 | + | |
| 21 | 25 | /** |
| 22 | 26 | * @since 3.1.1 |
| 23 | 27 | * |
| 24 | 28 | * @param $parsed_block |
| @@ -182,29 +186,111 @@ | ||
| 182 | 186 | } |
| 183 | 187 | } |
| 184 | 188 | } |
| 185 | 189 | |
| 186 | - public static function get_blocks_by_post( $post_id ): array { | |
| 190 | + public static function is_valid_form_post( $post_id, bool $allow_preview = false ): bool { | |
| 187 | 191 | $post = get_post( $post_id ); |
| 188 | 192 | |
| 189 | 193 | if ( ! is_a( $post, \WP_Post::class ) ) { |
| 194 | + return false; | |
| 195 | + } | |
| 196 | + | |
| 197 | + if ( | |
| 198 | + Post_Type_Module::SLUG === $post->post_type | |
| 199 | + && 'publish' === $post->post_status | |
| 200 | + ) { | |
| 201 | + return true; | |
| 202 | + } | |
| 203 | + | |
| 204 | + return $allow_preview && self::is_valid_preview_form_post( $post ); | |
| 205 | + } | |
| 206 | + | |
| 207 | + public static function is_valid_preview_form_post( $post_id ): bool { | |
| 208 | + $post = get_post( $post_id ); | |
| 209 | + | |
| 210 | + if ( ! is_a( $post, \WP_Post::class ) ) { | |
| 211 | + return false; | |
| 212 | + } | |
| 213 | + | |
| 214 | + $nonce = self::get_preview_nonce(); | |
| 215 | + | |
| 216 | + if ( ! $nonce || ! wp_verify_nonce( $nonce, self::PREVIEW_NONCE_ACTION ) ) { | |
| 217 | + return false; | |
| 218 | + } | |
| 219 | + | |
| 220 | + if ( Post_Type_Module::SLUG === $post->post_type ) { | |
| 221 | + return current_user_can( 'edit_jet_fb_form', $post->ID ); | |
| 222 | + } | |
| 223 | + | |
| 224 | + if ( 'revision' !== $post->post_type ) { | |
| 225 | + return false; | |
| 226 | + } | |
| 227 | + | |
| 228 | + $parent_form_id = (int) $post->post_parent; | |
| 229 | + $parent_form = $parent_form_id ? get_post( $parent_form_id ) : null; | |
| 230 | + | |
| 231 | + if ( ! $parent_form || Post_Type_Module::SLUG !== $parent_form->post_type ) { | |
| 232 | + return false; | |
| 233 | + } | |
| 234 | + | |
| 235 | + return current_user_can( 'edit_jet_fb_form', $parent_form_id ); | |
| 236 | + } | |
| 237 | + | |
| 238 | + public static function get_preview_nonce(): string { | |
| 239 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 240 | + if ( empty( $_REQUEST[ self::PREVIEW_NONCE_FIELD ] ) ) { | |
| 241 | + return ''; | |
| 242 | + } | |
| 243 | + | |
| 244 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
| 245 | + return sanitize_text_field( wp_unslash( $_REQUEST[ self::PREVIEW_NONCE_FIELD ] ) ); | |
| 246 | + } | |
| 247 | + | |
| 248 | + public static function get_blocks_by_post( | |
| 249 | + $post_id, | |
| 250 | + bool $require_form_post = true, | |
| 251 | + bool $allow_preview = false | |
| 252 | + ): array { | |
| 253 | + $post = get_post( $post_id ); | |
| 254 | + | |
| 255 | + if ( ! is_a( $post, \WP_Post::class ) ) { | |
| 190 | 256 | return array(); |
| 191 | 257 | } |
| 192 | 258 | |
| 259 | + if ( $require_form_post && ! self::is_valid_form_post( $post, $allow_preview ) ) { | |
| 260 | + return array(); | |
| 261 | + } | |
| 262 | + | |
| 263 | + return self::get_blocks_from_content( $post->post_content ); | |
| 264 | + } | |
| 265 | + | |
| 266 | + /** | |
| 267 | + * Parses block content and expands reusable (`core/block`) references in exactly the | |
| 268 | + * same way as `get_blocks_by_post()`. | |
| 269 | + * | |
| 270 | + * @param string $post_content | |
| 271 | + * | |
| 272 | + * @return array | |
| 273 | + */ | |
| 274 | + public static function get_blocks_from_content( string $post_content ): array { | |
| 193 | 275 | return array_map( |
| 194 | 276 | function ( $block ) { |
| 195 | - self::walk_by_reusable( $block ); | |
| 277 | + self::walk_by_reusable( $block, array() ); | |
| 196 | 278 | |
| 197 | 279 | return $block; |
| 198 | 280 | }, |
| 199 | - parse_blocks( $post->post_content ) | |
| 281 | + parse_blocks( $post_content ) | |
| 200 | 282 | ); |
| 201 | 283 | } |
| 202 | 284 | |
| 203 | - private static function walk_by_reusable( array &$block ) { | |
| 285 | + /** | |
| 286 | + * @param array $block | |
| 287 | + * @param array $visited_reusable_ids IDs already visited in the current reference path. | |
| 288 | + */ | |
| 289 | + private static function walk_by_reusable( array &$block, array $visited_reusable_ids ) { | |
| 204 | 290 | if ( ! empty( $block['innerBlocks'] ) ) { |
| 205 | 291 | foreach ( $block['innerBlocks'] as &$current ) { |
| 206 | - self::walk_by_reusable( $current ); | |
| 292 | + self::walk_by_reusable( $current, $visited_reusable_ids ); | |
| 207 | 293 | } |
| 208 | 294 | |
| 209 | 295 | return; |
| 210 | 296 | } |
| @@ -212,10 +298,33 @@ | ||
| 212 | 298 | if ( 'core/block' !== $block['blockName'] ) { |
| 213 | 299 | return; |
| 214 | 300 | } |
| 215 | 301 | |
| 216 | - $reusable_id = $block['attrs']['ref'] ?? 0; | |
| 217 | - $block['innerBlocks'] = self::get_blocks_by_post( $reusable_id ); | |
| 302 | + $reusable_id = (int) ( $block['attrs']['ref'] ?? 0 ); | |
| 303 | + | |
| 304 | + if ( ! $reusable_id || isset( $visited_reusable_ids[ $reusable_id ] ) ) { | |
| 305 | + $block['innerBlocks'] = array(); | |
| 306 | + | |
| 307 | + return; | |
| 308 | + } | |
| 309 | + | |
| 310 | + $reusable = get_post( $reusable_id ); | |
| 311 | + | |
| 312 | + if ( ! $reusable instanceof \WP_Post ) { | |
| 313 | + $block['innerBlocks'] = array(); | |
| 314 | + | |
| 315 | + return; | |
| 316 | + } | |
| 317 | + | |
| 318 | + $visited_reusable_ids[ $reusable_id ] = true; | |
| 319 | + $block['innerBlocks'] = array_map( | |
| 320 | + function ( $inner_block ) use ( $visited_reusable_ids ) { | |
| 321 | + self::walk_by_reusable( $inner_block, $visited_reusable_ids ); | |
| 322 | + | |
| 323 | + return $inner_block; | |
| 324 | + }, | |
| 325 | + parse_blocks( $reusable->post_content ) | |
| 326 | + ); | |
| 218 | 327 | } |
| 219 | 328 | |
| 220 | 329 | public static function delete_namespace( $block ): string { |
| 221 | 330 | if ( is_array( $block ) ) { |