Pro Feature
Please enter your SaaS License Key in the Settings tab to use the Content Auto-Scanner.
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'); ?>