PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.14.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.14.0
2.14.0 2.13.0 2.13.1 2.12.0 2.11.1 2.11.0 2.10.0 2.9.0 2.7.4 2.7.5 2.7.6 2.7.7 2.8.0 2.8.1 2.9.1 trunk 1.0 1.0-beta1 1.0-beta2 1.0-beta3 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 All 81 releases
← All changes | includes/helper.php +140 -35 2.13.1 → 2.14.0 View file →
@@ -220,16 +220,30 @@
220 220 * `minWidth` produces a real min-width query instead of being skipped —
221 221 * animations already behaved this way, style rules did not.
222 222 */
223 223 public static function breakpoint_media_query( $device ) {
224 - $device = (array) $device;
225 - $min = isset( $device['min'] ) ? (int) $device['min'] : 0;
226 - $max = isset( $device['max'] ) ? (int) $device['max'] : 0;
224 + list( $min, $max ) = self::breakpoint_bounds( $device );
227 225
228 226 if ( $min < 1 && $max < 1 ) {
229 227 return '';
230 228 }
231 229
230 + $condition = self::breakpoint_media_condition( $min, $max );
231 + return '' === $condition ? '' : '@media screen and ' . $condition;
232 + }
233 +
234 + /**
235 + * The [ min, max ] a device's media query actually covers. Mirror of the
236 + * JS `effectiveBounds()`.
237 + *
238 + * @param array $device A device entry.
239 + * @return int[] [ min, max ], 0 meaning unbounded.
240 + */
241 + public static function breakpoint_bounds( $device ) {
242 + $device = (array) $device;
243 + $min = isset( $device['min'] ) ? (int) $device['min'] : 0;
244 + $max = isset( $device['max'] ) ? (int) $device['max'] : 0;
245 +
232 246 /*
233 247 * Strict mode bounds a max-width breakpoint from below with the next
234 248 * narrower breakpoint, turning overlapping envelopes into exclusive
235 249 * bands. A breakpoint that already declares its own min is left alone —
@@ -241,10 +255,9 @@
241 255 $min = $narrower + 1;
242 256 }
243 257 }
244 258
245 - $condition = self::breakpoint_media_condition( $min, $max );
246 - return '' === $condition ? '' : '@media screen and ' . $condition;
259 + return [ $min, $max ];
247 260 }
248 261
249 262 /** The largest max-width bound narrower than $max, or 0 if none. */
250 263 private static function next_narrower_max( $max ) {
@@ -415,43 +428,135 @@
415 428 public static function get_array_value( $array, $key, $default ) {
416 429 return ( isset( $array[ $key ] ) && ! empty( $array[ $key ] ) ) ? $array[ $key ] : $default;
417 430 }
418 431
419 - public static function get_responsive_value( $attribute, $attribute_object_key, $device, $attribute_default_value = [] ) {
420 - // Closure to "clean" a value (similar to your JS clean() helper)
421 - $clean = function( $value ) {
422 - return self::has_value( $value ) ? $value : null;
423 - };
432 + /**
433 + * Whether a stored responsive value is set — the mirror of the editor's
434 + * hasValue(). 0 and '0' are set; null, '' and blank strings are unset (they
435 + * inherit); arrays/objects are set when non-empty. Unlike has_value(), which
436 + * many unrelated callers rely on, this never treats 0 as missing.
437 + *
438 + * @param mixed $value Stored value.
439 + * @return bool
440 + */
441 + public static function has_responsive_value( $value ) {
442 + if ( null === $value ) {
443 + return false;
444 + }
445 + if ( is_string( $value ) ) {
446 + return '' !== trim( $value );
447 + }
448 + if ( is_array( $value ) ) {
449 + return ! empty( $value );
450 + }
451 + if ( is_object( $value ) ) {
452 + return ! empty( get_object_vars( $value ) );
453 + }
454 + return true;
455 + }
424 456
425 - // Desktop value (fallback to default, or false if nothing found)
426 - $desktop_value =
427 - $clean( $attribute[ $attribute_object_key ] ?? null ) ??
428 - $clean( $attribute_default_value[ $attribute_object_key ] ?? null ) ??
429 - false;
457 + /**
458 + * The devices a device inherits from, nearest first, ending with Desktop ('')
459 + * — the mirror of the editor's getDeviceAncestors().
460 + *
461 + * A wider device is an ancestor only when its range contains the device's own
462 + * width, i.e. exactly when the frontend cascade applies its rule there, so a
463 + * min-only (≥1400) or banded (900–1200) breakpoint is never inherited by
464 + * Tablet. With the built-in devices this is Tablet ← Desktop, Mobile ← Tablet.
465 + * Desktop has no ancestors; an unknown suffix (e.g. the phantom probe)
466 + * inherits Desktop only.
467 + *
468 + * @param string $device Device suffix ('' | 'Desktop' | 'Tablet' | 'Bp…').
469 + * @return string[] Ancestor suffixes, nearest first.
470 + */
471 + public static function get_responsive_ancestors( $device ) {
472 + $target = 'Desktop' === $device ? '' : (string) $device;
473 + if ( '' === $target ) {
474 + return [];
475 + }
476 + $devices = array_values( self::get_responsive_devices() );
477 + $index = array_search( $target, array_column( $devices, 'suffix' ), true );
478 + if ( false === $index ) {
479 + return [ '' ];
480 + }
481 + $ancestors = [];
482 + for ( $i = $index - 1; $i >= 0; $i-- ) {
483 + if ( empty( $devices[ $i ]['min'] ) && empty( $devices[ $i ]['max'] ) ) {
484 + continue; // the base is always last
485 + }
486 + if ( self::breakpoint_contains( $devices[ $i ], $devices[ $index ] ) ) {
487 + $ancestors[] = $devices[ $i ]['suffix'];
488 + }
489 + }
490 + $ancestors[] = '';
491 + return $ancestors;
492 + }
430 493
431 - // Tablet value (fallback to desktop if nothing found)
432 - $tablet_key = $attribute_object_key . 'Tablet';
433 - $tablet_value =
434 - $clean( $attribute[ $tablet_key ] ?? null ) ??
435 - $clean( $attribute_default_value[ $tablet_key ] ?? null ) ??
436 - $desktop_value;
494 + /**
495 + * Whether breakpoint $outer's range contains breakpoint $inner, judged at
496 + * $inner's own width (its max, else its min) — i.e. whether $outer's rule
497 + * still applies where $inner starts. The base device contains everything.
498 + * Shared by inheritance (get_responsive_ancestors) and CSS dedupe.
499 + *
500 + * @param array $outer Breakpoint with `min` / `max` bounds.
501 + * @param array $inner Breakpoint with `min` / `max` bounds.
502 + * @return bool
503 + */
504 + /**
505 + * Run a per-device CSS builder for a custom-breakpoint (or phantom probe)
506 + * suffix. Block builders read `$attributes[ 'x' . $device ]` directly, and
507 + * attribute defaults only declare the built-in suffixes, so a custom suffix
508 + * key is missing — which is exactly "unset" for the caller
509 + * (custom_device_map() subtracts the phantom baseline). Only those
510 + * missing-key warnings are silenced, and only while the builder runs.
511 + *
512 + * @param callable $builder `( $device ) => styles`.
513 + * @param string $device Device suffix.
514 + * @return array Styles.
515 + */
516 + public static function call_device_builder( $builder, $device ) {
517 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
518 + set_error_handler(
519 + function ( $errno, $errstr ) {
520 + return (bool) preg_match( '/^Undefined (array key|index|offset)/', $errstr );
521 + },
522 + E_WARNING | E_NOTICE
523 + );
524 + try {
525 + return (array) call_user_func( $builder, $device );
526 + } finally {
527 + restore_error_handler();
528 + }
529 + }
437 530
438 - // Mobile value (fallback to tablet if nothing found)
439 - $mobile_key = $attribute_object_key . 'Mobile';
440 - $mobile_value =
441 - $clean( $attribute[ $mobile_key ] ?? null ) ??
442 - $clean( $attribute_default_value[ $mobile_key ] ?? null ) ??
443 - $tablet_value;
531 + public static function breakpoint_contains( $outer, $inner ) {
532 + $o_min = isset( $outer['min'] ) ? (int) $outer['min'] : 0;
533 + $o_max = isset( $outer['max'] ) ? (int) $outer['max'] : 0;
534 + $i_min = isset( $inner['min'] ) ? (int) $inner['min'] : 0;
535 + $i_max = isset( $inner['max'] ) ? (int) $inner['max'] : 0;
536 + $width = $i_max ? $i_max : $i_min;
537 + return ( ! $o_min || $width >= $o_min ) && ( ! $o_max || $width <= $o_max );
538 + }
444 539
445 - // Return based on device
446 - switch ( $device ) {
447 - case 'Mobile':
448 - return $mobile_value;
449 - case 'Tablet':
450 - return $tablet_value;
451 - default:
452 - return $desktop_value;
540 + /**
541 + * A device's value for a key: its own, else the nearest containing wider
542 + * device's (see get_responsive_ancestors()), else Desktop's — the mirror of
543 + * the editor's getResponsiveValue(). Stored values win over declared
544 + * defaults per device. Returns false when nothing is set anywhere.
545 + */
546 + public static function get_responsive_value( $attribute, $attribute_object_key, $device, $attribute_default_value = [] ) {
547 + $target = 'Desktop' === $device ? '' : (string) $device;
548 + $suffixes = array_merge( [ $target ], self::get_responsive_ancestors( $target ) );
549 + foreach ( $suffixes as $suffix ) {
550 + $key = $attribute_object_key . $suffix;
551 + if ( isset( $attribute[ $key ] ) && self::has_responsive_value( $attribute[ $key ] ) ) {
552 + return $attribute[ $key ];
553 + }
554 + if ( isset( $attribute_default_value[ $key ] ) && self::has_responsive_value( $attribute_default_value[ $key ] ) ) {
555 + return $attribute_default_value[ $key ];
556 + }
453 557 }
558 + return false;
454 559 }
455 560
456 561 public static function is_gutenberg_editor() {
457 562 global $pagenow;