| @@ -16,12 +16,15 @@ | ||
| 16 | 16 | use ThinkRank\Core\Settings; |
| 17 | 17 | use ThinkRank\Core\Database; |
| 18 | 18 | use ThinkRank\Core\Plan_Config; |
| 19 | 19 | use ThinkRank\Core\Capability_Manager; |
| 20 | +use ThinkRank\Config\Local_Business_Types_Config; | |
| 20 | 21 | use ThinkRank\Admin\Metabox_Manager; |
| 21 | 22 | use ThinkRank\Admin\Elementor_Metabox; |
| 22 | 23 | use ThinkRank\Admin\Oxygen_Metabox; |
| 23 | 24 | use ThinkRank\Admin\Divi_Metabox; |
| 25 | +use ThinkRank\Admin\Bricks_Metabox; | |
| 26 | +use ThinkRank\Admin\Beaver_Metabox; | |
| 24 | 27 | use ThinkRank\Admin\Bulk_Action_Manager; |
| 25 | 28 | use ThinkRank\Admin\Post_List_Filters; |
| 26 | 29 | |
| 27 | 30 | // Prevent direct access |
| @@ -80,8 +83,22 @@ | ||
| 80 | 83 | */ |
| 81 | 84 | private Divi_Metabox $divi_metabox; |
| 82 | 85 | |
| 83 | 86 | /** |
| 87 | + * Bricks builder metabox integration instance | |
| 88 | + * | |
| 89 | + * @var Bricks_Metabox | |
| 90 | + */ | |
| 91 | + private Bricks_Metabox $bricks_metabox; | |
| 92 | + | |
| 93 | + /** | |
| 94 | + * Beaver Builder metabox integration | |
| 95 | + * | |
| 96 | + * @var Beaver_Metabox | |
| 97 | + */ | |
| 98 | + private Beaver_Metabox $beaver_metabox; | |
| 99 | + | |
| 100 | + /** | |
| 84 | 101 | * Post list columns instance |
| 85 | 102 | * |
| 86 | 103 | * @var Post_List_Columns |
| 87 | 104 | */ |
| @@ -134,8 +151,10 @@ | ||
| 134 | 151 | $this->metabox_manager = new Metabox_Manager($this->settings); |
| 135 | 152 | $this->elementor_metabox = new Elementor_Metabox($this->metabox_manager); |
| 136 | 153 | $this->oxygen_metabox = new Oxygen_Metabox($this->metabox_manager); |
| 137 | 154 | $this->divi_metabox = new Divi_Metabox($this->metabox_manager); |
| 155 | + $this->bricks_metabox = new Bricks_Metabox($this->metabox_manager); | |
| 156 | + $this->beaver_metabox = new Beaver_Metabox($this->metabox_manager); | |
| 138 | 157 | $this->post_list_columns = new Post_List_Columns(); |
| 139 | 158 | $this->focus_keyword_ajax = new Focus_Keyword_Ajax(); |
| 140 | 159 | $this->seo_quick_edit_ajax = new Seo_Quick_Edit_Ajax(); |
| 141 | 160 | $this->bulk_action_manager = new Bulk_Action_Manager(); |
| @@ -171,8 +190,16 @@ | ||
| 171 | 190 | // Initialize Divi Visual Builder integration (hooks gate on the VB |
| 172 | 191 | // request, so they no-op without Divi) |
| 173 | 192 | $this->divi_metabox->init(); |
| 174 | 193 | |
| 194 | + // Initialize Bricks builder integration (hooks gate on Bricks' own | |
| 195 | + // builder detector, so they no-op without Bricks) | |
| 196 | + $this->bricks_metabox->init(); | |
| 197 | + | |
| 198 | + // Initialize Beaver Builder integration (hooks gate on Beaver Builder's | |
| 199 | + // own builder detector, so they no-op without Beaver Builder) | |
| 200 | + $this->beaver_metabox->init(); | |
| 201 | + | |
| 175 | 202 | // Initialize post list columns |
| 176 | 203 | $this->post_list_columns->init(); |
| 177 | 204 | |
| 178 | 205 | // Initialize focus keyword AJAX handler |
| @@ -401,8 +428,11 @@ | ||
| 401 | 428 | 'capabilities' => $this->get_user_capabilities(), |
| 402 | 429 | 'settings' => $this->get_admin_settings(), |
| 403 | 430 | 'i18n' => $this->get_i18n_strings(), |
| 404 | 431 | 'isAdmin' => current_user_can('manage_options'), |
| 432 | + // Simple / Advanced navigation for this user (#730). Read once | |
| 433 | + // when the page is built; the header switch updates it in place. | |
| 434 | + 'uiMode' => UI_Mode::get(), | |
| 405 | 435 | // One flag per half of the Import / Export page: the "import from |
| 406 | 436 | // another SEO plugin" section, and ThinkRank's own export/restore. |
| 407 | 437 | 'migrationToolsEnabled' => (bool) $this->settings->get('enable_migration_tools', false), |
| 408 | 438 | 'importExportEnabled' => (bool) $this->settings->get('enable_import_export', false), |
| @@ -418,8 +448,13 @@ | ||
| 418 | 448 | 'faviconUrl' => get_site_icon_url(64) ?: '', |
| 419 | 449 | 'adminEmail' => get_option('admin_email'), |
| 420 | 450 | // Post types for Global SEO navigation |
| 421 | 451 | 'postTypes' => $this->get_public_post_types(), |
| 452 | + // schema.org LocalBusiness subtypes for the Local SEO control. | |
| 453 | + // Localized rather than mirrored in a JS config: the list runs to | |
| 454 | + // ~150 entries and is also the MCP ability's enum, so a second copy | |
| 455 | + // would be a second thing to keep in step (#623). | |
| 456 | + 'localBusinessTypes' => Local_Business_Types_Config::get_options(), | |
| 422 | 457 | // Role Manager: capabilities the current user holds + the |
| 423 | 458 | // section → capability map, so the SPA can hide sections a role |
| 424 | 459 | // cannot access. Administrators receive every capability. |
| 425 | 460 | 'caps' => Capability_Manager::user_capabilities(), |
| @@ -426,13 +461,10 @@ | ||
| 426 | 461 | 'sectionCaps' => Capability_Manager::section_map(), |
| 427 | 462 | 'canManageRoles' => Capability_Manager::current_user_can(Capability_Manager::MANAGE_ROLES), |
| 428 | 463 | // Pro detection flag |
| 429 | 464 | 'isPro' => Plan_Config::is_pro(), |
| 430 | - // Data update frequency (Pro: daily, Free: every 3 days) | |
| 431 | - 'dataUpdateFrequency' => Plan_Config::is_pro() ? 'daily' : '3days', | |
| 432 | - // NB: no per-feature capability maps here. Email Reporting reads | |
| 433 | - // its capabilities from GET /thinkrank/v1/email-report/config; | |
| 434 | - // localizing a second copy only invited the two to drift. | |
| 465 | + // NB: no per-feature capability maps here; each screen reads its | |
| 466 | + // own state over REST, so a localized copy cannot drift from it. | |
| 435 | 467 | // MCP (Model Context Protocol) connection details for the MCP page. |
| 436 | 468 | 'mcp' => $this->get_mcp_globals(), |
| 437 | 469 | // Google OAuth: JS only ever gets a nonce-signed admin-post URL. |
| 438 | 470 | // The consent URL, client ID, and scopes are assembled by the proxy, |
| @@ -466,8 +498,13 @@ | ||
| 466 | 498 | } |
| 467 | 499 | |
| 468 | 500 | // Check for plugin updates |
| 469 | 501 | $this->check_plugin_updates(); |
| 502 | + | |
| 503 | + // One-time: a Key Features value saved while commas were delimiters | |
| 504 | + // becomes one feature per line, so the next regeneration publishes the | |
| 505 | + // bullets the site already had rather than one merged line. | |
| 506 | + \ThinkRank\SEO\LLMs_Txt_Manager::maybe_migrate_legacy_key_features(); | |
| 470 | 507 | } |
| 471 | 508 | |
| 472 | 509 | /** |
| 473 | 510 | * Load admin page |
| @@ -779,19 +816,14 @@ | ||
| 779 | 816 | wp_die(); |
| 780 | 817 | } |
| 781 | 818 | |
| 782 | 819 | /** |
| 783 | - * Check if API key is configured | |
| 820 | + * Check if the selected AI provider is configured | |
| 784 | 821 | * |
| 785 | - * @return bool True if at least one API key is configured | |
| 822 | + * @return bool True when the chosen provider has what it needs to run | |
| 786 | 823 | */ |
| 787 | 824 | private function has_api_key_configured(): bool { |
| 788 | - $settings = \ThinkRank\Core\Settings::instance(); | |
| 789 | - | |
| 790 | - return !empty($settings->get('openai_api_key')) | |
| 791 | - || !empty($settings->get('claude_api_key')) | |
| 792 | - || !empty($settings->get('gemini_api_key')) | |
| 793 | - || !empty($settings->get('openrouter_api_key')); | |
| 825 | + return \ThinkRank\Core\Settings::instance()->has_ai_provider_configured(); | |
| 794 | 826 | } |
| 795 | 827 | |
| 796 | 828 | /** |
| 797 | 829 | * Get menu icon |
| @@ -854,22 +886,17 @@ | ||
| 854 | 886 | ]; |
| 855 | 887 | } |
| 856 | 888 | |
| 857 | 889 | /** |
| 858 | - * Whether any AI provider API key is configured | |
| 890 | + * Whether the selected AI provider is configured | |
| 859 | 891 | * |
| 860 | - * Mirrors the check used by ThinkRank\AI\Manager. | |
| 892 | + * Same answer as ThinkRank\AI\Manager — both read | |
| 893 | + * Settings::has_ai_provider_configured(). | |
| 861 | 894 | * |
| 862 | 895 | * @return bool |
| 863 | 896 | */ |
| 864 | 897 | private function is_ai_configured(): bool { |
| 865 | - foreach (['openai_api_key', 'claude_api_key', 'gemini_api_key', 'openrouter_api_key'] as $key) { | |
| 866 | - if (!empty($this->settings->get($key, ''))) { | |
| 867 | - return true; | |
| 868 | - } | |
| 869 | - } | |
| 870 | - | |
| 871 | - return false; | |
| 898 | + return $this->settings->has_ai_provider_configured(); | |
| 872 | 899 | } |
| 873 | 900 | |
| 874 | 901 | /** |
| 875 | 902 | * Get internationalization strings |