Hey,
we know how important internal linking is and wanted to automate a bit of the process. For that, we’ve added the following Code to do the following:
- Caches categories and stores them
- Links automatically to all name-matching categories in posts
- Updates the Cache when changes to categories are made
- Updates the post automatically; no linking necessary
To use this, simply add this code to your functions.php file, or by using a Plugin like Code Snippets.
Newest Version
This code adds a check to only add the link to a category only to the first 2 instances; significally decreasing load time.
class Category_Link_Optimizer {
private static $instance = null;
private $category_links = [];
private $cache_key = 'category_links_cache';
private $cache_time = 3600; // 1 hour
private $excluded_words = ['Amazon', 'gooloo'];
private $max_links_per_category = 2;
private function __construct() {
add_filter('the_content', [$this, 'link_categories_in_content'], 99);
add_action('edited_category', [$this, 'clear_cache']);
add_action('create_category', [$this, 'clear_cache']);
add_action('delete_category', [$this, 'clear_cache']);
}
public static function get_instance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function get_cached_category_links() {
if (!empty($this->category_links)) {
return $this->category_links;
}
$this->category_links = wp_cache_get($this->cache_key);
if (false === $this->category_links) {
$this->category_links = $this->generate_category_links();
wp_cache_set($this->cache_key, $this->category_links, '', $this->cache_time);
}
return $this->category_links;
}
private function generate_category_links() {
$categories = get_categories(['hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC']);
$links = [];
foreach ($categories as $category) {
if (!in_array($category->name, $this->excluded_words)) {
$links[$category->name] = get_category_link($category->term_id);
}
}
return $links;
}
private function replace_category_with_limit($content, $name, $url) {
$count = 0;
$pattern = '/\b' . preg_quote($name, '/') . '\b(?![^<]*>)/i';
return preg_replace_callback($pattern, function($matches) use (&$count, $url, $name) {
if ($count < $this->max_links_per_category) {
$count++;
return '<a href="' . esc_url($url) . '">' . $name . '</a>';
}
return $matches[0];
}, $content);
}
public function link_categories_in_content($content) {
// Check if it's a single post and specifically of post type 'post'
if (!is_single() || get_post_type() !== 'post') {
return $content;
}
$category_links = $this->get_cached_category_links();
// Sort category names by length (descending) to match longer names first
uksort($category_links, function($a, $b) {
return strlen($b) - strlen($a);
});
// Split content by the amazon search div
$parts = preg_split('/<div class=["\']amazon-search["\'].*?<\/div>/s', $content);
// Process each part separately
foreach ($parts as &$part) {
foreach ($category_links as $name => $url) {
$part = $this->replace_category_with_limit($part, $name, $url);
}
}
// Reassemble the content with the original amazon search divs
$amazon_search_divs = [];
preg_match_all('/<div class=["\']amazon-search["\'].*?<\/div>/s', $content, $amazon_search_divs);
$final_content = '';
for ($i = 0; $i < count($parts); $i++) {
$final_content .= $parts[$i];
if (isset($amazon_search_divs[0][$i])) {
$final_content .= $amazon_search_divs[0][$i];
}
}
return $final_content;
}
public function clear_cache() {
wp_cache_delete($this->cache_key);
$this->category_links = [];
// Clear LiteSpeed Cache if it's active
if (class_exists('LiteSpeed_Cache_API')) {
LiteSpeed_Cache_API::purge_all();
}
}
}
// Initialize the Category Link Optimizer
add_action('init', function() {
Category_Link_Optimizer::get_instance();
});
New Version
This Version is a little more comprehensive and better with dynamically added content. It also uses less resources. In this example, we’re also excluding specific categories. In this case, “Amazon”.
Note: The Plugin linked below is still the “old version”.
class Category_Link_Optimizer {
private static $instance = null;
private $category_links = [];
private $cache_key = 'category_links_cache';
private $cache_time = 3600; // 1 hour
private $excluded_words = ['Amazon', 'gooloo'];
private function __construct() {
add_filter('the_content', [$this, 'link_categories_in_content'], 99);
add_action('edited_category', [$this, 'clear_cache']);
add_action('create_category', [$this, 'clear_cache']);
add_action('delete_category', [$this, 'clear_cache']);
}
public static function get_instance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function get_cached_category_links() {
if (!empty($this->category_links)) {
return $this->category_links;
}
$this->category_links = wp_cache_get($this->cache_key);
if (false === $this->category_links) {
$this->category_links = $this->generate_category_links();
wp_cache_set($this->cache_key, $this->category_links, '', $this->cache_time);
}
return $this->category_links;
}
private function generate_category_links() {
$categories = get_categories(['hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC']);
$links = [];
foreach ($categories as $category) {
if (!in_array($category->name, $this->excluded_words)) {
$links[$category->name] = get_category_link($category->term_id);
}
}
return $links;
}
public function link_categories_in_content($content) {
// Check if it's a single post, not a page or other post type
if (empty($content) || !is_single() || !is_singular('post')) {
return $content;
}
$category_links = $this->get_cached_category_links();
// Sort category names by length (descending) to match longer names first
uksort($category_links, function($a, $b) {
return strlen($b) - strlen($a);
});
// Split content by the amazon search div
$parts = preg_split('/<div class=["\']amazon-search["\'].*?<\/div>/s', $content);
// Process each part separately
foreach ($parts as &$part) {
foreach ($category_links as $name => $url) {
$pattern = '/\b' . preg_quote($name, '/') . '\b(?![^<]*>)/i';
$replacement = '<a href="' . esc_url($url) . '">' . $name . '</a>';
$part = preg_replace($pattern, $replacement, $part);
}
}
// Reassemble the content with the original amazon search divs
$amazon_search_divs = [];
preg_match_all('/<div class=["\']amazon-search["\'].*?<\/div>/s', $content, $amazon_search_divs);
$final_content = '';
for ($i = 0; $i < count($parts); $i++) {
$final_content .= $parts[$i];
if (isset($amazon_search_divs[0][$i])) {
$final_content .= $amazon_search_divs[0][$i];
}
}
return $final_content;
}
public function clear_cache() {
wp_cache_delete($this->cache_key);
$this->category_links = [];
// Clear LiteSpeed Cache if it's active
if (class_exists('LiteSpeed_Cache_API')) {
LiteSpeed_Cache_API::purge_all();
}
}
}
// Initialize the Category Link Optimizer
add_action('init', function() {
Category_Link_Optimizer::get_instance();
});
Previous Version
class Category_Link_Optimizer {
private static $instance = null;
private $category_links = [];
private $cache_key = 'category_links_cache';
private $cache_time = 3600; // 1 hour
private function __construct() {
add_filter('the_content', [$this, 'link_categories_in_content'], 20);
add_action('edited_category', [$this, 'clear_cache']);
add_action('create_category', [$this, 'clear_cache']);
add_action('delete_category', [$this, 'clear_cache']);
}
public static function get_instance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function get_cached_category_links() {
if (!empty($this->category_links)) {
return $this->category_links;
}
$this->category_links = wp_cache_get($this->cache_key);
if (false === $this->category_links) {
$this->category_links = $this->generate_category_links();
wp_cache_set($this->cache_key, $this->category_links, '', $this->cache_time);
}
return $this->category_links;
}
private function generate_category_links() {
$categories = get_categories(['hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC']);
$links = [];
foreach ($categories as $category) {
$links[$category->name] = get_category_link($category->term_id);
}
return $links;
}
public function link_categories_in_content($content) {
if (empty($content) || !is_singular('post')) {
return $content;
}
$category_links = $this->get_cached_category_links();
// Sort category names by length (descending) to match longer names first
uksort($category_links, function($a, $b) {
return strlen($b) - strlen($a);
});
$batch_size = 100;
$total_categories = count($category_links);
for ($i = 0; $i < $total_categories; $i += $batch_size) {
$batch = array_slice($category_links, $i, $batch_size, true);
foreach ($batch as $name => $url) {
$pattern = '/\b' . preg_quote($name, '/') . '\b(?![^<]*>)/i';
$replacement = '<a href="' . esc_url($url) . '">' . $name . '</a>';
$content = preg_replace($pattern, $replacement, $content);
}
}
return $content;
}
public function clear_cache() {
wp_cache_delete($this->cache_key);
$this->category_links = [];
// Clear LiteSpeed Cache if it's active
if (class_exists('LiteSpeed_Cache_API')) {
LiteSpeed_Cache_API::purge_all();
}
}
}
// Initialize the Category Link Optimizer
add_action('init', function() {
Category_Link_Optimizer::get_instance();
});
You can also create a Plugin for this. Add these prefixes to the code or download it here directly.
<?php
/*
* Plugin Name: Category Link Optimizer by gooloo.de
* Description: This code links category names in post content to their respective category pages.
* Version: 1.0.0
* Requires at least: 6.0
* Requires PHP: 8.0
* Author: gooloo.de
*/
class Category_Link_Optimizer {
private static $instance = null;
private $category_links = [];
private $cache_key = 'category_links_cache';
private $cache_time = 3600; // 1 hour
private function __construct() {
add_filter('the_content', [$this, 'link_categories_in_content'], 20);
add_action('edited_category', [$this, 'clear_cache']);
add_action('create_category', [$this, 'clear_cache']);
add_action('delete_category', [$this, 'clear_cache']);
}
public static function get_instance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function get_cached_category_links() {
if (!empty($this->category_links)) {
return $this->category_links;
}
$this->category_links = wp_cache_get($this->cache_key);
if (false === $this->category_links) {
$this->category_links = $this->generate_category_links();
wp_cache_set($this->cache_key, $this->category_links, '', $this->cache_time);
}
return $this->category_links;
}
private function generate_category_links() {
$categories = get_categories(['hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC']);
$links = [];
foreach ($categories as $category) {
$links[$category->name] = get_category_link($category->term_id);
}
return $links;
}
public function link_categories_in_content($content) {
if (empty($content) || !is_singular('post')) {
return $content;
}
$category_links = $this->get_cached_category_links();
// Sort category names by length (descending) to match longer names first
uksort($category_links, function($a, $b) {
return strlen($b) - strlen($a);
});
$batch_size = 100;
$total_categories = count($category_links);
for ($i = 0; $i < $total_categories; $i += $batch_size) {
$batch = array_slice($category_links, $i, $batch_size, true);
foreach ($batch as $name => $url) {
$pattern = '/\b' . preg_quote($name, '/') . '\b(?![^<]*>)/i';
$replacement = '<a href="' . esc_url($url) . '">' . $name . '</a>';
$content = preg_replace($pattern, $replacement, $content);
}
}
return $content;
}
public function clear_cache() {
wp_cache_delete($this->cache_key);
$this->category_links = [];
// Clear LiteSpeed Cache if it's active
if (class_exists('LiteSpeed_Cache_API')) {
LiteSpeed_Cache_API::purge_all();
}
}
}
// Initialize the Category Link Optimizer
add_action('init', function() {
Category_Link_Optimizer::get_instance();
});
Update 1
This update makes sure that these changes only apply to single posts, not pages, or other post types, and also excludes text within <div> containers.
It also excludes specific shortcodes and words, that can be changed in their respective code field, f.ex. “aawp”, “amazon”, and the DIV Class “.contentbox”.
class Category_Link_Optimizer {
private static $instance = null;
private $category_links = [];
private $cache_key = 'category_links_cache';
private $cache_time = 3600; // 1 hour
private $excluded_containers = [
'div.contentbox',
'div.aawp', // AAWP container
'div[class^="aawp-"]', // Classes starting with aawp-
'div.wp-block-embed', // Embedded content
'div.plugin-content', // Generic class for plugin content
];
private $excluded_shortcodes = ['aawp', 'product', 'amazon'];
private $excluded_words = ['Amazon'];
private function __construct() {
add_filter('the_content', [$this, 'link_categories_in_content'], 20);
add_action('edited_category', [$this, 'clear_cache']);
add_action('create_category', [$this, 'clear_cache']);
add_action('delete_category', [$this, 'clear_cache']);
}
public static function get_instance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function get_cached_category_links() {
if (!empty($this->category_links)) {
return $this->category_links;
}
$this->category_links = wp_cache_get($this->cache_key);
if (false === $this->category_links) {
$this->category_links = $this->generate_category_links();
wp_cache_set($this->cache_key, $this->category_links, '', $this->cache_time);
}
return $this->category_links;
}
private function generate_category_links() {
$categories = get_categories(['hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC']);
$links = [];
foreach ($categories as $category) {
if (!in_array($category->name, $this->excluded_words)) {
$links[$category->name] = get_category_link($category->term_id);
}
}
return $links;
}
public function link_categories_in_content($content) {
// Check if it's a single post, not a page or other post type
if (empty($content) || !is_single() || !is_singular('post')) {
return $content;
}
// Remove shortcodes before processing
$content = $this->remove_excluded_shortcodes($content);
$category_links = $this->get_cached_category_links();
// Sort category names by length (descending) to match longer names first
uksort($category_links, function($a, $b) {
return strlen($b) - strlen($a);
});
// Split content into excludable and non-excludable parts
$parts = $this->split_content($content);
foreach ($parts as &$part) {
if ($part['exclude']) {
continue; // Skip excluded parts
}
$batch_size = 100;
$total_categories = count($category_links);
for ($i = 0; $i < $total_categories; $i += $batch_size) {
$batch = array_slice($category_links, $i, $batch_size, true);
foreach ($batch as $name => $url) {
$pattern = '/\b' . preg_quote($name, '/') . '\b(?![^<]*>)/i';
$replacement = '<a href="' . esc_url($url) . '">' . $name . '</a>';
$part['content'] = preg_replace($pattern, $replacement, $part['content']);
}
}
}
// Reassemble the content
return $this->reassemble_content($parts);
}
private function split_content($content) {
$parts = [];
$excluded_regex = $this->get_excluded_regex();
if (preg_match_all($excluded_regex, $content, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
$last_end = 0;
foreach ($matches as $match) {
if ($match[0][1] > $last_end) {
$parts[] = [
'content' => substr($content, $last_end, $match[0][1] - $last_end),
'exclude' => false
];
}
$parts[] = [
'content' => $match[0][0],
'exclude' => true
];
$last_end = $match[0][1] + strlen($match[0][0]);
}
if ($last_end < strlen($content)) {
$parts[] = [
'content' => substr($content, $last_end),
'exclude' => false
];
}
} else {
$parts[] = [
'content' => $content,
'exclude' => false
];
}
return $parts;
}
private function reassemble_content($parts) {
return implode('', array_column($parts, 'content'));
}
private function get_excluded_regex() {
$selectors = array_map('preg_quote', $this->excluded_containers);
return '/<(' . implode('|', $selectors) . ')[^>]*>.*?<\/\1>/s';
}
private function remove_excluded_shortcodes($content) {
$shortcodes_regex = '\[(' . implode('|', $this->excluded_shortcodes) . ').*?\].*?\[\/\1\]';
return preg_replace('/' . $shortcodes_regex . '/s', '', $content);
}
public function clear_cache() {
wp_cache_delete($this->cache_key);
$this->category_links = [];
// Clear LiteSpeed Cache if it's active
if (class_exists('LiteSpeed_Cache_API')) {
LiteSpeed_Cache_API::purge_all();
}
}
}
// Initialize the Category Link Optimizer
add_action('init', function() {
Category_Link_Optimizer::get_instance();
});
Leave a Reply