I'm currently working on a WordPress plugin code that hyperlinks all instances of a keyword in a blogpost. below is the full code. the issue is that it hyperlinks all keywords in the blogpost including all keywords instances in the paragraphs and in the headings. This is not the behavior that I want. I only want all keyword instances in the paragraph tags to be hyperlinked and exclude all the heading tags. <?php // Prevent direct access to the file if (!defined('ABSPATH')) { exit; } // Add a meta box for custom internal linking keyword and a checkbox to remove links add_action('add_meta_boxes', 'ckil_add_meta_box'); function ckil_add_meta_box() { add_meta_box( 'ckil_meta_box', // Unique ID 'Internal Linking Options', // Box title 'ckil_meta_box_html', // Content callback 'post', // Post type 'side' // Position in the editor ); } // Meta box HTML function ckil_meta_box_html($post) { // Retrieve current values for the input fields $link_keyword = get_post_meta($post->ID, '_ckil_keyword', true); $remove_links = get_post_meta($post->ID, '_ckil_remove_links', true); ?> <label for="ckil_keyword">Keyword for internal linking:</label> <input type="text" id="ckil_keyword" name="ckil_keyword" value="<?php echo esc_attr($link_keyword); ?>" style="width: 100%;"><br><br> <input type="checkbox" id="ckil_remove_links" name="ckil_remove_links" value="1" <?php checked($remove_links, '1'); ?>> <label for="ckil_remove_links">Remove all hyperlinks for this keyword?</label> <?php } // Save the meta box data when the post is saved add_action('save_post', 'ckil_save_meta_box_data'); function ckil_save_meta_box_data($post_id) { if (array_key_exists('ckil_keyword', $_POST)) { update_post_meta($post_id, '_ckil_keyword', sanitize_text_field($_POST['ckil_keyword'])); } if (isset($_POST['ckil_remove_links'])) { update_post_meta($post_id, '_ckil_remove_links', '1'); } else { delete_post_meta($post_id, '_ckil_remove_links'); } } // Hook into post saving to create or remove internal links based on the custom keyword add_action('save_post', 'ckil_run_on_publish', 10, 3); function ckil_run_on_publish($post_id, $post, $update) { // Check if the post is being published or updated if (get_post_status($post_id) !== 'publish') { return; } // Don't run on autosave or if the post is a revision if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) { return; } // Get the custom keyword for internal linking $custom_keyword = get_post_meta($post_id, '_ckil_keyword', true); // Check if the "Remove hyperlinks" checkbox is checked $remove_links = get_post_meta($post_id, '_ckil_remove_links', true); // Get the original post content $content = $post->post_content; // Use DOMDocument to manipulate HTML content $dom = new DOMDocument(); // Suppress warnings caused by HTML5 tags libxml_use_internal_errors(true); $dom->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); libxml_clear_errors(); // Create an XPath object to query the DOM $xpath = new DOMXPath($dom); // Query for all paragraph elements <p>, excluding heading tags <h1> to <h6> $paragraphs = $xpath->query('//p'); // Get all published posts to use for internal linking, excluding the current post $all_posts = get_posts(array( 'numberposts' => -1, 'post_type' => 'post', 'post_status' => 'publish', 'exclude' => array($post_id), // Exclude the current post )); // Flag to track if any updates are made $updated = false; // Process for removing links if the checkbox is checked if (!empty($custom_keyword) && $remove_links) { foreach ($paragraphs as $paragraph) { $paragraph_html = $dom->saveHTML($paragraph) ?? ''; // Fix deprecated warning with null coalescing // Ensure the paragraph content is valid and contains the linked keyword if (!is_null($paragraph_html) && stripos($paragraph_html, $custom_keyword) !== false) { // Remove hyperlink around the keyword $new_paragraph_html = preg_replace( '/<a\s+href="[^"]*">(' . preg_quote($custom_keyword, '/') . ')<\/a>/i', '$1', $paragraph_html ?? '' // Fix deprecated warning with null coalescing ); // Only update if the paragraph has been modified if ($new_paragraph_html !== $paragraph_html) { $new_paragraph_node = $dom->createDocumentFragment(); $new_paragraph_node->appendXML($new_paragraph_html); if ($paragraph->parentNode !== null) { $paragraph->parentNode->replaceChild($new_paragraph_node, $paragraph); $updated = true; } } } } } // Process for adding links to the specified keyword if the checkbox is unchecked if (!empty($custom_keyword) && !$remove_links) { foreach ($paragraphs as $paragraph) { $paragraph_html = $dom->saveHTML($paragraph) ?? ''; // Fix deprecated warning with null coalescing if (!is_null($paragraph_html) && stripos($paragraph_html, $custom_keyword) !== false) { foreach ($all_posts as $single_post) { $post_url = get_permalink($single_post->ID); // Replace **all** occurrences of the custom keyword with a link (case-insensitive) within paragraph tags only $linked_keyword = '<a href="' . $post_url . '">' . $custom_keyword . '</a>'; $new_paragraph_html = preg_replace('/\b' . preg_quote($custom_keyword, '/') . '\b/i', $linked_keyword, $paragraph_html ?? '' // Fix deprecated warning with null coalescing ); if ($new_paragraph_html !== $paragraph_html && !is_null($new_paragraph_html)) { $new_paragraph_node = $dom->createDocumentFragment(); $new_paragraph_node->appendXML($new_paragraph_html); if ($paragraph->parentNode !== null) { $paragraph->parentNode->replaceChild($new_paragraph_node, $paragraph); $updated = true; } } } } } } // If updates were made, save the new content if ($updated) { // Save the updated content $updated_content = $dom->saveHTML(); // Prepare post data for update $updated_post = array( 'ID' => $post_id, 'post_content' => $updated_content, ); // Unhook save_post to avoid recursion remove_action('save_post', 'ckil_run_on_publish', 10); // Update the post with the modified content wp_update_post($updated_post); // Re-hook save_post after the update add_action('save_post', 'ckil_run_on_publish', 10, 3); } } I tried excluding the heading tags, but the code still includes the heading tags in hyperlinking all instances of a particular keyword. What could have gone wrong? I tried this // Query for all paragraph elements <p>, excluding heading tags <h1> to <h6> $paragraphs = $xpath->query('//p'); but still didn't work.