From 1ff88af016f2cd5fcd0067520a40715b70c62d44 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Mon, 19 Oct 2026 14:29:19 +0300 Subject: [PATCH 1/5] fix(bf): show the Black Friday banner again each year The banner dismissal was stored under a fixed 2025 key, so users who dismissed it last year would never see it again. The key and the UTM tag now use the current year. The BFCM2525 coupon is kept for 2026, as agreed in Codeinwp/marketing#365. Co-Authored-By: Claude Opus 5.5 --- inc/admin.php | 15 ++++++++++----- tests/test-admin.php | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/inc/admin.php b/inc/admin.php index 72fb017f..ef59bc4b 100755 --- a/inc/admin.php +++ b/inc/admin.php @@ -23,7 +23,7 @@ class Optml_Admin { const IMAGE_DATA_COLLECTED_BATCH = 100; - const BF_PROMO_DISMISS_KEY = 'optml_bf25_notice_dismiss'; + const BF_PROMO_DISMISS_KEY = 'optml_bf%s_notice_dismiss'; const SPC_BANNER_DISMISS_KEY = 'optml_spc_banner_dismiss'; @@ -1680,6 +1680,10 @@ public function get_bf_notices( $user_plan ) { */ $now = apply_filters( 'themeisle_sdk_current_date', new \DateTime( 'now' ) ); $current_year = $now->format( 'Y' ); + // Per-year dismissal and UTM, so a banner dismissed last year shows again this year. + $year_suffix = $now->format( 'y' ); + $dismiss_key = sprintf( self::BF_PROMO_DISMISS_KEY, $year_suffix ); + $utm_area = 'bfcm' . $year_suffix; $black_friday_day = new \DateTime( "last Friday of November $current_year" ); $sale_start = clone $black_friday_day; @@ -1690,6 +1694,7 @@ public function get_bf_notices( $user_plan ) { $end->modify( '+7 days' ); $end->setTime( 23, 59, 59 ); + // The coupon is reused for 2026 (Codeinwp/marketing#365). $promo_code = 'BFCM2525'; $notices['sidebar'] = [ @@ -1705,10 +1710,10 @@ public function get_bf_notices( $user_plan ) { '' . $promo_code . '', '' . __( '25% discount', 'optimole-wp' ) . '' ), - 'cta_link' => esc_url_raw( tsdk_utmify( tsdk_translate_link( self::get_upgrade_base_link() ), 'bfcm25', 'sidebarnotice' ) ), + 'cta_link' => esc_url_raw( tsdk_utmify( tsdk_translate_link( self::get_upgrade_base_link() ), $utm_area, 'sidebarnotice' ) ), ]; - if ( get_option( self::BF_PROMO_DISMISS_KEY ) === 'yes' ) { + if ( get_option( $dismiss_key ) === 'yes' ) { return $notices; } @@ -1718,7 +1723,7 @@ public function get_bf_notices( $user_plan ) { '' . $promo_code . '', '' . __( '25% discount', 'optimole-wp' ) . '' ); - $cta_link = esc_url_raw( tsdk_utmify( self::get_upgrade_base_link(), 'bfcm25', 'notice' ) ); + $cta_link = esc_url_raw( tsdk_utmify( self::get_upgrade_base_link(), $utm_area, 'notice' ) ); $cta_text = __( 'Claim now', 'optimole-wp' ); $notices['banner'] = [ @@ -1729,7 +1734,7 @@ public function get_bf_notices( $user_plan ) { 'subtitle' => $message, 'cta_text' => $cta_text, 'cta_link' => $cta_link, - 'dismiss_key' => self::BF_PROMO_DISMISS_KEY, + 'dismiss_key' => $dismiss_key, ]; return $notices; diff --git a/tests/test-admin.php b/tests/test-admin.php index 6cbd8010..9b043208 100644 --- a/tests/test-admin.php +++ b/tests/test-admin.php @@ -66,7 +66,7 @@ public function setUp(): void { $this->sale_end->setTime( 23, 59, 59 ); // Clean up options before each test - delete_option( Optml_Admin::BF_PROMO_DISMISS_KEY ); + delete_option( $this->bf_dismiss_key() ); // Remove any filters that might be set remove_all_filters( 'themeisle_sdk_is_black_friday_sale' ); @@ -77,12 +77,19 @@ public function setUp(): void { * Clean up after each test. */ public function tearDown(): void { - delete_option( Optml_Admin::BF_PROMO_DISMISS_KEY ); + delete_option( $this->bf_dismiss_key() ); remove_all_filters( 'themeisle_sdk_is_black_friday_sale' ); remove_all_filters( 'themeisle_sdk_current_date' ); parent::tearDown(); } + /** + * Helper to get this year's Black Friday dismiss key. + */ + private function bf_dismiss_key(): string { + return sprintf( Optml_Admin::BF_PROMO_DISMISS_KEY, $this->black_friday->format( 'y' ) ); + } + /** * Helper to set up Black Friday sale period. */ @@ -188,20 +195,38 @@ public function test_get_bf_notices_banner_dismissal() { $this->mock_date_to( clone $this->black_friday ); // Test banner is hidden when dismissed - update_option( Optml_Admin::BF_PROMO_DISMISS_KEY, 'yes' ); + update_option( $this->bf_dismiss_key(), 'yes' ); $result = $this->admin->get_bf_notices( 'free' ); $this->assertIsArray( $result ); $this->assertArrayHasKey( 'sidebar', $result ); $this->assertArrayNotHasKey( 'banner', $result ); // Test banner appears when not dismissed - delete_option( Optml_Admin::BF_PROMO_DISMISS_KEY ); + delete_option( $this->bf_dismiss_key() ); $result = $this->admin->get_bf_notices( 'free' ); $this->assertIsArray( $result ); $this->assertArrayHasKey( 'sidebar', $result ); $this->assertArrayHasKey( 'banner', $result ); } + /** + * Test a banner dismissed in a previous year shows again this year. + */ + public function test_get_bf_notices_banner_ignores_previous_year_dismissal() { + $this->setup_black_friday_sale_period(); + $this->mock_date_to( clone $this->black_friday ); + + $previous_year_key = sprintf( Optml_Admin::BF_PROMO_DISMISS_KEY, ( clone $this->black_friday )->modify( '-1 year' )->format( 'y' ) ); + update_option( $previous_year_key, 'yes' ); + + $result = $this->admin->get_bf_notices( 'free' ); + delete_option( $previous_year_key ); + + $this->assertArrayHasKey( 'banner', $result ); + $this->assertNotEquals( $previous_year_key, $result['banner']['dismiss_key'] ); + $this->assertStringContainsString( 'bfcm' . $this->black_friday->format( 'y' ), $result['banner']['cta_link'] ); + } + /** * Test get_bf_notices contains correct dismiss key in banner. */ @@ -213,7 +238,7 @@ public function test_get_bf_notices_banner_has_correct_dismiss_key() { $this->assertArrayHasKey( 'banner', $result ); $this->assertArrayHasKey( 'dismiss_key', $result['banner'] ); - $this->assertEquals( Optml_Admin::BF_PROMO_DISMISS_KEY, $result['banner']['dismiss_key'] ); + $this->assertEquals( $this->bf_dismiss_key(), $result['banner']['dismiss_key'] ); } /** @@ -307,7 +332,7 @@ public function test_get_bf_notices_banner_dismissal_values() { $test_values = [ 'no', '0', '', 'dismissed' ]; foreach ( $test_values as $value ) { - update_option( Optml_Admin::BF_PROMO_DISMISS_KEY, $value ); + update_option( $this->bf_dismiss_key(), $value ); $result = $this->admin->get_bf_notices( 'free' ); $this->assertArrayHasKey( 'banner', $result, "Banner should appear when dismiss key is '$value'" ); } From 58960faf77b2c13b0f4434f588a6573fd2f9314d Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Mon, 19 Oct 2026 16:15:21 +0300 Subject: [PATCH 2/5] test(bf): assert literal yearly dismiss keys and sidebar UTM Co-Authored-By: Claude Opus 5.5 --- tests/test-admin.php | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/tests/test-admin.php b/tests/test-admin.php index 9b043208..cc6f373d 100644 --- a/tests/test-admin.php +++ b/tests/test-admin.php @@ -210,21 +210,29 @@ public function test_get_bf_notices_banner_dismissal() { } /** - * Test a banner dismissed in a previous year shows again this year. + * Test the dismiss key and UTM tag follow the year, and 2025 keeps the legacy key. */ - public function test_get_bf_notices_banner_ignores_previous_year_dismissal() { + public function test_get_bf_notices_uses_yearly_dismiss_key_and_utm() { $this->setup_black_friday_sale_period(); - $this->mock_date_to( clone $this->black_friday ); - - $previous_year_key = sprintf( Optml_Admin::BF_PROMO_DISMISS_KEY, ( clone $this->black_friday )->modify( '-1 year' )->format( 'y' ) ); - update_option( $previous_year_key, 'yes' ); + // A dismissal from 2025 does not hide the 2026 banner. + update_option( 'optml_bf25_notice_dismiss', 'yes' ); + $this->mock_date_to( new DateTime( '2026-11-27 12:00:00' ) ); $result = $this->admin->get_bf_notices( 'free' ); - delete_option( $previous_year_key ); + delete_option( 'optml_bf25_notice_dismiss' ); $this->assertArrayHasKey( 'banner', $result ); - $this->assertNotEquals( $previous_year_key, $result['banner']['dismiss_key'] ); - $this->assertStringContainsString( 'bfcm' . $this->black_friday->format( 'y' ), $result['banner']['cta_link'] ); + $this->assertSame( 'optml_bf26_notice_dismiss', $result['banner']['dismiss_key'] ); + $this->assertStringContainsString( 'bfcm26', $result['banner']['cta_link'] ); + $this->assertStringContainsString( 'bfcm26', $result['sidebar']['cta_link'] ); + + // 2025 still uses the legacy key and UTM tag. + $this->mock_date_to( new DateTime( '2025-11-28 12:00:00' ) ); + $result = $this->admin->get_bf_notices( 'free' ); + + $this->assertSame( 'optml_bf25_notice_dismiss', $result['banner']['dismiss_key'] ); + $this->assertStringContainsString( 'bfcm25', $result['banner']['cta_link'] ); + $this->assertStringContainsString( 'bfcm25', $result['sidebar']['cta_link'] ); } /** From 7423ecfef82ba99a7f09174664c687b918f79f55 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Tue, 20 Oct 2026 16:00:08 +0300 Subject: [PATCH 3/5] fix(bf): derive the promo code from the year BFCM2525 becomes BFCM2625 in 2026, matching the dismiss key and UTM tag. Co-Authored-By: Claude Opus 5.5 --- inc/admin.php | 6 +++--- tests/test-admin.php | 14 +++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/inc/admin.php b/inc/admin.php index ef59bc4b..0c602971 100755 --- a/inc/admin.php +++ b/inc/admin.php @@ -1680,7 +1680,7 @@ public function get_bf_notices( $user_plan ) { */ $now = apply_filters( 'themeisle_sdk_current_date', new \DateTime( 'now' ) ); $current_year = $now->format( 'Y' ); - // Per-year dismissal and UTM, so a banner dismissed last year shows again this year. + // Per-year promo code, dismissal and UTM, so a banner dismissed last year shows again this year. $year_suffix = $now->format( 'y' ); $dismiss_key = sprintf( self::BF_PROMO_DISMISS_KEY, $year_suffix ); $utm_area = 'bfcm' . $year_suffix; @@ -1694,8 +1694,8 @@ public function get_bf_notices( $user_plan ) { $end->modify( '+7 days' ); $end->setTime( 23, 59, 59 ); - // The coupon is reused for 2026 (Codeinwp/marketing#365). - $promo_code = 'BFCM2525'; + // Code format: BFCM + 2-digit year + discount, e.g. BFCM2625. + $promo_code = 'BFCM' . $year_suffix . '25'; $notices['sidebar'] = [ 'title' => sprintf( diff --git a/tests/test-admin.php b/tests/test-admin.php index cc6f373d..6709144c 100644 --- a/tests/test-admin.php +++ b/tests/test-admin.php @@ -172,7 +172,7 @@ public function test_get_bf_notices_structure_for_free_plan() { // Check subtitle contains discount information for free users $this->assertStringContainsString( '25', $sidebar['subtitle'] ); - $this->assertStringContainsString( 'BFCM2525', $sidebar['subtitle'] ); + $this->assertStringContainsString( 'BFCM', $sidebar['subtitle'] ); $this->assertStringContainsString( 'Use code', $sidebar['subtitle'] ); // Check CTA link is not empty @@ -181,7 +181,7 @@ public function test_get_bf_notices_structure_for_free_plan() { // Check banner CTA text and message $this->assertNotEmpty( $banner['cta_text'] ); $this->assertStringContainsString( 'Use coupon code', $banner['subtitle'] ); - $this->assertStringContainsString( 'BFCM2525', $banner['subtitle'] ); + $this->assertStringContainsString( 'BFCM', $banner['subtitle'] ); $this->assertStringContainsString( '25', $banner['subtitle'] ); } @@ -210,9 +210,9 @@ public function test_get_bf_notices_banner_dismissal() { } /** - * Test the dismiss key and UTM tag follow the year, and 2025 keeps the legacy key. + * Test the promo code, dismiss key and UTM tag follow the year, and 2025 keeps the legacy values. */ - public function test_get_bf_notices_uses_yearly_dismiss_key_and_utm() { + public function test_get_bf_notices_uses_yearly_promo_code_dismiss_key_and_utm() { $this->setup_black_friday_sale_period(); // A dismissal from 2025 does not hide the 2026 banner. @@ -225,14 +225,18 @@ public function test_get_bf_notices_uses_yearly_dismiss_key_and_utm() { $this->assertSame( 'optml_bf26_notice_dismiss', $result['banner']['dismiss_key'] ); $this->assertStringContainsString( 'bfcm26', $result['banner']['cta_link'] ); $this->assertStringContainsString( 'bfcm26', $result['sidebar']['cta_link'] ); + $this->assertStringContainsString( 'BFCM2625', $result['banner']['subtitle'] ); + $this->assertStringContainsString( 'BFCM2625', $result['sidebar']['subtitle'] ); - // 2025 still uses the legacy key and UTM tag. + // 2025 still uses the legacy promo code, key and UTM tag. $this->mock_date_to( new DateTime( '2025-11-28 12:00:00' ) ); $result = $this->admin->get_bf_notices( 'free' ); $this->assertSame( 'optml_bf25_notice_dismiss', $result['banner']['dismiss_key'] ); $this->assertStringContainsString( 'bfcm25', $result['banner']['cta_link'] ); $this->assertStringContainsString( 'bfcm25', $result['sidebar']['cta_link'] ); + $this->assertStringContainsString( 'BFCM2525', $result['banner']['subtitle'] ); + $this->assertStringContainsString( 'BFCM2525', $result['sidebar']['subtitle'] ); } /** From fe76ec8283c4b93e1ead8eb8b580b4270dc5f3b9 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Tue, 20 Oct 2026 17:01:53 +0300 Subject: [PATCH 4/5] fix(bf): keep the BFCM2525 promo code The store keeps BFCM2525 active, so only the dismiss key and UTM tag follow the year. Co-Authored-By: Claude Opus 5.5 --- inc/admin.php | 5 ++--- tests/test-admin.php | 14 +++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/inc/admin.php b/inc/admin.php index 0c602971..ab4c5e18 100755 --- a/inc/admin.php +++ b/inc/admin.php @@ -1680,7 +1680,7 @@ public function get_bf_notices( $user_plan ) { */ $now = apply_filters( 'themeisle_sdk_current_date', new \DateTime( 'now' ) ); $current_year = $now->format( 'Y' ); - // Per-year promo code, dismissal and UTM, so a banner dismissed last year shows again this year. + // Per-year dismissal and UTM, so a banner dismissed last year shows again this year. $year_suffix = $now->format( 'y' ); $dismiss_key = sprintf( self::BF_PROMO_DISMISS_KEY, $year_suffix ); $utm_area = 'bfcm' . $year_suffix; @@ -1694,8 +1694,7 @@ public function get_bf_notices( $user_plan ) { $end->modify( '+7 days' ); $end->setTime( 23, 59, 59 ); - // Code format: BFCM + 2-digit year + discount, e.g. BFCM2625. - $promo_code = 'BFCM' . $year_suffix . '25'; + $promo_code = 'BFCM2525'; $notices['sidebar'] = [ 'title' => sprintf( diff --git a/tests/test-admin.php b/tests/test-admin.php index 6709144c..aec0468f 100644 --- a/tests/test-admin.php +++ b/tests/test-admin.php @@ -172,7 +172,7 @@ public function test_get_bf_notices_structure_for_free_plan() { // Check subtitle contains discount information for free users $this->assertStringContainsString( '25', $sidebar['subtitle'] ); - $this->assertStringContainsString( 'BFCM', $sidebar['subtitle'] ); + $this->assertStringContainsString( 'BFCM2525', $sidebar['subtitle'] ); $this->assertStringContainsString( 'Use code', $sidebar['subtitle'] ); // Check CTA link is not empty @@ -181,7 +181,7 @@ public function test_get_bf_notices_structure_for_free_plan() { // Check banner CTA text and message $this->assertNotEmpty( $banner['cta_text'] ); $this->assertStringContainsString( 'Use coupon code', $banner['subtitle'] ); - $this->assertStringContainsString( 'BFCM', $banner['subtitle'] ); + $this->assertStringContainsString( 'BFCM2525', $banner['subtitle'] ); $this->assertStringContainsString( '25', $banner['subtitle'] ); } @@ -210,9 +210,9 @@ public function test_get_bf_notices_banner_dismissal() { } /** - * Test the promo code, dismiss key and UTM tag follow the year, and 2025 keeps the legacy values. + * Test the dismiss key and UTM tag follow the year, and the promo code stays BFCM2525. */ - public function test_get_bf_notices_uses_yearly_promo_code_dismiss_key_and_utm() { + public function test_get_bf_notices_uses_yearly_dismiss_key_and_utm() { $this->setup_black_friday_sale_period(); // A dismissal from 2025 does not hide the 2026 banner. @@ -225,10 +225,10 @@ public function test_get_bf_notices_uses_yearly_promo_code_dismiss_key_and_utm() $this->assertSame( 'optml_bf26_notice_dismiss', $result['banner']['dismiss_key'] ); $this->assertStringContainsString( 'bfcm26', $result['banner']['cta_link'] ); $this->assertStringContainsString( 'bfcm26', $result['sidebar']['cta_link'] ); - $this->assertStringContainsString( 'BFCM2625', $result['banner']['subtitle'] ); - $this->assertStringContainsString( 'BFCM2625', $result['sidebar']['subtitle'] ); + $this->assertStringContainsString( 'BFCM2525', $result['banner']['subtitle'] ); + $this->assertStringContainsString( 'BFCM2525', $result['sidebar']['subtitle'] ); - // 2025 still uses the legacy promo code, key and UTM tag. + // 2025 still uses the legacy key and UTM tag. $this->mock_date_to( new DateTime( '2025-11-28 12:00:00' ) ); $result = $this->admin->get_bf_notices( 'free' ); From 47c1b28f7ebea3f51783bc7a666caae2d5463b29 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Mon, 26 Oct 2026 18:33:26 +0200 Subject: [PATCH 5/5] fix(bf): follow the SDK Black Friday rules Optimole is an SDK product but registered no Black Friday config, so the SDK showed the generic ThemeIsle notice on every admin page. Hide it when Optimole is the only ThemeIsle product; other products keep their notice. The dashboard banner now uses the SDK dismissal (per user, reset every year) instead of its own site option. Dismissing either one hides both. Co-Authored-By: Claude Opus 5.5 --- inc/admin.php | 70 +++++++++++++++++++++++++-- inc/rest.php | 6 +++ tests/test-admin.php | 110 ++++++++++++++++++++++++++++--------------- 3 files changed, 144 insertions(+), 42 deletions(-) diff --git a/inc/admin.php b/inc/admin.php index ab4c5e18..1c6a00fe 100755 --- a/inc/admin.php +++ b/inc/admin.php @@ -23,7 +23,9 @@ class Optml_Admin { const IMAGE_DATA_COLLECTED_BATCH = 100; - const BF_PROMO_DISMISS_KEY = 'optml_bf%s_notice_dismiss'; + const BF_PROMO_DISMISS_KEY = 'optml_bf_notice_dismiss'; + + const SDK_BF_DISMISS_META = 'themeisle_sdk_dismissed_notice_black_friday'; const SPC_BANNER_DISMISS_KEY = 'optml_spc_banner_dismiss'; @@ -114,6 +116,7 @@ public function __construct() { } add_filter( 'themeisle-sdk/survey/' . OPTML_PRODUCT_SLUG, [ $this, 'get_survey_metadata' ], 10, 2 ); + add_filter( 'themeisle_sdk_blackfriday_data', [ $this, 'add_black_friday_data' ] ); add_action( 'admin_init', [ $this, 'mark_user_with_offload' ] ); } @@ -1646,6 +1649,63 @@ private function get_spc_status() { return 'not-installed'; } + /** + * Hide the SDK Black Friday notice when Optimole is the only ThemeIsle product. + * + * Optimole shows its Black Friday offer only in its dashboard. Without a config, + * the SDK shows the generic ThemeIsle notice on every admin page. When other + * ThemeIsle products are active, the notice is theirs, so it stays. + * + * @param array $configs The Black Friday configs of the products. + * + * @return array The configs. + */ + public function add_black_friday_data( $configs ) { + if ( ! class_exists( '\ThemeisleSDK\Loader' ) || ! isset( $configs['default'] ) || ! is_array( $configs['default'] ) ) { + return $configs; + } + + if ( [ OPTML_PRODUCT_SLUG ] === array_keys( \ThemeisleSDK\Loader::get_products() ) ) { + $configs['default']['sale_url'] = ''; + } + + return $configs; + } + + /** + * Check if the current user dismissed the SDK Black Friday notice this year. + * + * The SDK uses one dismissal for the notices of all ThemeIsle products. + * + * @param DateTime $now The current date. + * + * @return bool + */ + public static function is_sdk_bf_notice_dismissed( $now ) { + $dismissed_at = get_user_meta( get_current_user_id(), self::SDK_BF_DISMISS_META, true ); + + return ! empty( $dismissed_at ) && wp_date( 'Y', (int) $dismissed_at ) === $now->format( 'Y' ); + } + + /** + * Dismiss the SDK Black Friday notice for the current user. + * + * Called when the user dismisses the Optimole banner. It also hides the SDK + * notice of the other ThemeIsle products. + * + * @return void + */ + public static function dismiss_sdk_bf_notice() { + /** + * The current date. + * + * @var DateTime $now Current date. + */ + $now = apply_filters( 'themeisle_sdk_current_date', new \DateTime( 'now' ) ); + + update_user_meta( get_current_user_id(), self::SDK_BF_DISMISS_META, $now->getTimestamp() ); + } + /** * Get the black friday notices. * @@ -1680,9 +1740,8 @@ public function get_bf_notices( $user_plan ) { */ $now = apply_filters( 'themeisle_sdk_current_date', new \DateTime( 'now' ) ); $current_year = $now->format( 'Y' ); - // Per-year dismissal and UTM, so a banner dismissed last year shows again this year. + // Per-year UTM, so each campaign is tracked separately. $year_suffix = $now->format( 'y' ); - $dismiss_key = sprintf( self::BF_PROMO_DISMISS_KEY, $year_suffix ); $utm_area = 'bfcm' . $year_suffix; $black_friday_day = new \DateTime( "last Friday of November $current_year" ); @@ -1712,7 +1771,8 @@ public function get_bf_notices( $user_plan ) { 'cta_link' => esc_url_raw( tsdk_utmify( tsdk_translate_link( self::get_upgrade_base_link() ), $utm_area, 'sidebarnotice' ) ), ]; - if ( get_option( $dismiss_key ) === 'yes' ) { + // The banner shares the SDK dismissal, which resets every year. + if ( self::is_sdk_bf_notice_dismissed( $now ) ) { return $notices; } @@ -1733,7 +1793,7 @@ public function get_bf_notices( $user_plan ) { 'subtitle' => $message, 'cta_text' => $cta_text, 'cta_link' => $cta_link, - 'dismiss_key' => $dismiss_key, + 'dismiss_key' => self::BF_PROMO_DISMISS_KEY, ]; return $notices; diff --git a/inc/rest.php b/inc/rest.php index c8e28d9b..c9e14eae 100644 --- a/inc/rest.php +++ b/inc/rest.php @@ -920,6 +920,12 @@ public function dismiss_notice( WP_REST_Request $request ) { return $this->response( [ 'error' => 'Invalid key' ], 'error' ); } + if ( Optml_Admin::BF_PROMO_DISMISS_KEY === $key ) { + Optml_Admin::dismiss_sdk_bf_notice(); + + return $this->response( [ 'success' => 'Notice dismissed' ] ); + } + $result = update_option( $key, 'yes' ); if ( ! $result ) { diff --git a/tests/test-admin.php b/tests/test-admin.php index aec0468f..c802aad0 100644 --- a/tests/test-admin.php +++ b/tests/test-admin.php @@ -65,8 +65,8 @@ public function setUp(): void { $this->sale_end->modify( '+7 days' ); $this->sale_end->setTime( 23, 59, 59 ); - // Clean up options before each test - delete_option( $this->bf_dismiss_key() ); + // Each test starts with an admin that has not dismissed the Black Friday notice. + wp_set_current_user( self::factory()->user->create( [ 'role' => 'administrator' ] ) ); // Remove any filters that might be set remove_all_filters( 'themeisle_sdk_is_black_friday_sale' ); @@ -77,19 +77,12 @@ public function setUp(): void { * Clean up after each test. */ public function tearDown(): void { - delete_option( $this->bf_dismiss_key() ); + delete_user_meta( get_current_user_id(), Optml_Admin::SDK_BF_DISMISS_META ); remove_all_filters( 'themeisle_sdk_is_black_friday_sale' ); remove_all_filters( 'themeisle_sdk_current_date' ); parent::tearDown(); } - /** - * Helper to get this year's Black Friday dismiss key. - */ - private function bf_dismiss_key(): string { - return sprintf( Optml_Admin::BF_PROMO_DISMISS_KEY, $this->black_friday->format( 'y' ) ); - } - /** * Helper to set up Black Friday sale period. */ @@ -195,14 +188,14 @@ public function test_get_bf_notices_banner_dismissal() { $this->mock_date_to( clone $this->black_friday ); // Test banner is hidden when dismissed - update_option( $this->bf_dismiss_key(), 'yes' ); + Optml_Admin::dismiss_sdk_bf_notice(); $result = $this->admin->get_bf_notices( 'free' ); $this->assertIsArray( $result ); $this->assertArrayHasKey( 'sidebar', $result ); $this->assertArrayNotHasKey( 'banner', $result ); // Test banner appears when not dismissed - delete_option( $this->bf_dismiss_key() ); + delete_user_meta( get_current_user_id(), Optml_Admin::SDK_BF_DISMISS_META ); $result = $this->admin->get_bf_notices( 'free' ); $this->assertIsArray( $result ); $this->assertArrayHasKey( 'sidebar', $result ); @@ -210,29 +203,29 @@ public function test_get_bf_notices_banner_dismissal() { } /** - * Test the dismiss key and UTM tag follow the year, and the promo code stays BFCM2525. + * Test the dismissal resets and the UTM tag follows the year, and the promo code stays BFCM2525. */ - public function test_get_bf_notices_uses_yearly_dismiss_key_and_utm() { + public function test_get_bf_notices_uses_yearly_dismissal_and_utm() { $this->setup_black_friday_sale_period(); // A dismissal from 2025 does not hide the 2026 banner. - update_option( 'optml_bf25_notice_dismiss', 'yes' ); + $this->mock_date_to( new DateTime( '2025-11-28 12:00:00' ) ); + Optml_Admin::dismiss_sdk_bf_notice(); $this->mock_date_to( new DateTime( '2026-11-27 12:00:00' ) ); $result = $this->admin->get_bf_notices( 'free' ); - delete_option( 'optml_bf25_notice_dismiss' ); + delete_user_meta( get_current_user_id(), Optml_Admin::SDK_BF_DISMISS_META ); $this->assertArrayHasKey( 'banner', $result ); - $this->assertSame( 'optml_bf26_notice_dismiss', $result['banner']['dismiss_key'] ); + $this->assertSame( Optml_Admin::BF_PROMO_DISMISS_KEY, $result['banner']['dismiss_key'] ); $this->assertStringContainsString( 'bfcm26', $result['banner']['cta_link'] ); $this->assertStringContainsString( 'bfcm26', $result['sidebar']['cta_link'] ); $this->assertStringContainsString( 'BFCM2525', $result['banner']['subtitle'] ); $this->assertStringContainsString( 'BFCM2525', $result['sidebar']['subtitle'] ); - // 2025 still uses the legacy key and UTM tag. + // 2025 still uses the legacy UTM tag. $this->mock_date_to( new DateTime( '2025-11-28 12:00:00' ) ); $result = $this->admin->get_bf_notices( 'free' ); - $this->assertSame( 'optml_bf25_notice_dismiss', $result['banner']['dismiss_key'] ); $this->assertStringContainsString( 'bfcm25', $result['banner']['cta_link'] ); $this->assertStringContainsString( 'bfcm25', $result['sidebar']['cta_link'] ); $this->assertStringContainsString( 'BFCM2525', $result['banner']['subtitle'] ); @@ -250,7 +243,7 @@ public function test_get_bf_notices_banner_has_correct_dismiss_key() { $this->assertArrayHasKey( 'banner', $result ); $this->assertArrayHasKey( 'dismiss_key', $result['banner'] ); - $this->assertEquals( $this->bf_dismiss_key(), $result['banner']['dismiss_key'] ); + $this->assertEquals( Optml_Admin::BF_PROMO_DISMISS_KEY, $result['banner']['dismiss_key'] ); } /** @@ -333,23 +326,6 @@ public function test_get_bf_notices_date_scenarios() { $this->assertArrayHasKey( 'sidebar', $result ); } - /** - * Test get_bf_notices banner appears with non-'yes' dismissal values. - */ - public function test_get_bf_notices_banner_dismissal_values() { - $this->setup_black_friday_sale_period(); - $this->mock_date_to( clone $this->black_friday ); - - // Banner only hides when dismissal key is exactly 'yes' - $test_values = [ 'no', '0', '', 'dismissed' ]; - - foreach ( $test_values as $value ) { - update_option( $this->bf_dismiss_key(), $value ); - $result = $this->admin->get_bf_notices( 'free' ); - $this->assertArrayHasKey( 'banner', $result, "Banner should appear when dismiss key is '$value'" ); - } - } - // ------------------------------------------------------------------------- // get_permissions_policy() tests // ------------------------------------------------------------------------- @@ -714,4 +690,64 @@ public function mock_offloaded_svg_download( $preempt, array $args, string $url return $preempt; } + + /** + * Test the SDK Black Friday notice is hidden only when Optimole is the only ThemeIsle product. + */ + public function test_add_black_friday_data_hides_sdk_notice_only_for_optimole_alone() { + // The SDK starts on a later hook, so load its registry class here. + if ( ! class_exists( \ThemeisleSDK\Loader::class ) ) { + require_once OPTML_PATH . 'vendor/codeinwp/themeisle-sdk/src/Loader.php'; + } + + $products = new ReflectionProperty( \ThemeisleSDK\Loader::class, 'products' ); + $products->setAccessible( true ); + $original = $products->getValue(); + + $configs = [ 'default' => [ 'sale_url' => 'https://themeisle.com/blackfriday/' ] ]; + + try { + $products->setValue( null, [ OPTML_PRODUCT_SLUG => 'optimole' ] ); + $result = $this->admin->add_black_friday_data( $configs ); + $this->assertSame( '', $result['default']['sale_url'] ); + + $products->setValue( null, [ OPTML_PRODUCT_SLUG => 'optimole', 'neve' => 'neve' ] ); + $this->assertSame( $configs, $this->admin->add_black_friday_data( $configs ) ); + } finally { + $products->setValue( null, $original ); + } + } + + /** + * Test the banner follows the SDK Black Friday dismissal of the current user. + */ + public function test_get_bf_notices_banner_follows_sdk_dismissal() { + $this->setup_black_friday_sale_period(); + $this->mock_date_to( clone $this->black_friday ); + $last_year = ( clone $this->black_friday )->modify( '-1 year' ); + update_user_meta( get_current_user_id(), Optml_Admin::SDK_BF_DISMISS_META, $last_year->getTimestamp() ); + $result = $this->admin->get_bf_notices( 'free' ); + $this->assertArrayHasKey( 'banner', $result ); + + Optml_Admin::dismiss_sdk_bf_notice(); + $this->assertSame( $this->black_friday->getTimestamp(), (int) get_user_meta( get_current_user_id(), Optml_Admin::SDK_BF_DISMISS_META, true ) ); + + $result = $this->admin->get_bf_notices( 'free' ); + $this->assertArrayHasKey( 'sidebar', $result ); + $this->assertArrayNotHasKey( 'banner', $result ); + } + + /** + * Test the REST dismissal of the banner writes the SDK dismissal, not an option. + */ + public function test_rest_dismiss_bf_notice_uses_sdk_dismissal() { + $this->mock_date_to( clone $this->black_friday ); + + $request = new WP_REST_Request( 'POST' ); + $request->set_param( 'key', Optml_Admin::BF_PROMO_DISMISS_KEY ); + ( new Optml_Rest() )->dismiss_notice( $request ); + + $this->assertFalse( get_option( Optml_Admin::BF_PROMO_DISMISS_KEY ) ); + $this->assertTrue( Optml_Admin::is_sdk_bf_notice_dismissed( clone $this->black_friday ) ); + } }