register(); // Analytics domain require_once plugin_dir_path(__FILE__) . 'includes/analytics/class-analytics-tag.php'; require_once plugin_dir_path(__FILE__) . 'includes/analytics/class-analytics-server.php'; require_once plugin_dir_path(__FILE__) . 'includes/database/MigrationRunner.php'; require_once plugin_dir_path(__FILE__) . 'includes/database/migrations/CreateKeywordsTable.php'; require_once plugin_dir_path(__FILE__) . 'includes/database/migrations/CreateAPILogsTable.php'; require_once plugin_dir_path(__FILE__) . 'includes/database/migrations/CreateJobsTable.php'; require_once plugin_dir_path(__FILE__) . 'includes/database/migrations/UpdateKeywordsTableStatus.php'; require_once plugin_dir_path(__FILE__) . 'includes/database/migrations/CreateInternalLinksTable.php'; require_once plugin_dir_path(__FILE__) . 'includes/database/migrations/BackfillAnalyticsMeta.php'; require_once plugin_dir_path(__FILE__) . 'includes/database/migrations/AddHoldFieldsToJobsTable.php'; /** * Build the migration runner with all registered migrations. * * Each migration is assigned a sequential version number. * New migrations MUST be appended at the end with the next version number. */ function contai_build_migration_runner(): ContaiMigrationRunner { $runner = new ContaiMigrationRunner(); $runner->register(1, new ContaiCreateKeywordsTable()); $runner->register(2, new ContaiCreateAPILogsTable()); $runner->register(3, new ContaiCreateJobsTable()); $runner->register(4, new ContaiUpdateKeywordsTableStatus()); $runner->register(5, new ContaiCreateInternalLinksTable()); $runner->register(6, new ContaiBackfillAnalyticsMeta()); $runner->register(7, new ContaiAddHoldFieldsToJobsTable()); return $runner; } function contai_activate_plugin() { $runner = contai_build_migration_runner(); $result = $runner->run(); if (!$result['success']) { contai_log('Migration batch failed: ' . $result['message']); } try { contai_register_job_processor_cron(); } catch (Exception $e) { contai_log("Cron registration error: " . $e->getMessage()); } try { contai_register_agent_actions_cron(); } catch (Exception $e) { contai_log("Agent cron registration error: " . $e->getMessage()); } // Set site hardening defaults for existing installs (opt-in for new installs) add_option('contai_disable_feeds', '1'); add_option('contai_disable_author_pages', '1'); add_option('contai_redirect_404', '1'); // Store version so contai_maybe_upgrade() can detect future updates update_option('contai_plugin_version', CONTAI_VERSION, false); } register_activation_hook(__FILE__, 'contai_activate_plugin'); register_deactivation_hook(__FILE__, 'contai_unregister_job_processor_cron'); register_deactivation_hook( __FILE__, 'contai_unregister_agent_actions_cron' ); /** * Run upgrade routines when the plugin version changes. * * WordPress does NOT fire register_activation_hook on updates — only on * first activation. This hook detects version changes on every page load * and runs pending migrations, re-registers crons, and invalidates caches * so that updates take effect without reinstalling. */ function contai_maybe_upgrade() { $stored_version = get_option('contai_plugin_version', '0'); if (version_compare($stored_version, CONTAI_VERSION, '>=')) { return; } // Run pending database migrations $runner = contai_build_migration_runner(); $result = $runner->run(); if (!$result['success']) { contai_log('Upgrade migration failed: ' . $result['message']); } // Re-register cron events (may have been lost during update) contai_register_job_processor_cron(); contai_register_agent_actions_cron(); // Invalidate OPcache so PHP serves the new files if (function_exists('opcache_reset')) { opcache_reset(); } // Flush plugin-related transients so stale cached data is not served global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_contai_%')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_timeout_contai_%')); // Always bump version even on migration failure to prevent infinite retry // loops. MigrationRunner stores its own error in contai_migration_error // and an admin notice alerts the user to investigate. update_option('contai_plugin_version', CONTAI_VERSION, false); contai_log(sprintf('Plugin upgraded from %s to %s', $stored_version, CONTAI_VERSION)); } add_action('plugins_loaded', 'contai_maybe_upgrade', 5); $toc_integration = ContaiTocFactory::create(); $toc_integration->register(); add_action('plugins_loaded', function() { $internal_links_integration = new ContaiInternalLinksWordPressIntegration(); $internal_links_integration->register(); // Analytics: GA4 tag injection + server-side events if (get_option('1platform_ga4_measurement_id', '')) { $analytics_tag = new OnePlatform_Analytics_Tag(); $analytics_tag->init(); $analytics_server = new OnePlatform_Analytics_Server(); $analytics_server->init(); } }); /** * Check if the license key option exists and is valid * * @return bool True if license key is present and non-empty, false otherwise */ function contai_has_license_key() { $license_key = get_option('contai_api_key', ''); if (empty($license_key)) { return false; } if (is_string($license_key)) { $trimmed = trim($license_key); return !empty($trimmed); } return false; } /** * Register all admin menus unconditionally. * * All menus are always accessible regardless of API key status. * When the API key is not configured, SaaS-dependent pages display * a connection CTA instead of blocking access. * Local features (TOC, Internal Links) work without any API key. * This satisfies WordPress.org Guideline 5 (no trialware hard-gating). */ function contai_register_admin_menus() { add_menu_page( '1Platform Content AI', '1Platform Content AI', 'manage_options', 'contai-website-settings', 'contai_website_settings_page', 'dashicons-admin-comments', 90 ); add_submenu_page( 'contai-website-settings', 'Settings', 'Settings', 'manage_options', 'contai-website-settings', 'contai_website_settings_page' ); add_submenu_page( 'contai-website-settings', 'Content', 'Content', 'manage_options', 'contai-content-generator', 'contai_content_generator_page' ); add_submenu_page( 'contai-website-settings', 'Tools', 'Tools', 'manage_options', 'contai-apps', 'contai_apps_page' ); add_submenu_page( 'contai-website-settings', 'Site Wizard', 'Site Wizard', 'manage_options', 'contai-ai-site-generator', 'contai_ai_site_generator_page' ); add_submenu_page( 'contai-website-settings', 'Jobs', 'Jobs', 'manage_options', 'contai-job-monitor', 'contai_render_job_monitor_page' ); add_submenu_page( 'contai-website-settings', 'Billing', 'Billing', 'manage_options', 'contai-billing', 'contai_billing_page' ); add_submenu_page( 'contai-website-settings', 'Logs', 'Logs', 'manage_options', 'contai-logs', 'contai_logs_page' ); add_submenu_page( 'contai-website-settings', 'Agents', 'Agents', 'manage_options', 'contai-agents', 'contai_agents_page' ); add_submenu_page( 'contai-website-settings', 'License', 'License', 'manage_options', 'contai-licenses', 'contai_licenses_page' ); } add_action( 'admin_menu', 'contai_register_admin_menus' ); add_action( 'rest_api_init', function() { $controller = new ContaiAgentRestController(); $controller->register_routes(); require_once plugin_dir_path( __FILE__ ) . 'includes/services/adsense/ContaiAdSenseRestController.php'; $adsense_controller = new ContaiAdSenseRestController(); $adsense_controller->register_routes(); require_once plugin_dir_path( __FILE__ ) . 'includes/services/search-console/ContaiSearchConsolePerformanceRestController.php'; $search_console_performance_controller = new ContaiSearchConsolePerformanceRestController(); $search_console_performance_controller->register_routes(); require_once plugin_dir_path( __FILE__ ) . 'includes/services/onboarding/ContaiOnboardingRestController.php'; $onboarding_controller = new ContaiOnboardingRestController(); $onboarding_controller->register_routes(); require_once plugin_dir_path( __FILE__ ) . 'includes/services/jobs/ContaiQueueRestController.php'; $queue_controller = new ContaiQueueRestController(); $queue_controller->register_routes(); } ); /** * Render the Agents admin page. */ function contai_agents_page() { ContaiAgentsAdminPage::render(); } /** * Enqueue the UI v3 foundation (tokens + components + JS helpers) on every * plugin admin page. Loads unconditionally — no feature flag, no per-screen * gate. Registered at a high priority so foundation tokens/components print * AFTER any per-screen stylesheets: this guarantees `contai-tokens.css` wins * the `:root` cascade while legacy per-screen CSS is phased out. */ add_action( 'admin_enqueue_scripts', function( $hook ) { if ( strpos( (string) $hook, 'contai' ) === false ) { return; } $base = plugin_dir_url( __FILE__ ) . 'includes/admin/assets/'; contai_enqueue_style_with_version( 'contai-tokens', $base . 'css/contai-tokens.css' ); contai_enqueue_style_with_version( 'contai-components', $base . 'css/contai-components.css', array( 'contai-tokens' ) ); wp_enqueue_style( 'dashicons' ); contai_enqueue_script_with_version( 'contai-ui', $base . 'js/contai-ui.js', array(), true ); }, 999 ); add_action( 'admin_enqueue_scripts', function( $hook ) { if ( strpos( $hook, 'contai-agents' ) === false ) { return; } wp_enqueue_script( 'contai-agents-admin', plugin_dir_url( __FILE__ ) . 'includes/admin/agents/contai-agents-admin.js', array(), CONTAI_VERSION, true ); wp_localize_script( 'contai-agents-admin', 'contaiAgents', array( 'restUrl' => rest_url( 'contai/v1/' ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'settings' => ContaiAgentSettingsService::getAllSettings(), 'adminUrl' => admin_url( 'admin.php?page=contai-agents' ), ) ); } ); /** * Render a connection-required notice when the API key is not configured. * * SaaS-dependent pages call this function to display a CTA instead of * blocking access with redirects. Returns true if notice was rendered * (caller should skip page content). * * @return bool True if the notice was rendered. */ function contai_render_connection_required_notice() { if ( contai_has_license_key() ) { return false; } $license_url = admin_url( 'admin.php?page=contai-licenses' ); ?>
render(); } /** * Sync buffered client logs to the API on shutdown. */ add_action('shutdown', function() { if (is_admin() && ContaiClientLogReporter::getBufferCount() > 0) { ContaiClientLogReporter::syncBuffer(); } }); /** * Display admin notices for authentication token errors on plugin pages. */ function contai_display_auth_error_notices(): void { if (!current_user_can('manage_options')) { return; } $screen = get_current_screen(); if (!$screen || strpos($screen->id, 'contai') === false) { return; } require_once plugin_dir_path(__FILE__) . 'includes/services/api/OnePlatformAuthService.php'; require_once plugin_dir_path(__FILE__) . 'includes/services/config/Config.php'; // If tokens are currently valid, clear any stale error notices // left over from a previous transient failure. $authService = ContaiOnePlatformAuthService::create(); if ($authService->validateToken()) { $authService->clearErrors(); return; } $app_error = ContaiOnePlatformAuthService::getAppTokenError(); if ($app_error !== null) { printf( '%s %s
%s %s %s
%s %s
%s
%s %s %s