api = $api; $this->settings = $settings; $this->wizard = $wizard; $this->statistics = $statistics; add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_assets')); add_action('admin_init', array($this, 'register_settings')); add_action('admin_init', array($this, 'maybe_redirect')); add_action('wp_ajax_aicp_disconnect', array($this, 'ajax_disconnect')); add_action('wp_ajax_aicp_save_settings', array($this, 'ajax_save_settings')); add_action('wp_ajax_aicp_reconfigure_capabilities', array($this, 'ajax_reconfigure_capabilities')); add_action('wp_ajax_aicp_save_sharing_settings', array($this, 'ajax_save_sharing_settings')); } /** * Add admin menu */ public function add_admin_menu() { add_menu_page( __('AI Content Plan', 'ai-content-plan'), __('AI Content Plan', 'ai-content-plan'), 'manage_aicp_settings', 'aicp-bridge', array($this, 'render_dashboard'), 'dashicons-edit-page', null ); add_submenu_page( 'aicp-bridge', __('Dashboard', 'ai-content-plan'), __('Dashboard', 'ai-content-plan'), 'manage_aicp_settings', 'aicp-bridge', array($this, 'render_dashboard') ); add_submenu_page( 'aicp-bridge', __('Settings', 'ai-content-plan'), __('Settings', 'ai-content-plan'), 'manage_aicp_settings', 'aicp-bridge-settings', array($this, 'render_settings') ); // Add setup wizard page (hidden from menu) $wizard_hook = add_submenu_page( '', __('Setup Wizard', 'ai-content-plan'), __('Setup Wizard', 'ai-content-plan'), 'manage_aicp_settings', 'aicp-bridge-wizard', array($this, 'render_wizard') ); // Set page title explicitly for hidden pages (PHP 8.1+ compat) if ($wizard_hook) { add_action("load-{$wizard_hook}", function () { global $title; $title = __('Setup Wizard', 'ai-content-plan'); }); } } /** * Enqueue admin assets * * @param string $hook Current admin page */ public function enqueue_assets($hook) { if (strpos($hook, 'aicp-bridge') === false) { return; } // CSS wp_enqueue_style( 'aicp-admin', AICP_BRIDGE_PLUGIN_URL . 'admin/css/aicp-admin.css', array(), AICP_BRIDGE_VERSION ); // JavaScript wp_enqueue_script( 'aicp-admin', AICP_BRIDGE_PLUGIN_URL . 'admin/js/aicp-settings.js', array('jquery'), AICP_BRIDGE_VERSION, true ); // Wizard specific JS if (strpos($hook, 'wizard') !== false) { wp_enqueue_script( 'aicp-wizard', AICP_BRIDGE_PLUGIN_URL . 'admin/js/aicp-wizard.js', array('jquery'), AICP_BRIDGE_VERSION, true ); } // Chart.js for statistics (only on dashboard) if ($hook === 'toplevel_page_aicp-bridge') { wp_enqueue_script( 'chartjs', AICP_BRIDGE_PLUGIN_URL . 'admin/js/chart.min.js', array(), '4.5.1', true ); // Dashboard chart data $chart_data = $this->statistics->get_chart_data(); wp_localize_script('aicp-admin', 'aicpDashboard', array( 'hasChartData' => !empty($chart_data['data']), 'chartLabels' => isset($chart_data['labels']) ? $chart_data['labels'] : array(), 'chartData' => isset($chart_data['data']) ? $chart_data['data'] : array(), )); } // Localize scripts wp_localize_script('aicp-admin', 'aicpAdmin', array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'wizardNonce' => wp_create_nonce('aicp_wizard_nonce'), 'settingsNonce' => wp_create_nonce('aicp_settings_nonce'), 'statsNonce' => wp_create_nonce('aicp_stats_nonce'), 'wcNonce' => wp_create_nonce('aicp_woocommerce_nonce'), 'strings' => array( 'connectionSuccess' => __('Connection successful!', 'ai-content-plan'), 'connectionFailed' => __('Connection failed. Please check your credentials.', 'ai-content-plan'), 'saving' => __('Saving...', 'ai-content-plan'), 'saved' => __('Saved!', 'ai-content-plan'), 'error' => __('An error occurred.', 'ai-content-plan'), 'confirmDisconnect' => __('Are you sure you want to disconnect? You will need to run the setup wizard again to reconnect.', 'ai-content-plan'), 'generating' => __('Generating...', 'ai-content-plan'), 'generateCredentials' => __('Generate Credentials', 'ai-content-plan'), 'generateFailed' => __('Failed to generate credentials', 'ai-content-plan'), 'connectionError' => __('Connection error. Please try again.', 'ai-content-plan'), 'enterLinkingCode' => __('Please enter a linking code', 'ai-content-plan'), 'linking' => __('Linking...', 'ai-content-plan'), 'linkSite' => __('Link Site', 'ai-content-plan'), 'linkFailed' => __('Failed to link site', 'ai-content-plan'), 'selectFeature' => __('Please select at least one feature', 'ai-content-plan'), 'continueText' => __('Continue', 'ai-content-plan'), 'saveFailed' => __('Failed to save. Please try again.', 'ai-content-plan'), 'saveAndContinue' => __('Save & Continue', 'ai-content-plan'), 'saveChanges' => __('Save Changes', 'ai-content-plan'), 'settingsSaved' => __('Settings saved successfully!', 'ai-content-plan'), 'sharingSaved' => __('Sharing settings saved!', 'ai-content-plan'), 'testing' => __('Testing...', 'ai-content-plan'), 'testConnection' => __('Test Connection', 'ai-content-plan'), 'articlesPublished' => __('Articles Published', 'ai-content-plan'), 'enabled' => __('Enabled', 'ai-content-plan'), 'disabled' => __('Disabled', 'ai-content-plan'), 'selectCapability' => __('Please select at least one capability.', 'ai-content-plan'), 'noChanges' => __('No changes detected.', 'ai-content-plan'), 'confirmReconfigure' => __('Reconfigure capabilities? This may redirect you to complete the process.', 'ai-content-plan'), 'reconfiguring' => __('Reconfiguring...', 'ai-content-plan'), 'reconfigureCapabilities' => __('Reconfigure Capabilities', 'ai-content-plan'), 'redirecting' => __('Redirecting...', 'ai-content-plan'), 'networkError' => __('Network error.', 'ai-content-plan'), ) )); // Wizard-specific data if (strpos($hook, 'wizard') !== false) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin page parameter $wizard_step = isset($_GET['step']) ? intval($_GET['step']) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin page parameter $is_reconfiguring = isset($_GET['reconfiguring']) ? sanitize_text_field(wp_unslash($_GET['reconfiguring'])) : ''; wp_localize_script('aicp-wizard', 'aicpWizard', array( 'wizardBaseUrl' => admin_url('admin.php?page=aicp-bridge-wizard'), 'settingsUrl' => admin_url('admin.php?page=aicp-bridge-settings'), 'currentStep' => $wizard_step, 'isReconfiguring' => $is_reconfiguring, 'capabilities' => $this->settings->get_selected_capabilities(), )); } // Settings page data if (strpos($hook, 'aicp-bridge-settings') !== false) { wp_localize_script('aicp-admin', 'aicpSettingsData', array( 'capabilities' => get_option('aicp_chosen_capabilities', array()), )); } } /** * Register settings */ public function register_settings() { // Publishing defaults are handled via AJAX } /** * Handle redirects before any output is sent */ public function maybe_redirect() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin menu page check, nonce not applicable if (!isset($_GET['page'])) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin menu page check, nonce not applicable $page = sanitize_text_field(wp_unslash($_GET['page'])); // Dashboard: redirect to wizard if setup incomplete if ($page === 'aicp-bridge' && $this->wizard->should_show_wizard()) { wp_safe_redirect(admin_url('admin.php?page=aicp-bridge-wizard')); exit; } } /** * Render dashboard page */ public function render_dashboard() { include AICP_BRIDGE_PLUGIN_DIR . 'admin/views/dashboard.php'; } /** * Render settings page */ public function render_settings() { include AICP_BRIDGE_PLUGIN_DIR . 'admin/views/settings.php'; } /** * Render setup wizard */ public function render_wizard() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin menu page parameter, nonce not applicable $step = isset($_GET['step']) ? intval($_GET['step']) : $this->wizard->get_current_step(); // Clamp to valid range if ($step < 0 || $step > 5) { $step = $this->wizard->get_current_step(); } switch ($step) { case 0: include AICP_BRIDGE_PLUGIN_DIR . 'admin/views/wizard-step-0.php'; break; case 1: include AICP_BRIDGE_PLUGIN_DIR . 'admin/views/wizard-step-1.php'; break; case 2: include AICP_BRIDGE_PLUGIN_DIR . 'admin/views/wizard-step-2.php'; break; case 3: include AICP_BRIDGE_PLUGIN_DIR . 'admin/views/wizard-step-3.php'; break; case 4: include AICP_BRIDGE_PLUGIN_DIR . 'admin/views/wizard-step-4.php'; break; case 5: include AICP_BRIDGE_PLUGIN_DIR . 'admin/views/wizard-step-5.php'; break; } } /** * AJAX: Disconnect from AICP */ public function ajax_disconnect() { check_ajax_referer('aicp_settings_nonce', 'nonce'); if (!current_user_can('manage_aicp_settings')) { wp_send_json_error(array('message' => __('Permission denied.', 'ai-content-plan'))); } $this->settings->disconnect(); $this->api->clear_cache(); wp_send_json_success(array( 'message' => __('Disconnected successfully.', 'ai-content-plan'), 'redirect' => admin_url('admin.php?page=aicp-bridge-wizard') )); } /** * AJAX: Save settings */ public function ajax_save_settings() { check_ajax_referer('aicp_settings_nonce', 'nonce'); if (!current_user_can('manage_aicp_settings')) { wp_send_json_error(array('message' => __('Permission denied.', 'ai-content-plan'))); } // Save publishing defaults if (isset($_POST['publishing_defaults'])) { $defaults = array( 'post_status' => isset($_POST['publishing_defaults']['post_status']) ? sanitize_text_field(wp_unslash($_POST['publishing_defaults']['post_status'])) : 'draft', 'default_category_id' => isset($_POST['publishing_defaults']['default_category_id']) ? absint($_POST['publishing_defaults']['default_category_id']) : 0, 'default_author_id' => isset($_POST['publishing_defaults']['default_author_id']) ? absint($_POST['publishing_defaults']['default_author_id']) : 0 ); $sanitized = AICP_Validator::sanitize_publishing_defaults($defaults); $this->settings->set_publishing_defaults($sanitized); // Resolve names and sync with SaaS API $category_id = $sanitized['default_category_id']; $author_id = $sanitized['default_author_id']; $category_name = null; if ($category_id > 0) { $cat = get_category($category_id); if ($cat && !is_wp_error($cat)) { $category_name = $cat->name; } } $author_name = null; if ($author_id > 0) { $user = get_userdata($author_id); if ($user) { $author_name = $user->display_name; } } $response = $this->api->configure_output_channel(array( 'post_status' => $sanitized['post_status'], 'category' => $category_id, 'category_name' => $category_name, 'author' => $author_id, 'author_name' => $author_name, )); if (is_wp_error($response)) { wp_send_json_error(array('message' => $response->get_error_message())); } } wp_send_json_success(array('message' => __('Settings saved.', 'ai-content-plan'))); } /** * AJAX: Reconfigure capabilities */ public function ajax_reconfigure_capabilities() { check_ajax_referer('aicp_settings_nonce', 'nonce'); if (!current_user_can('manage_aicp_settings')) { wp_send_json_error(array('message' => __('Permission denied.', 'ai-content-plan'))); } // Get and validate inputs $enable_output = isset($_POST['enable_output']) && $_POST['enable_output'] === 'true'; $enable_input = isset($_POST['enable_input']) && $_POST['enable_input'] === 'true'; // At least one capability must be enabled if (!$enable_output && !$enable_input) { wp_send_json_error(array( 'message' => __('At least one capability must be enabled.', 'ai-content-plan') )); } // Reconfigure capabilities $result = $this->settings->reconfigure_capabilities($enable_output, $enable_input); // Determine redirect URL $redirect_url = ''; if ($result['next_step'] >= 0) { // Needs wizard configuration $redirect_url = admin_url('admin.php?page=aicp-bridge-wizard&step=' . $result['next_step'] . '&reconfiguring=1'); } else { // No wizard needed, redirect back to settings $redirect_url = admin_url('admin.php?page=aicp-bridge-settings'); } wp_send_json_success(array( 'message' => __('Capabilities updated successfully.', 'ai-content-plan'), 'redirect' => $redirect_url, 'changes' => $result['changes'], 'needs_configuration' => $result['needs_configuration'] )); } /** * AJAX: Save sharing settings */ public function ajax_save_sharing_settings() { check_ajax_referer('aicp_settings_nonce', 'nonce'); if (!current_user_can('manage_aicp_settings')) { wp_send_json_error(array('message' => __('Permission denied.', 'ai-content-plan'))); } // Get and validate inputs $share_posts = isset($_POST['share_posts']) && $_POST['share_posts'] == '1'; $share_products = isset($_POST['share_products']) && $_POST['share_products'] == '1'; $post_category_ids = isset($_POST['post_category_ids']) && is_array($_POST['post_category_ids']) ? array_map('intval', $_POST['post_category_ids']) : array(); $product_category_ids = isset($_POST['product_category_ids']) && is_array($_POST['product_category_ids']) ? array_map('intval', $_POST['product_category_ids']) : array(); // Build settings array $settings = array( 'share_posts' => $share_posts, 'post_category_ids' => $post_category_ids, 'share_products' => $share_products, 'product_category_ids' => $product_category_ids ); // Save settings $result = $this->settings->set_input_channel_settings($settings); if ($result) { wp_send_json_success(array( 'message' => __('Sharing settings saved successfully!', 'ai-content-plan') )); } else { wp_send_json_error(array( 'message' => __('Failed to save sharing settings. Please try again.', 'ai-content-plan') )); } } /** * Get WordPress categories for dropdown * * @return array */ public function get_categories_dropdown() { $categories = get_categories(array( 'hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC' )); $options = array( 0 => __('Select a category...', 'ai-content-plan') ); foreach ($categories as $cat) { $options[$cat->term_id] = $cat->name; } return $options; } /** * Get WordPress users for dropdown * * @return array */ public function get_authors_dropdown() { $users = get_users(array( 'role__in' => array('administrator', 'editor', 'author'), 'orderby' => 'display_name', 'order' => 'ASC' )); $options = array( 0 => __('Select an author...', 'ai-content-plan') ); foreach ($users as $user) { $options[$user->ID] = $user->display_name; } return $options; } }