PluginProbe
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts / 2.8.13
Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts v2.8.13
2.8.14 2.8.13 2.8.12 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.10 2.8.11 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 trunk 1.0.1 1.0.2 1.1.0 1.1.1 All 147 releases
content-blocks-builder / includes / block-overrides.php

block-overrides.php in Content Blocks Builder – Create blocks, repeater blocks with carousel, grid, popup layouts 2.8.13, at includes/block-overrides.php

625 lines 19.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Block Overrides
4 *
5 * @package BoldBlocks
6 * @author Phi Phan <mrphipv@gmail.com>
7 * @copyright Copyright (c) 2024, Phi Phan
8 */
9
10 namespace BoldBlocks;
11
12 // Exit if accessed directly.
13 defined( 'ABSPATH' ) || exit;
14
15 if ( ! class_exists( BlockOverrides::class ) ) :
16 /**
17 * Create/edit custom content blocks.
18 */
19 class BlockOverrides extends CoreComponent {
20 /**
21 * Define a list of supported core blocks
22 *
23 * @var array
24 */
25 protected $supported_core_blocks = [
26 'core/paragraph' => [ 'content' ],
27 'core/heading' => [ 'content' ],
28 'core/button' => [ 'text', 'url', 'linkTarget', 'rel' ],
29 'core/image' => [ 'id', 'url', 'title', 'alt', 'caption' ],
30 ];
31
32 /**
33 * Define a list of supported block binding
34 *
35 * @var array
36 */
37 protected $supported_blocks = [
38 'boldblocks/svg-block' => [
39 'content',
40 'title',
41 'description',
42 'linkUrl',
43 'linkTarget',
44 'linkRel',
45 'buttonText',
46 ],
47 ];
48
49 /**
50 * Run main hooks
51 *
52 * @return void
53 */
54 public function run() {
55 // Register block bindings.
56 add_action( 'init', [ $this, 'register_block_bindings' ] );
57
58 // Register block binding contexts for supported blocks.
59 add_filter( 'register_block_type_args', [ $this, 'register_custom_args' ], 10, 2 );
60
61 // Register scripts.
62 add_action( 'init', [ $this, 'register_scripts' ] );
63
64 // Replace the dynamic values.
65 add_action( 'render_block', [ $this, 'render_block_replace_dynamic_content' ], 10, 3 );
66
67 // Render specific blocks.
68 add_filter( 'cbb_pre_render_dynamic_content', [ $this, 'pre_render_dynamic_content' ], 10, 4 );
69
70 // Support more blocks.
71 add_filter( 'cbb_block_overrides_supported_blocks', [ $this, 'support_additional_blocks' ] );
72 }
73
74 /**
75 * Register custom block bindings
76 *
77 * @return void
78 */
79 public function register_block_bindings() {
80 if ( function_exists( 'register_block_bindings_source' ) ) {
81 register_block_bindings_source(
82 'cbb/block-overrides',
83 array(
84 'label' => _x( 'Block Overrides', 'block bindings source', 'content-blocks-builder' ),
85 'get_value_callback' => [ $this, 'block_bindings_block_overrides_get_value' ],
86 'uses_context' => array( 'cbb/block-overrides' ),
87 )
88 );
89 }
90 }
91
92 /**
93 * Get value callback for block binding attributes
94 *
95 * @param array $source_args
96 * @param WP_Block $block_instance
97 * @param string $attribute_name
98 * @return mixed
99 */
100 public function block_bindings_block_overrides_get_value( array $source_args, $block_instance, string $attribute_name ) {
101 if ( empty( $block_instance->attributes['metadata']['name'] ) ) {
102 return null;
103 }
104 $metadata_name = $block_instance->attributes['metadata']['name'];
105 return _wp_array_get( $block_instance->context, array( 'cbb/block-overrides', $metadata_name, $attribute_name ), null );
106 }
107
108 /**
109 * Register block overrides context for blocks
110 *
111 * @param array $args
112 * @param string $block_name
113 * @return void
114 */
115 public function register_custom_args( $args, $block_name ) {
116 if ( array_key_exists( $block_name, $this->get_supported_blocks() ) ) {
117 if ( empty( $args['uses_context'] ) ) {
118 $args['uses_context'] = [];
119 }
120
121 $args['uses_context'][] = 'cbb/block-overrides';
122 }
123
124 return $args;
125 }
126
127 /**
128 * Register scripts
129 *
130 * @return void
131 */
132 public function register_scripts() {
133 // Custom blocks handle.
134 $custom_blocks_handler = $this->the_plugin_instance->get_component( CustomBlocks::class );
135
136 // Load all custom blocks for registration.
137 wp_add_inline_script( $custom_blocks_handler->custom_blocks_handle, 'var CBB_SYNCING_SUPPORTED_BLOCKS=' . wp_json_encode( apply_filters( 'cbb_block_overrides_get_all_supported_blocks', array_merge( $this->supported_core_blocks, $this->get_supported_blocks() ) ) ), 'before' );
138 }
139
140 /**
141 * Replace original content with content from the context
142 *
143 * @param string $block_content
144 * @param array $block
145 * @param WP_Block $block_instance
146 * @return void
147 */
148 public function render_block_replace_dynamic_content( $block_content, $block, $block_instance ) {
149 if ( ! function_exists( 'register_block_bindings_source' ) ) {
150 return $block_content;
151 }
152
153 // Get dynamic data.
154 $computed_attributes = $this->process_block_bindings( $block, $block_instance );
155
156 if ( empty( $computed_attributes ) || empty( $block_content ) ) {
157 return $block_content;
158 }
159
160 // Allow render blocks in a different way.
161 $pre_render = apply_filters( 'cbb_pre_render_dynamic_content', null, $block_content, $computed_attributes, $block_instance );
162
163 if ( ! is_null( $pre_render ) ) {
164 $block_content = $pre_render;
165 } else {
166 foreach ( $computed_attributes as $attribute_name => $source_value ) {
167 $block_content = $this->replace_html( $block_content, $attribute_name, $source_value, $block_instance, $computed_attributes );
168 }
169 }
170
171 return $block_content;
172 }
173
174 /**
175 * Render special blocks.
176 *
177 * @param null $content
178 * @param string $block_content
179 * @param array $computed_attributes
180 * @param WP_Block $block_instance
181 * @return null|string
182 */
183 public function pre_render_dynamic_content( $content, $block_content, $computed_attributes, $block_instance ) {
184 if ( 'boldblocks/svg-block' === $block_instance->name ) {
185 // Merge with dynamic content.
186 $attributes = array_merge( $block_instance->attributes, $computed_attributes );
187
188 // Get class and style attributes.
189 $block_reader = new \WP_HTML_Tag_Processor( $block_content );
190 $class = '';
191 $inner_class = '';
192 $style = '';
193 $inner_style = '';
194
195 if ( $block_reader->next_tag() ) {
196 $class = $block_reader->get_attribute( 'class' );
197 $style = $block_reader->get_attribute( 'style' );
198 }
199
200 if ( $block_reader->next_tag( [ 'class_name' => 'wp-block-boldblocks-svg-block__inner' ] ) ) {
201 $inner_class = $block_reader->get_attribute( 'class' );
202 $inner_style = $block_reader->get_attribute( 'style' );
203 }
204
205 // Don't display empty icon.
206 if ( empty( $attributes['content'] ) ) {
207 return '';
208 }
209
210 $svg = $attributes['content'];
211
212 $id = \uniqid();
213 $title = '';
214 $desc = '';
215 $texts = '';
216 if ( ! empty( $attributes['title'] ) ) {
217 $title = "<title id=\"svg_block_{$id}_title\">{$attributes['title']}</title>";
218 $texts .= $title;
219 }
220 if ( ! empty( $attributes['description'] ) ) {
221 $desc = "<desc id=\"svg_block_{$id}_desc\">{$attributes['description']}</desc>";
222 $texts .= $desc;
223 }
224
225 // Add title & desc.
226 $svg = \preg_replace(
227 '/' . \preg_quote( '>', '/' ) . '/',
228 '>' . $texts,
229 $svg,
230 1
231 );
232
233 $is_link = ! empty( $attributes['linkUrl'] ) || ! empty( $attributes['linkToPost'] );
234 $inner_tag = $is_link ? 'a' : 'div';
235 $inner_attributes = '';
236 $button = '';
237 if ( $is_link ) {
238 if ( ! empty( $attributes['linkToPost'] ) ) {
239 if ( isset( $block_instance->context['postId'] ) ) {
240 // Get post_id from the context first.
241 $post_id = $block_instance->context['postId'];
242 } else {
243 // Fallback to the current post id.
244 $post_id = get_queried_object_id();
245 }
246
247 $link_url = get_permalink( $post_id );
248 } else {
249 $link_url = $attributes['linkUrl'] ?? '';
250 }
251
252 if ( $link_url ) {
253 $inner_attributes .= ' href="' . $link_url . '"';
254
255 if ( ! empty( $attributes['linkTarget'] ) ) {
256 $inner_attributes .= ' target="' . $attributes['linkTarget'] . '"';
257 }
258
259 if ( ! empty( $attributes['linkRel'] ) ) {
260 $inner_attributes .= ' rel="' . $attributes['linkRel'] . '"';
261 }
262
263 if ( ! empty( $attributes['useAsButton'] ) && ! empty( $attributes['buttonText'] ) ) {
264 $button = '<span class="button-text">' . $attributes['buttonText'] . '</span>';
265 }
266 }
267 }
268
269 $new_content = "<div><{$inner_tag}{$inner_attributes}>{$svg}{$button}</{$inner_tag}></div>";
270 $block_reader = new \WP_HTML_Tag_Processor( $new_content );
271 $block_reader->next_tag();
272 $block_reader->set_attribute( 'class', $class );
273 if ( $style ) {
274 $block_reader->set_attribute( 'style', $style );
275 }
276
277 $block_reader->next_tag();
278 $block_reader->set_attribute( 'class', $inner_class );
279 if ( $inner_style ) {
280 $block_reader->set_attribute( 'style', $inner_style );
281 }
282
283 if ( $block_reader->next_tag( 'svg' ) ) {
284 $block_reader->set_attribute( 'role', 'img' );
285
286 if ( $texts ) {
287 if ( $title ) {
288 $block_reader->set_attribute( 'aria-labelledby', "svg_block_{$id}_title" );
289 }
290 if ( $desc ) {
291 $block_reader->set_attribute( 'aria-describedby', "svg_block_{$id}_desc" );
292 }
293 }
294 }
295
296 return $block_reader->get_updated_html();
297 } elseif ( 'core/embed' === $block_instance->name ) {
298 // Merge with dynamic content.
299 $attributes = array_merge( $block_instance->attributes, $computed_attributes );
300
301 if ( empty( $attributes['url'] ) ) {
302 return '';
303 }
304
305 // Get class and style attributes.
306 $block_reader = new \WP_HTML_Tag_Processor( $block_content );
307 $class = '';
308 $style = '';
309
310 if ( $block_reader->next_tag( 'figure' ) ) {
311 $class = $block_reader->get_attribute( 'class' );
312 if ( $class ) {
313 $class = explode( ' ', $class );
314 $class = array_filter(
315 $class,
316 function ( $item ) {
317 if ( 0 === stripos( $item, 'is-type-' ) || 0 === stripos( $item, 'is-provider-' ) || 0 === stripos( $item, 'wp-block-embed-' ) ) {
318 return false;
319 }
320
321 return $item;
322 }
323 );
324
325 $class = implode( ' ', $class );
326 }
327
328 $style = $block_reader->get_attribute( 'style' );
329 if ( $style ) {
330 $style = " style=\"{$style}\"";
331 }
332 }
333
334 $classes = [ 'wp-block-embed' ];
335 if ( $class ) {
336 $classes[] = $class;
337 }
338 if ( $attributes['type'] ?? '' ) {
339 $classes[] = "is-type-{$attributes['type']}";
340 }
341 if ( $attributes['providerNameSlug'] ?? '' ) {
342 $classes[] = "is-provider-{$attributes['providerNameSlug']}";
343 $classes[] = "wp-block-embed-{$attributes['providerNameSlug']}";
344 }
345 $markup = '<figure class="' . implode( ' ', $classes ) . '"' . $style . '><div class="wp-block-embed__wrapper">' . PHP_EOL;
346 $markup .= $attributes['url'] . PHP_EOL;
347 $markup .= '</div>';
348 if ( $attributes['caption'] ?? '' ) {
349 $markup .= '<figcaption class="wp-element-caption">' . $attributes['caption'] . '</figcaption>';
350 }
351 $markup .= '</figure>';
352
353 global $wp_embed;
354 return $wp_embed->autoembed( $markup );
355 } elseif ( 'boldblocks/youtube-block' === $block_instance->name ) {
356 if ( function_exists( 'better_youtube_embed_block_render_block' ) ) {
357 // Merge with dynamic content.
358 $attributes = array_merge( $block_instance->attributes, $computed_attributes );
359
360 // Get caption from the markup if there is no binding value.
361 if ( ! isset( $computed_attributes['caption'] ) ) {
362 $caption = $this->the_plugin_instance->get_component( CustomStyle::class )->get_element_inner_html( $block_content, 'FIGCAPTION' );
363 if ( $caption ) {
364 $attributes['caption'] = $caption;
365 }
366 }
367
368 // Allow inline tags.
369 $attributes['caption_kses'] = true;
370
371 return better_youtube_embed_block_render_block( $attributes );
372 }
373 } elseif ( 'boldblocks/counting-number' === $block_instance->name ) {
374 // Merge with dynamic content.
375 $attributes = array_merge( $block_instance->attributes, $computed_attributes );
376
377 // Get class and style attributes.
378 $block_reader = new \WP_HTML_Tag_Processor( $block_content );
379 $class = '';
380 $style = '';
381 $value_attrs = [];
382
383 if ( $block_reader->next_tag() ) {
384 $class = $block_reader->get_attribute( 'class' );
385 $style = $block_reader->get_attribute( 'style' );
386 }
387
388 if ( $block_reader->next_tag( [ 'class_name' => 'value' ] ) ) {
389 $value_attribute_names = $block_reader->get_attribute_names_with_prefix( '' );
390 foreach ( $value_attribute_names as $name ) {
391 $value_attrs[ $name ] = $block_reader->get_attribute( $name );
392 }
393 }
394
395 $new_content = '<div class="' . $class . '" style="' . $style . '">';
396 if ( $attributes['prefix'] ?? false ) {
397 $new_content .= '<span class="prefix">' . $attributes['prefix'] . '</span>';
398 }
399 $new_content .= '<span class="value">' . ( $attributes['startVal'] ?? '' ) . '</span>';
400 $new_content .= '<span class="sr-value">' . ( $attributes['value'] ?? '' ) . '</span>';
401 if ( $attributes['suffix'] ?? false ) {
402 $new_content .= '<span class="suffix">' . $attributes['suffix'] . '</span>';
403 }
404 $new_content .= '</div>';
405
406 $overridable_attrs = [ 'data-value', 'data-start-val', 'data-prefix', 'data-suffix' ];
407
408 $block_reader = new \WP_HTML_Tag_Processor( $new_content );
409 if ( $block_reader->next_tag( [ 'class_name' => 'value' ] ) ) {
410 foreach ( $value_attrs as $attribute_key => $attribute_value ) {
411 if ( in_array( $attribute_key, $overridable_attrs, true ) ) {
412 switch ( $attribute_key ) {
413 case 'data-value':
414 $attribute_value = $attributes['value'] ?? '';
415 break;
416
417 case 'data-start-val':
418 $attribute_value = $attributes['startVal'] ?? '';
419 break;
420
421 case 'data-prefix':
422 $attribute_value = $attributes['prefix'] ?? '';
423 break;
424
425 case 'data-suffix':
426 $attribute_value = $attributes['suffix'] ?? '';
427 break;
428
429 default:
430 break;
431 }
432 }
433
434 $block_reader->set_attribute( $attribute_key, $attribute_value );
435 }
436 }
437
438 return $block_reader->get_updated_html();
439 }
440
441 return $content;
442 }
443
444 /**
445 * Add more blocks to the supported list
446 *
447 * @param array $supported_blocks
448 * @return array
449 */
450 public function support_additional_blocks( $supported_blocks ) {
451 $supported_blocks['core/embed'] = [ 'url', 'caption', 'type', 'providerNameSlug', 'responsive', 'previewable' ];
452 $supported_blocks['core/video'] = [ 'src', 'caption' ];
453 $supported_blocks['core/audio'] = [ 'src', 'caption' ];
454 $supported_blocks['core/code'] = [ 'content' ];
455 $supported_blocks['core/verse'] = [ 'content' ];
456 $supported_blocks['core/preformatted'] = [ 'content' ];
457 $supported_blocks['core/pullquote'] = [ 'value', 'citation' ];
458
459 $supported_blocks['boldblocks/youtube-block'] = [ 'url', 'caption', 'aspectRatio', 'isMaxResThumbnail', 'customThumbnail' ];
460 $supported_blocks['boldblocks/counting-number'] = [ 'value', 'startVal', 'prefix', 'suffix' ];
461
462 return $supported_blocks;
463 }
464
465 /**
466 * Build dynamic contents
467 *
468 * @param array $parsed_block
469 * @param WP_Block $block_instance
470 * @return array
471 */
472 private function process_block_bindings( $parsed_block, $block_instance ) {
473 $computed_attributes = array();
474 $block_name = $parsed_block['blockName'] ?? '';
475 $supported_blocks = $this->get_supported_blocks();
476
477 // If the block doesn't have the bindings property, isn't one of the supported
478 // block types, or the bindings property is not an array, return the block content.
479 if (
480 ! isset( $supported_blocks[ $block_name ] ) ||
481 empty( $parsed_block['attrs']['metadata']['bindings'] ) ||
482 ! is_array( $parsed_block['attrs']['metadata']['bindings'] )
483 ) {
484 return $computed_attributes;
485 }
486
487 foreach ( $parsed_block['attrs']['metadata']['bindings'] as $attribute_name => $block_binding ) {
488 // If the attribute is not in the supported list, process next attribute.
489 if ( ! array_key_exists( $block_name, $supported_blocks ) ) {
490 continue;
491 }
492
493 // If no source is provided, or that source is not registered, process next attribute.
494 if ( ! isset( $block_binding['source'] ) || ! is_string( $block_binding['source'] ) ) {
495 continue;
496 }
497
498 $block_binding_source = get_block_bindings_source( $block_binding['source'] );
499 if ( null === $block_binding_source ) {
500 continue;
501 }
502
503 $source_args = ! empty( $block_binding['args'] ) && is_array( $block_binding['args'] ) ? $block_binding['args'] : array();
504 $source_value = $block_binding_source->get_value( $source_args, $block_instance, $attribute_name );
505
506 // If the value is not null, process the HTML based on the block and the attribute.
507 if ( ! is_null( $source_value ) ) {
508 $computed_attributes[ $attribute_name ] = $source_value;
509 }
510 }
511
512 return $computed_attributes;
513 }
514
515 /**
516 * Depending on the block attribute name, replace its value in the HTML based on the value provided.
517 *
518 * @param string $block_content Block content.
519 * @param string $attribute_name The attribute name to replace.
520 * @param mixed $source_value The value used to replace in the HTML.
521 * @param WP_Block $block_instance
522 * @param array $computed_attributes
523 * @return string The modified block content.
524 */
525 private function replace_html( string $block_content, string $attribute_name, $source_value, $block_instance, $computed_attributes ) {
526 $block_type = $block_instance->block_type;
527 $properties = $block_type->attributes[ $attribute_name ];
528
529 // Use this filter to alter source, selector for the property.
530 $properties = apply_filters( 'cbb_get_binding_attribute_properties', $properties, $attribute_name, $block_instance, $computed_attributes );
531
532 if ( ! isset( $properties['source'] ) ) {
533 return $block_content;
534 }
535
536 // Make sure the seletor is valid and don't support multiple selector.
537 if ( empty( $properties['selector'] ) || false !== stripos( $properties['selector'], ',' ) ) {
538 return $block_content;
539 }
540
541 // Depending on the attribute source, the processing will be different.
542 switch ( $properties['source'] ) {
543 case 'html':
544 case 'rich-text':
545 $parsed_selector = $this->parse_selector( $properties['selector'] );
546 $tag_name = $parsed_selector['tag_name'] ?? '';
547 $class_name = $parsed_selector['class_name'] ?? '';
548
549 // If there is no tag_name, attempt to get it from the source.
550 if ( ! $tag_name ) {
551 $block_reader = new \WP_HTML_Tag_Processor( $block_content );
552
553 // If any element matches the selector, get the tag name.
554 if ( $block_reader->next_tag( $parsed_selector['query'] ) ) {
555 $tag_name = $block_reader->get_tag();
556 }
557 }
558
559 // Bail if there is no tag name.
560 if ( ! $tag_name ) {
561 return $block_content;
562 }
563
564 $pattern = '/(<' . $tag_name . '[^>]*>).*?(<\/' . $tag_name . '>)/is';
565
566 return preg_replace( $pattern, '$1' . wp_kses_post( $source_value ) . '$2', $block_content );
567
568 case 'attribute':
569 $amended_content = new \WP_HTML_Tag_Processor( $block_content );
570 $parsed_selector = $this->parse_selector( $properties['selector'] );
571 if ( ! $amended_content->next_tag( $parsed_selector['query'] ) ) {
572 return $block_content;
573 }
574 $amended_content->set_attribute( $properties['attribute'], $source_value );
575 return $amended_content->get_updated_html();
576
577 default:
578 return $block_content;
579 }
580 }
581
582 /**
583 * Parse selector
584 *
585 * @param string $selector
586 * @return array
587 */
588 private function parse_selector( $selector ) {
589 $parsed_attrs = [];
590 $dot_pos = stripos( $selector, '.' );
591 if ( false === $dot_pos ) {
592 $parsed_attrs['tag_name'] = $selector;
593 } elseif ( $dot_pos > 0 ) {
594 $parts = explode( '.', $selector );
595 $parsed_attrs['tag_name'] = $parts[0];
596 $parsed_attrs['class_name'] = $parts[1];
597 } else {
598 $parsed_attrs['class_name'] = trim( $selector, '.' );
599 }
600
601 $query = [];
602 if ( ! empty( $parsed_attrs['tag_name'] ) ) {
603 $query['tag_name'] = $parsed_attrs['tag_name'];
604 }
605
606 if ( ! empty( $parsed_attrs['class_name'] ) ) {
607 $query['class_name'] = $parsed_attrs['class_name'];
608 }
609
610 $parsed_attrs['query'] = $query;
611
612 return $parsed_attrs;
613 }
614
615 /**
616 * Get supported blocks
617 *
618 * @return array
619 */
620 private function get_supported_blocks() {
621 return apply_filters( 'cbb_block_overrides_supported_blocks', $this->supported_blocks );
622 }
623 }
624 endif;
625