197 $permissive_match3 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?'; |
214 $permissive_match3 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?'; |
198 $permissive_match4 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?'; |
215 $permissive_match4 = '[\\s|\\(]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')[,|\\s]+(' . $css_unit . ')\\s*\\)?'; |
199 |
216 |
200 $rgb_regexp = '/^rgb' . $permissive_match3 . '$/'; |
217 $rgb_regexp = '/^rgb' . $permissive_match3 . '$/'; |
201 if ( preg_match( $rgb_regexp, $color_str, $match ) ) { |
218 if ( preg_match( $rgb_regexp, $color_str, $match ) ) { |
202 return wp_tinycolor_rgb_to_rgb( |
219 $rgb = wp_tinycolor_rgb_to_rgb( |
203 array( |
220 array( |
204 'r' => $match[1], |
221 'r' => $match[1], |
205 'g' => $match[2], |
222 'g' => $match[2], |
206 'b' => $match[3], |
223 'b' => $match[3], |
207 ) |
224 ) |
208 ); |
225 ); |
|
226 |
|
227 $rgb['a'] = 1; |
|
228 |
|
229 return $rgb; |
209 } |
230 } |
210 |
231 |
211 $rgba_regexp = '/^rgba' . $permissive_match4 . '$/'; |
232 $rgba_regexp = '/^rgba' . $permissive_match4 . '$/'; |
212 if ( preg_match( $rgba_regexp, $color_str, $match ) ) { |
233 if ( preg_match( $rgba_regexp, $color_str, $match ) ) { |
213 return wp_tinycolor_rgb_to_rgb( |
234 $rgb = wp_tinycolor_rgb_to_rgb( |
214 array( |
235 array( |
215 'r' => $match[1], |
236 'r' => $match[1], |
216 'g' => $match[2], |
237 'g' => $match[2], |
217 'b' => $match[3], |
238 'b' => $match[3], |
218 ) |
239 ) |
219 ); |
240 ); |
|
241 |
|
242 $rgb['a'] = _wp_tinycolor_bound_alpha( $match[4] ); |
|
243 |
|
244 return $rgb; |
220 } |
245 } |
221 |
246 |
222 $hsl_regexp = '/^hsl' . $permissive_match3 . '$/'; |
247 $hsl_regexp = '/^hsl' . $permissive_match3 . '$/'; |
223 if ( preg_match( $hsl_regexp, $color_str, $match ) ) { |
248 if ( preg_match( $hsl_regexp, $color_str, $match ) ) { |
224 return wp_tinycolor_hsl_to_rgb( |
249 $rgb = wp_tinycolor_hsl_to_rgb( |
225 array( |
250 array( |
226 'h' => $match[1], |
251 'h' => $match[1], |
227 's' => $match[2], |
252 's' => $match[2], |
228 'l' => $match[3], |
253 'l' => $match[3], |
229 ) |
254 ) |
230 ); |
255 ); |
|
256 |
|
257 $rgb['a'] = 1; |
|
258 |
|
259 return $rgb; |
231 } |
260 } |
232 |
261 |
233 $hsla_regexp = '/^hsla' . $permissive_match4 . '$/'; |
262 $hsla_regexp = '/^hsla' . $permissive_match4 . '$/'; |
234 if ( preg_match( $hsla_regexp, $color_str, $match ) ) { |
263 if ( preg_match( $hsla_regexp, $color_str, $match ) ) { |
235 return wp_tinycolor_hsl_to_rgb( |
264 $rgb = wp_tinycolor_hsl_to_rgb( |
236 array( |
265 array( |
237 'h' => $match[1], |
266 'h' => $match[1], |
238 's' => $match[2], |
267 's' => $match[2], |
239 'l' => $match[3], |
268 'l' => $match[3], |
240 ) |
269 ) |
241 ); |
270 ); |
|
271 |
|
272 $rgb['a'] = _wp_tinycolor_bound_alpha( $match[4] ); |
|
273 |
|
274 return $rgb; |
242 } |
275 } |
243 |
276 |
244 $hex8_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/'; |
277 $hex8_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/'; |
245 if ( preg_match( $hex8_regexp, $color_str, $match ) ) { |
278 if ( preg_match( $hex8_regexp, $color_str, $match ) ) { |
246 return wp_tinycolor_rgb_to_rgb( |
279 $rgb = wp_tinycolor_rgb_to_rgb( |
247 array( |
280 array( |
248 'r' => base_convert( $match[1], 16, 10 ), |
281 'r' => base_convert( $match[1], 16, 10 ), |
249 'g' => base_convert( $match[2], 16, 10 ), |
282 'g' => base_convert( $match[2], 16, 10 ), |
250 'b' => base_convert( $match[3], 16, 10 ), |
283 'b' => base_convert( $match[3], 16, 10 ), |
251 ) |
284 ) |
252 ); |
285 ); |
|
286 |
|
287 $rgb['a'] = _wp_tinycolor_bound_alpha( |
|
288 base_convert( $match[4], 16, 10 ) / 255 |
|
289 ); |
|
290 |
|
291 return $rgb; |
253 } |
292 } |
254 |
293 |
255 $hex6_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/'; |
294 $hex6_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/'; |
256 if ( preg_match( $hex6_regexp, $color_str, $match ) ) { |
295 if ( preg_match( $hex6_regexp, $color_str, $match ) ) { |
257 return wp_tinycolor_rgb_to_rgb( |
296 $rgb = wp_tinycolor_rgb_to_rgb( |
258 array( |
297 array( |
259 'r' => base_convert( $match[1], 16, 10 ), |
298 'r' => base_convert( $match[1], 16, 10 ), |
260 'g' => base_convert( $match[2], 16, 10 ), |
299 'g' => base_convert( $match[2], 16, 10 ), |
261 'b' => base_convert( $match[3], 16, 10 ), |
300 'b' => base_convert( $match[3], 16, 10 ), |
262 ) |
301 ) |
263 ); |
302 ); |
|
303 |
|
304 $rgb['a'] = 1; |
|
305 |
|
306 return $rgb; |
264 } |
307 } |
265 |
308 |
266 $hex4_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/'; |
309 $hex4_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/'; |
267 if ( preg_match( $hex4_regexp, $color_str, $match ) ) { |
310 if ( preg_match( $hex4_regexp, $color_str, $match ) ) { |
268 return wp_tinycolor_rgb_to_rgb( |
311 $rgb = wp_tinycolor_rgb_to_rgb( |
269 array( |
312 array( |
270 'r' => base_convert( $match[1] . $match[1], 16, 10 ), |
313 'r' => base_convert( $match[1] . $match[1], 16, 10 ), |
271 'g' => base_convert( $match[2] . $match[2], 16, 10 ), |
314 'g' => base_convert( $match[2] . $match[2], 16, 10 ), |
272 'b' => base_convert( $match[3] . $match[3], 16, 10 ), |
315 'b' => base_convert( $match[3] . $match[3], 16, 10 ), |
273 ) |
316 ) |
274 ); |
317 ); |
|
318 |
|
319 $rgb['a'] = _wp_tinycolor_bound_alpha( |
|
320 base_convert( $match[4] . $match[4], 16, 10 ) / 255 |
|
321 ); |
|
322 |
|
323 return $rgb; |
275 } |
324 } |
276 |
325 |
277 $hex3_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/'; |
326 $hex3_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/'; |
278 if ( preg_match( $hex3_regexp, $color_str, $match ) ) { |
327 if ( preg_match( $hex3_regexp, $color_str, $match ) ) { |
279 return wp_tinycolor_rgb_to_rgb( |
328 $rgb = wp_tinycolor_rgb_to_rgb( |
280 array( |
329 array( |
281 'r' => base_convert( $match[1] . $match[1], 16, 10 ), |
330 'r' => base_convert( $match[1] . $match[1], 16, 10 ), |
282 'g' => base_convert( $match[2] . $match[2], 16, 10 ), |
331 'g' => base_convert( $match[2] . $match[2], 16, 10 ), |
283 'b' => base_convert( $match[3] . $match[3], 16, 10 ), |
332 'b' => base_convert( $match[3] . $match[3], 16, 10 ), |
284 ) |
333 ) |
285 ); |
334 ); |
286 } |
335 |
287 } |
336 $rgb['a'] = 1; |
288 |
337 |
289 |
338 return $rgb; |
290 /** |
339 } |
291 * Registers the style and colors block attributes for block types that support it. |
340 |
292 * |
341 /* |
293 * @since 5.8.0 |
342 * The JS color picker considers the string "transparent" to be a hex value, |
294 * @access private |
343 * so we need to handle it here as a special case. |
295 * |
344 */ |
296 * @param WP_Block_Type $block_type Block Type. |
345 if ( 'transparent' === $color_str ) { |
297 */ |
346 return array( |
298 function wp_register_duotone_support( $block_type ) { |
347 'r' => 0, |
299 $has_duotone_support = false; |
348 'g' => 0, |
300 if ( property_exists( $block_type, 'supports' ) ) { |
349 'b' => 0, |
301 $has_duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); |
350 'a' => 0, |
302 } |
351 ); |
303 |
352 } |
304 if ( $has_duotone_support ) { |
353 } |
305 if ( ! $block_type->attributes ) { |
354 |
306 $block_type->attributes = array(); |
355 /** |
307 } |
356 * Returns the prefixed id for the duotone filter for use as a CSS id. |
308 |
357 * |
309 if ( ! array_key_exists( 'style', $block_type->attributes ) ) { |
358 * @since 5.9.1 |
310 $block_type->attributes['style'] = array( |
359 * @access private |
311 'type' => 'object', |
360 * |
312 ); |
361 * @param array $preset Duotone preset value as seen in theme.json. |
313 } |
362 * @return string Duotone filter CSS id. |
314 } |
363 */ |
315 } |
364 function wp_get_duotone_filter_id( $preset ) { |
316 |
365 if ( ! isset( $preset['slug'] ) ) { |
317 /** |
366 return ''; |
318 * Render out the duotone stylesheet and SVG. |
367 } |
319 * |
368 |
320 * @since 5.8.0 |
369 return 'wp-duotone-' . $preset['slug']; |
321 * @access private |
370 } |
322 * |
371 |
323 * @param string $block_content Rendered block content. |
372 /** |
324 * @param array $block Block object. |
373 * Returns the CSS filter property url to reference the rendered SVG. |
325 * |
374 * |
326 * @return string Filtered block content. |
375 * @since 5.9.0 |
327 */ |
376 * @access private |
328 function wp_render_duotone_support( $block_content, $block ) { |
377 * |
329 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); |
378 * @param array $preset Duotone preset value as seen in theme.json. |
330 |
379 * @return string Duotone CSS filter property url value. |
331 $duotone_support = false; |
380 */ |
332 if ( $block_type && property_exists( $block_type, 'supports' ) ) { |
381 function wp_get_duotone_filter_property( $preset ) { |
333 $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); |
382 $filter_id = wp_get_duotone_filter_id( $preset ); |
334 } |
383 return "url('#" . $filter_id . "')"; |
335 |
384 } |
336 $has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] ); |
385 |
337 |
386 /** |
338 if ( |
387 * Returns the duotone filter SVG string for the preset. |
339 ! $duotone_support || |
388 * |
340 ! $has_duotone_attribute |
389 * @since 5.9.1 |
341 ) { |
390 * @access private |
342 return $block_content; |
391 * |
343 } |
392 * @param array $preset Duotone preset value as seen in theme.json. |
344 |
393 * @return string Duotone SVG filter. |
345 $duotone_colors = $block['attrs']['style']['color']['duotone']; |
394 */ |
|
395 function wp_get_duotone_filter_svg( $preset ) { |
|
396 $filter_id = wp_get_duotone_filter_id( $preset ); |
346 |
397 |
347 $duotone_values = array( |
398 $duotone_values = array( |
348 'r' => array(), |
399 'r' => array(), |
349 'g' => array(), |
400 'g' => array(), |
350 'b' => array(), |
401 'b' => array(), |
|
402 'a' => array(), |
351 ); |
403 ); |
352 foreach ( $duotone_colors as $color_str ) { |
404 |
|
405 if ( ! isset( $preset['colors'] ) || ! is_array( $preset['colors'] ) ) { |
|
406 $preset['colors'] = array(); |
|
407 } |
|
408 |
|
409 foreach ( $preset['colors'] as $color_str ) { |
353 $color = wp_tinycolor_string_to_rgb( $color_str ); |
410 $color = wp_tinycolor_string_to_rgb( $color_str ); |
354 |
411 |
355 $duotone_values['r'][] = $color['r'] / 255; |
412 $duotone_values['r'][] = $color['r'] / 255; |
356 $duotone_values['g'][] = $color['g'] / 255; |
413 $duotone_values['g'][] = $color['g'] / 255; |
357 $duotone_values['b'][] = $color['b'] / 255; |
414 $duotone_values['b'][] = $color['b'] / 255; |
358 } |
415 $duotone_values['a'][] = $color['a']; |
359 |
416 } |
360 $duotone_id = 'wp-duotone-filter-' . uniqid(); |
|
361 |
|
362 $selectors = explode( ',', $duotone_support ); |
|
363 $selectors_scoped = array_map( |
|
364 function ( $selector ) use ( $duotone_id ) { |
|
365 return '.' . $duotone_id . ' ' . trim( $selector ); |
|
366 }, |
|
367 $selectors |
|
368 ); |
|
369 $selectors_group = implode( ', ', $selectors_scoped ); |
|
370 |
417 |
371 ob_start(); |
418 ob_start(); |
372 |
419 |
373 ?> |
420 ?> |
374 |
421 |
375 <style> |
|
376 <?php echo $selectors_group; ?> { |
|
377 filter: url( <?php echo esc_url( '#' . $duotone_id ); ?> ); |
|
378 } |
|
379 </style> |
|
380 |
|
381 <svg |
422 <svg |
382 xmlns:xlink="http://www.w3.org/1999/xlink" |
423 xmlns="http://www.w3.org/2000/svg" |
383 viewBox="0 0 0 0" |
424 viewBox="0 0 0 0" |
384 width="0" |
425 width="0" |
385 height="0" |
426 height="0" |
386 focusable="false" |
427 focusable="false" |
387 role="none" |
428 role="none" |
388 style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;" |
429 style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;" |
389 > |
430 > |
390 <defs> |
431 <defs> |
391 <filter id="<?php echo esc_attr( $duotone_id ); ?>"> |
432 <filter id="<?php echo esc_attr( $filter_id ); ?>"> |
392 <feColorMatrix |
433 <feColorMatrix |
|
434 color-interpolation-filters="sRGB" |
393 type="matrix" |
435 type="matrix" |
394 <?php // phpcs:disable Generic.WhiteSpace.DisallowSpaceIndent ?> |
436 values=" |
395 values=".299 .587 .114 0 0 |
437 .299 .587 .114 0 0 |
396 .299 .587 .114 0 0 |
438 .299 .587 .114 0 0 |
397 .299 .587 .114 0 0 |
439 .299 .587 .114 0 0 |
398 0 0 0 1 0" |
440 .299 .587 .114 0 0 |
399 <?php // phpcs:enable Generic.WhiteSpace.DisallowSpaceIndent ?> |
441 " |
400 /> |
442 /> |
401 <feComponentTransfer color-interpolation-filters="sRGB" > |
443 <feComponentTransfer color-interpolation-filters="sRGB" > |
402 <feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" /> |
444 <feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" /> |
403 <feFuncG type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['g'] ) ); ?>" /> |
445 <feFuncG type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['g'] ) ); ?>" /> |
404 <feFuncB type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['b'] ) ); ?>" /> |
446 <feFuncB type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['b'] ) ); ?>" /> |
|
447 <feFuncA type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['a'] ) ); ?>" /> |
405 </feComponentTransfer> |
448 </feComponentTransfer> |
|
449 <feComposite in2="SourceGraphic" operator="in" /> |
406 </filter> |
450 </filter> |
407 </defs> |
451 </defs> |
408 </svg> |
452 </svg> |
409 |
453 |
410 <?php |
454 <?php |
411 |
455 |
412 $duotone = ob_get_clean(); |
456 $svg = ob_get_clean(); |
|
457 |
|
458 if ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) { |
|
459 // Clean up the whitespace. |
|
460 $svg = preg_replace( "/[\r\n\t ]+/", ' ', $svg ); |
|
461 $svg = preg_replace( '/> </', '><', $svg ); |
|
462 $svg = trim( $svg ); |
|
463 } |
|
464 |
|
465 return $svg; |
|
466 } |
|
467 |
|
468 /** |
|
469 * Registers the style and colors block attributes for block types that support it. |
|
470 * |
|
471 * @since 5.8.0 |
|
472 * @access private |
|
473 * |
|
474 * @param WP_Block_Type $block_type Block Type. |
|
475 */ |
|
476 function wp_register_duotone_support( $block_type ) { |
|
477 $has_duotone_support = false; |
|
478 if ( property_exists( $block_type, 'supports' ) ) { |
|
479 $has_duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); |
|
480 } |
|
481 |
|
482 if ( $has_duotone_support ) { |
|
483 if ( ! $block_type->attributes ) { |
|
484 $block_type->attributes = array(); |
|
485 } |
|
486 |
|
487 if ( ! array_key_exists( 'style', $block_type->attributes ) ) { |
|
488 $block_type->attributes['style'] = array( |
|
489 'type' => 'object', |
|
490 ); |
|
491 } |
|
492 } |
|
493 } |
|
494 |
|
495 /** |
|
496 * Render out the duotone stylesheet and SVG. |
|
497 * |
|
498 * @since 5.8.0 |
|
499 * @access private |
|
500 * |
|
501 * @param string $block_content Rendered block content. |
|
502 * @param array $block Block object. |
|
503 * @return string Filtered block content. |
|
504 */ |
|
505 function wp_render_duotone_support( $block_content, $block ) { |
|
506 $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); |
|
507 |
|
508 $duotone_support = false; |
|
509 if ( $block_type && property_exists( $block_type, 'supports' ) ) { |
|
510 $duotone_support = _wp_array_get( $block_type->supports, array( 'color', '__experimentalDuotone' ), false ); |
|
511 } |
|
512 |
|
513 $has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] ); |
|
514 |
|
515 if ( |
|
516 ! $duotone_support || |
|
517 ! $has_duotone_attribute |
|
518 ) { |
|
519 return $block_content; |
|
520 } |
|
521 |
|
522 $filter_preset = array( |
|
523 'slug' => wp_unique_id( sanitize_key( implode( '-', $block['attrs']['style']['color']['duotone'] ) . '-' ) ), |
|
524 'colors' => $block['attrs']['style']['color']['duotone'], |
|
525 ); |
|
526 $filter_property = wp_get_duotone_filter_property( $filter_preset ); |
|
527 $filter_id = wp_get_duotone_filter_id( $filter_preset ); |
|
528 $filter_svg = wp_get_duotone_filter_svg( $filter_preset ); |
|
529 |
|
530 $scope = '.' . $filter_id; |
|
531 $selectors = explode( ',', $duotone_support ); |
|
532 $scoped = array(); |
|
533 foreach ( $selectors as $sel ) { |
|
534 $scoped[] = $scope . ' ' . trim( $sel ); |
|
535 } |
|
536 $selector = implode( ', ', $scoped ); |
|
537 |
|
538 // !important is needed because these styles render before global styles, |
|
539 // and they should be overriding the duotone filters set by global styles. |
|
540 $filter_style = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG |
|
541 ? $selector . " {\n\tfilter: " . $filter_property . " !important;\n}\n" |
|
542 : $selector . '{filter:' . $filter_property . ' !important;}'; |
|
543 |
|
544 wp_register_style( $filter_id, false, array(), true, true ); |
|
545 wp_add_inline_style( $filter_id, $filter_style ); |
|
546 wp_enqueue_style( $filter_id ); |
|
547 |
|
548 add_action( |
|
549 'wp_footer', |
|
550 static function () use ( $filter_svg, $selector ) { |
|
551 echo $filter_svg; |
|
552 |
|
553 /* |
|
554 * Safari renders elements incorrectly on first paint when the SVG |
|
555 * filter comes after the content that it is filtering, so we force |
|
556 * a repaint with a WebKit hack which solves the issue. |
|
557 */ |
|
558 global $is_safari; |
|
559 if ( $is_safari ) { |
|
560 printf( |
|
561 // Simply accessing el.offsetHeight flushes layout and style |
|
562 // changes in WebKit without having to wait for setTimeout. |
|
563 '<script>( function() { var el = document.querySelector( %s ); var display = el.style.display; el.style.display = "none"; el.offsetHeight; el.style.display = display; } )();</script>', |
|
564 wp_json_encode( $selector ) |
|
565 ); |
|
566 } |
|
567 } |
|
568 ); |
413 |
569 |
414 // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper. |
570 // Like the layout hook, this assumes the hook only applies to blocks with a single wrapper. |
415 $content = preg_replace( |
571 return preg_replace( |
416 '/' . preg_quote( 'class="', '/' ) . '/', |
572 '/' . preg_quote( 'class="', '/' ) . '/', |
417 'class="' . $duotone_id . ' ', |
573 'class="' . $filter_id . ' ', |
418 $block_content, |
574 $block_content, |
419 1 |
575 1 |
420 ); |
576 ); |
421 |
|
422 return $content . $duotone; |
|
423 } |
577 } |
424 |
578 |
425 // Register the block support. |
579 // Register the block support. |
426 WP_Block_Supports::get_instance()->register( |
580 WP_Block_Supports::get_instance()->register( |
427 'duotone', |
581 'duotone', |