PluginProbe
Gutenify – Visual Site Builder Blocks & Starter Templates / 1.9.0
Gutenify – Visual Site Builder Blocks & Starter Templates v1.9.0
1.9.0 1.8.0 1.7.0 1.6.6 1.6.5 1.6.4 trunk 0.0.1 0.0.2 0.0.3 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 All 74 releases
gutenify / core / inc / helpers / class-style-helpers.php

class-style-helpers.php in Gutenify – Visual Site Builder Blocks & Starter Templates 1.9.0, at core/inc/helpers/class-style-helpers.php

528 lines 18.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace gutenify;
3
4 // Exit if accessed directly.
5 defined( 'ABSPATH' ) || exit;
6
7 class Style_Helpers {
8 public static $tablet_max_width = '992px';
9 public static $mobile_max_width = '768px';
10 public static function value_has_unit( $val ) {
11 $val = strtolower( $val );
12 $units = array();
13 $all_units = array( 'px', '%', 'em', 'rem', 'vw', 'vh' );
14 foreach ( $all_units as $key => $unit ) {
15 if ( false !== strpos( $val, $unit ) ) {
16 $units[] = $unit;
17 }
18 }
19
20 return count( $units ) > 0;
21 }
22
23 public static function box_control( $args = array(), $prefix = '', $suffix = '' ) {
24 $css = '';
25 if ( ! empty( $args ) ) {
26 if ( ! is_array( $args ) ) {
27 $value = self::value_has_unit( $args ) ? $args : $args . 'px';
28 $css .= str_replace( '--', '-', $prefix . $suffix ) . ':' . esc_attr( $value ) . ';';
29 } else {
30 foreach ( $args as $key => $val ) {
31 if ( in_array( $key, array( 'top', 'bottom', 'left', 'right' ) ) ) {
32 $value = self::value_has_unit( $val ) ? $val : $val . 'px';
33 $css .= $prefix . $key . $suffix . ':' . esc_attr( $value ) . ';';
34 }
35 }
36 }
37 }
38 return $css;
39 }
40
41 public static function border_radius_control( $args, $prefix = 'border-', $suffix = '-radius' ) {
42 $css = '';
43 if ( ! empty( $args ) && ! is_array( $args ) ) {
44 $css .= str_replace( '--', '-', $prefix . $suffix ) . ':' . esc_attr( $args ) . ';';
45 }
46 if ( ! empty( $args ) && is_array( $args ) ) {
47 $areas = array(
48 'topLeft' => 'top-left',
49 'topRight' => 'top-right',
50 'bottomLeft' => 'bottom-left',
51 'bottomRight' => 'bottom-right',
52 );
53 foreach ( $args as $key => $val ) {
54 if ( in_array( $key, array_keys( $areas ) ) ) {
55 $css .= $prefix . $areas[ $key ] . $suffix . ':' . esc_attr( $val ) . ';';
56 }
57 }
58 }
59 return $css;
60 }
61
62 /**
63 * Recognizes a broader set of CSS units than value_has_unit() (px, %, em,
64 * rem, vh, vw, vmin, vmax, ch) — used only by position_control() below, kept
65 * separate so the existing box_control()/border_radius_control() behaviour
66 * (and every block currently relying on it) is left untouched.
67 */
68 public static function position_value_has_unit( $value ) {
69 return (bool) preg_match( '/^-?[\d.]+(px|%|em|rem|vh|vw|vmin|vmax|ch)$/i', trim( (string) $value ) );
70 }
71
72 public static function position_control( $args = array() ) {
73 $css = '';
74 if ( empty( $args ) || ! is_array( $args ) ) {
75 return $css;
76 }
77
78 $offset_types = array( 'relative', 'absolute', 'fixed', 'sticky' );
79
80 // array_key_exists (not just a truthy check on the value) matters here:
81 // a responsive breakpoint that explicitly picks "Static" must still emit
82 // `position:static` to cancel a wider breakpoint's relative/absolute/fixed/
83 // sticky — omitting it entirely (as a "nothing to do" no-op) would let the
84 // wider rule silently keep applying at the narrower viewport.
85 if ( array_key_exists( 'type', $args ) ) {
86 $type = in_array( $args['type'], $offset_types, true ) ? $args['type'] : 'static';
87 $css .= 'position:' . esc_attr( $type ) . ';';
88
89 if ( in_array( $type, $offset_types, true ) ) {
90 foreach ( array( 'top', 'right', 'bottom', 'left' ) as $side ) {
91 if ( isset( $args[ $side ] ) && '' !== $args[ $side ] ) {
92 $value = self::position_value_has_unit( $args[ $side ] ) ? $args[ $side ] : $args[ $side ] . 'px';
93 $css .= $side . ':' . esc_attr( $value ) . ';';
94 }
95 }
96 }
97 }
98
99 // Not nested inside the `type` block above: a breakpoint can validly set
100 // its own Z-Index while inheriting `type` from a wider breakpoint (the UI's
101 // showOffsets already hides this field unless the *resolved* type needs it,
102 // so by the time a value lands here it's already meaningful).
103 if ( isset( $args['zIndex'] ) && '' !== $args['zIndex'] ) {
104 $css .= 'z-index:' . esc_attr( (int) $args['zIndex'] ) . ';';
105 }
106
107 if ( ! empty( $args['width'] ) ) {
108 $value = self::position_value_has_unit( $args['width'] ) ? $args['width'] : $args['width'] . 'px';
109 $css .= 'width:' . esc_attr( $value ) . ';';
110 }
111
112 if ( ! empty( $args['maxWidth'] ) ) {
113 // !important: the theme's `.is-layout-constrained > :where(...)` rule has
114 // equal specificity and prints after ours, so it wins on a tie otherwise.
115 $value = self::position_value_has_unit( $args['maxWidth'] ) ? $args['maxWidth'] : $args['maxWidth'] . 'px';
116 $css .= 'max-width:' . esc_attr( $value ) . ' !important;';
117 } elseif ( ! empty( $args['width'] ) ) {
118 $css .= 'max-width:none !important;';
119 }
120
121 if ( ! empty( $args['height'] ) ) {
122 $value = self::position_value_has_unit( $args['height'] ) ? $args['height'] : $args['height'] . 'px';
123 $css .= 'height:' . esc_attr( $value ) . ';';
124 }
125
126 $allowed_overflow = array( 'visible', 'hidden', 'scroll', 'auto' );
127 if ( ! empty( $args['overflow'] ) && in_array( $args['overflow'], $allowed_overflow, true ) ) {
128 $css .= 'overflow:' . esc_attr( $args['overflow'] ) . ';';
129 }
130
131 // `display:none` at a given breakpoint doubles as responsive visibility — no separate "hide on X" toggle needed.
132 if ( ! empty( $args['display'] ) ) {
133 $css .= 'display:' . esc_attr( $args['display'] ) . ';';
134 }
135
136 return $css;
137 }
138
139 /**
140 * Builds the `transform:` function list (translateX/Y, rotate, scaleX/Y,
141 * skewX/Y) from a single bucket of values. Returns '' if none are set.
142 * Order (translate, rotate, scale, skew) matches the JS mirror in
143 * block-transform/inline-styles.js — keep both in sync.
144 */
145 public static function build_transform_value( $fields ) {
146 if ( empty( $fields ) || ! is_array( $fields ) ) {
147 return '';
148 }
149
150 $functions = array();
151
152 if ( isset( $fields['translateX'] ) && '' !== $fields['translateX'] ) {
153 $value = self::position_value_has_unit( $fields['translateX'] ) ? $fields['translateX'] : $fields['translateX'] . 'px';
154 $functions[] = 'translateX(' . esc_attr( $value ) . ')';
155 }
156 if ( isset( $fields['translateY'] ) && '' !== $fields['translateY'] ) {
157 $value = self::position_value_has_unit( $fields['translateY'] ) ? $fields['translateY'] : $fields['translateY'] . 'px';
158 $functions[] = 'translateY(' . esc_attr( $value ) . ')';
159 }
160 if ( isset( $fields['rotate'] ) && '' !== $fields['rotate'] ) {
161 $functions[] = 'rotate(' . esc_attr( (float) $fields['rotate'] ) . 'deg)';
162 }
163 if ( isset( $fields['scaleX'] ) && '' !== $fields['scaleX'] ) {
164 $functions[] = 'scaleX(' . esc_attr( (float) $fields['scaleX'] ) . ')';
165 }
166 if ( isset( $fields['scaleY'] ) && '' !== $fields['scaleY'] ) {
167 $functions[] = 'scaleY(' . esc_attr( (float) $fields['scaleY'] ) . ')';
168 }
169 if ( isset( $fields['skewX'] ) && '' !== $fields['skewX'] ) {
170 $functions[] = 'skewX(' . esc_attr( (float) $fields['skewX'] ) . 'deg)';
171 }
172 if ( isset( $fields['skewY'] ) && '' !== $fields['skewY'] ) {
173 $functions[] = 'skewY(' . esc_attr( (float) $fields['skewY'] ) . 'deg)';
174 }
175
176 return implode( ' ', $functions );
177 }
178
179 /**
180 * Builds the `transform-origin:` value from a single bucket of values.
181 * Returns '' if neither axis is set. A set axis with the other axis unset
182 * falls back to the CSS default (50%, i.e. center) for that axis only.
183 */
184 public static function build_transform_origin_value( $fields ) {
185 if ( empty( $fields ) || ! is_array( $fields ) ) {
186 return '';
187 }
188
189 $has_x = isset( $fields['originX'] ) && '' !== $fields['originX'];
190 $has_y = isset( $fields['originY'] ) && '' !== $fields['originY'];
191
192 if ( ! $has_x && ! $has_y ) {
193 return '';
194 }
195
196 $x = $has_x ? ( self::position_value_has_unit( $fields['originX'] ) ? $fields['originX'] : $fields['originX'] . 'px' ) : '50%';
197 $y = $has_y ? ( self::position_value_has_unit( $fields['originY'] ) ? $fields['originY'] : $fields['originY'] . 'px' ) : '50%';
198
199 return esc_attr( $x ) . ' ' . esc_attr( $y );
200 }
201
202 /**
203 * Non-responsive entry point: builds both `transform` and
204 * `transform-origin` declarations from one flat bucket of values. The
205 * responsive (per-breakpoint) path lives in
206 * Dynamic_Styles::get_transform_with_media() instead, since it needs to
207 * resolve values cumulatively across breakpoints (see that method's
208 * docblock for why `transform` can't be handled the same way as
209 * Position's independent longhand properties).
210 */
211 public static function transform_control( $args = array() ) {
212 $css = '';
213 if ( empty( $args ) || ! is_array( $args ) ) {
214 return $css;
215 }
216
217 $transform_value = self::build_transform_value( $args );
218 if ( $transform_value ) {
219 $css .= 'transform:' . $transform_value . ';';
220 }
221
222 $origin_value = self::build_transform_origin_value( $args );
223 if ( $origin_value ) {
224 $css .= 'transform-origin:' . $origin_value . ';';
225 }
226
227 return $css;
228 }
229
230 /**
231 * One table drives both `filter` and `backdrop-filter` — same CSS
232 * functions, applied to two different properties. The backdrop variant's
233 * field key is just the normal one prefixed with "backdrop" (see
234 * filter_field_key()), so both share this table instead of duplicating it.
235 * Mirrored in block-effects/inline-styles.js's FILTER_FUNCTIONS — keep
236 * both in sync.
237 */
238 public static function filter_functions() {
239 return array(
240 array(
241 'name' => 'Blur',
242 'cssFn' => 'blur',
243 'unit' => 'px',
244 ),
245 array(
246 'name' => 'Brightness',
247 'cssFn' => 'brightness',
248 'unit' => '%',
249 ),
250 array(
251 'name' => 'Contrast',
252 'cssFn' => 'contrast',
253 'unit' => '%',
254 ),
255 array(
256 'name' => 'Saturate',
257 'cssFn' => 'saturate',
258 'unit' => '%',
259 ),
260 array(
261 'name' => 'Grayscale',
262 'cssFn' => 'grayscale',
263 'unit' => '%',
264 ),
265 array(
266 'name' => 'Sepia',
267 'cssFn' => 'sepia',
268 'unit' => '%',
269 ),
270 array(
271 'name' => 'HueRotate',
272 'cssFn' => 'hue-rotate',
273 'unit' => 'deg',
274 ),
275 );
276 }
277
278 public static function filter_field_key( $name, $is_backdrop ) {
279 return $is_backdrop ? 'backdrop' . $name : lcfirst( $name );
280 }
281
282 /**
283 * Builds the `filter:`/`backdrop-filter:` function list from a single
284 * bucket of values. `$is_backdrop` selects which field-key variant
285 * (blur vs backdropBlur, etc) to read. Returns '' if none are set.
286 */
287 public static function build_filter_functions_value( $fields, $is_backdrop ) {
288 if ( empty( $fields ) || ! is_array( $fields ) ) {
289 return '';
290 }
291
292 $functions = array();
293 foreach ( self::filter_functions() as $fn ) {
294 $field = self::filter_field_key( $fn['name'], $is_backdrop );
295 if ( isset( $fields[ $field ] ) && '' !== $fields[ $field ] ) {
296 $functions[] = $fn['cssFn'] . '(' . esc_attr( (float) $fields[ $field ] ) . $fn['unit'] . ')';
297 }
298 }
299
300 return implode( ' ', $functions );
301 }
302
303 /** `opacity` is an independent longhand (unlike filter/backdrop-filter) — no cumulative merge needed, same treatment as Position's simple fields. */
304 public static function build_opacity_value( $fields ) {
305 if ( empty( $fields ) || ! is_array( $fields ) || ! isset( $fields['opacity'] ) || '' === $fields['opacity'] ) {
306 return '';
307 }
308 return (string) (float) $fields['opacity'];
309 }
310
311 /** `mix-blend-mode`/`clip-path` — independent longhands like opacity, grouped with Effects since both pair visually with filter/backdrop-filter. */
312 public static function build_blend_clip_value( $fields ) {
313 $css = '';
314 if ( empty( $fields ) || ! is_array( $fields ) ) {
315 return $css;
316 }
317
318 if ( ! empty( $fields['mixBlendMode'] ) ) {
319 $css .= 'mix-blend-mode:' . esc_attr( $fields['mixBlendMode'] ) . ';';
320 }
321
322 if ( ! empty( $fields['clipPath'] ) ) {
323 $css .= 'clip-path:' . esc_attr( $fields['clipPath'] ) . ';';
324 }
325
326 return $css;
327 }
328
329 /**
330 * Non-responsive entry point: builds `opacity`/`filter`/`backdrop-filter`
331 * declarations from one flat bucket of values. The responsive
332 * (per-breakpoint) path lives in Dynamic_Styles::get_effects_with_media()
333 * instead — same reasoning as transform_control() above, filter/
334 * backdrop-filter need cumulative cross-breakpoint resolution that this
335 * flat entry point doesn't.
336 */
337 public static function effects_control( $args = array() ) {
338 $css = '';
339 if ( empty( $args ) || ! is_array( $args ) ) {
340 return $css;
341 }
342
343 $opacity_value = self::build_opacity_value( $args );
344 if ( '' !== $opacity_value ) {
345 $css .= 'opacity:' . esc_attr( $opacity_value ) . ';';
346 }
347
348 $css .= self::build_blend_clip_value( $args );
349
350 $filter_value = self::build_filter_functions_value( $args, false );
351 if ( $filter_value ) {
352 $css .= 'filter:' . $filter_value . ';';
353 }
354
355 $backdrop_value = self::build_filter_functions_value( $args, true );
356 if ( $backdrop_value ) {
357 $css .= 'backdrop-filter:' . $backdrop_value . ';-webkit-backdrop-filter:' . $backdrop_value . ';';
358 }
359
360 return $css;
361 }
362
363 /**
364 * Builds `background-color`/`color` declarations from a single bucket of
365 * values. Both are independent longhand properties — unlike Transform/
366 * Effects' composed `transform`/`filter`, setting one at a narrower
367 * breakpoint or state never erases the other — so, like
368 * position_control(), this needs no cumulative-merge handling; each
369 * breakpoint/state's own bucket is emitted directly.
370 */
371 public static function colors_control( $args = array() ) {
372 $css = '';
373 if ( empty( $args ) || ! is_array( $args ) ) {
374 return $css;
375 }
376
377 if ( ! empty( $args['backgroundColor'] ) ) {
378 $css .= 'background-color:' . esc_attr( $args['backgroundColor'] ) . ';';
379 }
380
381 if ( ! empty( $args['color'] ) ) {
382 $css .= 'color:' . esc_attr( $args['color'] ) . ';';
383 }
384
385 return $css;
386 }
387
388 /**
389 * `transition-duration`/`transition-timing-function`/`transition-delay`
390 * — independent longhand properties, same treatment as colors_control().
391 * Deliberately does not emit `transition-property`: its CSS initial value
392 * is already `all`, matching what an unset control should mean.
393 */
394 public static function transition_control( $args = array() ) {
395 $css = '';
396 if ( empty( $args ) || ! is_array( $args ) ) {
397 return $css;
398 }
399
400 if ( isset( $args['duration'] ) && '' !== $args['duration'] ) {
401 $css .= 'transition-duration:' . (int) $args['duration'] . 'ms;';
402 }
403
404 if ( ! empty( $args['timingFunction'] ) ) {
405 $easing = 'cubic-bezier' === $args['timingFunction'] && ! empty( $args['customEasing'] )
406 ? 'cubic-bezier(' . esc_attr( $args['customEasing'] ) . ')'
407 : esc_attr( $args['timingFunction'] );
408 $css .= 'transition-timing-function:' . $easing . ';';
409 }
410
411 if ( isset( $args['delay'] ) && '' !== $args['delay'] ) {
412 $css .= 'transition-delay:' . (int) $args['delay'] . 'ms;';
413 }
414
415 return $css;
416 }
417
418 /**
419 * `flex-grow`/`flex-shrink`/`flex-basis`/`align-self`/`order`/`cursor` —
420 * independent longhands grouped as one "layout" feature since none of
421 * them are visual effects (Effects' bucket) or positioning (Position's
422 * bucket). Same treatment as colors_control() — each field is emitted
423 * independently, no cumulative-merge handling needed.
424 */
425 public static function layout_control( $args = array() ) {
426 $css = '';
427 if ( empty( $args ) || ! is_array( $args ) ) {
428 return $css;
429 }
430
431 if ( isset( $args['flexGrow'] ) && '' !== $args['flexGrow'] ) {
432 $css .= 'flex-grow:' . esc_attr( (float) $args['flexGrow'] ) . ';';
433 }
434
435 if ( isset( $args['flexShrink'] ) && '' !== $args['flexShrink'] ) {
436 $css .= 'flex-shrink:' . esc_attr( (float) $args['flexShrink'] ) . ';';
437 }
438
439 if ( ! empty( $args['flexBasis'] ) ) {
440 $css .= 'flex-basis:' . esc_attr( $args['flexBasis'] ) . ';';
441 }
442
443 if ( ! empty( $args['alignSelf'] ) ) {
444 $css .= 'align-self:' . esc_attr( $args['alignSelf'] ) . ';';
445 }
446
447 if ( isset( $args['order'] ) && '' !== $args['order'] ) {
448 $css .= 'order:' . esc_attr( (int) $args['order'] ) . ';';
449 }
450
451 if ( ! empty( $args['cursor'] ) ) {
452 $css .= 'cursor:' . esc_attr( $args['cursor'] ) . ';';
453 }
454
455 return $css;
456 }
457
458 /**
459 * `flex-direction`/`flex-wrap`/`gap`/`align-items`/`justify-content`/
460 * `grid-template-columns`/`grid-auto-flow` — the CONTAINER side of
461 * flex/grid layout (how this block lays out its children), as opposed to
462 * layout_control() above, which is the CHILD side (how this block
463 * behaves as a flex/grid item of its own parent). `display` itself is
464 * emitted by position_control(), not here — the UI gates these fields on
465 * that same resolved value (see block-position/index.js), but
466 * storage/emission stay independent. flexDirection/flexWrap only apply
467 * under flex; gridTemplateColumns/gridAutoFlow only under grid;
468 * gap/alignItems/justifyContent apply to both.
469 */
470 public static function container_layout_control( $args = array() ) {
471 $css = '';
472 if ( empty( $args ) || ! is_array( $args ) ) {
473 return $css;
474 }
475
476 if ( ! empty( $args['flexDirection'] ) ) {
477 $css .= 'flex-direction:' . esc_attr( $args['flexDirection'] ) . ';';
478 }
479
480 if ( ! empty( $args['flexWrap'] ) ) {
481 $css .= 'flex-wrap:' . esc_attr( $args['flexWrap'] ) . ';';
482 }
483
484 if ( ! empty( $args['gridTemplateColumns'] ) ) {
485 $css .= 'grid-template-columns:' . esc_attr( $args['gridTemplateColumns'] ) . ';';
486 }
487
488 if ( ! empty( $args['gridAutoFlow'] ) ) {
489 $css .= 'grid-auto-flow:' . esc_attr( $args['gridAutoFlow'] ) . ';';
490 }
491
492 if ( ! empty( $args['gap'] ) ) {
493 $value = self::position_value_has_unit( $args['gap'] ) ? $args['gap'] : $args['gap'] . 'px';
494 $css .= 'gap:' . esc_attr( $value ) . ';';
495 }
496
497 if ( ! empty( $args['alignItems'] ) ) {
498 $css .= 'align-items:' . esc_attr( $args['alignItems'] ) . ';';
499 }
500
501 if ( ! empty( $args['justifyContent'] ) ) {
502 $css .= 'justify-content:' . esc_attr( $args['justifyContent'] ) . ';';
503 }
504
505 return $css;
506 }
507
508 public static function custom_box_shadow_control( $args ) {
509 $color = '#7a7a7a';
510 $horizontal = '0px';
511 $vertical = '0px';
512 $blur = '32px';
513 $spread = '0px';
514 $position = '';
515
516 $color = ! empty( $args['color'] ) ? $args['color'] : $color;
517 $horizontal = ! empty( $args['horizontal'] ) ? $args['horizontal'] . 'px' : $horizontal;
518 $vertical = ! empty( $args['vertical'] ) ? $args['vertical'] . 'px' : $vertical;
519 $blur = ! empty( $args['blur'] ) ? $args['blur'] . 'px' : $blur;
520 $spread = ! empty( $args['spread'] ) ? $args['spread'] . 'px' : $spread;
521 $position = ! empty( $args['position'] ) && 'inset' === $args['position'] ? 'inset' : $position;
522
523 $css = ' box-shadow:' . esc_attr( $horizontal ) . ' ' . esc_attr( $vertical ) . ' ' . esc_attr( $blur ) . ' ' . esc_attr( $spread ) . ' ' . esc_attr( $color ) . ' ' . esc_attr( $position ) . ';';
524
525 return $css;
526 }
527 }
528