data_provider = new AILM_KI_Data_Provider(); add_action('admin_post_ailm_handle_actions', [$this, 'handle_bulk_delete']); add_action('wp_ajax_ailm_ki_init_scan', [$this, 'handle_init_scan']); add_action('wp_ajax_ailm_ki_process_batch', [$this, 'handle_process_batch']); add_action('wp_ajax_ailm_ki_analyze_context', [$this, 'handle_analyze_context']); add_action('wp_ajax_ailm_ki_get_targets', [$this, 'handle_get_targets']); add_action('wp_ajax_ailm_ki_create_rule', [$this, 'handle_create_rule']); add_action('wp_ajax_ailm_ki_create_manual_link', [$this, 'handle_create_manual_link']); add_action('wp_ajax_ailm_ki_get_post_snippets', [$this, 'handle_get_post_snippets']); add_action('wp_ajax_ailm_ki_remove_donor', [$this, 'ajax_remove_donor']); add_action('wp_ajax_ailm_ki_poll_context', [$this, 'handle_poll_context_task']); } public function handle_create_manual_link() { check_ajax_referer('ailm_ki_scan_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(__('Permission denied.', 'ailinkmaster-internal-auto-manager')); } $donor_id = isset($_POST['donor_id']) ? absint($_POST['donor_id']) : 0; $target_id = isset($_POST['target_id']) ? absint($_POST['target_id']) : 0; $anchor = isset($_POST['anchor']) ? sanitize_text_field(wp_unslash($_POST['anchor'])) : ''; $context = isset($_POST['context']) ? sanitize_text_field(wp_unslash($_POST['context'])) : ''; if (!$donor_id || !$target_id || empty($anchor) || empty($context)) { wp_send_json_error(__('Missing parameters.', 'ailinkmaster-internal-auto-manager')); } if ($donor_id === $target_id) { wp_send_json_error(__('Cannot link an article to itself.', 'ailinkmaster-internal-auto-manager')); } if (!class_exists('AILM_Link_Builder')) { require_once dirname(dirname(__DIR__)) . '/includes/class-ailm-link-builder.php'; } if (!class_exists('AILM_DOM_Helper')) { require_once dirname(dirname(__DIR__)) . '/includes/class-ailm-dom-helper.php'; } $link_builder = new AILM_Link_Builder(null); if (!$link_builder->can_inject_phrase($donor_id, $anchor)) { wp_send_json_error(__('Link insertion rejected by General Settings (limits exceeded or restricted zone).', 'ailinkmaster-internal-auto-manager')); } $donor_post = get_post($donor_id); if (!$donor_post) { wp_send_json_error(__('Donor post not found.', 'ailinkmaster-internal-auto-manager')); } $target_url = get_permalink($target_id); $dom_helper = new AILM_DOM_Helper($donor_post->post_content); $virtual_text = $dom_helper->get_virtual_text(); $context_pos = mb_strpos($virtual_text, $context, 0, 'UTF-8'); if ($context_pos === false) { $anchor_absolute_pos = mb_strpos($virtual_text, $anchor, 0, 'UTF-8'); if ($anchor_absolute_pos === false) { wp_send_json_error(__('Could not locate the text block in the document.', 'ailinkmaster-internal-auto-manager')); } } else { $anchor_pos_in_context = mb_strpos($context, $anchor, 0, 'UTF-8'); if ($anchor_pos_in_context === false) { wp_send_json_error(__('Anchor not found in context.', 'ailinkmaster-internal-auto-manager')); } $anchor_absolute_pos = $context_pos + $anchor_pos_in_context; } $escaped_parts = array_map(function($p) { return str_replace("'", "['’‘`´]", preg_quote($p, '/')); }, preg_split('/\s+/u', trim($anchor), -1, PREG_SPLIT_NO_EMPTY)); $fuzzy_kw = implode('[\p{Z}\s\p{P}\p{S}]+', $escaped_parts); $pattern = '/(? $match) { $byte_offset = $match[1]; $char_offset = mb_strlen(substr($virtual_text, 0, $byte_offset), 'UTF-8'); if ($char_offset === $anchor_absolute_pos) { $occurrence = $idx + 1; $found_word = $match[0]; break; } } if ($found_word === $anchor && !empty($matches[0][0][0])) { $found_word = $matches[0][0][0]; } } if (!class_exists('AILM_Link_Repository')) { require_once dirname(dirname(__DIR__)) . '/includes/class-ailm-link-repository.php'; } AILM_Link_Repository::register_link( $donor_id, $target_url, $found_word, 'manual_ui', $occurrence, false // in_html = 0; injector will insert and mark in_html=1. ); $link_builder->inject_physical_links($donor_id); // _ailm_inbound_count is incremented inside inject_physical_links() // on successful insertion. Sync from the authoritative rule count to // guard against drift. if (class_exists('AILM_Link_Repository')) { $authoritative = AILM_Link_Repository::get_active_inbound_rule_count($target_id); update_post_meta($target_id, '_ailm_inbound_count', $authoritative); } if (class_exists('AILM_Scout_Scanner')) { AILM_Scout_Scanner::process_post($donor_id); } $modified_context = $context; $final_anchor_pos = mb_strpos($context, $anchor, 0, 'UTF-8'); if ($final_anchor_pos !== false) { $anchor_length = mb_strlen($anchor, 'UTF-8'); $new_link_html = '' . esc_html($anchor) . ''; $modified_context = mb_substr($context, 0, $final_anchor_pos, 'UTF-8') . $new_link_html . mb_substr($context, $final_anchor_pos + $anchor_length, null, 'UTF-8'); } wp_send_json_success([ 'new_context_html' => $modified_context ]); } public function enqueue_scripts($hook) { if (strpos($hook, 'ailinkmaster-internal-auto-manager') === false) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $current_tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : 'settings'; if ('keyword-intelligence' !== $current_tab) { return; } $script_path = plugin_dir_url(__FILE__) . 'assets/admin-ki.js'; $script_asset_path = plugin_dir_path(__FILE__) . 'assets/admin-ki.js'; $version = file_exists($script_asset_path) ? filemtime($script_asset_path) : '1.0.0'; wp_enqueue_style('ailm-select2-css', plugin_dir_url(__FILE__) . '../../assets/select2.min.css', [], '4.0.13'); wp_enqueue_script('ailm-select2-js', plugin_dir_url(__FILE__) . '../../assets/select2.min.js', ['jquery'], '4.0.13', true); wp_enqueue_script('ailm-ki-admin-script', $script_path, ['jquery', 'ailm-select2-js'], $version, true); wp_localize_script('ailm-ki-admin-script', 'ailmKi', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('ailm_ki_scan_nonce'), 'i18n' => [ 'err_no_types' => esc_html__('Please select at least one content type to analyze.', 'ailinkmaster-internal-auto-manager'), 'initializing' => esc_html__('Initializing scan...', 'ailinkmaster-internal-auto-manager'), 'running_scan' => esc_html__('Running scan...', 'ailinkmaster-internal-auto-manager'), 'results_placeholder' => esc_html__('Results...', 'ailinkmaster-internal-auto-manager'), 'scanning' => esc_html__('Scanning posts:', 'ailinkmaster-internal-auto-manager'), 'err_unknown' => esc_html__('An unknown error occurred.', 'ailinkmaster-internal-auto-manager'), 'err_server' => esc_html__('Error from server: ', 'ailinkmaster-internal-auto-manager'), 'err_occurred' => esc_html__('An error occurred.', 'ailinkmaster-internal-auto-manager'), 'err_ajax' => esc_html__('AJAX Error: ', 'ailinkmaster-internal-auto-manager'), 'err_no_session' => esc_html__('Scan session not found. Please run a new scan.', 'ailinkmaster-internal-auto-manager'), /* translators: %s: keyword being analyzed */ 'analyzing_ctx' => esc_html__('Analyzing context for "%s"...', 'ailinkmaster-internal-auto-manager'), 'ctx_analysis_for' => esc_html__('Context Analysis for:', 'ailinkmaster-internal-auto-manager'), 'ctx_around' => esc_html__('Context "AROUND"', 'ailinkmaster-internal-auto-manager'), 'ctx_inside' => esc_html__('Context "INSIDE"', 'ailinkmaster-internal-auto-manager'), 'ctx_not_applicable' => esc_html__('Not applicable for single words.', 'ailinkmaster-internal-auto-manager'), 'no_ctx_found' => esc_html__('No context found.', 'ailinkmaster-internal-auto-manager'), 'lbl_keyword' => esc_html__('Keyword', 'ailinkmaster-internal-auto-manager'), 'lbl_phrase' => esc_html__('Phrase', 'ailinkmaster-internal-auto-manager'), 'lbl_count' => esc_html__('Count', 'ailinkmaster-internal-auto-manager'), 'lbl_actions' => esc_html__('Actions', 'ailinkmaster-internal-auto-manager'), 'btn_articles' => esc_html__('Articles', 'ailinkmaster-internal-auto-manager'), 'btn_articles_hide' => esc_html__('Hide Articles', 'ailinkmaster-internal-auto-manager'), 'btn_create_route' => esc_html__('+ Create Route', 'ailinkmaster-internal-auto-manager'), 'lbl_hits' => esc_html__('hits', 'ailinkmaster-internal-auto-manager'), 'lbl_cat' => esc_html__('Cat', 'ailinkmaster-internal-auto-manager'), 'btn_edit' => esc_html__('Edit Post', 'ailinkmaster-internal-auto-manager'), 'btn_view' => esc_html__('View Post', 'ailinkmaster-internal-auto-manager'), /* translators: %d: number of nested variations */ 'show_nested' => esc_html__('Show %d nested variations', 'ailinkmaster-internal-auto-manager'), 'hide_nested' => esc_html__('Hide nested variations', 'ailinkmaster-internal-auto-manager'), 'modal_title' => esc_html__('Create Topical Silo Routes', 'ailinkmaster-internal-auto-manager'), 'lbl_target_anchor' => esc_html__('Target Anchor', 'ailinkmaster-internal-auto-manager'), 'btn_cancel' => esc_html__('Cancel', 'ailinkmaster-internal-auto-manager'), 'btn_save_apply' => esc_html__('Save Routes & Apply', 'ailinkmaster-internal-auto-manager'), 'lbl_category' => esc_html__('Category', 'ailinkmaster-internal-auto-manager'), 'btn_custom_target' => esc_html__('Custom Target', 'ailinkmaster-internal-auto-manager'), 'lbl_specific_target' => esc_html__('Specific target for this post', 'ailinkmaster-internal-auto-manager'), 'opt_cat' => esc_html__('Category', 'ailinkmaster-internal-auto-manager'), 'opt_art' => esc_html__('Specific Article', 'ailinkmaster-internal-auto-manager'), 'lbl_loading' => esc_html__('Loading...', 'ailinkmaster-internal-auto-manager'), 'lbl_default_target' => esc_html__('Default target for this category cluster', 'ailinkmaster-internal-auto-manager'), 'err_no_selection' => esc_html__('Please select at least one article across all groups.', 'ailinkmaster-internal-auto-manager'), 'error' => esc_html__('Error: ', 'ailinkmaster-internal-auto-manager'), 'confirm_remove_donor' => esc_html__('Remove this donor from the route?', 'ailinkmaster-internal-auto-manager'), 'donor_removed' => esc_html__('Donor removed successfully.', 'ailinkmaster-internal-auto-manager'), 'err_remove_donor' => esc_html__('Failed to remove donor.', 'ailinkmaster-internal-auto-manager'), /* translators: %d: number of results shown */ 'showing_top' => esc_html__('Showing top %d results', 'ailinkmaster-internal-auto-manager'), 'btn_prev' => esc_html__('Prev', 'ailinkmaster-internal-auto-manager'), 'btn_next' => esc_html__('Next', 'ailinkmaster-internal-auto-manager'), 'lbl_of' => esc_html__('of', 'ailinkmaster-internal-auto-manager'), 'lbl_freq' => esc_html__('Frequency', 'ailinkmaster-internal-auto-manager'), 'no_results' => esc_html__('No results found.', 'ailinkmaster-internal-auto-manager'), 'btn_analyze_ctx' => esc_html__('Analyze Context', 'ailinkmaster-internal-auto-manager'), 'search_placeholder' => esc_html__('Search and filter...', 'ailinkmaster-internal-auto-manager'), ] ]); } public function handle_init_scan() { check_ajax_referer('ailm_ki_scan_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('Permission denied.', 'ailinkmaster-internal-auto-manager')]); } $form_data = []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $form_data_raw = isset($_POST['form_data']) ? wp_unslash($_POST['form_data']) : ''; if ($form_data_raw) { parse_str($form_data_raw, $form_data); } $args = [ 'post_types' => isset($form_data['ailm_ki_post_types']) ? array_map('sanitize_key', (array) $form_data['ailm_ki_post_types']) : ['post'], 'date_range' => isset($form_data['ailm_ki_date_range']) ? sanitize_text_field($form_data['ailm_ki_date_range']) : 'all_time', ]; $data_result = $this->data_provider->get_content($args); if (empty($data_result['post_ids'])) { wp_send_json_error(['message' => __('No content found for the selected criteria.', 'ailinkmaster-internal-auto-manager')]); } $stop_words_raw = isset($form_data['ailm_ki_stop_words']) ? sanitize_textarea_field($form_data['ailm_ki_stop_words']) : ''; $stop_words_raw = mb_strtolower($stop_words_raw, 'UTF-8'); $stop_words_array = preg_split('/[\r\n,]+/', $stop_words_raw); $stop_words = array_values(array_filter(array_map('trim', $stop_words_array))); $exclude_anchors = isset($form_data['ailm_ki_exclude_anchors']) && $form_data['ailm_ki_exclude_anchors'] === '1'; $max_results = isset($form_data['ailm_ki_max_results']) ? absint($form_data['ailm_ki_max_results']) : 3000; $total_posts = count($data_result['post_ids']); if (!class_exists('AILM_OpenAI_Provider')) { require_once dirname(dirname(__DIR__)) . '/ai-core/providers/class-ailm-openai-provider.php'; } $provider = new AILM_OpenAI_Provider(); $payload = [ 'post_ids' => $data_result['post_ids'], 'stop_words' => $stop_words, 'exclude_anchors' => $exclude_anchors, 'max_results' => $max_results, ]; $response = $provider->request_saas('/api/v1/ki/scan', 'POST', $payload, 15); if (is_wp_error($response)) { wp_send_json_error(['message' => __('SaaS Error: ', 'ailinkmaster-internal-auto-manager') . $response->get_error_message()]); } if (empty($response['task_id'])) { wp_send_json_error(['message' => __('Invalid response from SaaS.', 'ailinkmaster-internal-auto-manager')]); } set_transient('ailm_ki_current_scan', [ 'task_id' => $response['task_id'], 'total' => $total_posts, 'post_ids' => $data_result['post_ids'], ], HOUR_IN_SECONDS); wp_send_json_success([ 'scan_id' => 'ailm_ki_current_scan', 'task_id' => $response['task_id'], 'total' => $total_posts ]); } public function handle_process_batch() { check_ajax_referer('ailm_ki_scan_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('Permission denied.', 'ailinkmaster-internal-auto-manager')]); } $scan_id = isset($_POST['scan_id']) ? sanitize_key($_POST['scan_id']) : ''; $task_id = isset($_POST['task_id']) ? sanitize_text_field(wp_unslash($_POST['task_id'])) : ''; if (empty($scan_id) || empty($task_id)) { wp_send_json_error(['message' => __('Missing scan ID or task ID.', 'ailinkmaster-internal-auto-manager')]); } $cached_data = get_transient($scan_id); if (false === $cached_data) { wp_send_json_error(['message' => __('Scan session expired.', 'ailinkmaster-internal-auto-manager')]); } if (!class_exists('AILM_OpenAI_Provider')) { require_once dirname(dirname(__DIR__)) . '/ai-core/providers/class-ailm-openai-provider.php'; } $provider = new AILM_OpenAI_Provider(); $response = $provider->request_saas("/api/v1/tasks/{$task_id}", 'GET', [], 10); if (is_wp_error($response)) { wp_send_json_error(['message' => $response->get_error_message()]); } if (!isset($response['status'])) { wp_send_json_error(['message' => __('Invalid response from task API.', 'ailinkmaster-internal-auto-manager')]); } if ($response['status'] === 'SUCCESS') { $result = isset($response['result']) ? $response['result'] : []; if (empty($result) || !isset($result['is_complete'])) { wp_send_json_error(['message' => __('Empty result from SaaS.', 'ailinkmaster-internal-auto-manager')]); } wp_send_json_success($result); } elseif ($response['status'] === 'FAILURE') { $err = isset($response['result']) ? $response['result'] : __('Task failed on server.', 'ailinkmaster-internal-auto-manager'); wp_send_json_error(['message' => is_string($err) ? $err : wp_json_encode($err)]); } else { // Status is PENDING or STARTED wp_send_json_success([ 'is_complete' => false, 'processed' => 0 ]); } } public function handle_analyze_context() { check_ajax_referer('ailm_ki_scan_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(__('Permission denied.', 'ailinkmaster-internal-auto-manager')); } $phrase = isset($_POST['phrase']) ? sanitize_text_field(wp_unslash($_POST['phrase'])) : ''; $scan_id = isset($_POST['scan_id']) ? sanitize_key($_POST['scan_id']) : ''; if (empty($phrase) || empty($scan_id)) { wp_send_json_error(__('Missing params.', 'ailinkmaster-internal-auto-manager')); } $cached_data = get_transient($scan_id); if (false === $cached_data || empty($cached_data['post_ids'])) { wp_send_json_error(__('Scan session expired.', 'ailinkmaster-internal-auto-manager')); } $around_window = isset($_POST['around_window']) ? absint($_POST['around_window']) : 2; $inside_gap = isset($_POST['inside_gap']) ? absint($_POST['inside_gap']) : 1; if (!class_exists('AILM_OpenAI_Provider')) { require_once dirname(dirname(__DIR__)) . '/ai-core/providers/class-ailm-openai-provider.php'; } $provider = new AILM_OpenAI_Provider(); $payload = [ 'post_ids' => $cached_data['post_ids'], 'phrase' => $phrase, 'around_window' => $around_window, 'inside_gap' => $inside_gap, ]; $response = $provider->request_saas('/api/v1/ki/context', 'POST', $payload, 15); if (is_wp_error($response)) { wp_send_json_error(['message' => __('SaaS Error: ', 'ailinkmaster-internal-auto-manager') . $response->get_error_message()]); } if (empty($response['task_id'])) { wp_send_json_error(['message' => __('Invalid response from SaaS.', 'ailinkmaster-internal-auto-manager')]); } wp_send_json_success([ 'task_id' => $response['task_id'] ]); } public function handle_poll_context_task() { // This method will handle polling for the context task. check_ajax_referer('ailm_ki_scan_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('Permission denied.', 'ailinkmaster-internal-auto-manager')]); } $task_id = isset($_POST['task_id']) ? sanitize_text_field(wp_unslash($_POST['task_id'])) : ''; if (empty($task_id)) { wp_send_json_error(['message' => __('Missing task ID.', 'ailinkmaster-internal-auto-manager')]); } if (!class_exists('AILM_OpenAI_Provider')) { require_once dirname(dirname(__DIR__)) . '/ai-core/providers/class-ailm-openai-provider.php'; } $provider = new AILM_OpenAI_Provider(); $response = $provider->request_saas("/api/v1/tasks/{$task_id}", 'GET', [], 10); if (is_wp_error($response)) { wp_send_json_error(['message' => $response->get_error_message()]); } if (!isset($response['status'])) { wp_send_json_error(['message' => __('Invalid response from task API.', 'ailinkmaster-internal-auto-manager')]); } if ($response['status'] === 'SUCCESS') { $context_data = isset($response['result']) ? $response['result'] : []; if (empty($context_data) || !isset($context_data['around'])) { wp_send_json_error(['message' => __('Empty result from SaaS.', 'ailinkmaster-internal-auto-manager')]); } $format_posts = function ($data_array) { $formatted = []; foreach ($data_array as $p => $data) { $posts_info = []; foreach ($data['posts'] as $post_item) { $pid = $post_item['id']; $post_count = $post_item['count']; $cats = get_the_category($pid); $cat_id = 0; $cat_name = __('Uncategorized', 'ailinkmaster-internal-auto-manager'); if (!empty($cats)) { $best_cat = null; $yoast_id = get_post_meta($pid, '_yoast_wpseo_primary_category', true); $rm_id = get_post_meta($pid, 'rank_math_primary_category', true); $primary_id = $yoast_id ? $yoast_id : $rm_id; if ($primary_id) { foreach ($cats as $c) { if ($c->term_id == $primary_id) { $best_cat = $c; break; } } } if (!$best_cat) { $permalink = get_permalink($pid); $parsed_url = wp_parse_url($permalink, PHP_URL_PATH); $cats_in_url = []; foreach ($cats as $c) { if (strpos($parsed_url, '/' . $c->slug . '/') !== false) { $cats_in_url[] = $c; } } if (count($cats_in_url) > 0) { usort($cats_in_url, function ($a, $b) { return $a->count <=> $b->count; }); $best_cat = $cats_in_url[0]; } } if (!$best_cat) { usort($cats, function ($a, $b) { return $a->count <=> $b->count; }); $best_cat = $cats[0]; } $cat_id = $best_cat->term_id; $cat_name = $best_cat->name; } $posts_info[] = [ 'id' => $pid, 'title' => html_entity_decode(get_the_title($pid) ?: __('(Untitled)', 'ailinkmaster-internal-auto-manager'), ENT_QUOTES, 'UTF-8'), 'count' => $post_count, 'edit_url' => get_edit_post_link($pid, 'raw'), 'view_url' => get_permalink($pid), 'cat_id' => $cat_id, 'cat_name' => $cat_name, ]; } $formatted[$p] = [ 'count' => $data['count'], 'posts' => $posts_info, ]; } return $formatted; }; $context_data['around'] = $format_posts($context_data['around']); $context_data['inside'] = $format_posts($context_data['inside']); wp_send_json_success($context_data); } elseif ($response['status'] === 'FAILURE') { $err = isset($response['result']) ? $response['result'] : __('Task failed on server.', 'ailinkmaster-internal-auto-manager'); wp_send_json_error(['message' => is_string($err) ? $err : wp_json_encode($err)]); } else { // Status is PENDING or STARTED wp_send_json_success([ 'is_complete' => false, ]); } } public function handle_get_targets() { check_ajax_referer('ailm_ki_scan_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(__('Permission denied.', 'ailinkmaster-internal-auto-manager')); } $cats = get_categories(['hide_empty' => 0]); $cat_options = ''; foreach ($cats as $cat) { $cat_options .= ''; } $post_types = get_post_types(['public' => true]); unset($post_types['attachment']); $posts = get_posts([ 'post_type' => array_values($post_types), 'numberposts' => -1, 'post_status' => 'publish' ]); $post_options = ''; foreach ($posts as $p) { $post_options .= ''; } wp_send_json_success(['categories' => $cat_options, 'posts' => $post_options]); } public function handle_create_rule() { check_ajax_referer('ailm_ki_scan_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(__('Permission denied.', 'ailinkmaster-internal-auto-manager')); } $phrase = isset($_POST['phrase']) ? sanitize_text_field(wp_unslash($_POST['phrase'])) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $groups = isset($_POST['groups']) ? (array) wp_unslash($_POST['groups']) : []; if (empty($phrase) || empty($groups)) { wp_send_json_error(__('Missing phrase or group data.', 'ailinkmaster-internal-auto-manager')); } $rules = get_option('ailm_rules', []); $rules_created = 0; $rules_merged = 0; $rules_rejected = 0; $batch_queue = []; foreach ($groups as $group) { $donor_cat_id = absint($group['donor_cat_id'] ?? 0); $target_type = sanitize_text_field($group['target_type'] ?? 'category'); $target_id = absint($group['target_id'] ?? 0); $donor_posts = isset($group['donor_posts']) ? array_map('absint', (array) $group['donor_posts']) : []; $is_exception = filter_var($group['is_exception'] ?? false, FILTER_VALIDATE_BOOLEAN); if (empty($donor_posts)) continue; $is_target_singular = ($target_type !== 'category'); $is_surgical = $is_exception || $is_target_singular; $target_post_id = $is_target_singular ? $target_id : 0; $target_cat_id = (!$is_target_singular) ? $target_id : 0; if ($target_post_id > 0) { $donor_posts = array_values(array_diff($donor_posts, [$target_post_id])); } if (empty($donor_posts)) continue; if (!class_exists('AILM_Link_Builder')) { require_once dirname(dirname(__DIR__)) . '/includes/class-ailm-link-builder.php'; } $link_builder = new AILM_Link_Builder(null); $valid_donors = []; foreach ($donor_posts as $pid) { if ($link_builder->can_inject_phrase($pid, $phrase)) { $valid_donors[] = $pid; $batch_queue[] = [ 'donor_id' => $pid, 'target_id' => $target_post_id ?: $target_id, 'word' => $phrase, 'url' => $target_post_id ? get_permalink($target_post_id) : get_category_link($target_id) ]; } else { $rules_rejected++; } } $donor_posts = $valid_donors; if (empty($donor_posts)) continue; $merged = false; if ($is_surgical) { foreach ($rules as &$existing_rule) { if (!empty($existing_rule['donor_post_ids']) && !empty($existing_rule['target_post_id'])) { if ($existing_rule['target_post_id'] == $target_post_id) { $existing_kws = explode("\n", str_replace("\r", "", $existing_rule['keywords'])); $kw_match = false; foreach ($existing_kws as $kw_line) { $parts = explode('|', $kw_line); if (trim($parts[0]) === $phrase) { $kw_match = true; break; } } if ($kw_match) { $existing_rule['donor_post_ids'] = array_unique(array_merge($existing_rule['donor_post_ids'], $donor_posts)); $merged = true; $rules_merged++; break; } } } } unset($existing_rule); } else { foreach ($rules as &$existing_rule) { if (empty($existing_rule['donor_post_ids']) && $existing_rule['cat_id'] == $donor_cat_id) { $existing_target_cat = 0; if (!empty($existing_rule['mix_rules']) && $existing_rule['mix_rules'][0]['pct'] == 100) { $existing_target_cat = absint($existing_rule['mix_rules'][0]['cat']); } if ($existing_target_cat == $target_cat_id) { $existing_kws = explode("\n", str_replace("\r", "", $existing_rule['keywords'])); $kw_exists = false; foreach ($existing_kws as $kw_line) { $parts = explode('|', $kw_line); if (trim($parts[0]) === $phrase) { $kw_exists = true; break; } } if (!$kw_exists) { $existing_rule['keywords'] = trim($existing_rule['keywords']) . "\n" . $phrase . '|1'; } $merged = true; $rules_merged++; break; } } } unset($existing_rule); } if (!$merged) { $mix_rules = []; if (!$is_target_singular) { $mix_rules[] = ['cat' => $target_id, 'pct' => 100]; } $new_rule = [ 'id' => uniqid(), 'cat_id' => $donor_cat_id, 'keywords' => $phrase . '|1', 'donor_age' => 0, 'target_age' => 500, 'limit_total' => 3, 'limit_per_word_default' => 1, 'mix_rules' => $mix_rules, 'donor_post_ids' => $is_surgical ? $donor_posts : [], 'target_post_id' => $target_post_id, ]; $rules[] = $new_rule; $rules_created++; } } if ($rules_created > 0 || $rules_merged > 0) { update_option('ailm_rules', $rules); /* translators: 1: number of merged routes, 2: number of new routes */ $msg = sprintf(esc_html__('Success! Merged into %1$d existing routes and created %2$d new ones.', 'ailinkmaster-internal-auto-manager'), $rules_merged, $rules_created); if ($rules_rejected > 0) { /* translators: %d: number of skipped articles */ $msg .= "\n\n" . sprintf(esc_html__('Skipped %d articles (phrase is already occupied by other links or tags).', 'ailinkmaster-internal-auto-manager'), $rules_rejected); } wp_send_json_success(['message' => $msg, 'batch_queue' => $batch_queue]); } else { $err = __('No valid targets selected, no rules created.', 'ailinkmaster-internal-auto-manager'); if ($rules_rejected > 0) { /* translators: %d: number of skipped articles */ $err = sprintf(__('Skipped %d articles because the phrase is already occupied by other links or protected HTML tags.', 'ailinkmaster-internal-auto-manager'), $rules_rejected); } wp_send_json_error($err); } } public function ajax_remove_donor() { check_ajax_referer('ailm_ki_scan_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(__('Permission denied.', 'ailinkmaster-internal-auto-manager')); } $target_id = isset($_POST['target_id']) ? absint(wp_unslash($_POST['target_id'])) : 0; $donor_id = isset($_POST['donor_id']) ? absint(wp_unslash($_POST['donor_id'])) : 0; $keyword = isset($_POST['keyword']) ? sanitize_text_field(wp_unslash($_POST['keyword'])) : ''; if (!$target_id || !$donor_id || !$keyword) { wp_send_json_error(__('Missing parameters.', 'ailinkmaster-internal-auto-manager')); } $result = AILM_Admin::remove_surgical_donor($target_id, $donor_id, $keyword); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } wp_send_json_success(__('Donor removed successfully.', 'ailinkmaster-internal-auto-manager')); } public function render_tab() { $has_license = !empty(get_option('ailm_saas_license_key', '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $is_routes_tab = isset($_GET['ki_page']) || isset($_GET['ki_per_page']) || isset($_GET['ki_search']) || (isset($_GET['ki_tab']) && $_GET['ki_tab'] === 'routes'); ?>

Pro Feature

Please enter your SaaS License Key in the Settings tab to use the Content Auto-Scanner.

render_settings_panel(); ?>

Pro Feature

Please enter your SaaS License Key in the Settings tab to manage Active Custom Routes.

render_routes_panel(); ?>
true], 'objects'); unset($post_types['attachment']); $default_stop_words = "a, an, and, are, as, at, be, by, for, from, has, he, in, is, it, its, of, on, that, the, this, to, was, were, will, with"; ?>

donor_id; $donor_post = get_post( $donor_id ); if ( $donor_post && ! empty( $donor_post->post_content ) ) { $dom_helper = new AILM_DOM_Helper( $donor_post->post_content ); if ( $dom_helper->unwrap_link( $target_url, $word ) ) { wp_update_post( [ 'ID' => $donor_id, 'post_content' => $dom_helper->get_html() ] ); } } AILM_Link_Repository::delete_rule( (int) $rule->id ); $actual_deleted_count++; if ( class_exists( 'AILM_Scout_Scanner' ) ) { AILM_Scout_Scanner::process_post( $donor_id ); } } } if ( $actual_deleted_count > 0 ) { $inbound_count = (int) get_post_meta( $target_id, '_ailm_inbound_count', true ); update_post_meta( $target_id, '_ailm_inbound_count', max( 0, $inbound_count - $actual_deleted_count ) ); } } $redirect_url = admin_url('admin.php?page=ailinkmaster-internal-auto-manager&tab=keyword-intelligence'); $ki_search = isset($_POST['ki_search']) ? sanitize_text_field(wp_unslash($_POST['ki_search'])) : ''; $ki_per_page = isset($_POST['ki_per_page']) ? absint($_POST['ki_per_page']) : 20; $ki_page = isset($_POST['ki_page']) ? absint($_POST['ki_page']) : 1; if ($ki_search !== '') { $redirect_url = add_query_arg('ki_search', $ki_search, $redirect_url); } if ($ki_per_page !== 20) { $redirect_url = add_query_arg('ki_per_page', $ki_per_page, $redirect_url); } if ($ki_page > 1) { $redirect_url = add_query_arg('ki_page', $ki_page, $redirect_url); } $redirect_url = add_query_arg('ailm_notice', 'rule_deleted', remove_query_arg('ailm_notice', $redirect_url)); wp_safe_redirect($redirect_url); exit; } public function handle_get_post_snippets() { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if (!wp_verify_nonce($nonce, 'ailm_ki_scan_nonce') && !wp_verify_nonce($nonce, 'ailm_orphan_nonce')) { wp_send_json_error(__('Invalid security token.', 'ailinkmaster-internal-auto-manager')); } if (!current_user_can('manage_options')) { wp_send_json_error(__('Permission denied.', 'ailinkmaster-internal-auto-manager')); } $phrase = isset($_POST['phrase']) ? sanitize_text_field(wp_unslash($_POST['phrase'])) : ''; $post_ids = isset($_POST['post_ids']) ? array_map('absint', (array) wp_unslash($_POST['post_ids'])) : []; if (empty($phrase) || empty($post_ids)) { wp_send_json_error(__('Missing parameters.', 'ailinkmaster-internal-auto-manager')); } if (!class_exists('AILM_Link_Builder')) { require_once dirname(dirname(__DIR__)) . '/includes/class-ailm-link-builder.php'; } $link_builder = new AILM_Link_Builder(null); // Safe fallback for text processor path if (!class_exists('AILM_Text_Processor')) { $path1 = plugin_dir_path(dirname(__FILE__)) . '../includes/utils/class-ailm-text-processor.php'; $path2 = plugin_dir_path(dirname(__FILE__)) . '../includes/class-ailm-text-processor.php'; if (file_exists($path1)) require_once $path1; elseif (file_exists($path2)) require_once $path2; } $snippets = []; foreach ($post_ids as $pid) { // Do not break the whole batch, just return a warning for this specific post if (!$link_builder->can_inject_phrase($pid, $phrase)) { $snippets[$pid] = 'Cannot inject link: Phrase is already linked, inside a protected tag, or outbound limit reached.'; continue; } $post = get_post($pid); if ($post) { if (class_exists('AILM_Text_Processor')) { $context = AILM_Text_Processor::find_context($post->post_content, $phrase); if ($context) { $snippets[$pid] = $context; } } else { // Hard fallback $content_clean = wp_strip_all_tags($post->post_content); $pos = mb_strpos($content_clean, $phrase, 0, 'UTF-8'); if ($pos !== false) { $start = max(0, $pos - 100); $snippets[$pid] = mb_substr($content_clean, $start, 200, 'UTF-8'); } } } } wp_send_json_success($snippets); } private function render_routes_panel() { $allowed_per_page = [20, 50, 100]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $per_page = isset($_GET['ki_per_page']) ? intval($_GET['ki_per_page']) : 20; if (!in_array($per_page, $allowed_per_page, true)) { $per_page = 20; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $current_page = isset($_GET['ki_page']) ? max(1, intval($_GET['ki_page'])) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $search_query = isset($_GET['ki_search']) ? html_entity_decode(sanitize_text_field(wp_unslash($_GET['ki_search'])), ENT_QUOTES, 'UTF-8') : ''; $search_lower = mb_strtolower($search_query, 'UTF-8'); // DEPRECATED v2.0: _ailm_frozen_map meta is replaced by wp_ailm_link_rules. /* global $wpdb; $results = $wpdb->get_results("SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_ailm_frozen_map'"); $aggregated_routes = []; if (!empty($results)) { foreach ($results as $res) { $donor_id = (int) $res->post_id; $map = maybe_unserialize($res->meta_value); if (!is_array($map)) continue; foreach ($map as $link) { $target_id = isset($link['target_id']) ? (int)$link['target_id'] : url_to_postid($link['url']); $word = isset($link['word']) ? $link['word'] : ''; if (!$target_id || empty($word)) continue; $route_key = md5($word . $target_id); if (!isset($aggregated_routes[$route_key])) { $aggregated_routes[$route_key] = [ 'id' => $route_key, 'keywords' => $word . '|1', 'target_post_id' => $target_id, 'donor_post_ids' => [], 'cat_id' => 0 ]; } if (!in_array($donor_id, $aggregated_routes[$route_key]['donor_post_ids'])) { $aggregated_routes[$route_key]['donor_post_ids'][] = $donor_id; } } } } */ // v2.0: Read from unified rules table. $aggregated_routes = []; if (class_exists('AILM_Link_Repository')) { $rules = AILM_Link_Repository::get_all_rules(); foreach ($rules as $rule) { $donor_id = (int) $rule->donor_id; $target_id = (int) $rule->target_id; $word = $rule->anchor; if (!$target_id || empty($word)) { continue; } $route_key = md5($word . $target_id); if (!isset($aggregated_routes[$route_key])) { $aggregated_routes[$route_key] = [ 'id' => $route_key, 'keywords' => $word . '|1', 'target_post_id' => $target_id, 'donor_post_ids' => [], 'cat_id' => 0 ]; } if (!in_array($donor_id, $aggregated_routes[$route_key]['donor_post_ids'], true)) { $aggregated_routes[$route_key]['donor_post_ids'][] = $donor_id; } } } uasort($aggregated_routes, function ($a, $b) { $a_word = trim(explode('|', $a['keywords'])[0]); $b_word = trim(explode('|', $b['keywords'])[0]); return mb_strlen($b_word, 'UTF-8') <=> mb_strlen($a_word, 'UTF-8'); }); // Search filter by Donor or Target post title if ($search_lower !== '') { $aggregated_routes = array_filter($aggregated_routes, function ($route) use ($search_lower) { $target_title = html_entity_decode(mb_strtolower(get_the_title($route['target_post_id']) ?: '', 'UTF-8'), ENT_QUOTES, 'UTF-8'); if (strpos($target_title, $search_lower) !== false) { return true; } foreach ($route['donor_post_ids'] as $d_id) { $donor_title = html_entity_decode(mb_strtolower(get_the_title($d_id) ?: '', 'UTF-8'), ENT_QUOTES, 'UTF-8'); if (strpos($donor_title, $search_lower) !== false) { return true; } } return false; }); } $total_routes = count($aggregated_routes); $total_pages = (int) ceil($total_routes / $per_page); if ($current_page > $total_pages && $total_pages > 0) { $current_page = $total_pages; } $offset = ($current_page - 1) * $per_page; $paged_routes = array_slice($aggregated_routes, $offset, $per_page, true); ?>

0) { $from = (($current_page - 1) * $per_page) + 1; $to = min($current_page * $per_page, $total_routes); echo esc_html($from . ' - ' . $to . ' / ' . $total_routes); } else { echo esc_html__('0 / 0', 'ailinkmaster-internal-auto-manager'); } ?>
'ailinkmaster-internal-auto-manager', 'tab' => 'keyword-intelligence', 'ki_tab' => 'routes', ]; if ($search_query !== '') { $base_args['ki_search'] = $search_query; } if ($per_page !== 20) { $base_args['ki_per_page'] = $per_page; } $prev_page = max(1, $current_page - 1); $next_page = min($total_pages, $current_page + 1); $prev_url = add_query_arg(array_merge($base_args, ['ki_page' => $prev_page]), admin_url('admin.php')); $next_url = add_query_arg(array_merge($base_args, ['ki_page' => $next_page]), admin_url('admin.php')); ?> « »

|
|