PluginProbe
Better Block Editor (BBE) / trunk
Better Block Editor (BBE) vtrunk
1.8.1 1.8.0 1.7.0 1.6.0 1.5.1 1.5.0 trunk 1.0.0 1.0.1 1.0.1.1 1.0.1.2 1.1.0 1.2.0 1.2.1 1.2.2 1.3.0 1.4.0 1.4.1 1.4.2
better-block-editor / Core / BlockUtils.php

BlockUtils.php in Better Block Editor (BBE) trunk, at Core/BlockUtils.php

415 lines 12.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Utility class to assist with block-related logic.
4 *
5 * @package BetterBlockEditor
6 */
7
8 namespace BetterBlockEditor\Core;
9
10 use BetterBlockEditor\Modules\StyleEngine\Module as StyleEngineModule;
11 use WP_Block_Supports;
12 use WP_HTML_Tag_Processor;
13
14 defined( 'ABSPATH' ) || exit;
15
16 class BlockUtils {
17
18 const BLOCK_UNIQUE_CLASSNAME_PREFIX = 'wpbbe-';
19
20
21 /**
22 * Generates a unique class name for a block based on its attributes.
23 *
24 * @param array $block The $block->parsed_block, full block, including name and attributes.
25 * @return string A unique class name for the block.
26 */
27 static function create_unique_class_id( array $block = [] ): string {
28 if ( ! empty( $block ) ) {
29 return self::BLOCK_UNIQUE_CLASSNAME_PREFIX . substr( md5( serialize( $block ) ), 0, 8 );
30 }
31 return wp_unique_prefixed_id( self::BLOCK_UNIQUE_CLASSNAME_PREFIX );
32 }
33
34 /**
35 * Retrieves a unique class name from the block content or generates one if not found.
36 *
37 * @param string $block_content The block content.
38 * @param array $block The $block->parsed_block, full block, including name and attributes.
39 * @return string A unique class name for the block.
40 */
41 static function get_unique_class_id( $block_content, array $block = [] ): string {
42 $prefix = self::BLOCK_UNIQUE_CLASSNAME_PREFIX;
43
44 $tags = new WP_HTML_Tag_Processor( $block_content );
45 if ( $tags->next_tag() ) {
46 foreach ( ($tags->class_list() ?? array()) as $class_name ) {
47 $prefix_fine = $prefix === substr( $class_name, 0, strlen( $prefix ) );
48 $sufix_fine = preg_match( '/^[a-z0-9]+$/i', substr( $class_name, strlen( $prefix ) ) );
49 if ( $prefix_fine && $sufix_fine ) {
50 return $class_name;
51 }
52 }
53 }
54
55 return self::create_unique_class_id( $block );
56 }
57
58 /**
59 * Appends classes to first tag of block content.
60 *
61 * @param string $block_content The block content.
62 * @param array|string $content_classes The classes to add.
63 *
64 * @return string The modified block content.
65 */
66 static function append_classes( $block_content, $content_classes ) {
67 $tag = self::get_tag_to_modify( $block_content );
68 if ( empty( $content_classes ) || ! $tag ) {
69 return $block_content;
70 }
71
72 foreach ( (array) $content_classes as $class_name ) {
73 $tag->add_class( $class_name );
74 }
75
76 return $tag->get_updated_html();
77 }
78
79 static function remove_classes( $block_content, $content_classes ) {
80 $tag = self::get_tag_to_modify( $block_content );
81 if ( empty( $content_classes ) || ! $tag ) {
82 return $block_content;
83 }
84
85 foreach ( $content_classes as $class_name ) {
86 $tag->remove_class( $class_name );
87 }
88
89 return $tag->get_updated_html();
90 }
91
92 static function get_tag_to_modify( $block_content ) {
93 $p = new WP_HTML_Tag_Processor( $block_content );
94 while ( $p->next_tag() ) {
95 $tag_name = $p->get_tag();
96 if ( $tag_name !== 'STYLE' && $tag_name !== 'SCRIPT' ) {
97 return $p;
98 }
99 }
100
101 return null;
102 }
103
104 /**
105 * Sets an attribute on the first tag in the given block content.
106 *
107 * @param string $block_content The HTML content of the block.
108 * @param string $attribute The attribute name to set.
109 * @param string $value The value to set for the attribute.
110 *
111 * @return string The modified block content with the updated attribute.
112 */
113 static function set_attribute( $block_content, $attribute, $value ) {
114 $tag = self::get_tag_to_modify( $block_content );
115 if ( ! $tag ) {
116 return $block_content;
117 }
118
119 $tag->set_attribute( $attribute, $value );
120
121 return $tag->get_updated_html();
122 }
123
124 /**
125 * Appends inline CSS styles to the first tag in the given block content.
126 *
127 * @param string $block_content The HTML content of the block.
128 * @param array $css_style_rules An associative array of CSS property-value pairs to be added.
129 *
130 * @return string The modified block content with appended inline styles.
131 */
132 static function append_inline_styles( $block_content, $css_style_rules ) {
133 $tag = self::get_tag_to_modify( $block_content );
134 if ( empty( $css_style_rules ) || ! $tag ) {
135 return $block_content;
136 }
137
138 foreach ( $css_style_rules as $property => $value ) {
139 $tag->set_attribute( 'style', $tag->get_attribute( 'style' ) . '; ' . $property . ': ' . $value . ';' );
140 }
141
142 return $tag->get_updated_html();
143 }
144
145 static function append_inline_css_variables( $block_content, $css_variables ) {
146 $tag = self::get_tag_to_modify( $block_content );
147 if ( empty( $css_variables ) || ! $tag ) {
148 return $block_content;
149 }
150
151 $var_string = '';
152 foreach ( $css_variables as $name => $value ) {
153 $var_string .= $name . ':' . $value . ';';
154 }
155
156 $tag->set_attribute( 'style', $tag->get_attribute( 'style' ) . '; ' . $var_string );
157
158 return $tag->get_updated_html();
159 }
160
161 static function add_styles_from_css_rules( $css_rules ) {
162 if ( ! empty( $css_rules ) ) {
163 /*
164 * Add to the style engine store to enqueue and render layout styles.
165 * Return compiled layout styles to retain backwards compatibility.
166 * Since https://github.com/WordPress/gutenberg/pull/42452,
167 * wp_enqueue_block_support_styles is no longer called in this block supports file.
168 */
169 return StyleEngineModule::get_stylesheet_from_css_rules(
170 StyleEngineModule::preprocess_css_rules( $css_rules ),
171 array(
172 'context' => 'core',
173 'prettify' => false,
174 )
175 );
176 }
177
178 return '';
179 }
180
181 /**
182 * Adds CSS style declarations for a specific media query and selector.
183 * Just a useful wrapper for self::add_styles_from_css_rules().
184 *
185 * @param string $media_query The media query condition (e.g., '@media screen and (width <= 500px)').
186 * @param string $selector The CSS selector to which the declarations will apply.
187 * @param array $css_rules An associative array of CSS properties and their values.
188 * @return void
189 */
190 static function add_style_for_media_query( $media_query, $selector, $css_rules ) {
191 return self::add_styles_from_css_rules(
192 array(
193 array(
194 'selector' => $media_query,
195 'declarations' => array(
196 array(
197 'selector' => $selector,
198 'declarations' => $css_rules,
199 ),
200 ),
201 ),
202 )
203 );
204 }
205
206 /**
207 * Returns the corresponding CSS flexbox horizontal alignment value based on the given attribute value.
208 *
209 * Maps alignment attribute to their respective CSS flexbox alignment values.
210 * If $reverse_orientation is true, 'left' and 'right' are swapped.
211 *
212 * @param string $attribute_value The alignment attribute value ('left', 'right', 'center', 'stretch', 'space-between').
213 * @param bool $reverse_orientation Whether to reverse the alignment for orientation.
214 *
215 * @return string|null The CSS flexbox alignment value, or null if the attribute value is not recognized.
216 */
217 static function get_horizontal_alignment_by_attribute( $attribute_value, $reverse_orientation = false ) {
218 // Used with the default, horizontal(row) flex orientation.
219 $horizontal_alignment_map = array(
220 'left' => 'flex-start',
221 'right' => 'flex-end',
222 'center' => 'center',
223 'stretch' => 'stretch',
224 'space-between' => 'space-between',
225 );
226
227 $horizontal_alignment_reverse_map = array_merge(
228 $horizontal_alignment_map,
229 array(
230 'left' => 'flex-end',
231 'right' => 'flex-start',
232 )
233 );
234
235 return $reverse_orientation
236 ? $horizontal_alignment_reverse_map[ $attribute_value ]
237 : $horizontal_alignment_map[ $attribute_value ];
238 }
239
240
241 /**
242 * Generates an array of wrapper classes for a block.
243 *
244 * @param array $base_classes Initial classes to include.
245 * @return array Final array of wrapper classes.
246 */
247 static function append_block_wrapper_classes( array $base_classes ) {
248 $wrapper_classes = $base_classes;
249
250 $block_supports = WP_Block_Supports::get_instance();
251
252 if ( method_exists( $block_supports, 'apply_block_supports' ) ) {
253 $wrapper_attributes = $block_supports->apply_block_supports();
254
255 if ( isset( $wrapper_attributes['class'] ) && ! empty( $wrapper_attributes['class'] ) ) {
256 $wrapper_classes[] = $wrapper_attributes['class'];
257 }
258 }
259
260 return $wrapper_classes;
261 }
262
263 /**
264 * Generate CSS variables based on the settings and attributes.
265 *
266 * @param array $settings The settings to generate CSS variables for.
267 * @param array $attributes The block attributes.
268 * @param array $pefix Prefix for variable name
269 *
270 * @return array An associative array of CSS variable definitions.
271 */
272 static function generate_css_variables( $settings, $attributes, $prefix ='') {
273 $vars = array();
274
275 foreach ( $settings as $key => $suffix ) {
276 // Simple direct mapping
277 if ( is_string( $suffix ) ) {
278 if ( isset( $attributes[ $key ] ) && $attributes[ $key ] !== '' ) {
279 $vars[ $prefix . $suffix ] = $attributes[ $key ];
280 }
281 continue;
282 }
283 // Mapping with 'var' key
284 if ( isset( $suffix['var'] ) && isset( $attributes[ $key ] ) ) {
285 $vars[ $prefix . $suffix['var'] ] = $attributes[ $key ];
286 }
287 }
288 return $vars;
289 }
290
291 /**
292 * Normalize block attributes into CSS-ready values based on a settings schema.
293 *
294 * This function reads attribute values (including nested ones via dot notation),
295 * converts them according to their configured type (e.g. color, spacing, number,
296 * justify, border), and returns a flat array suitable for CSS variable generation.
297 *
298 * The same settings schema is intended to be shared between PHP and JS.
299 *
300 * @param array $settings Map of attribute keys to normalization configuration.
301 * Each item may contain:
302 * - 'attr' (string) Path to attribute using dot notation.
303 * - 'var' (string) CSS variable name.
304 * - 'type' (string) Normalization type (color, spacing, number, justify, border, etc.).
305 * - 'unit' (string) Optional unit for numeric values.
306 * - 'reverse'(bool) Optional flag for justify alignment.
307 * @param array $attributes Raw block attributes.
308 *
309 * @return array Flat array of normalized CSS values keyed by setting key (and
310 * additional keys for composite types like border).
311 */
312 public static function normalize_attributes_for_css(
313 array $settings,
314 array $attributes
315 ): array {
316 $result = array();
317
318 foreach ( $settings as $key => $config ) {
319 $attr_path = $config['attr'] ?? $key;
320 $value = self::get_attr_by_path( $attributes, $attr_path );
321
322 if ( $value === null ) {
323 continue;
324 }
325 switch ( $config['type'] ) {
326 case 'color':
327 $value = ColorUtils::color_attribute_to_css( $value );
328 break;
329 case 'spacing':
330 $value = (string) $value;
331 break;
332 case 'justify':
333 $value = self::get_horizontal_alignment_by_attribute(
334 $value,
335 $config['reverse'] ?? false
336 );
337 break;
338 case 'number':
339 $value = isset( $config['unit'] )
340 ? $value . $config['unit']
341 : (string) $value;
342 break;
343 case 'border':
344 // Mutates border array in place
345 ColorUtils::patch_border_colors( $attributes, $key );
346
347 $border = $attributes[ $key ];
348 $result[ $key ] = $border;
349
350 foreach ( $border as $border_key => $border_value ) {
351 if ( $border_value !== null ) {
352 $result[ "{$key}-{$border_key}" ] = $border_value;
353 }
354 }
355 break;
356 default:
357 $value = (string) $value;
358 }
359 $result[ $key ] = $value;
360 }
361
362 return $result;
363 }
364
365 /**
366 * Retrieve a nested attribute value using dot notation.
367 * @param array $attributes
368 * @param string $path
369 *
370 * @return array|mixed|null
371 */
372 private static function get_attr_by_path( array $attributes, string $path ) {
373 $parts = explode( '.', $path );
374 $value = $attributes;
375
376 foreach ( $parts as $part ) {
377 if ( ! is_array( $value ) || ! array_key_exists( $part, $value ) ) {
378 return null;
379 }
380 $value = $value[ $part ];
381 }
382
383 return $value;
384 }
385
386 /**
387 * Convert a preset string (e.g., 'var:preset|color|primary')
388 * to a CSS variable format (e.g., 'var(--wp--preset--color--primary)').
389 *
390 * @param string $preset_string The preset string to convert.
391 * @return string The converted CSS variable string.
392 */
393 public static function css_preset_to_css_variable( string $preset_string ): string {
394 return preg_replace( '/var:preset\|(.+)\|([a-z0-9-]+)/', 'var(--wp--preset--$1--$2)', $preset_string );
395 }
396
397 /**
398 * Convert a camelCase string to kebab-case.
399 * Useful for converting JS style definitions to CSS property names.
400 *
401 * @param string $string The camelCase string.
402 * @return string The kebab-case string.
403 */
404 public static function camel_case_to_kebab_case( string $string ): string {
405 // Replace non-alphanumeric characters with -
406 $string = preg_replace('/[^a-z0-9]+/i', '-', $string);
407
408 // Replace camelCase transitions
409 $string = preg_replace('/([a-z\d])([A-Z])/', '$1-$2', $string);
410
411 return strtolower(trim($string, '-'));
412 }
413
414 }
415