plugin_name = $plugin_name; $this->version = $version; $this->core = $core; add_action('admin_post_ailm_handle_actions', array($this, 'handle_admin_actions')); } public function handle_admin_actions() { // phpcs:ignore WordPress.Security.NonceVerification.Missing if (empty($_POST['submit_action']) && empty($_POST['action_nocsrf'])) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Missing $submit_action = isset($_POST['submit_action']) ? sanitize_key(wp_unslash($_POST['submit_action'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing $action_nocsrf = isset($_POST['action_nocsrf']) ? sanitize_key(wp_unslash($_POST['action_nocsrf'])) : ''; $action = $submit_action ? $submit_action : $action_nocsrf; $tab = 'settings'; $referer = wp_get_referer(); if ($referer && preg_match('/[?&]tab=([^&]+)/', $referer, $matches)) { $tab = sanitize_key($matches[1]); } elseif (isset($_REQUEST['tab'])) { $tab = sanitize_key(wp_unslash($_REQUEST['tab'])); } $base_redirect_url = admin_url('admin.php?page=' . $this->plugin_name . '&tab=' . $tab); check_admin_referer('ailm_form_nonce'); if ($action === 'ailm_save_settings_only' || $action === 'ailm_save_settings') { $this->save_settings(); wp_safe_redirect(add_query_arg('ailm_notice', 'settings_saved_only', $base_redirect_url)); exit; } if ($action === 'ailm_purge_cache') { $this->purge_cache(); wp_safe_redirect(add_query_arg('ailm_notice', 'cache_purged', $base_redirect_url)); exit; } if ($action === 'ailm_unfreeze_all') { $this->unfreeze_all_links(); wp_safe_redirect(add_query_arg('ailm_notice', 'all_unfrozen', $base_redirect_url)); exit; } } private function purge_cache() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_ailm_pool_%' OR option_name LIKE '_transient_timeout_ailm_pool_%'"); } private function unfreeze_all_links() { global $wpdb; if (!class_exists('AILM_Link_Repository')) { require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-ailm-link-repository.php'; } // Phase 4: 1. Delete all rules from the unified rules table. AILM_Link_Repository::delete_all_rules(); // 2. Physically unwrap all injected links from post content. $this->unwrap_all_links(); // 3. Reset inbound counters. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s", '_ailm_inbound_count')); // 4. Clear caches. AILM_Link_Repository::invalidate_cache(); } /** * Physically removes all plugin-injected links from post content. * * Iterates through all published posts and removes tags that have * the data-ailm attribute or the ailm-internal-link class. */ private function unwrap_all_links() { $args = array( 'post_type' => get_option('ailm_allowed_post_types', array('post', 'page')), 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', ); $post_ids = get_posts($args); foreach ($post_ids as $post_id) { $post = get_post($post_id); if (empty($post->post_content)) { continue; } $is_elementor = class_exists('AILM_Elementor_Handler') && AILM_Elementor_Handler::is_elementor_post($post_id); $unwrap_html = function ($html) { if (!class_exists('AILM_DOM_Helper')) { require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-ailm-dom-helper.php'; } $dom_helper = new AILM_DOM_Helper($html); $dom = $dom_helper->get_dom(); if (!$dom) { return $html; } $xpath = new DOMXPath($dom); $links = $xpath->query('//a[@data-ailm or contains(@class,"ailm-internal-link")]'); if ($links->length === 0) { return $html; } $modified = false; foreach ($links as $a) { $parent = $a->parentNode; if (!$parent) { continue; } // Extract the text content (anchor text) and replace the with a text node. $text = $a->textContent; $text_node = $dom->createTextNode($text); $parent->replaceChild($text_node, $a); $modified = true; } return $modified ? $dom_helper->get_html() : $html; }; if ($is_elementor) { AILM_Elementor_Handler::process_content($post_id, $unwrap_html); } else { $new_content = $unwrap_html($post->post_content); if ($new_content !== $post->post_content) { wp_update_post( array( 'ID' => $post_id, 'post_content' => $new_content, ) ); } } } } private function save_settings() { $options_map = [ 'ailm_saas_license_key' => 'sanitize_text_field', 'ailm_saas_server_url' => 'esc_url_raw', 'ailm_utm_source' => 'sanitize_text_field', 'ailm_utm_medium' => 'sanitize_text_field', 'ailm_utm_campaign' => 'sanitize_text_field', 'ailm_exclude_urls' => 'sanitize_text_field', 'ailm_global_limit' => 'intval', 'ailm_min_chars' => 'intval', 'ailm_skip_chars' => 'intval', 'ailm_target_limit' => 'intval', 'ailm_min_gap' => 'intval', ]; foreach ($options_map as $key => $callback) { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if (isset($_POST[$key])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized update_option($key, call_user_func($callback, wp_unslash($_POST[$key]))); } } // phpcs:ignore WordPress.Security.NonceVerification.Missing update_option('ailm_debug_mode', isset($_POST['ailm_debug_mode']) ? 1 : 0); // phpcs:ignore WordPress.Security.NonceVerification.Missing update_option('ailm_enable_utm', isset($_POST['ailm_enable_utm']) ? 1 : 0); // phpcs:ignore WordPress.Security.NonceVerification.Missing update_option('ailm_count_manual_links', isset($_POST['ailm_count_manual_links']) ? 1 : 0); // phpcs:ignore WordPress.Security.NonceVerification.Missing update_option('ailm_auto_index', isset($_POST['ailm_auto_index']) ? 1 : 0); $array_options = [ 'ailm_allowed_post_types' => 'sanitize_text_field', 'ailm_blacklisted_posts' => 'intval', 'ailm_excluded_terms' => 'intval', 'ailm_excluded_html' => 'sanitize_text_field', 'ailm_silo_taxonomies' => 'sanitize_text_field', ]; foreach ($array_options as $key => $sanitize_func) { // phpcs:ignore WordPress.Security.NonceVerification.Missing if (isset($_POST[$key]) && is_array($_POST[$key])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $cleaned = array_map($sanitize_func, wp_unslash($_POST[$key])); update_option($key, $cleaned); } else { update_option($key, []); } } } public function render_tab() { $options = [ 'ailm_saas_license_key' => get_option('ailm_saas_license_key', ''), 'ailm_saas_server_url' => get_option('ailm_saas_server_url', 'http://api.linkmesh.co'), 'ailm_debug_mode' => get_option('ailm_debug_mode', 0), 'ailm_exclude_urls' => get_option('ailm_exclude_urls', ''), 'ailm_global_limit' => get_option('ailm_global_limit', 5), 'ailm_target_limit' => get_option('ailm_target_limit', 5), 'ailm_min_chars' => get_option('ailm_min_chars', 2000), 'ailm_skip_chars' => get_option('ailm_skip_chars', 100), 'ailm_min_gap' => get_option('ailm_min_gap', 200), 'ailm_enable_utm' => get_option('ailm_enable_utm', 0), 'ailm_utm_source' => get_option('ailm_utm_source', 'auto_link'), 'ailm_utm_medium' => get_option('ailm_utm_medium', 'internal'), 'ailm_utm_campaign' => get_option('ailm_utm_campaign', 'seo_auto'), 'ailm_count_manual_links' => get_option('ailm_count_manual_links', 0), 'ailm_auto_index' => get_option('ailm_auto_index', 0), 'ailm_allowed_post_types' => get_option('ailm_allowed_post_types', ['post', 'page']), 'ailm_blacklisted_posts' => get_option('ailm_blacklisted_posts', []), 'ailm_excluded_terms' => get_option('ailm_excluded_terms', []), 'ailm_excluded_html' => get_option('ailm_excluded_html', ['h1-h6', 'code', 'blockquote']), 'ailm_silo_taxonomies' => get_option('ailm_silo_taxonomies', []), ]; $public_post_types = get_post_types(['public' => true], 'objects'); $public_taxonomies = get_taxonomies(['public' => true], 'objects'); $all_terms = get_terms([ 'taxonomy' => array_keys($public_taxonomies), 'hide_empty' => false, 'number' => 500, ]); $html_tags = [ 'h1-h6' => __('Headlines ()', 'ailinkmaster-internal-auto-manager'), 'strong' => __('Strong/Bold text (, )', 'ailinkmaster-internal-auto-manager'), 'em' => __('Italic text (, )', 'ailinkmaster-internal-auto-manager'), 'blockquote' => __('Quotes (
)', 'ailinkmaster-internal-auto-manager'), 'code' => __('Source code (,
)', 'ailinkmaster-internal-auto-manager'),
            'ul-ol' => __('Lists (
    ,
      )', 'ailinkmaster-internal-auto-manager') ]; // Server-side check: if a sync is already running, show a notice so // the user knows the state immediately (before JS loads) and can cancel. $sync_running = false; if (class_exists('AILM_Import_Existing') && AILM_Import_Existing::is_running()) { $sync_running = true; $sync_status = AILM_Import_Existing::get_status(); } ?>