| @@ -250,9 +250,11 @@ | ||
| 250 | 250 | 'loadedAddons' => $this->getLoadedAddonIds(), |
| 251 | 251 | 'registeredAddons' => $this->getRegisteredAddonIds(), |
| 252 | 252 | 'version' => defined( 'FORGE12_OPTIN_VERSION' ) ? FORGE12_OPTIN_VERSION : '0.0.0', |
| 253 | 253 | 'emailEditorUrl' => admin_url( 'admin.php?page=f12-doi-admin#/email-templates' ), |
| 254 | - 'upgradeUrl' => 'https://www.forge12.com', | |
| 254 | + 'upgradeUrl' => $this->productUrl( 'admin-upgrade', 'https://www.forge12.com' ), | |
| 255 | + 'supportUrl' => $this->supportUrl(), | |
| 256 | + 'feedbackUrl' => $this->feedbackUrl(), | |
| 255 | 257 | 'adminUrl' => admin_url(), |
| 256 | 258 | // Nonce for the legacy admin-ajax `doi_export_consent` |
| 257 | 259 | // handler. The handler hard-requires `_wpnonce` in |
| 258 | 260 | // `$_REQUEST` keyed on action `doi_consent_export`; without |
| @@ -261,8 +263,11 @@ | ||
| 261 | 263 | // 2026-05-13). REST routes use the separate `nonce` field |
| 262 | 264 | // above with action `wp_rest` — they live in different |
| 263 | 265 | // namespaces, so a single shared nonce won't work. |
| 264 | 266 | 'consentExportNonce' => wp_create_nonce( 'doi_consent_export' ), |
| 267 | + // Setup wizard state for the dashboard card. Null on sites that | |
| 268 | + // were installed before the wizard existed. | |
| 269 | + 'setup' => $this->setupState(), | |
| 265 | 270 | ); |
| 266 | 271 | |
| 267 | 272 | /** |
| 268 | 273 | * Filter the admin SPA configuration data. |
| @@ -276,8 +281,59 @@ | ||
| 276 | 281 | */ |
| 277 | 282 | $config = apply_filters( 'f12_doi_admin_localize_data', $config ); |
| 278 | 283 | |
| 279 | 284 | wp_localize_script( 'doi-admin-ui', 'doiAdmin', $config ); |
| 285 | + } | |
| 286 | + | |
| 287 | + /** | |
| 288 | + * Product-site link for the SPA. | |
| 289 | + * | |
| 290 | + * The URL builders live in core/feedback.php, a plain-function file in the | |
| 291 | + * legacy namespace rather than an autoloaded class. The guard is not | |
| 292 | + * ceremony: this controller is also exercised by tests that do not load that | |
| 293 | + * file, and a fatal there would be a poor trade for a marketing link. | |
| 294 | + * | |
| 295 | + * @param string $from Entry point recorded on the link. | |
| 296 | + * @param string $fallback Used when core/feedback.php is not loaded. | |
| 297 | + */ | |
| 298 | + /** | |
| 299 | + * @return array{status: string, step: int, steps: int}|null | |
| 300 | + */ | |
| 301 | + private function setupState(): ?array { | |
| 302 | + $state = new \Forge12\DoubleOptIn\Setup\SetupState(); | |
| 303 | + if ( ! $state->exists() ) { | |
| 304 | + return null; | |
| 305 | + } | |
| 306 | + $current = $state->get(); | |
| 307 | + return array( | |
| 308 | + 'status' => $current['status'], | |
| 309 | + 'step' => $current['step'], | |
| 310 | + 'steps' => \Forge12\DoubleOptIn\Setup\SetupState::STEPS, | |
| 311 | + ); | |
| 312 | + } | |
| 313 | + | |
| 314 | + private function productUrl( string $from, string $fallback ): string { | |
| 315 | + $fn = '\\forge12\\contactform7\\CF7DoubleOptIn\\get_product_url'; | |
| 316 | + | |
| 317 | + return function_exists( $fn ) ? esc_url_raw( $fn( $from ) ) : $fallback; | |
| 318 | + } | |
| 319 | + | |
| 320 | + /** | |
| 321 | + * Support link for the SPA. Empty when unavailable, so the UI can hide it. | |
| 322 | + */ | |
| 323 | + private function supportUrl(): string { | |
| 324 | + $fn = '\\forge12\\contactform7\\CF7DoubleOptIn\\get_support_url'; | |
| 325 | + | |
| 326 | + return function_exists( $fn ) ? esc_url_raw( $fn( 'admin-spa' ) ) : ''; | |
| 327 | + } | |
| 328 | + | |
| 329 | + /** | |
| 330 | + * Feedback link for the SPA. Empty when unavailable, so the UI can hide it. | |
| 331 | + */ | |
| 332 | + private function feedbackUrl(): string { | |
| 333 | + $fn = '\\forge12\\contactform7\\CF7DoubleOptIn\\get_feedback_url'; | |
| 334 | + | |
| 335 | + return function_exists( $fn ) ? esc_url_raw( $fn( 'admin-spa' ) ) : ''; | |
| 280 | 336 | } |
| 281 | 337 | |
| 282 | 338 | /** |
| 283 | 339 | * Feed the active locale's "double-opt-in" translations to wp.i18n so the |