diff --git a/pwa.php b/pwa.php index 406a28ad1..d8f0d69eb 100644 --- a/pwa.php +++ b/pwa.php @@ -242,6 +242,9 @@ function _pwa_check_disabled_navigation_preload() { /** Patch behavior in class-wp-query.php */ require_once PWA_PLUGIN_DIR . '/wp-includes/class-wp-query.php'; +/** Function to register maskable icon setting in customizer */ +require_once PWA_PLUGIN_DIR . '/wp-includes/class-wp-customize-manager.php'; + /** Hooks to add for when accessing admin. */ require_once PWA_PLUGIN_DIR . '/wp-admin/admin.php'; diff --git a/tests/test-class-wp-customize-manager.php b/tests/test-class-wp-customize-manager.php new file mode 100644 index 000000000..2fccfca0c --- /dev/null +++ b/tests/test-class-wp-customize-manager.php @@ -0,0 +1,71 @@ +user_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $this->user_id ); + + require_once ABSPATH . WPINC . '/class-wp-customize-manager.php'; + // @codingStandardsIgnoreStart + $GLOBALS['wp_customize'] = new WP_Customize_Manager(); + // @codingStandardsIgnoreStop + $this->wp_customize = $GLOBALS['wp_customize']; + } + + /** + * Tear down. + */ + public function tearDown() { + $this->wp_customize = null; + unset( $GLOBALS['wp_customize'] ); + parent::tearDown(); + } + + /** + * @covers ::pwa_customize_register_site_icon_maskable + */ + public function test_pwa_customize_register_site_icon_maskable() { + do_action( 'customize_register', $this->wp_customize ); + pwa_customize_register_site_icon_maskable( $this->wp_customize ); + + $this->assertEquals( 1000, has_action( 'customize_register', 'pwa_customize_register_site_icon_maskable' ) ); + $this->assertInstanceOf( 'WP_Customize_Setting', $this->wp_customize->get_setting( 'site_icon_maskable' ) ); + $this->assertInstanceOf( 'WP_Customize_Control', $this->wp_customize->get_control( 'site_icon_maskable' ) ); + } + + /** + * @covers ::pwa_customize_controls_enqueue_site_icon_maskable_script + */ + public function test_pwa_customize_controls_enqueue_site_icon_maskable_script() { + pwa_customize_controls_enqueue_site_icon_maskable_script(); + + $this->assertEquals( 10, has_action( 'customize_controls_enqueue_scripts', 'pwa_customize_controls_enqueue_site_icon_maskable_script' ) ); + $this->assertTrue( wp_script_is( 'customize-controls', 'enqueued' ) ); + $this->assertTrue( wp_script_is( 'customize-controls-site-icon-maskable', 'enqueued' ) ); + } + +} diff --git a/tests/test-class-wp-web-app-manifest.php b/tests/test-class-wp-web-app-manifest.php index 3752263db..675ae7cfa 100644 --- a/tests/test-class-wp-web-app-manifest.php +++ b/tests/test-class-wp-web-app-manifest.php @@ -264,6 +264,18 @@ public function test_get_manifest() { ); $this->assertEquals( $expected_manifest, $actual_manifest ); + // Check that icon purpose is `any maskable` if site icon is maskable. + update_option( 'site_icon_maskable', true ); + $actual_manifest = $this->instance->get_manifest(); + $expected_manifest['icons'] = array_map( + function ( $icon ) { + $icon['purpose'] = 'any maskable'; + return $icon; + }, + $expected_manifest['icons'] + ); + $this->assertEquals( $expected_manifest, $actual_manifest ); + // Check that long names do not automatically copy to short name. $blogname = str_repeat( 'x', 13 ); update_option( 'blogname', $blogname ); diff --git a/wp-admin/js/customize-controls-site-icon-maskable.js b/wp-admin/js/customize-controls-site-icon-maskable.js new file mode 100644 index 000000000..d27fb80fc --- /dev/null +++ b/wp-admin/js/customize-controls-site-icon-maskable.js @@ -0,0 +1,57 @@ +wp.customize( + 'site_icon', + 'site_icon_maskable', + (siteIconSetting, siteIconMaskableSetting) => { + wp.customize.control( + 'site_icon', + 'site_icon_maskable', + (siteIconControl, siteIconMaskableControl) => { + /** + * Determine whether the site_icon setting has been set. + * + * @return {boolean} Whether set. + */ + const hasSiteIcon = () => { + const siteIconValue = siteIconSetting(); + return ( + typeof siteIconValue === 'number' && siteIconValue > 0 + ); + }; + + /** + * Toggle site icon maskable active state based on whether the site icon is set. + */ + const updateActive = () => { + siteIconMaskableControl.active(hasSiteIcon()); + }; + + // Set initial active state. + updateActive(); + + // Update active state whenever the site_icon setting changes. + siteIconSetting.bind(updateActive); + + /** + * Update the icon styling based on whether the site icon maskable is enabled. + */ + const updateIconStyle = () => { + siteIconControl.container + .find('img.app-icon-preview') + .css( + 'clipPath', + siteIconMaskableSetting() + ? 'inset(10% round 50%)' + : '' + ); + }; + + // Set initial style. + updateIconStyle(); + + // Update style whenever the site_icon or the site_icon_maskable changes. + siteIconSetting.bind(updateIconStyle); + siteIconMaskableSetting.bind(updateIconStyle); + } + ); + } +); diff --git a/wp-includes/class-wp-customize-manager.php b/wp-includes/class-wp-customize-manager.php new file mode 100644 index 000000000..b3b864c83 --- /dev/null +++ b/wp-includes/class-wp-customize-manager.php @@ -0,0 +1,67 @@ +add_setting( + 'site_icon_maskable', + array( + 'capability' => 'manage_options', + 'type' => 'option', + 'default' => false, + 'transport' => 'postMessage', + ) + ); + + $site_icon_control = $wp_customize->get_control( 'site_icon' ); + if ( $site_icon_control ) { + $wp_customize->add_control( + 'site_icon_maskable', + array( + 'type' => 'checkbox', + 'section' => 'title_tagline', + 'label' => __( 'Maskable icon', 'pwa' ), + 'priority' => $site_icon_control->priority + 1, + ) + ); + } +} + +add_action( 'customize_register', 'pwa_customize_register_site_icon_maskable', 1000 ); + +/** + * Enqueue script for site_icon_maskable control. + * + * This may end up making sense being enqueued as part of WP_Customize_Site_Icon_Control or just added to logic in + * . + * + * @see WP_Customize_Site_Icon_Control::enqueue() + */ +function pwa_customize_controls_enqueue_site_icon_maskable_script() { + wp_enqueue_script( + 'customize-controls-site-icon-maskable', + plugins_url( 'wp-admin/js/customize-controls-site-icon-maskable.js', PWA_PLUGIN_FILE ), + array( 'customize-controls' ), + PWA_VERSION, + true + ); +} + +add_action( 'customize_controls_enqueue_scripts', 'pwa_customize_controls_enqueue_site_icon_maskable_script' ); diff --git a/wp-includes/class-wp-web-app-manifest.php b/wp-includes/class-wp-web-app-manifest.php index 94d8f0206..b2e9d6e60 100644 --- a/wp-includes/class-wp-web-app-manifest.php +++ b/wp-includes/class-wp-web-app-manifest.php @@ -396,16 +396,27 @@ public function get_icons() { return array(); } - $icons = array(); $mime_type = get_post_mime_type( $site_icon_id ); + if ( ! $mime_type ) { + return array(); + } + + $icons = array(); + $maskable = get_option( 'site_icon_maskable', false ); foreach ( $this->default_manifest_icon_sizes as $size ) { $src = get_site_icon_url( $size ); if ( $src ) { - $icons[] = array( + $icon = array( 'src' => $src, 'sizes' => sprintf( '%1$dx%1$d', $size ), 'type' => $mime_type, ); + + if ( $maskable ) { + $icon['purpose'] = 'any maskable'; + } + + $icons[] = $icon; } } return $icons;