defined('FORGE_DOCS_URL_LANGFORGE') ? FORGE_DOCS_URL_LANGFORGE : '', 'rankforge' => defined('FORGE_DOCS_URL_RANKFORGE') ? FORGE_DOCS_URL_RANKFORGE : '', 'formforge' => defined('FORGE_DOCS_URL_FORMFORGE') ? FORGE_DOCS_URL_FORMFORGE : '', 'fieldforge' => defined('FORGE_DOCS_URL_FIELDFORGE') ? FORGE_DOCS_URL_FIELDFORGE : '', // The four newer products define FORGE_DOCS_ (a /docs/ URL, // not a site root) in their main plugin file. 'guardforge' => defined('FORGE_DOCS_GUARDFORGE') ? FORGE_DOCS_GUARDFORGE : '', 'flowforge' => defined('FORGE_DOCS_FLOWFORGE') ? FORGE_DOCS_FLOWFORGE : '', 'velocityforge' => defined('FORGE_DOCS_VELOCITYFORGE') ? FORGE_DOCS_VELOCITYFORGE : '', 'draftforge' => defined('FORGE_DOCS_DRAFTFORGE') ? FORGE_DOCS_DRAFTFORGE : '', 'avakode' => defined('FORGE_DOCS_URL_AVAKODE') ? FORGE_DOCS_URL_AVAKODE : '', ]; $base = rtrim($map[$plugin] ?? '', '/'); if (!$base) return ''; $path = self::docs_path($plugin, $page); $url = $base . $path; if (!empty($args)) { $url = add_query_arg($args, $url); } return $url; } private static function docs_path($plugin, $page = '') { $page = trim((string) $page, '/'); if ($page === '') { return '/docs/'; } if (strpos($page, '/') !== false) { return '/docs/' . $page . '/'; } $developer_pages = [ 'langforge' => [ '19-migration-from-wpml' => true, '20-glossary-api' => true, '21-translation-hooks' => true, ], 'rankforge' => [ '46-migration-from-yoast' => true, '47-migration-from-rank-math' => true, '48-migration-from-aioseo' => true, ], 'formforge' => [ '26-stripe-setup' => true, '30-maps-integration' => true, '31-mailchimp-integration' => true, '32-google-sheets-integration' => true, '33-google-calendar-integration' => true, '34-hubspot-integration' => true, ], 'fieldforge' => [ '33-typescript-generation' => true, '34-graphql-integration' => true, ], ]; $section = !empty($developer_pages[$plugin][$page]) ? 'developer-guide' : 'user-guide'; return '/docs/' . $section . '/' . $page . '/'; } /** * Inline tooltip pill: question-mark icon with hover description + click * through to the docs page on the product landing. * * @param string $plugin langforge|rankforge|formforge|fieldforge|avakode * @param string $page docs slug * @param string $title feature title (shown in tooltip heading) * @param string $summary one-line description */ public static function tooltip($plugin, $page, $title, $summary = '') { $url = self::url($plugin, $page); if (!$url) return ''; $aria = $summary ? $title . ' — ' . $summary : $title; return sprintf( '' . '' . '' . '%s' . ($summary ? '%s' : '%s') . '%s →' . '' . '', esc_url($url), esc_attr($aria), esc_attr($title), esc_attr($summary), esc_html($title), esc_html($summary ?: ''), esc_html__('Read the docs', 'guardforge') ); } /** Echo-variant of tooltip for render methods. */ public static function render($plugin, $page, $title, $summary = '') { echo wp_kses_post( self::tooltip($plugin, $page, $title, $summary) ); } /** * Register the tooltip CSS/JS through the enqueue API on * admin_enqueue_scripts so they always emit in the document , * outside any caller's HTML context. tooltip is frequently rendered * inline from inside an

(admin headings carry a docs-link pill); * routing assets through wp_add_inline_style/script keeps them out of * that heading markup instead of leaking a raw style block into the

. */ public static function init() { add_action('admin_enqueue_scripts', [self::class, 'enqueue_assets']); } public static function enqueue_assets() { if (self::$styles_printed) return; self::$styles_printed = true; // CSS + JS registered through the enqueue API (WordPress.org: no raw //