Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 68 additions & 4 deletions inc/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ 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_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 @@ -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' ] );
}

Expand Down Expand Up @@ -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<string, mixed> $configs The Black Friday configs of the products.
*
* @return array<string, mixed> 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.
*
Expand Down Expand Up @@ -1680,6 +1740,9 @@ public function get_bf_notices( $user_plan ) {
*/
$now = apply_filters( 'themeisle_sdk_current_date', new \DateTime( 'now' ) );
$current_year = $now->format( 'Y' );
// Per-year UTM, so each campaign is tracked separately.
$year_suffix = $now->format( 'y' );
$utm_area = 'bfcm' . $year_suffix;

$black_friday_day = new \DateTime( "last Friday of November $current_year" );
$sale_start = clone $black_friday_day;
Expand All @@ -1705,10 +1768,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() ), $utm_area, '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 +1782,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(), $utm_area, 'notice' ) );
$cta_text = __( 'Claim now', 'optimole-wp' );

$notices['banner'] = [
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::BF_PROMO_DISMISS_KEY === $key ) {
Optml_Admin::dismiss_sdk_bf_notice();

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

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

if ( ! $result ) {
Expand Down
117 changes: 95 additions & 22 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,50 @@ 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 and the UTM tag follows the 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::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 UTM tag.
$this->mock_date_to( new DateTime( '2025-11-28 12:00:00' ) );
$result = $this->admin->get_bf_notices( 'free' );

$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'] );
}

/**
* Test get_bf_notices contains correct dismiss key in banner.
*/
Expand Down Expand Up @@ -296,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( 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 +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 ) );
}
}
Loading