Skip to content
45 changes: 40 additions & 5 deletions inc/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Optml_Admin {

const IMAGE_DATA_COLLECTED_BATCH = 100;

const BF_PROMO_DISMISS_KEY = 'optml_bf25_notice_dismiss';
const SDK_BF_DISMISS_META = 'themeisle_sdk_dismissed_notice_black_friday';

const SPC_BANNER_DISMISS_KEY = 'optml_spc_banner_dismiss';

Expand Down Expand Up @@ -1646,6 +1646,40 @@ private function get_spc_status() {
return 'not-installed';
}

/**
* 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.
*
Expand Down Expand Up @@ -1705,10 +1739,11 @@ public function get_bf_notices( $user_plan ) {
'<span class="border-b border-0 border-white border-dashed text-promo-orange">' . $promo_code . '</span>',
'<span class="text-promo-orange uppercase">' . __( '25% discount', 'optimole-wp' ) . '</span>'
),
'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() ), 'bfcm', 'sidebarnotice' ) ),
];

if ( get_option( self::BF_PROMO_DISMISS_KEY ) === 'yes' ) {
// The banner shares the SDK dismissal, which resets every year.
if ( self::is_sdk_bf_notice_dismissed( $now ) ) {
return $notices;
}

Expand All @@ -1718,7 +1753,7 @@ public function get_bf_notices( $user_plan ) {
'<span class="border-b border-0 border-white border-dashed text-promo-orange">' . $promo_code . '</span>',
'<span class="text-promo-orange uppercase">' . __( '25% discount', 'optimole-wp' ) . '</span>'
);
$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(), 'bfcm', 'notice' ) );
$cta_text = __( 'Claim now', 'optimole-wp' );

$notices['banner'] = [
Expand All @@ -1729,7 +1764,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' => self::SDK_BF_DISMISS_META,
];

return $notices;
Expand Down
6 changes: 6 additions & 0 deletions inc/rest.php
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,12 @@ public function dismiss_notice( WP_REST_Request $request ) {
return $this->response( [ 'error' => 'Invalid key' ], 'error' );
}

if ( Optml_Admin::SDK_BF_DISMISS_META === $key ) {
Optml_Admin::dismiss_sdk_bf_notice();

return $this->response( [ 'success' => 'Notice dismissed' ] );
}

$result = update_option( $key, 'yes' );

if ( ! $result ) {
Expand Down
84 changes: 61 additions & 23 deletions tests/test-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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( Optml_Admin::BF_PROMO_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' );
Expand All @@ -77,7 +77,7 @@ public function setUp(): void {
* Clean up after each test.
*/
public function tearDown(): void {
delete_option( Optml_Admin::BF_PROMO_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();
Expand Down Expand Up @@ -188,20 +188,42 @@ 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' );
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( Optml_Admin::BF_PROMO_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 );
$this->assertArrayHasKey( 'banner', $result );
}

/**
* Test the dismissal resets every year, the UTM campaign has no year, and the promo code stays BFCM2525.
*/
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.
$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_user_meta( get_current_user_id(), Optml_Admin::SDK_BF_DISMISS_META );

$this->assertArrayHasKey( 'banner', $result );
$this->assertSame( Optml_Admin::SDK_BF_DISMISS_META, $result['banner']['dismiss_key'] );
foreach ( [ 'banner', 'sidebar' ] as $notice ) {
parse_str( (string) wp_parse_url( $result[ $notice ]['cta_link'], PHP_URL_QUERY ), $query );
$this->assertSame( 'bfcm', $query['utm_campaign'] );
$this->assertStringContainsString( 'BFCM2525', $result[ $notice ]['subtitle'] );
}
}

/**
* Test get_bf_notices contains correct dismiss key in banner.
*/
Expand All @@ -213,7 +235,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( Optml_Admin::SDK_BF_DISMISS_META, $result['banner']['dismiss_key'] );
}

/**
Expand Down Expand Up @@ -296,23 +318,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( Optml_Admin::BF_PROMO_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
// -------------------------------------------------------------------------
Expand Down Expand Up @@ -677,4 +682,37 @@ public function mock_offloaded_svg_download( $preempt, array $args, string $url

return $preempt;
}

/**
* 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::SDK_BF_DISMISS_META );
( new Optml_Rest() )->dismiss_notice( $request );

$this->assertFalse( get_option( Optml_Admin::SDK_BF_DISMISS_META ) );
$this->assertTrue( Optml_Admin::is_sdk_bf_notice_dismissed( clone $this->black_friday ) );
}
}
Loading