Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
960edff
Legacy license field improvements.
pramodjodhani Jul 27, 2026
fa916c2
changelog
pramodjodhani Jul 27, 2026
859574d
fix broken test
pramodjodhani Jul 27, 2026
ae61456
improve comment for filter_pre_http_request function
pramodjodhani Jul 27, 2026
b18e87d
rename functions for better understanding
pramodjodhani Jul 27, 2026
3d08cb4
add link to the Liquid Web License Manager in the under field notice
pramodjodhani Jul 27, 2026
07c8007
fix harbor-managed license field check to use per-site activated capa…
pramodjodhani Jul 28, 2026
c401e02
utiilise the new harbor function lw_harbor_is_capability_license_acti…
pramodjodhani Jul 29, 2026
aab9e63
updated composer.lock
pramodjodhani Jul 29, 2026
0b03b38
1. Fix @since TBD versions
pramodjodhani Aug 10, 2026
a8f9d74
update the error message for unified key - replace LW License manager…
pramodjodhani Aug 10, 2026
e2231e8
replace tribe_puc_pre_validate_key with tec_common_pue_pre_validate_key
pramodjodhani Aug 10, 2026
ddce579
handle condition when no pro plugins are active and harbor doesnt loa…
pramodjodhani Aug 10, 2026
f6e1239
revert composer to previous state and utilise the existing function l…
pramodjodhani Aug 11, 2026
085650f
comments
pramodjodhani Aug 11, 2026
bdabde4
make PORTAL_URL constant
pramodjodhani Aug 11, 2026
98e3f64
replace portal constant with get_portal_url() function
pramodjodhani Aug 11, 2026
90489d5
Improve PUE tests:
pramodjodhani Aug 11, 2026
52d1051
collapse the three tests related to tribe_settings_save_field_value f…
pramodjodhani Aug 11, 2026
6298ee3
prevent tribe_exit from existing the test script
pramodjodhani Aug 11, 2026
c7b54fd
tests: ensure tribe_exit is mocked
dpanta94 Aug 4, 2026
c53de37
remove the global filter on tribe_exit
pramodjodhani Aug 11, 2026
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
4 changes: 4 additions & 0 deletions changelog/update-license-field-improvements
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets make sure we release new version of harbor before merging so we dont ship a dev version.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been reverted back to ^1.4 since we dont need any new functions, we are now utilising the existing functions lw_harbor_is_feature_enabled πŸ‘

"stellarwp/installer": "^1.1.0",
"stellarwp/telemetry": "^2.3.4",
"stellarwp/uplink": "2.2.2",
Expand Down
17 changes: 9 additions & 8 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

165 changes: 164 additions & 1 deletion src/Common/Integrations/Harbor/PUE.php
Original file line number Diff line number Diff line change
Expand Up @@ -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, 'prevent_storing_unified_license_key' ], 10, 2 );
add_filter( 'tribe_license_fields', [ $this, 'readonly_and_disable_harbor_managed_license_fields' ], 30 );
}

/**
Expand All @@ -56,6 +59,154 @@ 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, 'prevent_storing_unified_license_key' ] );
remove_filter( 'tribe_license_fields', [ $this, 'readonly_and_disable_harbor_managed_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 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;
}

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' => sprintf(
/* translators: URL to the Liquid Web License Manager */
__( 'Licensed via <a href="%s" target="_blank">Liquid Web License Manager</a>', 'tribe-common' ),
esc_url( lw_harbor_get_license_page_url() ),
),
];
}

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 prevent_storing_unified_license_key( $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 {
Comment thread
pramodjodhani marked this conversation as resolved.
$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;
}

/**
Expand Down Expand Up @@ -122,7 +273,19 @@ 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. 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 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.
*
* 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
*
Expand Down
73 changes: 71 additions & 2 deletions src/Common/Libraries/Harbor.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,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.
Expand All @@ -29,12 +30,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-';
Comment thread
dpanta94 marked this conversation as resolved.
Outdated

/**
* The TEC product slug to Harbor product slug map.
*
* @since 6.11.0
*
* @var array
* @var array<string, string>
*/
private const TEC_PRODUCT_SLUG_TO_HARBOR_PRODUCT_SLUG_MAP = [
'the-events-calendar' => 'the-events-calendar',
Expand Down Expand Up @@ -232,7 +242,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'] )
)
);
}
Expand Down Expand Up @@ -265,6 +275,65 @@ 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
Comment thread
pramodjodhani marked this conversation as resolved.
Outdated
*
* @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.
*
* 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.
Comment thread
pramodjodhani marked this conversation as resolved.
*
* @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()`.
*
* @param string $tec_product_slug The TEC product slug.
*
* @return bool
*/
public function is_license_field_managed_by_harbor( string $tec_product_slug ): bool {
if ( ! lw_harbor_has_unified_license_key() ) {
return false;
}

return lw_harbor_is_capability_license_active(
$this->get_harbor_product_slug( $tec_product_slug )
);
Comment on lines +353 to +360

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ—„οΈ Data Integrity & Integration | 🟠 Major | πŸ—οΈ Heavy lift

Use the active-capability check for all legacy PUE paths.

Line 313 correctly checks whether the capability is active on this site. However, PUE::filter_pre_get_option(), PUE::filter_stellarwp_uplink_tec_license_get_key(), PUE::filter_pue_get_update_url(), and PUE::filter_pre_http_request() still use is_product_licensed(), which checks feature availability.

If a customer is entitled to a capability but has not activated it on this site, the field remains editable but PUE still reads and uses the unified key. A saved legacy key cannot become effective.

Use the active-capability predicate for these legacy PUE paths. Add coverage with an entitled capability that is not activated on the current site.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Common/Libraries/Harbor.php` around lines 308 - 315, Update the legacy
PUE methods filter_pre_get_option(),
filter_stellarwp_uplink_tec_license_get_key(), filter_pue_get_update_url(), and
filter_pre_http_request() to use the active-capability predicate,
is_license_field_managed_by_harbor(), instead of is_product_licensed(). Preserve
existing behavior for activated capabilities and add coverage for an entitled
but inactive capability.

@pramodjodhani pramodjodhani Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dpanta94 I think this comment makes sense and should be implemented. But I want to confirm this with you, as its possible I might have not full context of why is_product_licensed was used in these places.

is_product_licensed internally calls lw_harbor_is_feature_available which only checks if the feature is available in the tier, not whether its active or not.

}

/**
* 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' ),
'<a href="' . esc_url( lw_harbor_get_license_page_url() ) . '" target=_blank >',
'</a>'
);
}

/**
* Get the unified license key if the feature is enabled.
*
Expand Down
17 changes: 17 additions & 0 deletions src/Tribe/PUE/Checker.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Comment thread
pramodjodhani marked this conversation as resolved.
Outdated
if ( is_array( $pre_validate ) ) {
return $pre_validate;
}

$uplink_resource = $this->get_uplink_resource( $this->get_slug() );

if ( $uplink_resource ) {
Expand Down
2 changes: 1 addition & 1 deletion src/resources/postcss/utilities
Comment thread
pramodjodhani marked this conversation as resolved.
Loading
Loading