Skip to content

Commit dc9f24f

Browse files
committed
Expose activation URLs through stable global functions
Consumers were told to resolve Activation_Url from the container, which ties them to whichever Harbor version's class their own copy ships rather than the loaded, highest-version one. Add lw_harbor_get_activation_url() and lw_harbor_get_product_activation_url() -- version-keyed global functions, the stable public API -- wrapping Activation_Url::get_base() and for_product(). Point the activation-URL guide and the API reference at the functions instead of the class. Claude-Session: https://claude.ai/code/session_011uxoDruAaZXzRy3hk8mr4W
1 parent 0a4a279 commit dc9f24f

6 files changed

Lines changed: 121 additions & 19 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
significance: minor
22
type: feature
3-
entry: Added a reusable activation URL API. Host plugins can build Liquid Web portal activation URLs from PHP via the Activation_Url service, or in the browser via the shared lw-harbor-activation script, which exposes window.lwHarbor.buildActivationUrl()
3+
entry: Added a reusable activation URL API. Host plugins can build Liquid Web portal activation URLs from PHP via the lw_harbor_get_activation_url() and lw_harbor_get_product_activation_url() global functions, or in the browser via the shared lw-harbor-activation script, which exposes window.lwHarbor.buildActivationUrl()
44
timestamp: 2026-07-22T00:00:00.000Z

docs/guides/activation-urls.md

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,29 +55,29 @@ Consequences worth knowing:
5555

5656
## From PHP
5757

58-
Resolve `Activation_Url` from the container.
58+
Call the global functions. Like the rest of Harbor's public API they resolve to
59+
the highest-version Harbor copy on the site, so you always get the loaded
60+
version's logic — do not build `Activation_Url` from your own bundled copy, which
61+
may not be the one actually running.
5962

6063
```php
61-
use LiquidWeb\Harbor\Portal\Activation_Url;
62-
63-
$activation_url = Config::get_container()->get( Activation_Url::class );
64-
6564
// Product-scoped, returning the user to your onboarding screen.
66-
$href = $activation_url->for_product(
65+
$href = lw_harbor_get_product_activation_url(
6766
'kadence',
6867
'pro',
6968
admin_url( 'admin.php?page=kadence-onboarding&step=2' )
7069
);
7170
```
7271

73-
| Method | Returns |
74-
| ------------------------------------------------------------------ | ----------------------------------------------------------------------- |
75-
| `get_base( ?string $redirect_url )` | The portal subscriptions URL with referral, redirect, and domain params |
76-
| `for_product( string $slug, string $tier, ?string $redirect_url )` | The same, plus `sku={slug}:{tier}` |
72+
| Function | Returns |
73+
| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
74+
| `lw_harbor_get_activation_url( ?string $redirect_url )` | The portal subscriptions URL with referral, redirect, and domain params |
75+
| `lw_harbor_get_product_activation_url( string $slug, string $tier, ?string $redirect_url )` | The same, plus `sku={slug}:{tier}` |
7776

78-
Omit `$redirect_url` to fall back to Harbor's Software Manager page. Pass your
79-
own whenever the user started somewhere else — otherwise they will not come
80-
back to where they were.
77+
Both return an empty string when no Harbor instance is active — treat that as
78+
"hide the button". Omit `$redirect_url` to fall back to Harbor's Software Manager
79+
page. Pass your own whenever the user started somewhere else — otherwise they
80+
will not come back to where they were.
8181

8282
### Getting the return URL right
8383

@@ -106,11 +106,7 @@ Harbor registers a dependency-free script exposing `window.lwHarbor`. Declare it
106106
as a dependency:
107107

