|
1 <?php |
|
2 /** |
|
3 * WP_Duotone class |
|
4 * |
|
5 * Parts of this source were derived and modified from colord, |
|
6 * released under the MIT license. |
|
7 * |
|
8 * https://github.com/omgovich/colord |
|
9 * |
|
10 * Copyright (c) 2020 Vlad Shilov omgovich@ya.ru |
|
11 * |
|
12 * Permission is hereby granted, free of charge, to any person obtaining |
|
13 * a copy of this software and associated documentation files (the |
|
14 * "Software"), to deal in the Software without restriction, including |
|
15 * without limitation the rights to use, copy, modify, merge, publish, |
|
16 * distribute, sublicense, and/or sell copies of the Software, and to |
|
17 * permit persons to whom the Software is furnished to do so, subject to |
|
18 * the following conditions: |
|
19 * |
|
20 * The above copyright notice and this permission notice shall be |
|
21 * included in all copies or substantial portions of the Software. |
|
22 * |
|
23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
24 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
25 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
26 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|
27 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|
28 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|
29 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
30 * |
|
31 * @package WordPress |
|
32 * @since 6.3.0 |
|
33 */ |
|
34 |
|
35 /** |
|
36 * Manages duotone block supports and global styles. |
|
37 * |
|
38 * @access private |
|
39 */ |
|
40 class WP_Duotone { |
|
41 /** |
|
42 * Block names from global, theme, and custom styles that use duotone presets and the slug of |
|
43 * the preset they are using. |
|
44 * |
|
45 * Example: |
|
46 * [ |
|
47 * 'core/featured-image' => 'blue-orange', |
|
48 * … |
|
49 * ] |
|
50 * |
|
51 * @internal |
|
52 * |
|
53 * @since 6.3.0 |
|
54 * |
|
55 * @var array |
|
56 */ |
|
57 private static $global_styles_block_names; |
|
58 |
|
59 /** |
|
60 * An array of duotone filter data from global, theme, and custom presets. |
|
61 * |
|
62 * Example: |
|
63 * [ |
|
64 * 'wp-duotone-blue-orange' => [ |
|
65 * 'slug' => 'blue-orange', |
|
66 * 'colors' => [ '#0000ff', '#ffcc00' ], |
|
67 * ], |
|
68 * 'wp-duotone-red-yellow' => [ |
|
69 * 'slug' => 'red-yellow', |
|
70 * 'colors' => [ '#cc0000', '#ffff33' ], |
|
71 * ], |
|
72 * … |
|
73 * ] |
|
74 * |
|
75 * @internal |
|
76 * |
|
77 * @since 6.3.0 |
|
78 * |
|
79 * @var array |
|
80 */ |
|
81 private static $global_styles_presets; |
|
82 |
|
83 /** |
|
84 * All of the duotone filter data from presets for CSS custom properties on |
|
85 * the page. |
|
86 * |
|
87 * Example: |
|
88 * [ |
|
89 * 'wp-duotone-blue-orange' => [ |
|
90 * 'slug' => 'blue-orange', |
|
91 * 'colors' => [ '#0000ff', '#ffcc00' ], |
|
92 * ], |
|
93 * … |
|
94 * ] |
|
95 * |
|
96 * @internal |
|
97 * |
|
98 * @since 6.3.0 |
|
99 * |
|
100 * @var array |
|
101 */ |
|
102 private static $used_global_styles_presets = array(); |
|
103 |
|
104 /** |
|
105 * All of the duotone filter data for SVGs on the page. Includes both |
|
106 * presets and custom filters. |
|
107 * |
|
108 * Example: |
|
109 * [ |
|
110 * 'wp-duotone-blue-orange' => [ |
|
111 * 'slug' => 'blue-orange', |
|
112 * 'colors' => [ '#0000ff', '#ffcc00' ], |
|
113 * ], |
|
114 * 'wp-duotone-000000-ffffff-2' => [ |
|
115 * 'slug' => '000000-ffffff-2', |
|
116 * 'colors' => [ '#000000', '#ffffff' ], |
|
117 * ], |
|
118 * … |
|
119 * ] |
|
120 * |
|
121 * @internal |
|
122 * |
|
123 * @since 6.3.0 |
|
124 * |
|
125 * @var array |
|
126 */ |
|
127 private static $used_svg_filter_data = array(); |
|
128 |
|
129 /** |
|
130 * All of the block CSS declarations for styles on the page. |
|
131 * |
|
132 * Example: |
|
133 * [ |
|
134 * [ |
|
135 * 'selector' => '.wp-duotone-000000-ffffff-2.wp-block-image img', |
|
136 * 'declarations' => [ |
|
137 * 'filter' => 'url(#wp-duotone-000000-ffffff-2)', |
|
138 * ], |
|
139 * ], |
|
140 * … |
|
141 * ] |
|
142 * |
|
143 * @internal |
|
144 * |
|
145 * @since 6.3.0 |
|
146 * |
|
147 * @var array |
|
148 */ |
|
149 private static $block_css_declarations = array(); |
|
150 |
|
151 /** |
|
152 * Clamps a value between an upper and lower bound. |
|
153 * |
|
154 * Direct port of colord's clamp function. |
|
155 * |
|
156 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/helpers.ts#L23 Sourced from colord. |
|
157 * |
|
158 * @internal |
|
159 * |
|
160 * @since 6.3.0 |
|
161 * |
|
162 * @param float $number The number to clamp. |
|
163 * @param float $min The minimum value. |
|
164 * @param float $max The maximum value. |
|
165 * @return float The clamped value. |
|
166 */ |
|
167 private static function colord_clamp( $number, $min = 0, $max = 1 ) { |
|
168 return $number > $max ? $max : ( $number > $min ? $number : $min ); |
|
169 } |
|
170 |
|
171 /** |
|
172 * Processes and clamps a degree (angle) value properly. |
|
173 * |
|
174 * Direct port of colord's clampHue function. |
|
175 * |
|
176 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/helpers.ts#L32 Sourced from colord. |
|
177 * |
|
178 * @internal |
|
179 * |
|
180 * @since 6.3.0 |
|
181 * |
|
182 * @param float $degrees The hue to clamp. |
|
183 * @return float The clamped hue. |
|
184 */ |
|
185 private static function colord_clamp_hue( $degrees ) { |
|
186 $degrees = is_finite( $degrees ) ? $degrees % 360 : 0; |
|
187 return $degrees > 0 ? $degrees : $degrees + 360; |
|
188 } |
|
189 |
|
190 /** |
|
191 * Converts a hue value to degrees from 0 to 360 inclusive. |
|
192 * |
|
193 * Direct port of colord's parseHue function. |
|
194 * |
|
195 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/helpers.ts#L40 Sourced from colord. |
|
196 * |
|
197 * @internal |
|
198 * |
|
199 * @since 6.3.0 |
|
200 * |
|
201 * @param float $value The hue value to parse. |
|
202 * @param string $unit The unit of the hue value. |
|
203 * @return float The parsed hue value. |
|
204 */ |
|
205 private static function colord_parse_hue( $value, $unit = 'deg' ) { |
|
206 $angle_units = array( |
|
207 'grad' => 360 / 400, |
|
208 'turn' => 360, |
|
209 'rad' => 360 / ( M_PI * 2 ), |
|
210 ); |
|
211 |
|
212 $factor = isset( $angle_units[ $unit ] ) ? $angle_units[ $unit ] : 1; |
|
213 |
|
214 return (float) $value * $factor; |
|
215 } |
|
216 |
|
217 /** |
|
218 * Parses any valid Hex3, Hex4, Hex6 or Hex8 string and converts it to an RGBA object. |
|
219 * |
|
220 * Direct port of colord's parseHex function. |
|
221 * |
|
222 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hex.ts#L8 Sourced from colord. |
|
223 * |
|
224 * @internal |
|
225 * |
|
226 * @since 6.3.0 |
|
227 * |
|
228 * @param string $hex The hex string to parse. |
|
229 * @return array|null An array of RGBA values or null if the hex string is invalid. |
|
230 */ |
|
231 private static function colord_parse_hex( $hex ) { |
|
232 $is_match = preg_match( |
|
233 '/^#([0-9a-f]{3,8})$/i', |
|
234 $hex, |
|
235 $hex_match |
|
236 ); |
|
237 |
|
238 if ( ! $is_match ) { |
|
239 return null; |
|
240 } |
|
241 |
|
242 $hex = $hex_match[1]; |
|
243 |
|
244 if ( 4 >= strlen( $hex ) ) { |
|
245 return array( |
|
246 'r' => (int) base_convert( $hex[0] . $hex[0], 16, 10 ), |
|
247 'g' => (int) base_convert( $hex[1] . $hex[1], 16, 10 ), |
|
248 'b' => (int) base_convert( $hex[2] . $hex[2], 16, 10 ), |
|
249 'a' => 4 === strlen( $hex ) ? round( base_convert( $hex[3] . $hex[3], 16, 10 ) / 255, 2 ) : 1, |
|
250 ); |
|
251 } |
|
252 |
|
253 if ( 6 === strlen( $hex ) || 8 === strlen( $hex ) ) { |
|
254 return array( |
|
255 'r' => (int) base_convert( substr( $hex, 0, 2 ), 16, 10 ), |
|
256 'g' => (int) base_convert( substr( $hex, 2, 2 ), 16, 10 ), |
|
257 'b' => (int) base_convert( substr( $hex, 4, 2 ), 16, 10 ), |
|
258 'a' => 8 === strlen( $hex ) ? round( (int) base_convert( substr( $hex, 6, 2 ), 16, 10 ) / 255, 2 ) : 1, |
|
259 ); |
|
260 } |
|
261 |
|
262 return null; |
|
263 } |
|
264 |
|
265 /** |
|
266 * Clamps an array of RGBA values. |
|
267 * |
|
268 * Direct port of colord's clampRgba function. |
|
269 * |
|
270 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/rgb.ts#L5 Sourced from colord. |
|
271 * |
|
272 * @internal |
|
273 * |
|
274 * @since 6.3.0 |
|
275 * |
|
276 * @param array $rgba The RGBA array to clamp. |
|
277 * @return array The clamped RGBA array. |
|
278 */ |
|
279 private static function colord_clamp_rgba( $rgba ) { |
|
280 $rgba['r'] = self::colord_clamp( $rgba['r'], 0, 255 ); |
|
281 $rgba['g'] = self::colord_clamp( $rgba['g'], 0, 255 ); |
|
282 $rgba['b'] = self::colord_clamp( $rgba['b'], 0, 255 ); |
|
283 $rgba['a'] = self::colord_clamp( $rgba['a'] ); |
|
284 |
|
285 return $rgba; |
|
286 } |
|
287 |
|
288 /** |
|
289 * Parses a valid RGB[A] CSS color function/string. |
|
290 * |
|
291 * Direct port of colord's parseRgbaString function. |
|
292 * |
|
293 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/rgbString.ts#L18 Sourced from colord. |
|
294 * |
|
295 * @internal |
|
296 * |
|
297 * @since 6.3.0 |
|
298 * |
|
299 * @param string $input The RGBA string to parse. |
|
300 * @return array|null An array of RGBA values or null if the RGB string is invalid. |
|
301 */ |
|
302 private static function colord_parse_rgba_string( $input ) { |
|
303 // Functional syntax. |
|
304 $is_match = preg_match( |
|
305 '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i', |
|
306 $input, |
|
307 $match |
|
308 ); |
|
309 |
|
310 if ( ! $is_match ) { |
|
311 // Whitespace syntax. |
|
312 $is_match = preg_match( |
|
313 '/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i', |
|
314 $input, |
|
315 $match |
|
316 ); |
|
317 } |
|
318 |
|
319 if ( ! $is_match ) { |
|
320 return null; |
|
321 } |
|
322 |
|
323 /* |
|
324 * For some reason, preg_match doesn't include empty matches at the end |
|
325 * of the array, so we add them manually to make things easier later. |
|
326 */ |
|
327 for ( $i = 1; $i <= 8; $i++ ) { |
|
328 if ( ! isset( $match[ $i ] ) ) { |
|
329 $match[ $i ] = ''; |
|
330 } |
|
331 } |
|
332 |
|
333 if ( $match[2] !== $match[4] || $match[4] !== $match[6] ) { |
|
334 return null; |
|
335 } |
|
336 |
|
337 return self::colord_clamp_rgba( |
|
338 array( |
|
339 'r' => (float) $match[1] / ( $match[2] ? 100 / 255 : 1 ), |
|
340 'g' => (float) $match[3] / ( $match[4] ? 100 / 255 : 1 ), |
|
341 'b' => (float) $match[5] / ( $match[6] ? 100 / 255 : 1 ), |
|
342 'a' => '' === $match[7] ? 1 : (float) $match[7] / ( $match[8] ? 100 : 1 ), |
|
343 ) |
|
344 ); |
|
345 } |
|
346 |
|
347 /** |
|
348 * Clamps an array of HSLA values. |
|
349 * |
|
350 * Direct port of colord's clampHsla function. |
|
351 * |
|
352 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsl.ts#L6 Sourced from colord. |
|
353 * |
|
354 * @internal |
|
355 * |
|
356 * @since 6.3.0 |
|
357 * |
|
358 * @param array $hsla The HSLA array to clamp. |
|
359 * @return array The clamped HSLA array. |
|
360 */ |
|
361 private static function colord_clamp_hsla( $hsla ) { |
|
362 $hsla['h'] = self::colord_clamp_hue( $hsla['h'] ); |
|
363 $hsla['s'] = self::colord_clamp( $hsla['s'], 0, 100 ); |
|
364 $hsla['l'] = self::colord_clamp( $hsla['l'], 0, 100 ); |
|
365 $hsla['a'] = self::colord_clamp( $hsla['a'] ); |
|
366 |
|
367 return $hsla; |
|
368 } |
|
369 |
|
370 /** |
|
371 * Converts an HSVA array to RGBA. |
|
372 * |
|
373 * Direct port of colord's hsvaToRgba function. |
|
374 * |
|
375 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsv.ts#L52 Sourced from colord. |
|
376 * |
|
377 * @internal |
|
378 * |
|
379 * @since 6.3.0 |
|
380 * |
|
381 * @param array $hsva The HSVA array to convert. |
|
382 * @return array The RGBA array. |
|
383 */ |
|
384 private static function colord_hsva_to_rgba( $hsva ) { |
|
385 $h = ( $hsva['h'] / 360 ) * 6; |
|
386 $s = $hsva['s'] / 100; |
|
387 $v = $hsva['v'] / 100; |
|
388 $a = $hsva['a']; |
|
389 |
|
390 $hh = floor( $h ); |
|
391 $b = $v * ( 1 - $s ); |
|
392 $c = $v * ( 1 - ( $h - $hh ) * $s ); |
|
393 $d = $v * ( 1 - ( 1 - $h + $hh ) * $s ); |
|
394 $module = $hh % 6; |
|
395 |
|
396 return array( |
|
397 'r' => array( $v, $c, $b, $b, $d, $v )[ $module ] * 255, |
|
398 'g' => array( $d, $v, $v, $c, $b, $b )[ $module ] * 255, |
|
399 'b' => array( $b, $b, $d, $v, $v, $c )[ $module ] * 255, |
|
400 'a' => $a, |
|
401 ); |
|
402 } |
|
403 |
|
404 /** |
|
405 * Converts an HSLA array to HSVA. |
|
406 * |
|
407 * Direct port of colord's hslaToHsva function. |
|
408 * |
|
409 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsl.ts#L33 Sourced from colord. |
|
410 * |
|
411 * @internal |
|
412 * |
|
413 * @since 6.3.0 |
|
414 * |
|
415 * @param array $hsla The HSLA array to convert. |
|
416 * @return array The HSVA array. |
|
417 */ |
|
418 private static function colord_hsla_to_hsva( $hsla ) { |
|
419 $h = $hsla['h']; |
|
420 $s = $hsla['s']; |
|
421 $l = $hsla['l']; |
|
422 $a = $hsla['a']; |
|
423 |
|
424 $s *= ( $l < 50 ? $l : 100 - $l ) / 100; |
|
425 |
|
426 return array( |
|
427 'h' => $h, |
|
428 's' => $s > 0 ? ( ( 2 * $s ) / ( $l + $s ) ) * 100 : 0, |
|
429 'v' => $l + $s, |
|
430 'a' => $a, |
|
431 ); |
|
432 } |
|
433 |
|
434 /** |
|
435 * Converts an HSLA array to RGBA. |
|
436 * |
|
437 * Direct port of colord's hslaToRgba function. |
|
438 * |
|
439 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hsl.ts#L55 Sourced from colord. |
|
440 * |
|
441 * @internal |
|
442 * |
|
443 * @since 6.3.0 |
|
444 * |
|
445 * @param array $hsla The HSLA array to convert. |
|
446 * @return array The RGBA array. |
|
447 */ |
|
448 private static function colord_hsla_to_rgba( $hsla ) { |
|
449 return self::colord_hsva_to_rgba( self::colord_hsla_to_hsva( $hsla ) ); |
|
450 } |
|
451 |
|
452 /** |
|
453 * Parses a valid HSL[A] CSS color function/string. |
|
454 * |
|
455 * Direct port of colord's parseHslaString function. |
|
456 * |
|
457 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/colorModels/hslString.ts#L17 Sourced from colord. |
|
458 * |
|
459 * @internal |
|
460 * |
|
461 * @since 6.3.0 |
|
462 * |
|
463 * @param string $input The HSLA string to parse. |
|
464 * @return array|null An array of RGBA values or null if the RGB string is invalid. |
|
465 */ |
|
466 private static function colord_parse_hsla_string( $input ) { |
|
467 // Functional syntax. |
|
468 $is_match = preg_match( |
|
469 '/^hsla?\(\s*([+-]?\d*\.?\d+)(deg|rad|grad|turn)?\s*,\s*([+-]?\d*\.?\d+)%\s*,\s*([+-]?\d*\.?\d+)%\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i', |
|
470 $input, |
|
471 $match |
|
472 ); |
|
473 |
|
474 if ( ! $is_match ) { |
|
475 // Whitespace syntax. |
|
476 $is_match = preg_match( |
|
477 '/^hsla?\(\s*([+-]?\d*\.?\d+)(deg|rad|grad|turn)?\s+([+-]?\d*\.?\d+)%\s+([+-]?\d*\.?\d+)%\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i', |
|
478 $input, |
|
479 $match |
|
480 ); |
|
481 } |
|
482 |
|
483 if ( ! $is_match ) { |
|
484 return null; |
|
485 } |
|
486 |
|
487 /* |
|
488 * For some reason, preg_match doesn't include empty matches at the end |
|
489 * of the array, so we add them manually to make things easier later. |
|
490 */ |
|
491 for ( $i = 1; $i <= 6; $i++ ) { |
|
492 if ( ! isset( $match[ $i ] ) ) { |
|
493 $match[ $i ] = ''; |
|
494 } |
|
495 } |
|
496 |
|
497 $hsla = self::colord_clamp_hsla( |
|
498 array( |
|
499 'h' => self::colord_parse_hue( $match[1], $match[2] ), |
|
500 's' => (float) $match[3], |
|
501 'l' => (float) $match[4], |
|
502 'a' => '' === $match[5] ? 1 : (float) $match[5] / ( $match[6] ? 100 : 1 ), |
|
503 ) |
|
504 ); |
|
505 |
|
506 return self::colord_hsla_to_rgba( $hsla ); |
|
507 } |
|
508 |
|
509 /** |
|
510 * Tries to convert an incoming string into RGBA values. |
|
511 * |
|
512 * Direct port of colord's parse function simplified for our use case. This |
|
513 * version only supports string parsing and only returns RGBA values. |
|
514 * |
|
515 * @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/parse.ts#L37 Sourced from colord. |
|
516 * |
|
517 * @internal |
|
518 * |
|
519 * @since 6.3.0 |
|
520 * |
|
521 * @param string $input The string to parse. |
|
522 * @return array|null An array of RGBA values or null if the string is invalid. |
|
523 */ |
|
524 private static function colord_parse( $input ) { |
|
525 $result = self::colord_parse_hex( $input ); |
|
526 |
|
527 if ( ! $result ) { |
|
528 $result = self::colord_parse_rgba_string( $input ); |
|
529 } |
|
530 |
|
531 if ( ! $result ) { |
|
532 $result = self::colord_parse_hsla_string( $input ); |
|
533 } |
|
534 |
|
535 return $result; |
|
536 } |
|
537 |
|
538 /** |
|
539 * Takes the inline CSS duotone variable from a block and return the slug. |
|
540 * |
|
541 * Handles styles slugs like: |
|
542 * var:preset|duotone|blue-orange |
|
543 * var(--wp--preset--duotone--blue-orange) |
|
544 * |
|
545 * @internal |
|
546 * |
|
547 * @since 6.3.0 |
|
548 * |
|
549 * @param string $duotone_attr The duotone attribute from a block. |
|
550 * @return string The slug of the duotone preset or an empty string if no slug is found. |
|
551 */ |
|
552 private static function get_slug_from_attribute( $duotone_attr ) { |
|
553 // Uses Branch Reset Groups `(?|…)` to return one capture group. |
|
554 preg_match( '/(?|var:preset\|duotone\|(\S+)|var\(--wp--preset--duotone--(\S+)\))/', $duotone_attr, $matches ); |
|
555 |
|
556 return ! empty( $matches[1] ) ? $matches[1] : ''; |
|
557 } |
|
558 |
|
559 /** |
|
560 * Checks if we have a valid duotone preset. |
|
561 * |
|
562 * Valid presets are defined in the $global_styles_presets array. |
|
563 * |
|
564 * @internal |
|
565 * |
|
566 * @since 6.3.0 |
|
567 * |
|
568 * @param string $duotone_attr The duotone attribute from a block. |
|
569 * @return bool True if the duotone preset present and valid. |
|
570 */ |
|
571 private static function is_preset( $duotone_attr ) { |
|
572 $slug = self::get_slug_from_attribute( $duotone_attr ); |
|
573 $filter_id = self::get_filter_id( $slug ); |
|
574 |
|
575 return array_key_exists( $filter_id, self::get_all_global_styles_presets() ); |
|
576 } |
|
577 |
|
578 /** |
|
579 * Gets the CSS variable name for a duotone preset. |
|
580 * |
|
581 * Example output: |
|
582 * --wp--preset--duotone--blue-orange |
|
583 * |
|
584 * @internal |
|
585 * |
|
586 * @since 6.3.0 |
|
587 * |
|
588 * @param string $slug The slug of the duotone preset. |
|
589 * @return string The CSS variable name. |
|
590 */ |
|
591 private static function get_css_custom_property_name( $slug ) { |
|
592 return "--wp--preset--duotone--$slug"; |
|
593 } |
|
594 |
|
595 /** |
|
596 * Get the ID of the duotone filter. |
|
597 * |
|
598 * Example output: |
|
599 * wp-duotone-blue-orange |
|
600 * |
|
601 * @internal |
|
602 * |
|
603 * @since 6.3.0 |
|
604 * |
|
605 * @param string $slug The slug of the duotone preset. |
|
606 * @return string The ID of the duotone filter. |
|
607 */ |
|
608 private static function get_filter_id( $slug ) { |
|
609 return "wp-duotone-$slug"; |
|
610 } |
|
611 |
|
612 /** |
|
613 * Get the CSS variable for a duotone preset. |
|
614 * |
|
615 * Example output: |
|
616 * var(--wp--preset--duotone--blue-orange) |
|
617 * |
|
618 * @internal |
|
619 * |
|
620 * @since 6.3.0 |
|
621 * |
|
622 * @param string $slug The slug of the duotone preset. |
|
623 * @return string The CSS variable. |
|
624 */ |
|
625 private static function get_css_var( $slug ) { |
|
626 $name = self::get_css_custom_property_name( $slug ); |
|
627 return "var($name)"; |
|
628 } |
|
629 |
|
630 /** |
|
631 * Get the URL for a duotone filter. |
|
632 * |
|
633 * Example output: |
|
634 * url(#wp-duotone-blue-orange) |
|
635 * |
|
636 * @internal |
|
637 * |
|
638 * @since 6.3.0 |
|
639 * |
|
640 * @param string $filter_id The ID of the filter. |
|
641 * @return string The URL for the duotone filter. |
|
642 */ |
|
643 private static function get_filter_url( $filter_id ) { |
|
644 return "url(#$filter_id)"; |
|
645 } |
|
646 |
|
647 /** |
|
648 * Gets the SVG for the duotone filter definition. |
|
649 * |
|
650 * Whitespace is removed when SCRIPT_DEBUG is not enabled. |
|
651 * |
|
652 * @internal |
|
653 * |
|
654 * @since 6.3.0 |
|
655 * |
|
656 * @param string $filter_id The ID of the filter. |
|
657 * @param array $colors An array of color strings. |
|
658 * @return string An SVG with a duotone filter definition. |
|
659 */ |
|
660 private static function get_filter_svg( $filter_id, $colors ) { |
|
661 $duotone_values = array( |
|
662 'r' => array(), |
|
663 'g' => array(), |
|
664 'b' => array(), |
|
665 'a' => array(), |
|
666 ); |
|
667 |
|
668 foreach ( $colors as $color_str ) { |
|
669 $color = self::colord_parse( $color_str ); |
|
670 |
|
671 if ( null === $color ) { |
|
672 $error_message = sprintf( |
|
673 /* translators: 1: Duotone colors, 2: theme.json, 3: settings.color.duotone */ |
|
674 __( '"%1$s" in %2$s %3$s is not a hex or rgb string.' ), |
|
675 $color_str, |
|
676 'theme.json', |
|
677 'settings.color.duotone' |
|
678 ); |
|
679 _doing_it_wrong( __METHOD__, $error_message, '6.3.0' ); |
|
680 } else { |
|
681 $duotone_values['r'][] = $color['r'] / 255; |
|
682 $duotone_values['g'][] = $color['g'] / 255; |
|
683 $duotone_values['b'][] = $color['b'] / 255; |
|
684 $duotone_values['a'][] = $color['a']; |
|
685 } |
|
686 } |
|
687 |
|
688 ob_start(); |
|
689 |
|
690 ?> |
|
691 |
|
692 <svg |
|
693 xmlns="http://www.w3.org/2000/svg" |
|
694 viewBox="0 0 0 0" |
|
695 width="0" |
|
696 height="0" |
|
697 focusable="false" |
|
698 role="none" |
|
699 style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;" |
|
700 > |
|
701 <defs> |
|
702 <filter id="<?php echo esc_attr( $filter_id ); ?>"> |
|
703 <feColorMatrix |
|
704 color-interpolation-filters="sRGB" |
|
705 type="matrix" |
|
706 values=" |
|
707 .299 .587 .114 0 0 |
|
708 .299 .587 .114 0 0 |
|
709 .299 .587 .114 0 0 |
|
710 .299 .587 .114 0 0 |
|
711 " |
|
712 /> |
|
713 <feComponentTransfer color-interpolation-filters="sRGB" > |
|
714 <feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" /> |
|
715 <feFuncG type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['g'] ) ); ?>" /> |
|
716 <feFuncB type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['b'] ) ); ?>" /> |
|
717 <feFuncA type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['a'] ) ); ?>" /> |
|
718 </feComponentTransfer> |
|
719 <feComposite in2="SourceGraphic" operator="in" /> |
|
720 </filter> |
|
721 </defs> |
|
722 </svg> |
|
723 |
|
724 <?php |
|
725 |
|
726 $svg = ob_get_clean(); |
|
727 |
|
728 if ( ! SCRIPT_DEBUG ) { |
|
729 // Clean up the whitespace. |
|
730 $svg = preg_replace( "/[\r\n\t ]+/", ' ', $svg ); |
|
731 $svg = str_replace( '> <', '><', $svg ); |
|
732 $svg = trim( $svg ); |
|
733 } |
|
734 |
|
735 return $svg; |
|
736 } |
|
737 |
|
738 /** |
|
739 * Returns the prefixed id for the duotone filter for use as a CSS id. |
|
740 * |
|
741 * Exported for the deprecated function wp_get_duotone_filter_id(). |
|
742 * |
|
743 * @internal |
|
744 * |
|
745 * @since 6.3.0 |
|
746 * @deprecated 6.3.0 |
|
747 * |
|
748 * @param array $preset Duotone preset value as seen in theme.json. |
|
749 * @return string Duotone filter CSS id. |
|
750 */ |
|
751 public static function get_filter_id_from_preset( $preset ) { |
|
752 _deprecated_function( __FUNCTION__, '6.3.0' ); |
|
753 |
|
754 $filter_id = ''; |
|
755 if ( isset( $preset['slug'] ) ) { |
|
756 $filter_id = self::get_filter_id( $preset['slug'] ); |
|
757 } |
|
758 return $filter_id; |
|
759 } |
|
760 |
|
761 /** |
|
762 * Gets the SVG for the duotone filter definition from a preset. |
|
763 * |
|
764 * Exported for the deprecated function wp_get_duotone_filter_property(). |
|
765 * |
|
766 * @internal |
|
767 * |
|
768 * @since 6.3.0 |
|
769 * @deprecated 6.3.0 |
|
770 * |
|
771 * @param array $preset The duotone preset. |
|
772 * @return string The SVG for the filter definition. |
|
773 */ |
|
774 public static function get_filter_svg_from_preset( $preset ) { |
|
775 _deprecated_function( __FUNCTION__, '6.3.0' ); |
|
776 |
|
777 $filter_id = self::get_filter_id_from_preset( $preset ); |
|
778 return self::get_filter_svg( $filter_id, $preset['colors'] ); |
|
779 } |
|
780 |
|
781 /** |
|
782 * Get the SVGs for the duotone filters. |
|
783 * |
|
784 * Example output: |
|
785 * <svg><defs><filter id="wp-duotone-blue-orange">…</filter></defs></svg><svg>…</svg> |
|
786 * |
|
787 * @internal |
|
788 * |
|
789 * @since 6.3.0 |
|
790 * |
|
791 * @param array $sources The duotone presets. |
|
792 * @return string The SVGs for the duotone filters. |
|
793 */ |
|
794 private static function get_svg_definitions( $sources ) { |
|
795 $svgs = ''; |
|
796 foreach ( $sources as $filter_id => $filter_data ) { |
|
797 $colors = $filter_data['colors']; |
|
798 $svgs .= self::get_filter_svg( $filter_id, $colors ); |
|
799 } |
|
800 return $svgs; |
|
801 } |
|
802 |
|
803 /** |
|
804 * Get the CSS for global styles. |
|
805 * |
|
806 * Example output: |
|
807 * body{--wp--preset--duotone--blue-orange:url('#wp-duotone-blue-orange');} |
|
808 * |
|
809 * @internal |
|
810 * |
|
811 * @since 6.3.0 |
|
812 * @since 6.6.0 Replaced body selector with `WP_Theme_JSON::ROOT_CSS_PROPERTIES_SELECTOR`. |
|
813 * |
|
814 * @param array $sources The duotone presets. |
|
815 * @return string The CSS for global styles. |
|
816 */ |
|
817 private static function get_global_styles_presets( $sources ) { |
|
818 $css = WP_Theme_JSON::ROOT_CSS_PROPERTIES_SELECTOR . '{'; |
|
819 foreach ( $sources as $filter_id => $filter_data ) { |
|
820 $slug = $filter_data['slug']; |
|
821 $colors = $filter_data['colors']; |
|
822 $css_property_name = self::get_css_custom_property_name( $slug ); |
|
823 $declaration_value = is_string( $colors ) ? $colors : self::get_filter_url( $filter_id ); |
|
824 $css .= "$css_property_name:$declaration_value;"; |
|
825 } |
|
826 $css .= '}'; |
|
827 return $css; |
|
828 } |
|
829 |
|
830 /** |
|
831 * Enqueue a block CSS declaration for the page. |
|
832 * |
|
833 * This does not include any SVGs. |
|
834 * |
|
835 * @internal |
|
836 * |
|
837 * @since 6.3.0 |
|
838 * |
|
839 * @param string $filter_id The filter ID. e.g. 'wp-duotone-000000-ffffff-2'. |
|
840 * @param string $duotone_selector The block's duotone selector. e.g. '.wp-block-image img'. |
|
841 * @param string $filter_value The filter CSS value. e.g. 'url(#wp-duotone-000000-ffffff-2)' or 'unset'. |
|
842 */ |
|
843 private static function enqueue_block_css( $filter_id, $duotone_selector, $filter_value ) { |
|
844 // Build the CSS selectors to which the filter will be applied. |
|
845 $selectors = explode( ',', $duotone_selector ); |
|
846 |
|
847 $selectors_scoped = array(); |
|
848 foreach ( $selectors as $selector_part ) { |
|
849 /* |
|
850 * Assuming the selector part is a subclass selector (not a tag name) |
|
851 * so we can prepend the filter id class. If we want to support elements |
|
852 * such as `img` or namespaces, we'll need to add a case for that here. |
|
853 */ |
|
854 $selectors_scoped[] = '.' . $filter_id . trim( $selector_part ); |
|
855 } |
|
856 |
|
857 $selector = implode( ', ', $selectors_scoped ); |
|
858 |
|
859 self::$block_css_declarations[] = array( |
|
860 'selector' => $selector, |
|
861 'declarations' => array( |
|
862 'filter' => $filter_value, |
|
863 ), |
|
864 ); |
|
865 } |
|
866 |
|
867 /** |
|
868 * Enqueue custom filter assets for the page. |
|
869 * |
|
870 * Includes an SVG filter and block CSS declaration. |
|
871 * |
|
872 * @internal |
|
873 * |
|
874 * @since 6.3.0 |
|
875 * |
|
876 * @param string $filter_id The filter ID. e.g. 'wp-duotone-000000-ffffff-2'. |
|
877 * @param string $duotone_selector The block's duotone selector. e.g. '.wp-block-image img'. |
|
878 * @param string $filter_value The filter CSS value. e.g. 'url(#wp-duotone-000000-ffffff-2)' or 'unset'. |
|
879 * @param array $filter_data Duotone filter data with 'slug' and 'colors' keys. |
|
880 */ |
|
881 private static function enqueue_custom_filter( $filter_id, $duotone_selector, $filter_value, $filter_data ) { |
|
882 self::$used_svg_filter_data[ $filter_id ] = $filter_data; |
|
883 self::enqueue_block_css( $filter_id, $duotone_selector, $filter_value ); |
|
884 } |
|
885 |
|
886 /** |
|
887 * Enqueue preset assets for the page. |
|
888 * |
|
889 * Includes a CSS custom property, SVG filter, and block CSS declaration. |
|
890 * |
|
891 * @internal |
|
892 * |
|
893 * @since 6.3.0 |
|
894 * |
|
895 * @param string $filter_id The filter ID. e.g. 'wp-duotone-blue-orange'. |
|
896 * @param string $duotone_selector The block's duotone selector. e.g. '.wp-block-image img'. |
|
897 * @param string $filter_value The filter CSS value. e.g. 'url(#wp-duotone-blue-orange)' or 'unset'. |
|
898 */ |
|
899 private static function enqueue_global_styles_preset( $filter_id, $duotone_selector, $filter_value ) { |
|
900 $global_styles_presets = self::get_all_global_styles_presets(); |
|
901 if ( ! array_key_exists( $filter_id, $global_styles_presets ) ) { |
|
902 $error_message = sprintf( |
|
903 /* translators: 1: Duotone filter ID, 2: theme.json */ |
|
904 __( 'The duotone id "%1$s" is not registered in %2$s settings' ), |
|
905 $filter_id, |
|
906 'theme.json' |
|
907 ); |
|
908 _doing_it_wrong( __METHOD__, $error_message, '6.3.0' ); |
|
909 return; |
|
910 } |
|
911 self::$used_global_styles_presets[ $filter_id ] = $global_styles_presets[ $filter_id ]; |
|
912 self::enqueue_custom_filter( $filter_id, $duotone_selector, $filter_value, $global_styles_presets[ $filter_id ] ); |
|
913 } |
|
914 |
|
915 /** |
|
916 * Registers the style and colors block attributes for block types that support it. |
|
917 * |
|
918 * Block support is added with `supports.filter.duotone` in block.json. |
|
919 * |
|
920 * @since 6.3.0 |
|
921 * |
|
922 * @param WP_Block_Type $block_type Block Type. |
|
923 */ |
|
924 public static function register_duotone_support( $block_type ) { |
|
925 /* |
|
926 * Previous `color.__experimentalDuotone` support flag is migrated |
|
927 * to `filter.duotone` via `block_type_metadata_settings` filter. |
|
928 */ |
|
929 if ( block_has_support( $block_type, array( 'filter', 'duotone' ), null ) ) { |
|
930 if ( ! $block_type->attributes ) { |
|
931 $block_type->attributes = array(); |
|
932 } |
|
933 |
|
934 if ( ! array_key_exists( 'style', $block_type->attributes ) ) { |
|
935 $block_type->attributes['style'] = array( |
|
936 'type' => 'object', |
|
937 ); |
|
938 } |
|
939 } |
|
940 } |
|
941 |
|
942 /** |
|
943 * Get the CSS selector for a block type. |
|
944 * |
|
945 * This handles selectors defined in `color.__experimentalDuotone` support |
|
946 * if `filter.duotone` support is not defined. |
|
947 * |
|
948 * @internal |
|
949 * @since 6.3.0 |
|
950 * |
|
951 * @param WP_Block_Type $block_type Block type to check for support. |
|
952 * @return string|null The CSS selector or null if there is no support. |
|
953 */ |
|
954 private static function get_selector( $block_type ) { |
|
955 if ( ! ( $block_type instanceof WP_Block_Type ) ) { |
|
956 return null; |
|
957 } |
|
958 |
|
959 /* |
|
960 * Backward compatibility with `supports.color.__experimentalDuotone` |
|
961 * is provided via the `block_type_metadata_settings` filter. If |
|
962 * `supports.filter.duotone` has not been set and the experimental |
|
963 * property has been, the experimental property value is copied into |
|
964 * `supports.filter.duotone`. |
|
965 */ |
|
966 $duotone_support = block_has_support( $block_type, array( 'filter', 'duotone' ) ); |
|
967 if ( ! $duotone_support ) { |
|
968 return null; |
|
969 } |
|
970 |
|
971 /* |
|
972 * If the experimental duotone support was set, that value is to be |
|
973 * treated as a selector and requires scoping. |
|
974 */ |
|
975 $experimental_duotone = isset( $block_type->supports['color']['__experimentalDuotone'] ) |
|
976 ? $block_type->supports['color']['__experimentalDuotone'] |
|
977 : false; |
|
978 if ( $experimental_duotone ) { |
|
979 $root_selector = wp_get_block_css_selector( $block_type ); |
|
980 return is_string( $experimental_duotone ) |
|
981 ? WP_Theme_JSON::scope_selector( $root_selector, $experimental_duotone ) |
|
982 : $root_selector; |
|
983 } |
|
984 |
|
985 // Regular filter.duotone support uses filter.duotone selectors with fallbacks. |
|
986 return wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ), true ); |
|
987 } |
|
988 |
|
989 /** |
|
990 * Scrape all possible duotone presets from global and theme styles and |
|
991 * store them in self::$global_styles_presets. |
|
992 * |
|
993 * Used in conjunction with self::render_duotone_support for blocks that |
|
994 * use duotone preset filters. |
|
995 * |
|
996 * @since 6.3.0 |
|
997 * |
|
998 * @return array An array of global styles presets, keyed on the filter ID. |
|
999 */ |
|
1000 private static function get_all_global_styles_presets() { |
|
1001 if ( isset( self::$global_styles_presets ) ) { |
|
1002 return self::$global_styles_presets; |
|
1003 } |
|
1004 // Get the per block settings from the theme.json. |
|
1005 $tree = wp_get_global_settings(); |
|
1006 $presets_by_origin = isset( $tree['color']['duotone'] ) ? $tree['color']['duotone'] : array(); |
|
1007 |
|
1008 self::$global_styles_presets = array(); |
|
1009 foreach ( $presets_by_origin as $presets ) { |
|
1010 foreach ( $presets as $preset ) { |
|
1011 $filter_id = self::get_filter_id( _wp_to_kebab_case( $preset['slug'] ) ); |
|
1012 |
|
1013 self::$global_styles_presets[ $filter_id ] = $preset; |
|
1014 } |
|
1015 } |
|
1016 |
|
1017 return self::$global_styles_presets; |
|
1018 } |
|
1019 |
|
1020 /** |
|
1021 * Scrape all block names from global styles and store in self::$global_styles_block_names. |
|
1022 * |
|
1023 * Used in conjunction with self::render_duotone_support to output the |
|
1024 * duotone filters defined in the theme.json global styles. |
|
1025 * |
|
1026 * @since 6.3.0 |
|
1027 * |
|
1028 * @return string[] An array of global style block slugs, keyed on the block name. |
|
1029 */ |
|
1030 private static function get_all_global_style_block_names() { |
|
1031 if ( isset( self::$global_styles_block_names ) ) { |
|
1032 return self::$global_styles_block_names; |
|
1033 } |
|
1034 // Get the per block settings from the theme.json. |
|
1035 $tree = WP_Theme_JSON_Resolver::get_merged_data(); |
|
1036 $block_nodes = $tree->get_styles_block_nodes(); |
|
1037 $theme_json = $tree->get_raw_data(); |
|
1038 |
|
1039 self::$global_styles_block_names = array(); |
|
1040 |
|
1041 foreach ( $block_nodes as $block_node ) { |
|
1042 // This block definition doesn't include any duotone settings. Skip it. |
|
1043 if ( empty( $block_node['duotone'] ) ) { |
|
1044 continue; |
|
1045 } |
|
1046 |
|
1047 // Value looks like this: 'var(--wp--preset--duotone--blue-orange)' or 'var:preset|duotone|blue-orange'. |
|
1048 $duotone_attr_path = array_merge( $block_node['path'], array( 'filter', 'duotone' ) ); |
|
1049 $duotone_attr = _wp_array_get( $theme_json, $duotone_attr_path, array() ); |
|
1050 |
|
1051 if ( empty( $duotone_attr ) ) { |
|
1052 continue; |
|
1053 } |
|
1054 // If it has a duotone filter preset, save the block name and the preset slug. |
|
1055 $slug = self::get_slug_from_attribute( $duotone_attr ); |
|
1056 |
|
1057 if ( $slug && $slug !== $duotone_attr ) { |
|
1058 self::$global_styles_block_names[ $block_node['name'] ] = $slug; |
|
1059 } |
|
1060 } |
|
1061 return self::$global_styles_block_names; |
|
1062 } |
|
1063 |
|
1064 /** |
|
1065 * Render out the duotone CSS styles and SVG. |
|
1066 * |
|
1067 * The hooks self::set_global_style_block_names and self::set_global_styles_presets |
|
1068 * must be called before this function. |
|
1069 * |
|
1070 * @since 6.3.0 |
|
1071 * |
|
1072 * @param string $block_content Rendered block content. |
|
1073 * @param array $block Block object. |
|
1074 * @param WP_Block $wp_block The block instance. |
|
1075 * @return string Filtered block content. |
|
1076 */ |
|
1077 public static function render_duotone_support( $block_content, $block, $wp_block ) { |
|
1078 if ( ! $block['blockName'] ) { |
|
1079 return $block_content; |
|
1080 } |
|
1081 $duotone_selector = self::get_selector( $wp_block->block_type ); |
|
1082 |
|
1083 if ( ! $duotone_selector ) { |
|
1084 return $block_content; |
|
1085 } |
|
1086 |
|
1087 $global_styles_block_names = self::get_all_global_style_block_names(); |
|
1088 |
|
1089 // The block should have a duotone attribute or have duotone defined in its theme.json to be processed. |
|
1090 $has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] ); |
|
1091 $has_global_styles_duotone = array_key_exists( $block['blockName'], $global_styles_block_names ); |
|
1092 |
|
1093 if ( ! $has_duotone_attribute && ! $has_global_styles_duotone ) { |
|
1094 return $block_content; |
|
1095 } |
|
1096 |
|
1097 // Generate the pieces needed for rendering a duotone to the page. |
|
1098 if ( $has_duotone_attribute ) { |
|
1099 |
|
1100 /* |
|
1101 * Possible values for duotone attribute: |
|
1102 * 1. Array of colors - e.g. array('#000000', '#ffffff'). |
|
1103 * 2. Variable for an existing Duotone preset - e.g. 'var:preset|duotone|blue-orange' or 'var(--wp--preset--duotone--blue-orange)'' |
|
1104 * 3. A CSS string - e.g. 'unset' to remove globally applied duotone. |
|
1105 */ |
|
1106 |
|
1107 $duotone_attr = $block['attrs']['style']['color']['duotone']; |
|
1108 $is_preset = is_string( $duotone_attr ) && self::is_preset( $duotone_attr ); |
|
1109 $is_css = is_string( $duotone_attr ) && ! $is_preset; |
|
1110 $is_custom = is_array( $duotone_attr ); |
|
1111 |
|
1112 if ( $is_preset ) { |
|
1113 |
|
1114 $slug = self::get_slug_from_attribute( $duotone_attr ); // e.g. 'blue-orange'. |
|
1115 $filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-blue-orange'. |
|
1116 $filter_value = self::get_css_var( $slug ); // e.g. 'var(--wp--preset--duotone--blue-orange)'. |
|
1117 |
|
1118 // CSS custom property, SVG filter, and block CSS. |
|
1119 self::enqueue_global_styles_preset( $filter_id, $duotone_selector, $filter_value ); |
|
1120 |
|
1121 } elseif ( $is_css ) { |
|
1122 $slug = wp_unique_id( sanitize_key( $duotone_attr . '-' ) ); // e.g. 'unset-1'. |
|
1123 $filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-unset-1'. |
|
1124 $filter_value = $duotone_attr; // e.g. 'unset'. |
|
1125 |
|
1126 // Just block CSS. |
|
1127 self::enqueue_block_css( $filter_id, $duotone_selector, $filter_value ); |
|
1128 } elseif ( $is_custom ) { |
|
1129 $slug = wp_unique_id( sanitize_key( implode( '-', $duotone_attr ) . '-' ) ); // e.g. '000000-ffffff-2'. |
|
1130 $filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-000000-ffffff-2'. |
|
1131 $filter_value = self::get_filter_url( $filter_id ); // e.g. 'url(#wp-duotone-filter-000000-ffffff-2)'. |
|
1132 $filter_data = array( |
|
1133 'slug' => $slug, |
|
1134 'colors' => $duotone_attr, |
|
1135 ); |
|
1136 |
|
1137 // SVG filter and block CSS. |
|
1138 self::enqueue_custom_filter( $filter_id, $duotone_selector, $filter_value, $filter_data ); |
|
1139 } |
|
1140 } elseif ( $has_global_styles_duotone ) { |
|
1141 $slug = $global_styles_block_names[ $block['blockName'] ]; // e.g. 'blue-orange'. |
|
1142 $filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-blue-orange'. |
|
1143 $filter_value = self::get_css_var( $slug ); // e.g. 'var(--wp--preset--duotone--blue-orange)'. |
|
1144 |
|
1145 // CSS custom property, SVG filter, and block CSS. |
|
1146 self::enqueue_global_styles_preset( $filter_id, $duotone_selector, $filter_value ); |
|
1147 } |
|
1148 |
|
1149 // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper. |
|
1150 $tags = new WP_HTML_Tag_Processor( $block_content ); |
|
1151 if ( $tags->next_tag() ) { |
|
1152 $tags->add_class( $filter_id ); |
|
1153 } |
|
1154 return $tags->get_updated_html(); |
|
1155 } |
|
1156 |
|
1157 /** |
|
1158 * Fixes the issue with our generated class name not being added to the block's outer container |
|
1159 * in classic themes due to gutenberg_restore_image_outer_container from layout block supports. |
|
1160 * |
|
1161 * @since 6.6.0 |
|
1162 * |
|
1163 * @param string $block_content Rendered block content. |
|
1164 * @return string Filtered block content. |
|
1165 */ |
|
1166 public static function restore_image_outer_container( $block_content ) { |
|
1167 if ( wp_theme_has_theme_json() ) { |
|
1168 return $block_content; |
|
1169 } |
|
1170 |
|
1171 $tags = new WP_HTML_Tag_Processor( $block_content ); |
|
1172 $wrapper_query = array( |
|
1173 'tag_name' => 'div', |
|
1174 'class_name' => 'wp-block-image', |
|
1175 ); |
|
1176 if ( ! $tags->next_tag( $wrapper_query ) ) { |
|
1177 return $block_content; |
|
1178 } |
|
1179 |
|
1180 $tags->set_bookmark( 'wrapper-div' ); |
|
1181 $tags->next_tag(); |
|
1182 |
|
1183 $inner_classnames = explode( ' ', $tags->get_attribute( 'class' ) ); |
|
1184 foreach ( $inner_classnames as $classname ) { |
|
1185 if ( 0 === strpos( $classname, 'wp-duotone' ) ) { |
|
1186 $tags->remove_class( $classname ); |
|
1187 $tags->seek( 'wrapper-div' ); |
|
1188 $tags->add_class( $classname ); |
|
1189 break; |
|
1190 } |
|
1191 } |
|
1192 |
|
1193 return $tags->get_updated_html(); |
|
1194 } |
|
1195 |
|
1196 /** |
|
1197 * Appends the used block duotone filter declarations to the inline block supports CSS. |
|
1198 * |
|
1199 * Uses the declarations saved in earlier calls to self::enqueue_block_css. |
|
1200 * |
|
1201 * @since 6.3.0 |
|
1202 */ |
|
1203 public static function output_block_styles() { |
|
1204 if ( ! empty( self::$block_css_declarations ) ) { |
|
1205 wp_style_engine_get_stylesheet_from_css_rules( |
|
1206 self::$block_css_declarations, |
|
1207 array( |
|
1208 'context' => 'block-supports', |
|
1209 ) |
|
1210 ); |
|
1211 } |
|
1212 } |
|
1213 |
|
1214 /** |
|
1215 * Appends the used global style duotone filter presets (CSS custom |
|
1216 * properties) to the inline global styles CSS. |
|
1217 * |
|
1218 * Uses the declarations saved in earlier calls to self::enqueue_global_styles_preset. |
|
1219 * |
|
1220 * @since 6.3.0 |
|
1221 */ |
|
1222 public static function output_global_styles() { |
|
1223 if ( ! empty( self::$used_global_styles_presets ) ) { |
|
1224 wp_add_inline_style( 'global-styles', self::get_global_styles_presets( self::$used_global_styles_presets ) ); |
|
1225 } |
|
1226 } |
|
1227 |
|
1228 /** |
|
1229 * Outputs all necessary SVG for duotone filters, CSS for classic themes. |
|
1230 * |
|
1231 * Uses the declarations saved in earlier calls to self::enqueue_global_styles_preset |
|
1232 * and self::enqueue_custom_filter. |
|
1233 * |
|
1234 * @since 6.3.0 |
|
1235 */ |
|
1236 public static function output_footer_assets() { |
|
1237 if ( ! empty( self::$used_svg_filter_data ) ) { |
|
1238 echo self::get_svg_definitions( self::$used_svg_filter_data ); |
|
1239 } |
|
1240 |
|
1241 // In block themes, the CSS is added in the head via wp_add_inline_style in the wp_enqueue_scripts action. |
|
1242 if ( ! wp_is_block_theme() ) { |
|
1243 $style_tag_id = 'core-block-supports-duotone'; |
|
1244 wp_register_style( $style_tag_id, false ); |
|
1245 if ( ! empty( self::$used_global_styles_presets ) ) { |
|
1246 wp_add_inline_style( $style_tag_id, self::get_global_styles_presets( self::$used_global_styles_presets ) ); |
|
1247 } |
|
1248 if ( ! empty( self::$block_css_declarations ) ) { |
|
1249 wp_add_inline_style( $style_tag_id, wp_style_engine_get_stylesheet_from_css_rules( self::$block_css_declarations ) ); |
|
1250 } |
|
1251 wp_enqueue_style( $style_tag_id ); |
|
1252 } |
|
1253 } |
|
1254 |
|
1255 /** |
|
1256 * Adds the duotone SVGs and CSS custom properties to the editor settings. |
|
1257 * |
|
1258 * This allows the properties to be pulled in by the EditorStyles component |
|
1259 * in JS and rendered in the post editor. |
|
1260 * |
|
1261 * @since 6.3.0 |
|
1262 * |
|
1263 * @param array $settings The block editor settings from the `block_editor_settings_all` filter. |
|
1264 * @return array The editor settings with duotone SVGs and CSS custom properties. |
|
1265 */ |
|
1266 public static function add_editor_settings( $settings ) { |
|
1267 $global_styles_presets = self::get_all_global_styles_presets(); |
|
1268 if ( ! empty( $global_styles_presets ) ) { |
|
1269 if ( ! isset( $settings['styles'] ) ) { |
|
1270 $settings['styles'] = array(); |
|
1271 } |
|
1272 |
|
1273 $settings['styles'][] = array( |
|
1274 // For the editor we can add all of the presets by default. |
|
1275 'assets' => self::get_svg_definitions( $global_styles_presets ), |
|
1276 // The 'svgs' type is new in 6.3 and requires the corresponding JS changes in the EditorStyles component to work. |
|
1277 '__unstableType' => 'svgs', |
|
1278 // These styles not generated by global styles, so this must be false or they will be stripped out in wp_get_block_editor_settings. |
|
1279 'isGlobalStyles' => false, |
|
1280 ); |
|
1281 |
|
1282 $settings['styles'][] = array( |
|
1283 // For the editor we can add all of the presets by default. |
|
1284 'css' => self::get_global_styles_presets( $global_styles_presets ), |
|
1285 // This must be set and must be something other than 'theme' or they will be stripped out in the post editor <Editor> component. |
|
1286 '__unstableType' => 'presets', |
|
1287 // These styles are no longer generated by global styles, so this must be false or they will be stripped out in wp_get_block_editor_settings. |
|
1288 'isGlobalStyles' => false, |
|
1289 ); |
|
1290 } |
|
1291 |
|
1292 return $settings; |
|
1293 } |
|
1294 |
|
1295 /** |
|
1296 * Migrates the experimental duotone support flag to the stabilized location. |
|
1297 * |
|
1298 * This moves `supports.color.__experimentalDuotone` to `supports.filter.duotone`. |
|
1299 * |
|
1300 * @since 6.3.0 |
|
1301 * |
|
1302 * @param array $settings Current block type settings. |
|
1303 * @param array $metadata Block metadata as read in via block.json. |
|
1304 * @return array Filtered block type settings. |
|
1305 */ |
|
1306 public static function migrate_experimental_duotone_support_flag( $settings, $metadata ) { |
|
1307 $duotone_support = isset( $metadata['supports']['color']['__experimentalDuotone'] ) |
|
1308 ? $metadata['supports']['color']['__experimentalDuotone'] |
|
1309 : null; |
|
1310 |
|
1311 if ( ! isset( $settings['supports']['filter']['duotone'] ) && null !== $duotone_support ) { |
|
1312 _wp_array_set( $settings, array( 'supports', 'filter', 'duotone' ), (bool) $duotone_support ); |
|
1313 } |
|
1314 |
|
1315 return $settings; |
|
1316 } |
|
1317 |
|
1318 /** |
|
1319 * Gets the CSS filter property value from a preset. |
|
1320 * |
|
1321 * Exported for the deprecated function wp_get_duotone_filter_id(). |
|
1322 * |
|
1323 * @internal |
|
1324 * |
|
1325 * @since 6.3.0 |
|
1326 * @deprecated 6.3.0 |
|
1327 * |
|
1328 * @param array $preset The duotone preset. |
|
1329 * @return string The CSS filter property value. |
|
1330 */ |
|
1331 public static function get_filter_css_property_value_from_preset( $preset ) { |
|
1332 _deprecated_function( __FUNCTION__, '6.3.0' ); |
|
1333 |
|
1334 if ( isset( $preset['colors'] ) && is_string( $preset['colors'] ) ) { |
|
1335 return $preset['colors']; |
|
1336 } |
|
1337 |
|
1338 $filter_id = self::get_filter_id_from_preset( $preset ); |
|
1339 |
|
1340 return 'url(#' . $filter_id . ')'; |
|
1341 } |
|
1342 } |