Plugin Directory

Changeset 3422890


Ignore:
Timestamp:
12/18/2025 01:24:57 PM (3 months ago)
Author:
Atastic
Message:

Version 1.0.6: Fix PCRE regex limit for large URL lists, improved URL parsing and example output

Location:
410-link-unlinker
Files:
2 edited
3 copied

Legend:

Unmodified
Added
Removed
  • 410-link-unlinker/tags/1.0.6/410-link-unlinker.php

    r3421603 r3422890  
    33 * Plugin Name: 410 Link Unlinker
    44 * Description: Removes anchor wrappers (<a href="…">…</a>) in posts/pages (and optionally postmeta) when links match a provided list (e.g., 410 Gone). Keeps the visible text.
    5  * Version:     1.0.5
     5 * Version:     1.0.6
    66 * Author:      Arnjen
    77 * License:     GPL-2.0-or-later
     
    1717    const OPT_KEY = 'link410_urls';
    1818    const NONCE   = 'link410_nonce';
    19     const VER     = '1.0.5';
     19    const VER     = '1.0.6';
    2020
    2121    public function __construct() {
     
    4242            if ($u === '') { continue; }
    4343            if ($loose) {
     44                // Add scheme if missing so wp_parse_url can identify the host
     45                if (strpos($u, '://') === false) {
     46                    $u = 'https://' . $u;
     47                }
    4448                $p = wp_parse_url($u);
    4549                if (!$p || empty($p['host'])) { continue; }
     
    9094                </p>
    9195                <p><label><input type="checkbox" name="link410_dry" value="1" checked> <?php esc_html_e('Dry-run (preview only; no saving)', '410-link-unlinker'); ?></label></p>
     96                <p><label><input type="checkbox" name="link410_show_all" value="1"> <?php esc_html_e('Show all changes (not just first 10)', '410-link-unlinker'); ?></label></p>
    9297                <p>
    9398                    <button class="button button-primary" name="link410_action" value="run"><?php esc_html_e('Scan / Unlink Now', '410-link-unlinker'); ?></button>
     
    134139            $dry    = !empty($_POST['link410_dry']);
    135140            $include_meta = !empty($_POST['link410_include_meta']);
     141            $show_all = !empty($_POST['link410_show_all']);
    136142
    137143            $urls = $this->get_saved_urls();
     
    141147            }
    142148
    143             $result = $this->process($urls, $limit, $offset, $dry, $include_meta);
     149            $result = $this->process($urls, $limit, $offset, $dry, $include_meta, $show_all);
    144150            add_action('admin_notices', function() use ($result, $dry){
    145151                $mode = $dry ? __('Dry-run', '410-link-unlinker') : __('Updated', '410-link-unlinker');
     
    159165    }
    160166
    161     private function process(array $urls, int $limit, int $offset, bool $dry, bool $include_meta) {
     167    private function process(array $urls, int $limit, int $offset, bool $dry, bool $include_meta, bool $show_all = false) {
    162168        global $wpdb;
    163169
     
    167173        $posts = $wpdb->get_results(
    168174            $wpdb->prepare(
    169                 "SELECT ID, post_content FROM {$wpdb->posts} WHERE post_type IN ('post','page') AND post_status = 'publish' ORDER BY ID ASC LIMIT %d OFFSET %d",
     175                "SELECT ID, post_name, post_content FROM {$wpdb->posts} WHERE post_type IN ('post','page') AND post_status = 'publish' ORDER BY ID ASC LIMIT %d OFFSET %d",
    170176                $limit, $offset
    171177            ),
     
    184190                $posts_changed++;
    185191                $anchors_removed += $removed;
    186                 if ($example && count($examples) < 5) { $examples[] = 'Post '.$row['ID'].': '.$example; }
     192                // Fallback: extract matched URL from original if example wasn't captured
     193                if (!$example) {
     194                    foreach ($patterns['href_regexes'] as $rx) {
     195                        if (preg_match('/href=["\']('.$rx.')["\']/', $orig, $href_match)) {
     196                            $example = 'Removed: '.(strlen($href_match[1]) > 80 ? substr($href_match[1], 0, 80).'...' : $href_match[1]);
     197                            break;
     198                        }
     199                    }
     200                }
     201                if ($show_all || count($examples) < 10) { $examples[] = '/'.$row['post_name'].'/: '.($example ?: 'Unknown match in content'); }
    187202                if (!$dry) {
    188203                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Direct update required for bulk processing
     
    199214            $meta = $wpdb->get_results(
    200215                $wpdb->prepare(
    201                     "SELECT meta_id, post_id, meta_key, meta_value FROM {$wpdb->postmeta} ORDER BY meta_id ASC LIMIT %d OFFSET %d",
     216                    "SELECT m.meta_id, m.post_id, m.meta_key, m.meta_value, p.post_name FROM {$wpdb->postmeta} m LEFT JOIN {$wpdb->posts} p ON m.post_id = p.ID ORDER BY m.meta_id ASC LIMIT %d OFFSET %d",
    202217                    $limit, $offset
    203218                ),
     
    208223                $orig = (string) $m['meta_value'];
    209224                if ($orig === '' || strlen($orig) < 4) { continue; }
    210                 [$new, $removed, $example] = $this->unlink_in_html($orig, $patterns);
    211                 // Shortcode attribute neutralization
     225                // Only process values that look like HTML (contain tags)
     226                if (strpos($orig, '<') === false || strpos($orig, '>') === false) { continue; }
     227                // Use full-URL-only patterns for postmeta (no relative path matching)
     228                $meta_patterns = [
     229                    'href_regexes'     => $patterns['href_regexes_full'],
     230                    'href_regexes_raw' => $patterns['href_regexes_full'],
     231                ];
     232                [$new, $removed, $example] = $this->unlink_in_html($orig, $meta_patterns);
     233                // Shortcode attribute neutralization (if no anchors were removed)
    212234                if ($removed === 0) {
    213                     foreach ($patterns['href_regexes_raw'] as $rx) {
     235                    foreach ($patterns['href_regexes_full'] as $rx) {
    214236                        $before = $new;
     237                        $matched_url = null;
     238                        $matched_attr = null;
    215239                        $new = preg_replace_callback(
    216                             '/(\[?[a-z0-9_-]+\b[^]\n\r]*\b(url|link|href)=)\"('.$rx.')\"/i',
    217                             function($matches) use (&$example) {
    218                                 if (!$example) {
    219                                     $example = 'Neutralized shortcode attr: '.$matches[2].'="'.$matches[3].'"';
    220                                 }
     240                            '/(\[?[a-z0-9_-]+\b[^]\n\r]*\b(url|link|href)=)["\']('.$rx.')["\']/',
     241                            function($matches) use (&$matched_url, &$matched_attr) {
     242                                $matched_attr = $matches[2];
     243                                $matched_url = $matches[3];
    221244                                return $matches[1].'"#"';
    222245                            },
    223246                            $new
    224247                        );
    225                         if ($new !== $before) { $removed++; }
     248                        if ($new !== null && $new !== $before) {
     249                            $removed++;
     250                            if (!$example && $matched_url) {
     251                                $example = 'Neutralized: '.$matched_attr.'="'.(strlen($matched_url) > 60 ? substr($matched_url, 0, 60).'...' : $matched_url).'"';
     252                            }
     253                        }
    226254                    }
    227255                }
    228256                if ($removed > 0) {
    229257                    $meta_changed++;
    230                     if ($example && count($examples) < 5) { $examples[] = 'Meta '.$m['meta_id'].' (post '.$m['post_id'].'): '.$example; }
     258                    // Fallback: extract matched URL from original if example wasn't captured
     259                    if (!$example) {
     260                        // Try to find a URL from our patterns in the original content
     261                        foreach ($patterns['href_regexes_full'] as $rx) {
     262                            if (preg_match('/href=["\']('.$rx.')["\']/', $orig, $href_match)) {
     263                                $example = 'Removed: '.(strlen($href_match[1]) > 80 ? substr($href_match[1], 0, 80).'...' : $href_match[1]);
     264                                break;
     265                            }
     266                            if (preg_match('/(url|link)=["\']('.$rx.')["\']/', $orig, $url_match)) {
     267                                $example = 'Neutralized: '.$url_match[1].'="'.(strlen($url_match[2]) > 60 ? substr($url_match[2], 0, 60).'...' : $url_match[2]).'"';
     268                                break;
     269                            }
     270                        }
     271                    }
     272                    if ($show_all || count($examples) < 10) {
     273                        $examples[] = '/'.($m['post_name'] ?: 'post-'.$m['post_id']).'/  ['.$m['meta_key'].']: '.($example ?: 'Modified');
     274                    }
    231275                    if (!$dry) {
    232276                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Direct update required for bulk processing
     
    251295        $full_url_patterns = [];
    252296        $path_only_patterns = [];
    253         $hosts_only = [];
    254297
    255298        foreach ($urls as $u) {
     
    266309            }
    267310
    268             // Build pattern for this specific host+path combination
     311            // Build individual pattern for this specific host+path combination
    269312            if ($path) {
    270313                // Match: https://host/path (with optional trailing slash and query string)
     
    274317            } else {
    275318                // Only host provided (no path) - match host with optional trailing slash
    276                 $hosts_only[] = $host;
    277             }
    278         }
    279 
    280         $list = [];
    281 
    282         // Add patterns for full URLs (host+path pairs)
    283         if ($full_url_patterns) {
    284             $list[] = '(?:' . implode('|', $full_url_patterns) . ')';
    285         }
    286 
    287         // Add patterns for hosts without paths
    288         if ($hosts_only) {
    289             $host_rx = '(?:' . implode('|', array_map($esc, $hosts_only)) . ')';
    290             $list[] = 'https?:\/\/(?:www\.)?' . $host_rx . '(?:\/)?(?:\?[^"\']*)?';
    291         }
    292 
    293         // Add relative path patterns
    294         if ($path_only_patterns) {
    295             $list[] = '(?:' . implode('|', $path_only_patterns) . ')';
    296         }
    297 
     319                $full_url_patterns[] = 'https?:\/\/(?:www\.)?' . $esc($host) . '(?:\/)?(?:\?[^"\']*)?';
     320            }
     321        }
     322
     323        // Return individual patterns (not combined) to avoid PCRE limits with large URL lists
    298324        return [
    299             'href_regexes'     => $list,
    300             'href_regexes_raw' => $list,
     325            'href_regexes'      => array_merge($full_url_patterns, $path_only_patterns),  // For post_content
     326            'href_regexes_raw'  => array_merge($full_url_patterns, $path_only_patterns),
     327            'href_regexes_full' => $full_url_patterns,   // For postmeta (full URLs only)
    301328        ];
    302329    }
     
    332359                    $a->parentNode->replaceChild($frag, $a);
    333360                    $removed++;
    334                     if (!$example) { $example = 'Removed link href="'.$href.'"'; }
     361                    if (!$example) { $example = 'Removed: '.(strlen($href) > 80 ? substr($href, 0, 80).'...' : $href); }
    335362                }
    336363            }
     
    341368        foreach ($patterns['href_regexes'] as $rx) {
    342369            $before = $new;
    343             $new = preg_replace('/<a\b[^>]*href="(?:'.$rx.')"[^>]*>(.*?)<\/a>/i', '$1', $new);
    344             $new = preg_replace("/<a\\b[^>]*href='(?:".$rx.")'[^>]*>(.*?)<\/a>/i", '$1', $new);
    345             if ($new !== $before) { $removed++; if (!$example) { $example = 'Removed link by regex'; } }
     370            $matched_url = null;
     371            $new = preg_replace_callback(
     372                '/<a\b[^>]*href="('.$rx.')"[^>]*>(.*?)<\/a>/i',
     373                function($matches) use (&$matched_url) {
     374                    if (!$matched_url) { $matched_url = $matches[1]; }
     375                    return $matches[2];
     376                },
     377                $new
     378            );
     379            $new = preg_replace_callback(
     380                "/<a\\b[^>]*href='(".$rx.")'[^>]*>(.*?)<\\/a>/i",
     381                function($matches) use (&$matched_url) {
     382                    if (!$matched_url) { $matched_url = $matches[1]; }
     383                    return $matches[2];
     384                },
     385                $new
     386            );
     387            if ($new !== $before) {
     388                $removed++;
     389                if (!$example && $matched_url) {
     390                    $example = 'Removed: '.(strlen($matched_url) > 80 ? substr($matched_url, 0, 80).'...' : $matched_url);
     391                }
     392            }
    346393        }
    347394        return [$new, $removed, $example];
  • 410-link-unlinker/tags/1.0.6/readme.txt

    r3421603 r3422890  
    33Tags: links, 410, unlink, cleanup
    44Requires at least: 5.8
    5 Tested up to: 6.8
     5Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.0.5
     7Stable tag: 1.0.6
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    2424
    2525== Changelog ==
     26= 1.0.6 =
     27* Fix URL parsing to accept URLs without https:// prefix.
     28* Improved examples: show post/page URL slugs and meta_key names.
     29* Add "Show all changes" checkbox to list every modification instead of first 10.
     30* Fix shortcode matching to support both single and double quotes.
     31* Only process postmeta values that contain HTML tags (skip internal WP meta).
     32* Relative path matching now only applies to post_content, not postmeta (prevents false positives across domains).
     33* Fix PCRE regex limit error when processing large URL lists (600+ URLs now supported).
     34
    2635= 1.0.5 =
    2736* Only scan published posts/pages (skip drafts for faster processing).
  • 410-link-unlinker/trunk/410-link-unlinker.php

    r3421603 r3422890  
    33 * Plugin Name: 410 Link Unlinker
    44 * Description: Removes anchor wrappers (<a href="…">…</a>) in posts/pages (and optionally postmeta) when links match a provided list (e.g., 410 Gone). Keeps the visible text.
    5  * Version:     1.0.5
     5 * Version:     1.0.6
    66 * Author:      Arnjen
    77 * License:     GPL-2.0-or-later
     
    1717    const OPT_KEY = 'link410_urls';
    1818    const NONCE   = 'link410_nonce';
    19     const VER     = '1.0.5';
     19    const VER     = '1.0.6';
    2020
    2121    public function __construct() {
     
    4242            if ($u === '') { continue; }
    4343            if ($loose) {
     44                // Add scheme if missing so wp_parse_url can identify the host
     45                if (strpos($u, '://') === false) {
     46                    $u = 'https://' . $u;
     47                }
    4448                $p = wp_parse_url($u);
    4549                if (!$p || empty($p['host'])) { continue; }
     
    9094                </p>
    9195                <p><label><input type="checkbox" name="link410_dry" value="1" checked> <?php esc_html_e('Dry-run (preview only; no saving)', '410-link-unlinker'); ?></label></p>
     96                <p><label><input type="checkbox" name="link410_show_all" value="1"> <?php esc_html_e('Show all changes (not just first 10)', '410-link-unlinker'); ?></label></p>
    9297                <p>
    9398                    <button class="button button-primary" name="link410_action" value="run"><?php esc_html_e('Scan / Unlink Now', '410-link-unlinker'); ?></button>
     
    134139            $dry    = !empty($_POST['link410_dry']);
    135140            $include_meta = !empty($_POST['link410_include_meta']);
     141            $show_all = !empty($_POST['link410_show_all']);
    136142
    137143            $urls = $this->get_saved_urls();
     
    141147            }
    142148
    143             $result = $this->process($urls, $limit, $offset, $dry, $include_meta);
     149            $result = $this->process($urls, $limit, $offset, $dry, $include_meta, $show_all);
    144150            add_action('admin_notices', function() use ($result, $dry){
    145151                $mode = $dry ? __('Dry-run', '410-link-unlinker') : __('Updated', '410-link-unlinker');
     
    159165    }
    160166
    161     private function process(array $urls, int $limit, int $offset, bool $dry, bool $include_meta) {
     167    private function process(array $urls, int $limit, int $offset, bool $dry, bool $include_meta, bool $show_all = false) {
    162168        global $wpdb;
    163169
     
    167173        $posts = $wpdb->get_results(
    168174            $wpdb->prepare(
    169                 "SELECT ID, post_content FROM {$wpdb->posts} WHERE post_type IN ('post','page') AND post_status = 'publish' ORDER BY ID ASC LIMIT %d OFFSET %d",
     175                "SELECT ID, post_name, post_content FROM {$wpdb->posts} WHERE post_type IN ('post','page') AND post_status = 'publish' ORDER BY ID ASC LIMIT %d OFFSET %d",
    170176                $limit, $offset
    171177            ),
     
    184190                $posts_changed++;
    185191                $anchors_removed += $removed;
    186                 if ($example && count($examples) < 5) { $examples[] = 'Post '.$row['ID'].': '.$example; }
     192                // Fallback: extract matched URL from original if example wasn't captured
     193                if (!$example) {
     194                    foreach ($patterns['href_regexes'] as $rx) {
     195                        if (preg_match('/href=["\']('.$rx.')["\']/', $orig, $href_match)) {
     196                            $example = 'Removed: '.(strlen($href_match[1]) > 80 ? substr($href_match[1], 0, 80).'...' : $href_match[1]);
     197                            break;
     198                        }
     199                    }
     200                }
     201                if ($show_all || count($examples) < 10) { $examples[] = '/'.$row['post_name'].'/: '.($example ?: 'Unknown match in content'); }
    187202                if (!$dry) {
    188203                    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Direct update required for bulk processing
     
    199214            $meta = $wpdb->get_results(
    200215                $wpdb->prepare(
    201                     "SELECT meta_id, post_id, meta_key, meta_value FROM {$wpdb->postmeta} ORDER BY meta_id ASC LIMIT %d OFFSET %d",
     216                    "SELECT m.meta_id, m.post_id, m.meta_key, m.meta_value, p.post_name FROM {$wpdb->postmeta} m LEFT JOIN {$wpdb->posts} p ON m.post_id = p.ID ORDER BY m.meta_id ASC LIMIT %d OFFSET %d",
    202217                    $limit, $offset
    203218                ),
     
    208223                $orig = (string) $m['meta_value'];
    209224                if ($orig === '' || strlen($orig) < 4) { continue; }
    210                 [$new, $removed, $example] = $this->unlink_in_html($orig, $patterns);
    211                 // Shortcode attribute neutralization
     225                // Only process values that look like HTML (contain tags)
     226                if (strpos($orig, '<') === false || strpos($orig, '>') === false) { continue; }
     227                // Use full-URL-only patterns for postmeta (no relative path matching)
     228                $meta_patterns = [
     229                    'href_regexes'     => $patterns['href_regexes_full'],
     230                    'href_regexes_raw' => $patterns['href_regexes_full'],
     231                ];
     232                [$new, $removed, $example] = $this->unlink_in_html($orig, $meta_patterns);
     233                // Shortcode attribute neutralization (if no anchors were removed)
    212234                if ($removed === 0) {
    213                     foreach ($patterns['href_regexes_raw'] as $rx) {
     235                    foreach ($patterns['href_regexes_full'] as $rx) {
    214236                        $before = $new;
     237                        $matched_url = null;
     238                        $matched_attr = null;
    215239                        $new = preg_replace_callback(
    216                             '/(\[?[a-z0-9_-]+\b[^]\n\r]*\b(url|link|href)=)\"('.$rx.')\"/i',
    217                             function($matches) use (&$example) {
    218                                 if (!$example) {
    219                                     $example = 'Neutralized shortcode attr: '.$matches[2].'="'.$matches[3].'"';
    220                                 }
     240                            '/(\[?[a-z0-9_-]+\b[^]\n\r]*\b(url|link|href)=)["\']('.$rx.')["\']/',
     241                            function($matches) use (&$matched_url, &$matched_attr) {
     242                                $matched_attr = $matches[2];
     243                                $matched_url = $matches[3];
    221244                                return $matches[1].'"#"';
    222245                            },
    223246                            $new
    224247                        );
    225                         if ($new !== $before) { $removed++; }
     248                        if ($new !== null && $new !== $before) {
     249                            $removed++;
     250                            if (!$example && $matched_url) {
     251                                $example = 'Neutralized: '.$matched_attr.'="'.(strlen($matched_url) > 60 ? substr($matched_url, 0, 60).'...' : $matched_url).'"';
     252                            }
     253                        }
    226254                    }
    227255                }
    228256                if ($removed > 0) {
    229257                    $meta_changed++;
    230                     if ($example && count($examples) < 5) { $examples[] = 'Meta '.$m['meta_id'].' (post '.$m['post_id'].'): '.$example; }
     258                    // Fallback: extract matched URL from original if example wasn't captured
     259                    if (!$example) {
     260                        // Try to find a URL from our patterns in the original content
     261                        foreach ($patterns['href_regexes_full'] as $rx) {
     262                            if (preg_match('/href=["\']('.$rx.')["\']/', $orig, $href_match)) {
     263                                $example = 'Removed: '.(strlen($href_match[1]) > 80 ? substr($href_match[1], 0, 80).'...' : $href_match[1]);
     264                                break;
     265                            }
     266                            if (preg_match('/(url|link)=["\']('.$rx.')["\']/', $orig, $url_match)) {
     267                                $example = 'Neutralized: '.$url_match[1].'="'.(strlen($url_match[2]) > 60 ? substr($url_match[2], 0, 60).'...' : $url_match[2]).'"';
     268                                break;
     269                            }
     270                        }
     271                    }
     272                    if ($show_all || count($examples) < 10) {
     273                        $examples[] = '/'.($m['post_name'] ?: 'post-'.$m['post_id']).'/  ['.$m['meta_key'].']: '.($example ?: 'Modified');
     274                    }
    231275                    if (!$dry) {
    232276                        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Direct update required for bulk processing
     
    251295        $full_url_patterns = [];
    252296        $path_only_patterns = [];
    253         $hosts_only = [];
    254297
    255298        foreach ($urls as $u) {
     
    266309            }
    267310
    268             // Build pattern for this specific host+path combination
     311            // Build individual pattern for this specific host+path combination
    269312            if ($path) {
    270313                // Match: https://host/path (with optional trailing slash and query string)
     
    274317            } else {
    275318                // Only host provided (no path) - match host with optional trailing slash
    276                 $hosts_only[] = $host;
    277             }
    278         }
    279 
    280         $list = [];
    281 
    282         // Add patterns for full URLs (host+path pairs)
    283         if ($full_url_patterns) {
    284             $list[] = '(?:' . implode('|', $full_url_patterns) . ')';
    285         }
    286 
    287         // Add patterns for hosts without paths
    288         if ($hosts_only) {
    289             $host_rx = '(?:' . implode('|', array_map($esc, $hosts_only)) . ')';
    290             $list[] = 'https?:\/\/(?:www\.)?' . $host_rx . '(?:\/)?(?:\?[^"\']*)?';
    291         }
    292 
    293         // Add relative path patterns
    294         if ($path_only_patterns) {
    295             $list[] = '(?:' . implode('|', $path_only_patterns) . ')';
    296         }
    297 
     319                $full_url_patterns[] = 'https?:\/\/(?:www\.)?' . $esc($host) . '(?:\/)?(?:\?[^"\']*)?';
     320            }
     321        }
     322
     323        // Return individual patterns (not combined) to avoid PCRE limits with large URL lists
    298324        return [
    299             'href_regexes'     => $list,
    300             'href_regexes_raw' => $list,
     325            'href_regexes'      => array_merge($full_url_patterns, $path_only_patterns),  // For post_content
     326            'href_regexes_raw'  => array_merge($full_url_patterns, $path_only_patterns),
     327            'href_regexes_full' => $full_url_patterns,   // For postmeta (full URLs only)
    301328        ];
    302329    }
     
    332359                    $a->parentNode->replaceChild($frag, $a);
    333360                    $removed++;
    334                     if (!$example) { $example = 'Removed link href="'.$href.'"'; }
     361                    if (!$example) { $example = 'Removed: '.(strlen($href) > 80 ? substr($href, 0, 80).'...' : $href); }
    335362                }
    336363            }
     
    341368        foreach ($patterns['href_regexes'] as $rx) {
    342369            $before = $new;
    343             $new = preg_replace('/<a\b[^>]*href="(?:'.$rx.')"[^>]*>(.*?)<\/a>/i', '$1', $new);
    344             $new = preg_replace("/<a\\b[^>]*href='(?:".$rx.")'[^>]*>(.*?)<\/a>/i", '$1', $new);
    345             if ($new !== $before) { $removed++; if (!$example) { $example = 'Removed link by regex'; } }
     370            $matched_url = null;
     371            $new = preg_replace_callback(
     372                '/<a\b[^>]*href="('.$rx.')"[^>]*>(.*?)<\/a>/i',
     373                function($matches) use (&$matched_url) {
     374                    if (!$matched_url) { $matched_url = $matches[1]; }
     375                    return $matches[2];
     376                },
     377                $new
     378            );
     379            $new = preg_replace_callback(
     380                "/<a\\b[^>]*href='(".$rx.")'[^>]*>(.*?)<\\/a>/i",
     381                function($matches) use (&$matched_url) {
     382                    if (!$matched_url) { $matched_url = $matches[1]; }
     383                    return $matches[2];
     384                },
     385                $new
     386            );
     387            if ($new !== $before) {
     388                $removed++;
     389                if (!$example && $matched_url) {
     390                    $example = 'Removed: '.(strlen($matched_url) > 80 ? substr($matched_url, 0, 80).'...' : $matched_url);
     391                }
     392            }
    346393        }
    347394        return [$new, $removed, $example];
  • 410-link-unlinker/trunk/readme.txt

    r3421603 r3422890  
    33Tags: links, 410, unlink, cleanup
    44Requires at least: 5.8
    5 Tested up to: 6.8
     5Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.0.5
     7Stable tag: 1.0.6
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    2424
    2525== Changelog ==
     26= 1.0.6 =
     27* Fix URL parsing to accept URLs without https:// prefix.
     28* Improved examples: show post/page URL slugs and meta_key names.
     29* Add "Show all changes" checkbox to list every modification instead of first 10.
     30* Fix shortcode matching to support both single and double quotes.
     31* Only process postmeta values that contain HTML tags (skip internal WP meta).
     32* Relative path matching now only applies to post_content, not postmeta (prevents false positives across domains).
     33* Fix PCRE regex limit error when processing large URL lists (600+ URLs now supported).
     34
    2635= 1.0.5 =
    2736* Only scan published posts/pages (skip drafts for faster processing).
Note: See TracChangeset for help on using the changeset viewer.