108108
```php
109-
use LiquidWeb\Harbor\Config;
110109
use LiquidWeb\Harbor\Portal\Activation_Script;
111-
use LiquidWeb\Harbor\Portal\Activation_Url;
112-
113-
$activation_url = Config::get_container()->get( Activation_Url::class );
114110

115111
wp_enqueue_script(
116112
'kadence-onboarding',
@@ -125,7 +121,7 @@ wp_localize_script(
125121
'kadence-onboarding',
126122
'kadenceOnboarding',
127123
[
128-
'activationBaseUrl' => $activation_url->get_base(
124+
'activationBaseUrl' => lw_harbor_get_activation_url(
129125
admin_url( 'admin.php?page=kadence-onboarding&step=2' )
130126
),
131127
]

src/Harbor/API/Functions/Global_Function_Registry.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use LiquidWeb\Harbor\Config;
99
use LiquidWeb\Harbor\Features\Manager;
1010
use LiquidWeb\Harbor\Licensing\Repositories\License_Repository;
11+
use LiquidWeb\Harbor\Portal\Activation_Url;
1112
use LiquidWeb\Harbor\Portal\Catalog_Repository;
1213
use LiquidWeb\Harbor\Site\Data;
1314
use LiquidWeb\Harbor\Traits\With_Debugging;
@@ -131,6 +132,34 @@ static function (): string {
131132
}
132133
);
133134

135+
\_lw_harbor_global_function_registry(
136+
'lw_harbor_get_activation_url',
137+
$version,
138+
static function ( ?string $redirect_url = null ): string {
139+
try {
140+
return Config::get_container()->get( Activation_Url::class )->get_base( $redirect_url );
141+
} catch ( Throwable $e ) {
142+
self::debug_log_throwable( $e, 'Error building activation URL' );
143+
144+
return '';
145+
}
146+
}
147+
);
148+
149+
\_lw_harbor_global_function_registry(
150+
'lw_harbor_get_product_activation_url',
151+
$version,
152+
static function ( string $product_slug, string $tier, ?string $redirect_url = null ): string {
153+
try {
154+
return Config::get_container()->get( Activation_Url::class )->for_product( $product_slug, $tier, $redirect_url );
155+
} catch ( Throwable $e ) {
156+
self::debug_log_throwable( $e, 'Error building product activation URL' );
157+
158+
return '';
159+
}
160+
}
161+
);
162+
134163
\_lw_harbor_global_function_registry(
135164
'lw_harbor_display_legacy_license_page_notice',
136165
$version,

src/Harbor/API/Functions/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ Strauss rewrites class references at parse time. `License_Repository::class` ins
125125
| `lw_harbor_is_feature_enabled( $slug )` | Whether a feature is in the catalog AND currently enabled/active |
126126
| `lw_harbor_is_feature_available( $slug )` | Whether a feature exists in the catalog, regardless of enabled state |
127127
| `lw_harbor_get_license_page_url()` | Returns the admin URL for the Harbor Feature Manager page |
128+
| `lw_harbor_get_activation_url( $redirect_url = null )` | Returns a portal activation URL for this site, returning the user to `$redirect_url` |
129+
| `lw_harbor_get_product_activation_url( $slug, $tier, $redirect = null )` | The activation URL scoped to a product and tier via `sku` |
128130
| `lw_harbor_get_licensed_domain()` | Returns the domain Harbor uses for licensing on the current site |
129131
| `lw_harbor_register_submenu( $parent_slug )` | Appends a Licensing submenu item under a plugin's top-level admin menu |
130132
| `lw_harbor_display_legacy_license_page_notice( $product_name = '' )` | Renders an info notice on a plugin's legacy license page pointing users to the unified system |

src/Harbor/global-functions.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,53 @@ function lw_harbor_get_license_page_url(): string {
240240
}
241241
}
242242

243+
if ( ! function_exists( 'lw_harbor_get_activation_url' ) ) {
244+
/**
245+
* Returns a Liquid Web portal activation URL for this site.
246+
*
247+
* Drops the user on the portal's subscriptions screen to activate a product
248+
* against this site, then returns them to $redirect_url (or the Software
249+
* Manager page when none is given).
250+
*
251+
* @since TBD
252+
*
253+
* @param string|null $redirect_url Where the portal returns the user afterwards.
254+
*
255+
* @return string The activation URL, or an empty string if no instance is active.
256+
*/
257+
function lw_harbor_get_activation_url( ?string $redirect_url = null ): string {
258+
$callback = _lw_harbor_global_function_registry( 'lw_harbor_get_activation_url' );
259+
260+
$result = $callback ? $callback( $redirect_url ) : '';
261+
262+
return is_string( $result ) ? $result : '';
263+
}
264+
}
265+
266+
if ( ! function_exists( 'lw_harbor_get_product_activation_url' ) ) {
267+
/**
268+
* Returns a portal activation URL scoped to a single product and tier.
269+
*
270+
* Like lw_harbor_get_activation_url(), but adds an `sku` param so the portal
271+
* pre-selects the given product and tier instead of an unfiltered list.
272+
*
273+
* @since TBD
274+
*
275+
* @param string $product_slug The product slug, e.g. 'learndash'.
276+
* @param string $tier The tier slug, e.g. 'elite'.
277+
* @param string|null $redirect_url Where the portal returns the user afterwards.
278+
*
279+
* @return string The activation URL, or an empty string if no instance is active.
280+
*/
281+
function lw_harbor_get_product_activation_url( string $product_slug, string $tier, ?string $redirect_url = null ): string {
282+
$callback = _lw_harbor_global_function_registry( 'lw_harbor_get_product_activation_url' );
283+
284+
$result = $callback ? $callback( $product_slug, $tier, $redirect_url ) : '';
285+
286+
return is_string( $result ) ? $result : '';
287+
}
288+
}
289+
243290
if ( ! function_exists( 'lw_harbor_refresh_catalog' ) ) {
244291
/**
245292
* Forces a synchronous fresh catalog fetch from the Commerce Portal API.

tests/wpunit/API/Functions/GlobalFunctionsTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,32 @@ public function test_refresh_catalog_returns_false_when_refresh_returns_wp_error
276276

277277
$this->assertFalse( lw_harbor_refresh_catalog() );
278278
}
279+
280+
// -------------------------------------------------------------------------
281+
// lw_harbor_get_activation_url() / lw_harbor_get_product_activation_url()
282+
// -------------------------------------------------------------------------
283+
284+
public function test_get_activation_url_targets_the_subscriptions_screen(): void {
285+
$url = lw_harbor_get_activation_url();
286+
287+
$this->assertStringContainsString( '/subscriptions/', $url );
288+
$this->assertStringContainsString( 'portal-referral=plugin', $url );
289+
$this->assertStringNotContainsString( 'sku=', $url );
290+
}
291+
292+
public function test_get_activation_url_carries_the_given_return_destination(): void {
293+
$url = lw_harbor_get_activation_url( 'https://example.test/onboarding' );
294+
295+
$this->assertStringContainsString( rawurlencode( 'https://example.test/onboarding' ), $url );
296+
// The return trip is tagged so Harbor refreshes its cache on the way back.
297+
$this->assertStringContainsString( 'lw-harbor-activated', $url );
298+
}
299+
300+
public function test_get_product_activation_url_scopes_to_the_product_and_tier(): void {
301+
$url = lw_harbor_get_product_activation_url( 'learndash', 'elite' );
302+
303+
$this->assertStringContainsString( '/subscriptions/', $url );
304+
// The sku is RFC3986-encoded, so the colon becomes %3A.
305+
$this->assertStringContainsString( 'sku=learndash%3Aelite', $url );
306+
}
279307
}

0 commit comments

Comments
 (0)