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

', esc_html__('Content AI Authentication Error:', '1platform-content-ai'), esc_html($app_error) ); } $user_error = ContaiOnePlatformAuthService::getUserTokenError(); if ($user_error !== null) { printf( '

%s %s %s

', esc_html__('Content AI License Error:', '1platform-content-ai'), esc_html($user_error), esc_url(admin_url('admin.php?page=contai-licenses')), esc_html__('Check your API key settings.', '1platform-content-ai') ); } } add_action('admin_notices', 'contai_display_auth_error_notices'); /** * Display admin notice when database migrations have failed. */ function contai_display_migration_error_notice(): 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/database/MigrationRunner.php'; $error = ContaiMigrationRunner::getError(); if ($error === null) { return; } printf( '

%s %s

', esc_html__('Content AI Database Error:', '1platform-content-ai'), esc_html($error) ); } add_action('admin_notices', 'contai_display_migration_error_notice'); /** * Display AdSense policy issue notices. */ function contai_display_adsense_policy_notice() { if ( ! current_user_can( 'manage_options' ) ) { return; } $policy_count = get_transient( 'contai_adsense_policy_count' ); if ( $policy_count === false || (int) $policy_count === 0 ) { return; } printf( '

%s

', esc_html( sprintf( /* translators: %d: number of policy issues */ __( '%d AdSense policy issue(s) detected. Review in Tools > Ads Manager.', '1platform-content-ai' ), (int) $policy_count ) ) ); } add_action( 'admin_notices', 'contai_display_adsense_policy_notice' ); /** * Warn the administrator when WP-Cron has not fired the job-processor tick * within the last 5 minutes. * * Only renders on plugin admin pages to avoid noise across the rest of WP * admin. The CTA links to the Jobs page where the "Ejecutar ahora" button * lives. */ function contai_display_queue_overdue_notice(): 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/jobs/metrics/QueueHealthService.php'; $snapshot = ( new ContaiQueueHealthService() )->getSnapshot(); if ( (int) ( $snapshot['next_run_overdue_seconds'] ?? 0 ) <= 300 ) { return; } $jobs_url = admin_url( 'admin.php?page=contai-job-monitor' ); printf( '

%s %s %s

', esc_html__( 'Cola de jobs inactiva:', '1platform-content-ai' ), esc_html__( 'WP-Cron parece estar inactivo. Use el botón "Ejecutar ahora" o asegúrese de que el sitio reciba tráfico.', '1platform-content-ai' ), esc_url( $jobs_url ), esc_html__( 'Abrir Jobs', '1platform-content-ai' ) ); } add_action( 'admin_notices', 'contai_display_queue_overdue_notice' );