PluginProbe
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder / 2.13.0
aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder v2.13.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 1.1.2 All 80 releases
ablocks / includes / classes / design-audit.php

design-audit.php in aBlocks – Gutenberg Blocks, User Dashboard Builder, Popup Builder, Form Builder & Animation Builder 2.13.0, at includes/classes/design-audit.php

184 lines 5.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace ABlocks\Classes;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 /**
9 * Finds blocks that carry a hand-picked colour or typography value instead of a
10 * reference to the site's global presets.
11 *
12 * The design system lock (Settings → Editor Options) stops new ones from being
13 * created, but deliberately leaves saved content alone so switching it on cannot
14 * restyle a live site. This is the other half: a list of what is already there,
15 * so a team can clean it up on purpose.
16 *
17 * @package ABlocks
18 */
19 class DesignAudit {
20
21 /**
22 * A typography attribute always carries these keys, whatever the block calls it.
23 */
24 const TYPOGRAPHY_MARKERS = [ 'fontFamily', 'fontSizeUnit' ];
25
26 /**
27 * Keys inside a typography object that only ever hold a unit, never a choice.
28 */
29 const UNIT_SUFFIX = 'Unit';
30
31 /**
32 * Walk parsed blocks and collect the attribute names holding custom values.
33 *
34 * @param array $blocks Parsed blocks (from parse_blocks()).
35 * @param array $found Accumulator: [ 'typography' => [...], 'colors' => [...] ].
36 * @return array
37 */
38 public static function scan_blocks( $blocks, $found = null ) {
39 if ( null === $found ) {
40 $found = [
41 // A hand-picked typeface — what the default lock prevents.
42 'families' => [],
43 // Other custom typography (size, spacing, weight). Allowed unless
44 // the strict lock is on, so reported separately.
45 'typography' => [],
46 'colors' => [],
47 ];
48 }
49
50 if ( ! is_array( $blocks ) ) {
51 return $found;
52 }
53
54 foreach ( $blocks as $block ) {
55 if ( empty( $block['blockName'] ) ) {
56 continue;
57 }
58
59 if ( 0 === strpos( $block['blockName'], 'ablocks/' ) && ! empty( $block['attrs'] ) ) {
60 $found = self::scan_attributes( $block['attrs'], $found );
61 }
62
63 if ( ! empty( $block['innerBlocks'] ) ) {
64 $found = self::scan_blocks( $block['innerBlocks'], $found );
65 }
66 }
67
68 return $found;
69 }
70
71 /**
72 * Inspect one block's attributes.
73 *
74 * Attributes are checked at the top level rather than recursively, because the
75 * "is this compliant?" test needs the sibling `<name>Global` attribute — a
76 * typography object is only a violation when nothing global is referenced.
77 *
78 * @param array $attrs Block attributes.
79 * @param array $found Accumulator.
80 * @return array
81 */
82 protected static function scan_attributes( $attrs, $found ) {
83 foreach ( $attrs as $name => $value ) {
84 // The global reference itself is never a violation.
85 if ( self::ends_with( $name, 'Global' ) ) {
86 continue;
87 }
88
89 $has_global = ! empty( $attrs[ $name . 'Global' ] );
90
91 if ( self::is_typography( $value ) ) {
92 if ( $has_global ) {
93 continue;
94 }
95 if ( ! empty( $value['fontFamily'] ) && 'Default' !== $value['fontFamily'] ) {
96 $found['families'][] = $name;
97 } elseif ( self::has_custom_typography( $value ) ) {
98 $found['typography'][] = $name;
99 }
100 continue;
101 }
102
103 if ( ! $has_global && self::is_custom_color( $value ) ) {
104 $found['colors'][] = $name;
105 }
106 }
107
108 return $found;
109 }
110
111 /**
112 * Whether an attribute value is a typography object.
113 *
114 * @param mixed $value Attribute value.
115 * @return bool
116 */
117 public static function is_typography( $value ) {
118 if ( ! is_array( $value ) ) {
119 return false;
120 }
121 foreach ( self::TYPOGRAPHY_MARKERS as $marker ) {
122 if ( ! array_key_exists( $marker, $value ) ) {
123 return false;
124 }
125 }
126 return true;
127 }
128
129 /**
130 * Whether a typography object holds anything an author actually chose.
131 *
132 * Unit keys default to 'px' whether or not anyone touched the control, so they
133 * would report every block on the site as a violation.
134 *
135 * @param array $value Typography attribute value.
136 * @return bool
137 */
138 public static function has_custom_typography( $value ) {
139 foreach ( $value as $key => $entry ) {
140 if ( self::ends_with( $key, self::UNIT_SUFFIX )
141 || self::ends_with( $key, self::UNIT_SUFFIX . 'Tablet' )
142 || self::ends_with( $key, self::UNIT_SUFFIX . 'Mobile' ) ) {
143 continue;
144 }
145 if ( '' !== $entry && null !== $entry && 'Default' !== $entry ) {
146 return true;
147 }
148 }
149 return false;
150 }
151
152 /**
153 * Whether a value is a literal colour rather than a preset reference.
154 *
155 * Global colours are stored as `var:global|color|<id>` and theme presets as
156 * `var:preset|color|<slug>`; both stay inside a design system.
157 *
158 * @param mixed $value Attribute value.
159 * @return bool
160 */
161 public static function is_custom_color( $value ) {
162 if ( ! is_string( $value ) ) {
163 return false;
164 }
165 $value = trim( $value );
166 if ( '' === $value || 0 === strpos( $value, 'var:' ) || 0 === strpos( $value, 'var(' ) ) {
167 return false;
168 }
169 return (bool) preg_match( '/^(#|rgba?\(|hsla?\(|(linear|radial|conic)-gradient\()/i', $value );
170 }
171
172 /**
173 * str_ends_with(), which is PHP 8 only and this plugin supports 7.4.
174 *
175 * @param string $haystack Subject.
176 * @param string $needle Suffix.
177 * @return bool
178 */
179 protected static function ends_with( $haystack, $needle ) {
180 $length = strlen( $needle );
181 return 0 !== $length && substr( $haystack, -$length ) === $needle;
182 }
183 }
184