From 960edff53603e698fe2a6bbc3f91d3e7fa02e586 Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Mon, 27 Jul 2026 12:07:42 +0530 Subject: [PATCH 01/22] Legacy license field improvements. 1. If unified license key is set, the legacy license fields are made readonly and disabled. 2. Warn user away from entering unified license key to the legacy license fields. Guide to the LW license manager. 3. Fix bug where incorrect message is show when user tried to enter legacy license when already unified license key is set. --- src/Common/Integrations/Harbor/PUE.php | 163 +++++++++++++++++- src/Common/Libraries/Harbor.php | 60 ++++++- src/Tribe/PUE/Checker.php | 17 ++ src/resources/postcss/utilities | 2 +- .../Common/Integrations/Harbor/PUE_Test.php | 129 ++++++++++++++ .../Tribe/Common/Libraries/Harbor_Test.php | 65 +++++++ 6 files changed, 432 insertions(+), 4 deletions(-) diff --git a/src/Common/Integrations/Harbor/PUE.php b/src/Common/Integrations/Harbor/PUE.php index 02132afb28..c265b95da2 100644 --- a/src/Common/Integrations/Harbor/PUE.php +++ b/src/Common/Integrations/Harbor/PUE.php @@ -41,6 +41,9 @@ protected function do_register(): void { add_filter( 'stellarwp/uplink/tec/license_get_key', [ $this, 'filter_stellarwp_uplink_tec_license_get_key' ], 10, 2 ); add_filter( 'tec_common_uplink_auth_url', [ $this, 'filter_stellarwp_uplink_tec_authorize_button_url' ], 10, 2 ); add_filter( 'pue_get_update_url', [ $this, 'filter_pue_get_update_url' ], 10, 2 ); + add_filter( 'tribe_puc_pre_validate_key', [ $this, 'filter_tribe_puc_pre_validate_key' ], 10, 3 ); + add_filter( 'tribe_settings_save_field_value', [ $this, 'filter_tribe_settings_save_field_value' ], 10, 2 ); + add_filter( 'tribe_license_fields', [ $this, 'filter_tribe_license_fields' ], 30 ); } /** @@ -56,6 +59,150 @@ public function unregister(): void { remove_filter( 'stellarwp/uplink/tec/license_get_key', [ $this, 'filter_stellarwp_uplink_tec_license_get_key' ] ); remove_filter( 'tec_common_uplink_auth_url', [ $this, 'filter_stellarwp_uplink_tec_authorize_button_url' ] ); remove_filter( 'pue_get_update_url', [ $this, 'filter_pue_get_update_url' ] ); + remove_filter( 'tribe_puc_pre_validate_key', [ $this, 'filter_tribe_puc_pre_validate_key' ] ); + remove_filter( 'tribe_settings_save_field_value', [ $this, 'filter_tribe_settings_save_field_value' ] ); + remove_filter( 'tribe_license_fields', [ $this, 'filter_tribe_license_fields' ], 30 ); + } + + /** + * Modify the Harbor-managed legacy fields and make them + * readonly and disabled when the product is licensed via Harbor. + * + * @since TBD + * + * @param array $fields The license fields. + * + * @return array + */ + public function filter_tribe_license_fields( array $fields ): array { + foreach ( $fields as $field_id => &$field ) { + if ( ! is_array( $field ) || ! is_string( $field_id ) ) { + continue; + } + + if ( ! str_starts_with( $field_id, 'pue_install_key_' ) ) { + continue; + } + + if ( ( $field['type'] ?? '' ) !== 'license_key' ) { + continue; + } + + $product = str_replace( [ 'pue_install_key_', '_' ], [ '', '-' ], $field_id ); + if ( ! $this->harbor->is_license_field_managed_by_harbor( $product ) ) { + continue; + } + + $field['attributes'] = array_merge( + $field['attributes'] ?? [], + [ + 'disabled' => 'disabled', + 'readonly' => 'readonly', + ] + ); + } + + return $fields; + } + + /** + * Short-circuit legacy PUE key validation for Harbor unified licenses. + * + * Harbor-managed products already use the unified key, so remote validation is + * skipped. Unified keys pasted into non-managed product fields are rejected with + * a link to the LW License Manager. + * + * @since TBD + * + * @param array|null $response Early response, or null to continue. + * @param string $key The license key being validated. + * @param \Tribe__PUE__Checker|null $checker The PUE checker instance. + * + * @return array|null + */ + public function filter_tribe_puc_pre_validate_key( $response, string $key, $checker ) { + if ( null !== $response ) { + return $response; + } + + if ( ! $checker instanceof \Tribe__PUE__Checker ) { + return $response; + } + + $slug = $checker->get_slug(); + + if ( $this->harbor->is_license_field_managed_by_harbor( $slug ) ) { + return [ + 'status' => 1, + 'message' => __( 'Licensed via Liquid Web License Manager.', 'tribe-common' ), + ]; + } + + if ( $this->harbor->is_unified_license_key( $key ) ) { + return [ + 'status' => 0, + 'message' => $this->harbor->get_unified_license_key_entry_error_message(), + ]; + } + + return $response; + } + + /** + * Prevent storing unified license keys in per-product PUE options. + * + * Harbor-managed fields ignore submitted values so the unified key is not written + * into product options. Unified keys pasted into non-managed fields are rejected + * and the previously stored product key is kept. + * + * @since TBD + * + * @param mixed $value The field value about to be saved. + * @param string $field_id The settings field ID. + * + * @return mixed + */ + public function filter_tribe_settings_save_field_value( $value, $field_id ) { + if ( ! is_string( $field_id ) || ! str_starts_with( $field_id, 'pue_install_key_' ) ) { + return $value; + } + + $product = str_replace( [ 'pue_install_key_', '_' ], [ '', '-' ], $field_id ); + + if ( $this->harbor->is_license_field_managed_by_harbor( $product ) ) { + return $this->get_stored_product_license_key( $field_id ); + } + + if ( is_string( $value ) && $this->harbor->is_unified_license_key( $value ) ) { + return $this->get_stored_product_license_key( $field_id ); + } + + return $value; + } + + /** + * Get the stored product license key without Harbor option overlays. + * + * @since TBD + * + * @param string $option_name The option name. + * + * @return string + */ + private function get_stored_product_license_key( string $option_name ): string { + $had_filter = has_filter( 'pre_option', [ $this, 'filter_pre_get_option' ] ); + + if ( $had_filter ) { + remove_filter( 'pre_option', [ $this, 'filter_pre_get_option' ], 10 ); + } + + $stored = (string) get_option( $option_name, '' ); + + if ( $had_filter ) { + add_filter( 'pre_option', [ $this, 'filter_pre_get_option' ], 10, 3 ); + } + + return $stored; } /** @@ -122,7 +269,21 @@ public function filter_pre_get_option( $value, $option, $default_value ) { } /** - * Filter the pre HTTP request. + * Short-circuit PUE license validation HTTP requests for Harbor-licensed products. + * + * Legacy PUE code (e.g. Tribe__PUE__Checker) validates license keys by POSTing to + * `/api/plugins/v2/license/validate` on Stellar's licensing servers. Harbor-hosted + * sites use a unified platform license instead of per-plugin keys, so those remote + * calls are unnecessary and may fail or return stale data. + * + * This filter intercepts matching outbound requests via `pre_http_request` and returns + * a synthetic HTTP 200 response shaped like the PUE API, built from Harbor's cached + * license and catalog data. When catalog details are available, the response includes + * product metadata and expiration; otherwise a minimal stub response is returned so + * validation still succeeds for licensed Harbor products. + * + * Only requests to the validate endpoint for products reported as licensed by Harbor + * are intercepted. All other HTTP traffic is left unchanged. * * @since 6.11.0 * diff --git a/src/Common/Libraries/Harbor.php b/src/Common/Libraries/Harbor.php index e55f6efbc5..ee71ecf3ff 100644 --- a/src/Common/Libraries/Harbor.php +++ b/src/Common/Libraries/Harbor.php @@ -29,12 +29,21 @@ * @package TEC\Common\Libraries\Harbor */ class Harbor extends Controller_Contract { + /** + * Prefix for Liquid Web unified license keys. + * + * @since 6.12.0 + * + * @var string + */ + public const UNIFIED_LICENSE_KEY_PREFIX = 'LWSW-'; + /** * The TEC product slug to Harbor product slug map. * * @since 6.11.0 * - * @var array + * @var array */ private const TEC_PRODUCT_SLUG_TO_HARBOR_PRODUCT_SLUG_MAP = [ 'the-events-calendar' => 'the-events-calendar', @@ -232,7 +241,7 @@ public function register_legacy_licenses( array $licenses ): array { return array_values( array_filter( $licenses, - static fn( array $license ): bool => ! empty( $license['key'] ) && ! str_starts_with( $license['key'], 'LWSW-' ) + fn( array $license ): bool => ! empty( $license['key'] ) && ! $this->is_unified_license_key( $license['key'] ) ) ); } @@ -265,6 +274,53 @@ public function is_product_licensed( string $product ): bool { return lw_harbor_is_feature_available( $product ); } + /** + * Whether a license key uses the unified Liquid Web format. + * + * @since 6.12.0 + * + * @param string $key The license key. + * + * @return bool + */ + public function is_unified_license_key( string $key ): bool { + return str_starts_with( trim( $key ), self::UNIFIED_LICENSE_KEY_PREFIX ); + } + + /** + * Whether a TEC product license field is managed by Harbor. + * + * When true, the unified license key should be shown read-only and should not + * be validated through legacy PUE per-product fields. + * + * @since 6.12.0 + * + * @param string $tec_product_slug The TEC product slug. + * + * @return bool + */ + public function is_license_field_managed_by_harbor( string $tec_product_slug ): bool { + return $this->is_product_licensed( + $this->get_harbor_product_slug( $tec_product_slug ) + ); + } + + /** + * Error message shown when a unified license key is entered in a per-product field. + * + * @since 6.12.0 + * + * @return string + */ + public function get_unified_license_key_entry_error_message(): string { + return sprintf( + /* translators: %1$s: opening anchor tag, %2$s: closing anchor tag. */ + __( 'It seems to be a unified license key. Please %1$sclick here%2$s to enter it in the LW License Manager.', 'tribe-common' ), + '', + '' + ); + } + /** * Get the unified license key if the feature is enabled. * diff --git a/src/Tribe/PUE/Checker.php b/src/Tribe/PUE/Checker.php index 9184cbccb4..e0777341db 100755 --- a/src/Tribe/PUE/Checker.php +++ b/src/Tribe/PUE/Checker.php @@ -1238,6 +1238,23 @@ public function validate_key( string $key, bool $network = false ): array { return []; } + /** + * Filter early license key validation before remote PUE checks. + * + * Returning a non-null array short-circuits remote validation and is used as + * the AJAX/validation response. Return null to continue normal validation. + * + * @since 6.12.0 + * + * @param array|null $response Early response, or null to continue. + * @param string $key The license key being validated. + * @param Tribe__PUE__Checker $checker The PUE checker instance. + */ + $pre_validate = apply_filters( 'tribe_puc_pre_validate_key', null, $key, $this ); + if ( is_array( $pre_validate ) ) { + return $pre_validate; + } + $uplink_resource = $this->get_uplink_resource( $this->get_slug() ); if ( $uplink_resource ) { diff --git a/src/resources/postcss/utilities b/src/resources/postcss/utilities index c1f168a5dd..5d5bc15005 160000 --- a/src/resources/postcss/utilities +++ b/src/resources/postcss/utilities @@ -1 +1 @@ -Subproject commit c1f168a5dd51e0ded1d68bd2e2d54ac5b902ab88 +Subproject commit 5d5bc150052b5d044d45f17ee8cebabab8958d2c diff --git a/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php b/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php index 1a1d9c200c..798424fb3b 100644 --- a/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php +++ b/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php @@ -351,4 +351,133 @@ public function auth_url_decorator_provider(): array { 'non-matching slug leaves URL untouched' => [ 'events-calendar-pro', 'https://example.com/seating-connect/', false ], ]; } + + /** + * @test + */ + public function it_should_skip_remote_validation_for_harbor_managed_product(): void { + $this->seed_unified_license_key(); + $this->seed_harbor_catalog_for_tec( [ 'events-calendar-pro' ] ); + + $checker = new \Tribe__PUE__Checker( 'deprecated', 'events-calendar-pro', [], 'events-calendar-pro/events-calendar-pro.php' ); + $response = $checker->validate_key( 'any-key-value' ); + + $this->assertSame( 1, $response['status'] ); + $this->assertStringContainsString( 'Liquid Web License Manager', $response['message'] ); + } + + /** + * @test + */ + public function it_should_reject_unified_key_for_non_harbor_managed_product(): void { + $this->seed_unified_license_key(); + $this->seed_harbor_catalog_for_tec( [ 'events-calendar-pro' ] ); + + $checker = new \Tribe__PUE__Checker( 'deprecated', 'tribe-filterbar', [], 'the-events-calendar-filterbar/the-events-calendar-filterbar.php' ); + $response = $checker->validate_key( 'LWSW-PASTED-INTO-WRONG-FIELD' ); + + $this->assertSame( 0, $response['status'] ); + $this->assertStringContainsString( 'unified license key', strtolower( wp_strip_all_tags( $response['message'] ) ) ); + $this->assertStringContainsString( 'assertStringContainsString( 'LW License Manager', $message ); + } + /** * @test */ From fa916c2489fa824ed1c3647ffb1200fb49464496 Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Mon, 27 Jul 2026 12:23:51 +0530 Subject: [PATCH 02/22] changelog --- changelog/update-license-field-improvements | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelog/update-license-field-improvements diff --git a/changelog/update-license-field-improvements b/changelog/update-license-field-improvements new file mode 100644 index 0000000000..7936ee7a88 --- /dev/null +++ b/changelog/update-license-field-improvements @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Warn users when they attempt to enter a Unified license key into the standalone license key field From 859574d6f71c180c74cebc306fe5a8fb4e322013 Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Mon, 27 Jul 2026 13:32:04 +0530 Subject: [PATCH 03/22] fix broken test --- src/Common/Libraries/Harbor.php | 2 +- .../Tribe/Common/Integrations/Harbor/PUE_Test.php | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Common/Libraries/Harbor.php b/src/Common/Libraries/Harbor.php index ee71ecf3ff..7294d68a49 100644 --- a/src/Common/Libraries/Harbor.php +++ b/src/Common/Libraries/Harbor.php @@ -316,7 +316,7 @@ public function get_unified_license_key_entry_error_message(): string { return sprintf( /* translators: %1$s: opening anchor tag, %2$s: closing anchor tag. */ __( 'It seems to be a unified license key. Please %1$sclick here%2$s to enter it in the LW License Manager.', 'tribe-common' ), - '', + '', '' ); } diff --git a/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php b/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php index 798424fb3b..0e16def1e3 100644 --- a/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php +++ b/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php @@ -398,11 +398,11 @@ public function it_should_pass_through_pre_validate_for_normal_key_on_non_manage * @test */ public function it_should_ignore_submitted_value_for_harbor_managed_license_field_on_save(): void { + update_option( 'pue_install_key_events_calendar_pro', 'legacy-ecp-key' ); + $this->seed_unified_license_key(); $this->seed_harbor_catalog_for_tec( [ 'events-calendar-pro' ] ); - update_option( 'pue_install_key_events_calendar_pro', 'legacy-ecp-key' ); - $saved = apply_filters( 'tribe_settings_save_field_value', 'LWSW-SHOULD-NOT-BE-STORED', @@ -463,21 +463,17 @@ public function it_should_disable_harbor_managed_legacy_license_fields(): void { 'pue_install_key_events_calendar_pro' => [ 'type' => 'license_key', 'attributes' => [], - 'tooltip' => 'A valid license key is required', ], 'pue_install_key_tribe_filterbar' => [ 'type' => 'license_key', 'attributes' => [], - 'tooltip' => 'A valid license key is required', ], ] ); $this->assertSame( 'disabled', $fields['pue_install_key_events_calendar_pro']['attributes']['disabled'] ); $this->assertSame( 'readonly', $fields['pue_install_key_events_calendar_pro']['attributes']['readonly'] ); - $this->assertStringContainsString( 'Liquid Web License Manager', $fields['pue_install_key_events_calendar_pro']['tooltip'] ); $this->assertArrayNotHasKey( 'disabled', $fields['pue_install_key_tribe_filterbar']['attributes'] ); - $this->assertSame( 'A valid license key is required', $fields['pue_install_key_tribe_filterbar']['tooltip'] ); } } From ae61456f865463869f9142b3acc69a7be87c713d Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Mon, 27 Jul 2026 14:32:06 +0530 Subject: [PATCH 04/22] improve comment for filter_pre_http_request function --- src/Common/Integrations/Harbor/PUE.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Common/Integrations/Harbor/PUE.php b/src/Common/Integrations/Harbor/PUE.php index c265b95da2..cb83b967f3 100644 --- a/src/Common/Integrations/Harbor/PUE.php +++ b/src/Common/Integrations/Harbor/PUE.php @@ -272,15 +272,13 @@ public function filter_pre_get_option( $value, $option, $default_value ) { * Short-circuit PUE license validation HTTP requests for Harbor-licensed products. * * Legacy PUE code (e.g. Tribe__PUE__Checker) validates license keys by POSTing to - * `/api/plugins/v2/license/validate` on Stellar's licensing servers. Harbor-hosted - * sites use a unified platform license instead of per-plugin keys, so those remote + * `/api/plugins/v2/license/validate` on Stellar's licensing servers. For unified licensed + * sites, the license is managed by Harbor and at the whole site level, so those remote * calls are unnecessary and may fail or return stale data. * - * This filter intercepts matching outbound requests via `pre_http_request` and returns + * This filter intercepts those license validation requests via `pre_http_request` and returns * a synthetic HTTP 200 response shaped like the PUE API, built from Harbor's cached - * license and catalog data. When catalog details are available, the response includes - * product metadata and expiration; otherwise a minimal stub response is returned so - * validation still succeeds for licensed Harbor products. + * license and catalog data. * * Only requests to the validate endpoint for products reported as licensed by Harbor * are intercepted. All other HTTP traffic is left unchanged. From b18e87d47073620f77134c5f4d9932a91dfee52f Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Mon, 27 Jul 2026 17:15:44 +0530 Subject: [PATCH 05/22] rename functions for better understanding --- src/Common/Integrations/Harbor/PUE.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Common/Integrations/Harbor/PUE.php b/src/Common/Integrations/Harbor/PUE.php index cb83b967f3..acec6a07d9 100644 --- a/src/Common/Integrations/Harbor/PUE.php +++ b/src/Common/Integrations/Harbor/PUE.php @@ -42,8 +42,8 @@ protected function do_register(): void { add_filter( 'tec_common_uplink_auth_url', [ $this, 'filter_stellarwp_uplink_tec_authorize_button_url' ], 10, 2 ); add_filter( 'pue_get_update_url', [ $this, 'filter_pue_get_update_url' ], 10, 2 ); add_filter( 'tribe_puc_pre_validate_key', [ $this, 'filter_tribe_puc_pre_validate_key' ], 10, 3 ); - add_filter( 'tribe_settings_save_field_value', [ $this, 'filter_tribe_settings_save_field_value' ], 10, 2 ); - add_filter( 'tribe_license_fields', [ $this, 'filter_tribe_license_fields' ], 30 ); + add_filter( 'tribe_settings_save_field_value', [ $this, 'prevent_storing_unified_license_key' ], 10, 2 ); + add_filter( 'tribe_license_fields', [ $this, 'readonly_and_disable_harbor_managed_license_fields' ], 30 ); } /** @@ -60,8 +60,8 @@ public function unregister(): void { remove_filter( 'tec_common_uplink_auth_url', [ $this, 'filter_stellarwp_uplink_tec_authorize_button_url' ] ); remove_filter( 'pue_get_update_url', [ $this, 'filter_pue_get_update_url' ] ); remove_filter( 'tribe_puc_pre_validate_key', [ $this, 'filter_tribe_puc_pre_validate_key' ] ); - remove_filter( 'tribe_settings_save_field_value', [ $this, 'filter_tribe_settings_save_field_value' ] ); - remove_filter( 'tribe_license_fields', [ $this, 'filter_tribe_license_fields' ], 30 ); + remove_filter( 'tribe_settings_save_field_value', [ $this, 'prevent_storing_unified_license_key' ] ); + remove_filter( 'tribe_license_fields', [ $this, 'readonly_and_disable_harbor_managed_license_fields' ], 30 ); } /** @@ -74,7 +74,7 @@ public function unregister(): void { * * @return array */ - public function filter_tribe_license_fields( array $fields ): array { + public function readonly_and_disable_harbor_managed_license_fields( array $fields ): array { foreach ( $fields as $field_id => &$field ) { if ( ! is_array( $field ) || ! is_string( $field_id ) ) { continue; @@ -162,7 +162,7 @@ public function filter_tribe_puc_pre_validate_key( $response, string $key, $chec * * @return mixed */ - public function filter_tribe_settings_save_field_value( $value, $field_id ) { + public function prevent_storing_unified_license_key( $value, $field_id ) { if ( ! is_string( $field_id ) || ! str_starts_with( $field_id, 'pue_install_key_' ) ) { return $value; } From 3d08cb4ef671b26b7ce512d00bed907c2124cb2a Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Mon, 27 Jul 2026 17:37:07 +0530 Subject: [PATCH 06/22] add link to the Liquid Web License Manager in the under field notice --- src/Common/Integrations/Harbor/PUE.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Common/Integrations/Harbor/PUE.php b/src/Common/Integrations/Harbor/PUE.php index acec6a07d9..e3d959652e 100644 --- a/src/Common/Integrations/Harbor/PUE.php +++ b/src/Common/Integrations/Harbor/PUE.php @@ -134,7 +134,11 @@ public function filter_tribe_puc_pre_validate_key( $response, string $key, $chec if ( $this->harbor->is_license_field_managed_by_harbor( $slug ) ) { return [ 'status' => 1, - 'message' => __( 'Licensed via Liquid Web License Manager.', 'tribe-common' ), + 'message' => sprintf( + /* translators: URL to the Liquid Web License Manager */ + __( 'Licensed via Liquid Web License Manager', 'tribe-common' ), + esc_url( lw_harbor_get_license_page_url() ), + ), ]; } From 07c8007e059f38f11b49174402e69c4973c9cabf Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Tue, 28 Jul 2026 11:58:28 +0530 Subject: [PATCH 07/22] fix harbor-managed license field check to use per-site activated capabilities `is_license_field_managed_by_harbor()` was checking tier entitlement (`is_product_licensed()`), which only confirms a customer's tier covers a product, not that it's actually licensed for this site. It also didn't account for accounts holding license entries for multiple sites, so a capability granted only to another site's entry could incorrectly read as active here. Add `is_capability_license_active()`, which checks the `capabilities` list of `Product_Entry` rows that are both valid and `activated_here`, matching the real shape of Harbor's licensing data (one entry per bundle+tier, add-ons listed as capabilities rather than their own product slug). --- src/Common/Libraries/Harbor.php | 49 ++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/Common/Libraries/Harbor.php b/src/Common/Libraries/Harbor.php index 7294d68a49..3e1d4c0800 100644 --- a/src/Common/Libraries/Harbor.php +++ b/src/Common/Libraries/Harbor.php @@ -11,6 +11,8 @@ use TEC\Common\Integrations\Harbor\EventAggregator; use TEC\Common\Integrations\Harbor\PUE; use TEC\Common\Integrations\Harbor\PUE_Resolver; +use TEC\Common\LiquidWeb\Harbor\Licensing\Product_Collection; +use TEC\Common\LiquidWeb\Harbor\Licensing\Repositories\License_Repository; use TEC\Common\StellarWP\Uplink\API\V3\Auth\Contracts\Auth_Url; use TEC\Common\Integrations\Uplink\Auth_URL_Decorator; use Tribe__Dependency as Dependency; @@ -293,18 +295,63 @@ public function is_unified_license_key( string $key ): bool { * When true, the unified license key should be shown read-only and should not * be validated through legacy PUE per-product fields. * + * Checks that the product's license is actually active/valid for this site, not + * merely that the customer's tier entitles them to it — a customer can be entitled + * to a product without having activated it here, in which case a legacy per-product + * key should still be accepted. + * * @since 6.12.0 + * @since TBD Check per-site license activation of the product's capability via + * `is_capability_license_active()` instead of tier entitlement via + * `is_product_licensed()`. * * @param string $tec_product_slug The TEC product slug. * * @return bool */ public function is_license_field_managed_by_harbor( string $tec_product_slug ): bool { - return $this->is_product_licensed( + if ( ! lw_harbor_has_unified_license_key() ) { + return false; + } + + return $this->is_capability_license_active( $this->get_harbor_product_slug( $tec_product_slug ) ); } + /** + * Whether a capability/add-on slug is granted by any activated, valid Harbor license. + * + * The Harbor licensing API returns one Product_Entry per licensed bundle+tier + * (e.g. "the-events-calendar:pro"), not one entry per add-on. Individual add-on + * slugs (e.g. "events-calendar-pro", "event-aggregator") only appear inside + * that entry's `capabilities` list. + * + * A single account can hold entries for multiple sites that's why only entries with + * `activated_here` true are eligible. + * + * @since TBD + * + * @param string $capability_slug The Harbor capability/add-on slug. + * + * @return bool + */ + private function is_capability_license_active( string $capability_slug ): bool { + $products = Config::get_container()->get( License_Repository::class )->get_products(); + + if ( ! $products instanceof Product_Collection ) { + return false; + } + + foreach ( $products as $entry ) { + if ( $entry->is_valid() && $entry->get_activated_here() && in_array( $capability_slug, $entry->get_capabilities(), true ) ) { + return true; + } + } + + return false; + } + /** * Error message shown when a unified license key is entered in a per-product field. * From c401e0279a091060bd58be54fe49095dd236db70 Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Wed, 29 Jul 2026 12:52:43 +0530 Subject: [PATCH 08/22] utiilise the new harbor function lw_harbor_is_capability_license_active instead of creating a new one --- composer.json | 2 +- src/Common/Libraries/Harbor.php | 40 +++------------------------------ 2 files changed, 4 insertions(+), 38 deletions(-) diff --git a/composer.json b/composer.json index 372496f1a2..0d03150107 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "stellarwp/assets": "^1.5", "stellarwp/container-contract": "^1.0.4", "stellarwp/db": "^1.0.3", - "stellarwp/harbor": "^1.4", + "stellarwp/harbor": "dev-SMTNC-1895", "stellarwp/installer": "^1.1.0", "stellarwp/telemetry": "^2.3.4", "stellarwp/uplink": "2.2.2", diff --git a/src/Common/Libraries/Harbor.php b/src/Common/Libraries/Harbor.php index 3e1d4c0800..a7017bbfb2 100644 --- a/src/Common/Libraries/Harbor.php +++ b/src/Common/Libraries/Harbor.php @@ -11,8 +11,6 @@ use TEC\Common\Integrations\Harbor\EventAggregator; use TEC\Common\Integrations\Harbor\PUE; use TEC\Common\Integrations\Harbor\PUE_Resolver; -use TEC\Common\LiquidWeb\Harbor\Licensing\Product_Collection; -use TEC\Common\LiquidWeb\Harbor\Licensing\Repositories\License_Repository; use TEC\Common\StellarWP\Uplink\API\V3\Auth\Contracts\Auth_Url; use TEC\Common\Integrations\Uplink\Auth_URL_Decorator; use Tribe__Dependency as Dependency; @@ -22,6 +20,7 @@ use function lw_harbor_get_unified_license_key; use function lw_harbor_is_feature_enabled; use function lw_harbor_is_feature_available; +use function lw_harbor_is_capability_license_active; /** * Controller for setting up the Harbor library. @@ -302,7 +301,7 @@ public function is_unified_license_key( string $key ): bool { * * @since 6.12.0 * @since TBD Check per-site license activation of the product's capability via - * `is_capability_license_active()` instead of tier entitlement via + * `lw_harbor_is_capability_license_active()` instead of tier entitlement via * `is_product_licensed()`. * * @param string $tec_product_slug The TEC product slug. @@ -314,44 +313,11 @@ public function is_license_field_managed_by_harbor( string $tec_product_slug ): return false; } - return $this->is_capability_license_active( + return lw_harbor_is_capability_license_active( $this->get_harbor_product_slug( $tec_product_slug ) ); } - /** - * Whether a capability/add-on slug is granted by any activated, valid Harbor license. - * - * The Harbor licensing API returns one Product_Entry per licensed bundle+tier - * (e.g. "the-events-calendar:pro"), not one entry per add-on. Individual add-on - * slugs (e.g. "events-calendar-pro", "event-aggregator") only appear inside - * that entry's `capabilities` list. - * - * A single account can hold entries for multiple sites that's why only entries with - * `activated_here` true are eligible. - * - * @since TBD - * - * @param string $capability_slug The Harbor capability/add-on slug. - * - * @return bool - */ - private function is_capability_license_active( string $capability_slug ): bool { - $products = Config::get_container()->get( License_Repository::class )->get_products(); - - if ( ! $products instanceof Product_Collection ) { - return false; - } - - foreach ( $products as $entry ) { - if ( $entry->is_valid() && $entry->get_activated_here() && in_array( $capability_slug, $entry->get_capabilities(), true ) ) { - return true; - } - } - - return false; - } - /** * Error message shown when a unified license key is entered in a per-product field. * From aab9e63169c7d426bf841a9ed5bc2a2ec8cd48ce Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Wed, 29 Jul 2026 12:56:33 +0530 Subject: [PATCH 09/22] updated composer.lock --- composer.lock | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index 5b41e8be1f..e00de5bab8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3af55c29ec587b9a48fe41ecbcf19dbe", + "content-hash": "29b2926e462fbcbb52bf2698baba5707", "packages": [ { "name": "firebase/php-jwt", @@ -895,16 +895,16 @@ }, { "name": "stellarwp/harbor", - "version": "v1.4.0", + "version": "dev-SMTNC-1895", "source": { "type": "git", "url": "https://github.com/stellarwp/harbor.git", - "reference": "0d21b6e6da4352364610168c053ed0ec9d59253d" + "reference": "08d1f6da0b4ed54024886915f1143f6731fa1f20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stellarwp/harbor/zipball/0d21b6e6da4352364610168c053ed0ec9d59253d", - "reference": "0d21b6e6da4352364610168c053ed0ec9d59253d", + "url": "https://api.github.com/repos/stellarwp/harbor/zipball/08d1f6da0b4ed54024886915f1143f6731fa1f20", + "reference": "08d1f6da0b4ed54024886915f1143f6731fa1f20", "shasum": "" }, "require": { @@ -959,9 +959,9 @@ "description": "A library that integrates a WordPress product with the Liquid Web licensing system.", "support": { "issues": "https://github.com/stellarwp/harbor/issues", - "source": "https://github.com/stellarwp/harbor/tree/v1.4.0" + "source": "https://github.com/stellarwp/harbor/tree/SMTNC-1895" }, - "time": "2026-05-21T17:13:36+00:00" + "time": "2026-07-29T06:33:31+00:00" }, { "name": "stellarwp/installer", @@ -7816,6 +7816,7 @@ "minimum-stability": "dev", "stability-flags": { "stellarwp/coding-standards": 20, + "stellarwp/harbor": 20, "the-events-calendar/tec-testing-facilities": 20 }, "prefer-stable": true, @@ -7825,5 +7826,5 @@ "platform-overrides": { "php": "7.4.0" }, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" } From 0b03b38b7b0e4027653b9273f4850bb632949456 Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Mon, 10 Aug 2026 12:21:09 +0530 Subject: [PATCH 10/22] 1. Fix @since TBD versions 2. Restore submodule reference to src/resources/postcss/utilities 3. Added type hints to the PUE functions --- src/Common/Integrations/Harbor/PUE.php | 10 +++++----- src/Common/Libraries/Harbor.php | 13 +++++-------- src/Tribe/PUE/Checker.php | 2 +- src/resources/postcss/utilities | 2 +- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/Common/Integrations/Harbor/PUE.php b/src/Common/Integrations/Harbor/PUE.php index e3d959652e..946fe93437 100644 --- a/src/Common/Integrations/Harbor/PUE.php +++ b/src/Common/Integrations/Harbor/PUE.php @@ -120,7 +120,7 @@ public function readonly_and_disable_harbor_managed_license_fields( array $field * * @return array|null */ - public function filter_tribe_puc_pre_validate_key( $response, string $key, $checker ) { + public function filter_tribe_puc_pre_validate_key( ?array $response, string $key, $checker ): ?array { if ( null !== $response ) { return $response; } @@ -237,7 +237,7 @@ public function filter_pue_get_update_url( string $update_url, string $slug ): s * * @return ?string */ - public function filter_stellarwp_uplink_tec_license_get_key( ?string $license, Uplink_Resource $uplink_resource ) { + public function filter_stellarwp_uplink_tec_license_get_key( ?string $license, Uplink_Resource $uplink_resource ): ?string { $harbor_slug = $this->harbor->get_harbor_product_slug( $uplink_resource->get_slug() ); if ( ! $this->harbor->is_product_licensed( $harbor_slug ) ) { return $license; @@ -289,9 +289,9 @@ public function filter_pre_get_option( $value, $option, $default_value ) { * * @since 6.11.0 * - * @param false|array|WP_Error $response The response. - * @param array $parsed_args The parsed arguments. - * @param string $url The URL. + * @param false|array|\WP_Error $response The response. + * @param array $parsed_args The parsed arguments. + * @param string $url The URL. * * @return false|array */ diff --git a/src/Common/Libraries/Harbor.php b/src/Common/Libraries/Harbor.php index a7017bbfb2..43f35c5e3e 100644 --- a/src/Common/Libraries/Harbor.php +++ b/src/Common/Libraries/Harbor.php @@ -33,7 +33,7 @@ class Harbor extends Controller_Contract { /** * Prefix for Liquid Web unified license keys. * - * @since 6.12.0 + * @since TBD * * @var string */ @@ -278,7 +278,7 @@ public function is_product_licensed( string $product ): bool { /** * Whether a license key uses the unified Liquid Web format. * - * @since 6.12.0 + * @since TBD * * @param string $key The license key. * @@ -299,10 +299,7 @@ public function is_unified_license_key( string $key ): bool { * to a product without having activated it here, in which case a legacy per-product * key should still be accepted. * - * @since 6.12.0 - * @since TBD Check per-site license activation of the product's capability via - * `lw_harbor_is_capability_license_active()` instead of tier entitlement via - * `is_product_licensed()`. + * @since TBD * * @param string $tec_product_slug The TEC product slug. * @@ -321,7 +318,7 @@ public function is_license_field_managed_by_harbor( string $tec_product_slug ): /** * Error message shown when a unified license key is entered in a per-product field. * - * @since 6.12.0 + * @since TBD * * @return string */ @@ -329,7 +326,7 @@ public function get_unified_license_key_entry_error_message(): string { return sprintf( /* translators: %1$s: opening anchor tag, %2$s: closing anchor tag. */ __( 'It seems to be a unified license key. Please %1$sclick here%2$s to enter it in the LW License Manager.', 'tribe-common' ), - '', + '', '' ); } diff --git a/src/Tribe/PUE/Checker.php b/src/Tribe/PUE/Checker.php index e0777341db..91503d29e4 100755 --- a/src/Tribe/PUE/Checker.php +++ b/src/Tribe/PUE/Checker.php @@ -1244,7 +1244,7 @@ public function validate_key( string $key, bool $network = false ): array { * Returning a non-null array short-circuits remote validation and is used as * the AJAX/validation response. Return null to continue normal validation. * - * @since 6.12.0 + * @since TBD * * @param array|null $response Early response, or null to continue. * @param string $key The license key being validated. diff --git a/src/resources/postcss/utilities b/src/resources/postcss/utilities index 5d5bc15005..c1f168a5dd 160000 --- a/src/resources/postcss/utilities +++ b/src/resources/postcss/utilities @@ -1 +1 @@ -Subproject commit 5d5bc150052b5d044d45f17ee8cebabab8958d2c +Subproject commit c1f168a5dd51e0ded1d68bd2e2d54ac5b902ab88 From a8f9d7436cff5db358395d3be48a17a92bfad402 Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Mon, 10 Aug 2026 12:49:20 +0530 Subject: [PATCH 11/22] update the error message for unified key - replace LW License manager with Unified License Manager --- src/Common/Libraries/Harbor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/Libraries/Harbor.php b/src/Common/Libraries/Harbor.php index 43f35c5e3e..1566529760 100644 --- a/src/Common/Libraries/Harbor.php +++ b/src/Common/Libraries/Harbor.php @@ -325,7 +325,7 @@ public function is_license_field_managed_by_harbor( string $tec_product_slug ): public function get_unified_license_key_entry_error_message(): string { return sprintf( /* translators: %1$s: opening anchor tag, %2$s: closing anchor tag. */ - __( 'It seems to be a unified license key. Please %1$sclick here%2$s to enter it in the LW License Manager.', 'tribe-common' ), + __( 'It is a unified license key. Please %1$sclick here%2$s to enter it in the Unified License Manager.', 'tribe-common' ), '', '' ); From e2231e84ef9cbbce0372ffcdaa1e3038f1d9fc19 Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Mon, 10 Aug 2026 12:59:10 +0530 Subject: [PATCH 12/22] replace tribe_puc_pre_validate_key with tec_common_pue_pre_validate_key --- src/Common/Integrations/Harbor/PUE.php | 6 +++--- src/Tribe/PUE/Checker.php | 2 +- .../Tribe/Common/Integrations/Harbor/PUE_Test.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Common/Integrations/Harbor/PUE.php b/src/Common/Integrations/Harbor/PUE.php index 946fe93437..92f805a6f4 100644 --- a/src/Common/Integrations/Harbor/PUE.php +++ b/src/Common/Integrations/Harbor/PUE.php @@ -41,7 +41,7 @@ protected function do_register(): void { add_filter( 'stellarwp/uplink/tec/license_get_key', [ $this, 'filter_stellarwp_uplink_tec_license_get_key' ], 10, 2 ); add_filter( 'tec_common_uplink_auth_url', [ $this, 'filter_stellarwp_uplink_tec_authorize_button_url' ], 10, 2 ); add_filter( 'pue_get_update_url', [ $this, 'filter_pue_get_update_url' ], 10, 2 ); - add_filter( 'tribe_puc_pre_validate_key', [ $this, 'filter_tribe_puc_pre_validate_key' ], 10, 3 ); + add_filter( 'tec_common_pue_pre_validate_key', [ $this, 'filter_tec_common_pue_pre_validate_key' ], 10, 3 ); add_filter( 'tribe_settings_save_field_value', [ $this, 'prevent_storing_unified_license_key' ], 10, 2 ); add_filter( 'tribe_license_fields', [ $this, 'readonly_and_disable_harbor_managed_license_fields' ], 30 ); } @@ -59,7 +59,7 @@ public function unregister(): void { remove_filter( 'stellarwp/uplink/tec/license_get_key', [ $this, 'filter_stellarwp_uplink_tec_license_get_key' ] ); remove_filter( 'tec_common_uplink_auth_url', [ $this, 'filter_stellarwp_uplink_tec_authorize_button_url' ] ); remove_filter( 'pue_get_update_url', [ $this, 'filter_pue_get_update_url' ] ); - remove_filter( 'tribe_puc_pre_validate_key', [ $this, 'filter_tribe_puc_pre_validate_key' ] ); + remove_filter( 'tec_common_pue_pre_validate_key', [ $this, 'filter_tec_common_pue_pre_validate_key' ] ); remove_filter( 'tribe_settings_save_field_value', [ $this, 'prevent_storing_unified_license_key' ] ); remove_filter( 'tribe_license_fields', [ $this, 'readonly_and_disable_harbor_managed_license_fields' ], 30 ); } @@ -120,7 +120,7 @@ public function readonly_and_disable_harbor_managed_license_fields( array $field * * @return array|null */ - public function filter_tribe_puc_pre_validate_key( ?array $response, string $key, $checker ): ?array { + public function filter_tec_common_pue_pre_validate_key( ?array $response, string $key, $checker ): ?array { if ( null !== $response ) { return $response; } diff --git a/src/Tribe/PUE/Checker.php b/src/Tribe/PUE/Checker.php index 91503d29e4..528e22aa81 100755 --- a/src/Tribe/PUE/Checker.php +++ b/src/Tribe/PUE/Checker.php @@ -1250,7 +1250,7 @@ public function validate_key( string $key, bool $network = false ): array { * @param string $key The license key being validated. * @param Tribe__PUE__Checker $checker The PUE checker instance. */ - $pre_validate = apply_filters( 'tribe_puc_pre_validate_key', null, $key, $this ); + $pre_validate = apply_filters( 'tec_common_pue_pre_validate_key', null, $key, $this ); if ( is_array( $pre_validate ) ) { return $pre_validate; } diff --git a/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php b/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php index 0e16def1e3..860342b3c8 100644 --- a/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php +++ b/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php @@ -389,7 +389,7 @@ public function it_should_pass_through_pre_validate_for_normal_key_on_non_manage $this->seed_harbor_catalog_for_tec( [ 'events-calendar-pro' ] ); $checker = new \Tribe__PUE__Checker( 'deprecated', 'tribe-filterbar', [], 'the-events-calendar-filterbar/the-events-calendar-filterbar.php' ); - $result = apply_filters( 'tribe_puc_pre_validate_key', null, 'legacy-product-key', $checker ); + $result = apply_filters( 'tec_common_pue_pre_validate_key', null, 'legacy-product-key', $checker ); $this->assertNull( $result ); } From ddce579054f823ddad409c7acb3e9db473705a56 Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Mon, 10 Aug 2026 17:22:42 +0530 Subject: [PATCH 13/22] handle condition when no pro plugins are active and harbor doesnt load - update the error message to guide user accordingly --- src/Common/Libraries/Harbor.php | 49 ++++++++++- .../Tribe/Common/Libraries/Harbor_Test.php | 88 +++++++++++++++++++ 2 files changed, 135 insertions(+), 2 deletions(-) diff --git a/src/Common/Libraries/Harbor.php b/src/Common/Libraries/Harbor.php index 1566529760..b6d18e5cae 100644 --- a/src/Common/Libraries/Harbor.php +++ b/src/Common/Libraries/Harbor.php @@ -37,7 +37,7 @@ class Harbor extends Controller_Contract { * * @var string */ - public const UNIFIED_LICENSE_KEY_PREFIX = 'LWSW-'; + private const UNIFIED_LICENSE_KEY_PREFIX = 'LWSW-'; /** * The TEC product slug to Harbor product slug map. @@ -91,6 +91,9 @@ public function do_register(): void { add_filter( 'lw-harbor/legacy_licenses', [ $this,'register_legacy_licenses' ] ); add_filter( 'lw_harbor/premium_plugin_exists', [ $this, 'register_premium_plugin_exists' ] ); + // Runs even when Harbor does not fully load (no premium plugin), so unified keys + // pasted into free-plugin PUE fields still get a clear activation message. + add_filter( 'tec_common_pue_pre_validate_key', [ $this, 'filter_tec_common_pue_pre_validate_key' ], 10, 3 ); Harbor_Provider::init(); @@ -115,6 +118,7 @@ public function do_register(): void { public function unregister(): void { remove_filter( 'lw-harbor/legacy_licenses', [ $this,'register_legacy_licenses' ] ); remove_filter( 'lw_harbor/premium_plugin_exists', [ $this, 'register_premium_plugin_exists' ] ); + remove_filter( 'tec_common_pue_pre_validate_key', [ $this, 'filter_tec_common_pue_pre_validate_key' ] ); remove_action( 'init', [ $this, 'decorate_uplinks_auth_url' ] ); } @@ -129,6 +133,47 @@ public function decorate_uplinks_auth_url(): void { $this->container->bind( Auth_Url::class, Auth_URL_Decorator::class ); } + /** + * Reject unified license keys when Harbor is not loaded. + * + * Without a premium plugin, Harbor never fires `lw_harbor/loaded` and the PUE + * Harbor integration is not registered — so this callback is the only guard that + * stops a unified key from being sent through legacy PUE validation. + * + * @since TBD + * + * @param array|null $response Early response, or null to continue. + * @param string $key The license key being validated. + * @param \Tribe__PUE__Checker|null $checker The PUE checker instance. + * + * @return array|null + */ + public function filter_tec_common_pue_pre_validate_key( ?array $response, string $key, $checker ): ?array { + if ( null !== $response ) { + return $response; + } + + if ( did_action( 'lw_harbor/loaded' ) ) { + return $response; + } + + if ( ! $this->is_unified_license_key( $key ) ) { + return $response; + } + + return [ + 'status' => 0, + 'message' => sprintf( + /* translators: %s: My account page link. */ + __( + 'This is a unified license key. To activate it, install The Events Calendar Pro or Event Tickets Pro, then add your license in the Unified License Manager. You can download the plugin from your account.', + 'tribe-common' + ), + "https://software.liquidweb.com/" + ), + ]; + } + /** * Get the premium plugin existence callbacks. * @@ -325,7 +370,7 @@ public function is_license_field_managed_by_harbor( string $tec_product_slug ): public function get_unified_license_key_entry_error_message(): string { return sprintf( /* translators: %1$s: opening anchor tag, %2$s: closing anchor tag. */ - __( 'It is a unified license key. Please %1$sclick here%2$s to enter it in the Unified License Manager.', 'tribe-common' ), + __( 'This is a unified license key. Please %1$sclick here%2$s to enter it in the Unified License Manager.', 'tribe-common' ), '', '' ); diff --git a/tests/integration/Tribe/Common/Libraries/Harbor_Test.php b/tests/integration/Tribe/Common/Libraries/Harbor_Test.php index ca8ec88721..3ab857bf6e 100644 --- a/tests/integration/Tribe/Common/Libraries/Harbor_Test.php +++ b/tests/integration/Tribe/Common/Libraries/Harbor_Test.php @@ -78,6 +78,94 @@ public function unified_license_key_provider(): array { ]; } + /** + * When Harbor never fully boots (no premium plugin), unified keys must still + * be rejected from free-plugin PUE fields instead of hitting remote validation. + * + * @test + */ + public function it_should_reject_unified_key_via_pre_validate_when_harbor_is_not_loaded(): void { + global $wp_actions; + + $previous_loaded = $wp_actions['lw_harbor/loaded'] ?? null; + unset( $wp_actions['lw_harbor/loaded'] ); + + $checker = new \Tribe__PUE__Checker( + 'deprecated', + 'the-events-calendar', + [], + 'the-events-calendar/the-events-calendar.php' + ); + + $result = apply_filters( 'tec_common_pue_pre_validate_key', null, 'LWSW-PASTED-WITHOUT-PREMIUM', $checker ); + + if ( null !== $previous_loaded ) { + $wp_actions['lw_harbor/loaded'] = $previous_loaded; + } + + $this->assertIsArray( $result ); + $this->assertSame( 0, $result['status'] ); + $this->assertStringContainsString( 'unified license key', strtolower( $result['message'] ) ); + $this->assertStringContainsString( 'The Events Calendar Pro', $result['message'] ); + $this->assertStringContainsString( 'Event Tickets Pro', $result['message'] ); + $this->assertStringContainsString( 'Unified License Manager', $result['message'] ); + } + + /** + * @test + */ + public function it_should_pass_through_legacy_key_via_pre_validate_when_harbor_is_not_loaded(): void { + global $wp_actions; + + $previous_loaded = $wp_actions['lw_harbor/loaded'] ?? null; + unset( $wp_actions['lw_harbor/loaded'] ); + + $checker = new \Tribe__PUE__Checker( + 'deprecated', + 'the-events-calendar', + [], + 'the-events-calendar/the-events-calendar.php' + ); + + $result = tribe( Harbor::class )->filter_tec_common_pue_pre_validate_key( null, 'legacy-product-key', $checker ); + + if ( null !== $previous_loaded ) { + $wp_actions['lw_harbor/loaded'] = $previous_loaded; + } + + $this->assertNull( $result ); + } + + /** + * Once Harbor is loaded, the PUE integration owns unified-key messaging; + * this callback must not short-circuit. + * + * @test + */ + public function it_should_pass_through_unified_key_via_pre_validate_when_harbor_is_loaded(): void { + global $wp_actions; + + $previous_loaded = $wp_actions['lw_harbor/loaded'] ?? null; + $wp_actions['lw_harbor/loaded'] = 1; + + $checker = new \Tribe__PUE__Checker( + 'deprecated', + 'the-events-calendar', + [], + 'the-events-calendar/the-events-calendar.php' + ); + + $result = tribe( Harbor::class )->filter_tec_common_pue_pre_validate_key( null, 'LWSW-SHOULD-PASS-THROUGH', $checker ); + + if ( null !== $previous_loaded ) { + $wp_actions['lw_harbor/loaded'] = $previous_loaded; + } else { + unset( $wp_actions['lw_harbor/loaded'] ); + } + + $this->assertNull( $result ); + } + /** * @test */ From f6e1239bc3dd8647b72838cada6d8c6c8fc188e9 Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Tue, 11 Aug 2026 11:43:20 +0530 Subject: [PATCH 14/22] revert composer to previous state and utilise the existing function lw_harbor_is_feature_enabled instead of creating a new one called we created in harbod (lw_harbor_is_capability_license_active) --- composer.json | 2 +- composer.lock | 17 ++++++++--------- src/Common/Integrations/Harbor/PUE.php | 2 +- src/Common/Libraries/Harbor.php | 3 +-- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 0d03150107..372496f1a2 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "stellarwp/assets": "^1.5", "stellarwp/container-contract": "^1.0.4", "stellarwp/db": "^1.0.3", - "stellarwp/harbor": "dev-SMTNC-1895", + "stellarwp/harbor": "^1.4", "stellarwp/installer": "^1.1.0", "stellarwp/telemetry": "^2.3.4", "stellarwp/uplink": "2.2.2", diff --git a/composer.lock b/composer.lock index e00de5bab8..5b41e8be1f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "29b2926e462fbcbb52bf2698baba5707", + "content-hash": "3af55c29ec587b9a48fe41ecbcf19dbe", "packages": [ { "name": "firebase/php-jwt", @@ -895,16 +895,16 @@ }, { "name": "stellarwp/harbor", - "version": "dev-SMTNC-1895", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/stellarwp/harbor.git", - "reference": "08d1f6da0b4ed54024886915f1143f6731fa1f20" + "reference": "0d21b6e6da4352364610168c053ed0ec9d59253d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stellarwp/harbor/zipball/08d1f6da0b4ed54024886915f1143f6731fa1f20", - "reference": "08d1f6da0b4ed54024886915f1143f6731fa1f20", + "url": "https://api.github.com/repos/stellarwp/harbor/zipball/0d21b6e6da4352364610168c053ed0ec9d59253d", + "reference": "0d21b6e6da4352364610168c053ed0ec9d59253d", "shasum": "" }, "require": { @@ -959,9 +959,9 @@ "description": "A library that integrates a WordPress product with the Liquid Web licensing system.", "support": { "issues": "https://github.com/stellarwp/harbor/issues", - "source": "https://github.com/stellarwp/harbor/tree/SMTNC-1895" + "source": "https://github.com/stellarwp/harbor/tree/v1.4.0" }, - "time": "2026-07-29T06:33:31+00:00" + "time": "2026-05-21T17:13:36+00:00" }, { "name": "stellarwp/installer", @@ -7816,7 +7816,6 @@ "minimum-stability": "dev", "stability-flags": { "stellarwp/coding-standards": 20, - "stellarwp/harbor": 20, "the-events-calendar/tec-testing-facilities": 20 }, "prefer-stable": true, @@ -7826,5 +7825,5 @@ "platform-overrides": { "php": "7.4.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/src/Common/Integrations/Harbor/PUE.php b/src/Common/Integrations/Harbor/PUE.php index 92f805a6f4..6fad004124 100644 --- a/src/Common/Integrations/Harbor/PUE.php +++ b/src/Common/Integrations/Harbor/PUE.php @@ -136,7 +136,7 @@ public function filter_tec_common_pue_pre_validate_key( ?array $response, string 'status' => 1, 'message' => sprintf( /* translators: URL to the Liquid Web License Manager */ - __( 'Licensed via Liquid Web License Manager', 'tribe-common' ), + __( 'Licensed via Unified License Manager', 'tribe-common' ), esc_url( lw_harbor_get_license_page_url() ), ), ]; diff --git a/src/Common/Libraries/Harbor.php b/src/Common/Libraries/Harbor.php index b6d18e5cae..23439837a8 100644 --- a/src/Common/Libraries/Harbor.php +++ b/src/Common/Libraries/Harbor.php @@ -20,7 +20,6 @@ use function lw_harbor_get_unified_license_key; use function lw_harbor_is_feature_enabled; use function lw_harbor_is_feature_available; -use function lw_harbor_is_capability_license_active; /** * Controller for setting up the Harbor library. @@ -355,7 +354,7 @@ public function is_license_field_managed_by_harbor( string $tec_product_slug ): return false; } - return lw_harbor_is_capability_license_active( + return lw_harbor_is_feature_enabled( $this->get_harbor_product_slug( $tec_product_slug ) ); } From 085650f636377303e4861a42352050e55f9d52fa Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Tue, 11 Aug 2026 11:56:46 +0530 Subject: [PATCH 15/22] comments --- src/Common/Libraries/Harbor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Common/Libraries/Harbor.php b/src/Common/Libraries/Harbor.php index 23439837a8..95e892c3b7 100644 --- a/src/Common/Libraries/Harbor.php +++ b/src/Common/Libraries/Harbor.php @@ -91,7 +91,7 @@ public function do_register(): void { add_filter( 'lw-harbor/legacy_licenses', [ $this,'register_legacy_licenses' ] ); add_filter( 'lw_harbor/premium_plugin_exists', [ $this, 'register_premium_plugin_exists' ] ); // Runs even when Harbor does not fully load (no premium plugin), so unified keys - // pasted into free-plugin PUE fields still get a clear activation message. + // pasted into free-plugin PUE fields still get a clear guidance message. add_filter( 'tec_common_pue_pre_validate_key', [ $this, 'filter_tec_common_pue_pre_validate_key' ], 10, 3 ); Harbor_Provider::init(); From bdabde4aad66991252a34cec8613f1476039e8dd Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Tue, 11 Aug 2026 12:02:52 +0530 Subject: [PATCH 16/22] make PORTAL_URL constant --- src/Common/Libraries/Harbor.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Common/Libraries/Harbor.php b/src/Common/Libraries/Harbor.php index 95e892c3b7..50b67acb04 100644 --- a/src/Common/Libraries/Harbor.php +++ b/src/Common/Libraries/Harbor.php @@ -38,6 +38,15 @@ class Harbor extends Controller_Contract { */ private const UNIFIED_LICENSE_KEY_PREFIX = 'LWSW-'; + /** + * The URL of the Liquid Web portal. + * + * @since TBD + * + * @var string + */ + private const PORTAL_URL = 'https://software.liquidweb.com'; + /** * The TEC product slug to Harbor product slug map. * @@ -168,7 +177,7 @@ public function filter_tec_common_pue_pre_validate_key( ?array $response, string 'This is a unified license key. To activate it, install The Events Calendar Pro or Event Tickets Pro, then add your license in the Unified License Manager. You can download the plugin from your account.', 'tribe-common' ), - "https://software.liquidweb.com/" + esc_url( self::PORTAL_URL ) ), ]; } From 98e3f64eefaf6ffe1c7724d02d46f5c3bf414383 Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Tue, 11 Aug 2026 12:04:40 +0530 Subject: [PATCH 17/22] replace portal constant with get_portal_url() function --- src/Common/Libraries/Harbor.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/Common/Libraries/Harbor.php b/src/Common/Libraries/Harbor.php index 50b67acb04..4fe0b2f38f 100644 --- a/src/Common/Libraries/Harbor.php +++ b/src/Common/Libraries/Harbor.php @@ -38,15 +38,6 @@ class Harbor extends Controller_Contract { */ private const UNIFIED_LICENSE_KEY_PREFIX = 'LWSW-'; - /** - * The URL of the Liquid Web portal. - * - * @since TBD - * - * @var string - */ - private const PORTAL_URL = 'https://software.liquidweb.com'; - /** * The TEC product slug to Harbor product slug map. * @@ -177,7 +168,7 @@ public function filter_tec_common_pue_pre_validate_key( ?array $response, string 'This is a unified license key. To activate it, install The Events Calendar Pro or Event Tickets Pro, then add your license in the Unified License Manager. You can download the plugin from your account.', 'tribe-common' ), - esc_url( self::PORTAL_URL ) + esc_url( $this->get_portal_url() ) ), ]; } From 90489d59415cbd78142c79b06e414c441061f977 Mon Sep 17 00:00:00 2001 From: pramodjodhani Date: Tue, 11 Aug 2026 13:15:58 +0530 Subject: [PATCH 18/22] Improve PUE tests: - spy that remote validation is skipped - cover pre_validate paths via data provider --- .../Common/Integrations/Harbor/PUE_Test.php | 69 +++++++++++++++++-- .../Tribe/Common/Libraries/Harbor_Test.php | 2 +- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php b/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php index 860342b3c8..ffcb7a09ff 100644 --- a/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php +++ b/tests/integration/Tribe/Common/Integrations/Harbor/PUE_Test.php @@ -6,6 +6,7 @@ use TEC\Common\Libraries\Harbor; use TEC\Common\StellarWP\Uplink\Resources\Plugin as Uplink_Plugin; use Tribe\Tests\Traits\With_Harbor_State; +use Tribe\Tests\Traits\With_Uopz; /** * Exercises the four PUE filter hooks that the Harbor consolidation introduces @@ -20,6 +21,7 @@ */ class PUE_Test extends WPTestCase { use With_Harbor_State; + use With_Uopz; /** * The priority airplane-mode (a wp-browser test harness plugin) registers its @@ -356,14 +358,27 @@ public function auth_url_decorator_provider(): array { * @test */ public function it_should_skip_remote_validation_for_harbor_managed_product(): void { + $remote_call_count = 0; + // validate_key() → request_info() uses wp_remote_post for remote PUE checks. + $this->set_fn_return( + 'wp_remote_post', + static function () use ( &$remote_call_count ) { + ++$remote_call_count; + + return new \WP_Error( 'unexpected_remote_validation', 'Remote validation should not be performed.' ); + }, + true + ); + $this->seed_unified_license_key(); $this->seed_harbor_catalog_for_tec( [ 'events-calendar-pro' ] ); $checker = new \Tribe__PUE__Checker( 'deprecated', 'events-calendar-pro', [], 'events-calendar-pro/events-calendar-pro.php' ); $response = $checker->validate_key( 'any-key-value' ); + $this->assertSame( 0, $remote_call_count, 'Remote validation should not be performed for Harbor-managed products.' ); $this->assertSame( 1, $response['status'] ); - $this->assertStringContainsString( 'Liquid Web License Manager', $response['message'] ); + $this->assertStringContainsString( 'Unified License Manager', $response['message'] ); } /** @@ -383,15 +398,59 @@ public function it_should_reject_unified_key_for_non_harbor_managed_product(): v /** * @test + * @dataProvider pre_validate_key_provider */ - public function it_should_pass_through_pre_validate_for_normal_key_on_non_managed_product(): void { + public function it_should_handle_pre_validate_key_filter( + string $slug, + string $plugin_file, + string $key, + ?int $expected_status, + ?string $expected_message_fragment + ): void { $this->seed_unified_license_key(); $this->seed_harbor_catalog_for_tec( [ 'events-calendar-pro' ] ); - $checker = new \Tribe__PUE__Checker( 'deprecated', 'tribe-filterbar', [], 'the-events-calendar-filterbar/the-events-calendar-filterbar.php' ); - $result = apply_filters( 'tec_common_pue_pre_validate_key', null, 'legacy-product-key', $checker ); + $checker = new \Tribe__PUE__Checker( 'deprecated', $slug, [], $plugin_file ); + $result = apply_filters( 'tec_common_pue_pre_validate_key', null, $key, $checker ); + + if ( null === $expected_status ) { + $this->assertNull( $result ); + + return; + } - $this->assertNull( $result ); + $this->assertIsArray( $result ); + $this->assertSame( $expected_status, $result['status'] ); + $this->assertStringContainsString( + $expected_message_fragment, + strtolower( wp_strip_all_tags( $result['message'] ) ) + ); + } + + public function pre_validate_key_provider(): array { + return [ + 'legacy key on non-managed product passes through' => [ + 'tribe-filterbar', + 'the-events-calendar-filterbar/the-events-calendar-filterbar.php', + 'legacy-product-key', + null, + null, + ], + 'harbor-managed product short-circuits as valid' => [ + 'events-calendar-pro', + 'events-calendar-pro/events-calendar-pro.php', + 'any-key-value', + 1, + 'unified license manager', + ], + 'unified key on non-managed product is rejected' => [ + 'tribe-filterbar', + 'the-events-calendar-filterbar/the-events-calendar-filterbar.php', + 'LWSW-PASTED-INTO-WRONG-FIELD', + 0, + 'unified license key', + ], + ]; } /** diff --git a/tests/integration/Tribe/Common/Libraries/Harbor_Test.php b/tests/integration/Tribe/Common/Libraries/Harbor_Test.php index 3ab857bf6e..1cb1f34766 100644 --- a/tests/integration/Tribe/Common/Libraries/Harbor_Test.php +++ b/tests/integration/Tribe/Common/Libraries/Harbor_Test.php @@ -210,7 +210,7 @@ public function it_should_include_license_manager_link_in_unified_key_entry_erro $this->assertStringContainsString( 'unified license key', strtolower( $message ) ); $this->assertStringContainsString( '