Changeset 3423068
- Timestamp:
- 12/18/2025 03:56:47 PM (3 months ago)
- Location:
- 410-link-unlinker
- Files:
-
- 2 edited
- 3 copied
-
tags/1.0.7 (copied) (copied from 410-link-unlinker/trunk)
-
tags/1.0.7/410-link-unlinker.php (copied) (copied from 410-link-unlinker/trunk/410-link-unlinker.php) (6 diffs)
-
tags/1.0.7/readme.txt (copied) (copied from 410-link-unlinker/trunk/readme.txt) (2 diffs)
-
trunk/410-link-unlinker.php (modified) (6 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
410-link-unlinker/tags/1.0.7/410-link-unlinker.php
r3422890 r3423068 3 3 * Plugin Name: 410 Link Unlinker 4 4 * 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. 65 * Version: 1.0.7 6 6 * Author: Arnjen 7 7 * License: GPL-2.0-or-later … … 17 17 const OPT_KEY = 'link410_urls'; 18 18 const NONCE = 'link410_nonce'; 19 const VER = '1.0. 6';19 const VER = '1.0.7'; 20 20 21 21 public function __construct() { … … 85 85 <p><button class="button button-secondary" name="link410_action" value="save"><?php esc_html_e('Save URL list', '410-link-unlinker'); ?></button></p> 86 86 87 <h2><?php esc_html_e('2) R un', '410-link-unlinker'); ?></h2>87 <h2><?php esc_html_e('2) Remove Links', '410-link-unlinker'); ?></h2> 88 88 <p><label><input type="checkbox" name="link410_include_meta" value="1"> <?php esc_html_e('Also process wp_postmeta', '410-link-unlinker'); ?></label></p> 89 89 <p> … … 98 98 <button class="button button-primary" name="link410_action" value="run"><?php esc_html_e('Scan / Unlink Now', '410-link-unlinker'); ?></button> 99 99 </p> 100 101 <h2><?php esc_html_e('3) Trash Matching Posts', '410-link-unlinker'); ?></h2> 102 <p><?php esc_html_e('Find and trash posts whose URL/slug matches your 410 list. This removes them from author pages, archives, etc.', '410-link-unlinker'); ?></p> 103 <p> 104 <button class="button button-secondary" name="link410_action" value="trash"><?php esc_html_e('Find & Trash Matching Posts', '410-link-unlinker'); ?></button> 105 </p> 100 106 </form> 101 107 </div> … … 159 165 echo '<div class="notice notice-info"><p><strong>Examples:</strong></p><ol>'; 160 166 foreach ($result['examples'] as $ex) { echo '<li>'.esc_html($ex).'</li>'; } 167 echo '</ol></div>'; 168 } 169 }); 170 } 171 172 if ($action === 'trash') { 173 $dry = !empty($_POST['link410_dry']); 174 $urls = $this->get_saved_urls(); 175 if (!$urls) { 176 add_action('admin_notices', function(){ echo '<div class="notice notice-error"><p>'.esc_html__('No URLs saved. Please add your 410 list and try again.', '410-link-unlinker').'</p></div>'; }); 177 return; 178 } 179 180 $result = $this->trash_matching_posts($urls, $dry); 181 add_action('admin_notices', function() use ($result, $dry){ 182 $mode = $dry ? __('Dry-run', '410-link-unlinker') : __('Trashed', '410-link-unlinker'); 183 $msg = sprintf('%s: %d posts found matching your 410 URLs.', $mode, $result['count']); 184 echo '<div class="notice notice-success"><p>'.esc_html($msg).'</p></div>'; 185 if (!empty($result['posts'])) { 186 echo '<div class="notice notice-info"><p><strong>'.($dry ? 'Posts that would be trashed:' : 'Trashed posts:').'</strong></p><ol>'; 187 foreach ($result['posts'] as $p) { echo '<li>'.esc_html($p).'</li>'; } 161 188 echo '</ol></div>'; 162 189 } … … 291 318 } 292 319 320 private function trash_matching_posts(array $urls, bool $dry) { 321 global $wpdb; 322 323 // Extract slugs from URLs 324 $slugs = []; 325 foreach ($urls as $u) { 326 // Parse the URL to get the path 327 if (strpos($u, '://') !== false) { 328 $p = wp_parse_url($u); 329 $path = isset($p['path']) ? $p['path'] : ''; 330 } else { 331 $parts = explode('/', $u, 2); 332 $path = isset($parts[1]) ? '/'.$parts[1] : ''; 333 } 334 // Get the last segment of the path as the slug 335 $path = rtrim($path, '/'); 336 if ($path) { 337 $segments = explode('/', $path); 338 $slug = end($segments); 339 if ($slug && $slug !== '') { 340 $slugs[] = $slug; 341 } 342 } 343 } 344 345 if (empty($slugs)) { 346 return ['count' => 0, 'posts' => []]; 347 } 348 349 $slugs = array_unique($slugs); 350 351 // Find posts matching these slugs 352 $placeholders = implode(',', array_fill(0, count($slugs), '%s')); 353 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Bulk operation 354 $posts = $wpdb->get_results( 355 $wpdb->prepare( 356 "SELECT ID, post_title, post_name, post_type FROM {$wpdb->posts} WHERE post_name IN ($placeholders) AND post_status = 'publish'", 357 ...$slugs 358 ), 359 ARRAY_A 360 ); 361 362 $result = ['count' => 0, 'posts' => []]; 363 364 foreach ((array)$posts as $post) { 365 $result['count']++; 366 $result['posts'][] = $post['post_title'] . ' (/' . $post['post_name'] . '/) [' . $post['post_type'] . ']'; 367 368 if (!$dry) { 369 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Bulk operation 370 $wpdb->update( 371 $wpdb->posts, 372 ['post_status' => 'trash'], 373 ['ID' => (int)$post['ID']] 374 ); 375 clean_post_cache((int)$post['ID']); 376 } 377 } 378 379 return $result; 380 } 381 293 382 private function build_patterns(array $urls) { 294 383 $esc = static function($s){ return preg_quote($s, '/'); }; -
410-link-unlinker/tags/1.0.7/readme.txt
r3422890 r3423068 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1.0. 67 Stable tag: 1.0.7 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 24 24 25 25 == Changelog == 26 = 1.0.7 = 27 * Add "Trash Matching Posts" feature to bulk-trash posts whose URLs match your 410 list (removes them from author pages, archives, etc.). 28 26 29 = 1.0.6 = 27 30 * Fix URL parsing to accept URLs without https:// prefix. -
410-link-unlinker/trunk/410-link-unlinker.php
r3422890 r3423068 3 3 * Plugin Name: 410 Link Unlinker 4 4 * 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. 65 * Version: 1.0.7 6 6 * Author: Arnjen 7 7 * License: GPL-2.0-or-later … … 17 17 const OPT_KEY = 'link410_urls'; 18 18 const NONCE = 'link410_nonce'; 19 const VER = '1.0. 6';19 const VER = '1.0.7'; 20 20 21 21 public function __construct() { … … 85 85 <p><button class="button button-secondary" name="link410_action" value="save"><?php esc_html_e('Save URL list', '410-link-unlinker'); ?></button></p> 86 86 87 <h2><?php esc_html_e('2) R un', '410-link-unlinker'); ?></h2>87 <h2><?php esc_html_e('2) Remove Links', '410-link-unlinker'); ?></h2> 88 88 <p><label><input type="checkbox" name="link410_include_meta" value="1"> <?php esc_html_e('Also process wp_postmeta', '410-link-unlinker'); ?></label></p> 89 89 <p> … … 98 98 <button class="button button-primary" name="link410_action" value="run"><?php esc_html_e('Scan / Unlink Now', '410-link-unlinker'); ?></button> 99 99 </p> 100 101 <h2><?php esc_html_e('3) Trash Matching Posts', '410-link-unlinker'); ?></h2> 102 <p><?php esc_html_e('Find and trash posts whose URL/slug matches your 410 list. This removes them from author pages, archives, etc.', '410-link-unlinker'); ?></p> 103 <p> 104 <button class="button button-secondary" name="link410_action" value="trash"><?php esc_html_e('Find & Trash Matching Posts', '410-link-unlinker'); ?></button> 105 </p> 100 106 </form> 101 107 </div> … … 159 165 echo '<div class="notice notice-info"><p><strong>Examples:</strong></p><ol>'; 160 166 foreach ($result['examples'] as $ex) { echo '<li>'.esc_html($ex).'</li>'; } 167 echo '</ol></div>'; 168 } 169 }); 170 } 171 172 if ($action === 'trash') { 173 $dry = !empty($_POST['link410_dry']); 174 $urls = $this->get_saved_urls(); 175 if (!$urls) { 176 add_action('admin_notices', function(){ echo '<div class="notice notice-error"><p>'.esc_html__('No URLs saved. Please add your 410 list and try again.', '410-link-unlinker').'</p></div>'; }); 177 return; 178 } 179 180 $result = $this->trash_matching_posts($urls, $dry); 181 add_action('admin_notices', function() use ($result, $dry){ 182 $mode = $dry ? __('Dry-run', '410-link-unlinker') : __('Trashed', '410-link-unlinker'); 183 $msg = sprintf('%s: %d posts found matching your 410 URLs.', $mode, $result['count']); 184 echo '<div class="notice notice-success"><p>'.esc_html($msg).'</p></div>'; 185 if (!empty($result['posts'])) { 186 echo '<div class="notice notice-info"><p><strong>'.($dry ? 'Posts that would be trashed:' : 'Trashed posts:').'</strong></p><ol>'; 187 foreach ($result['posts'] as $p) { echo '<li>'.esc_html($p).'</li>'; } 161 188 echo '</ol></div>'; 162 189 } … … 291 318 } 292 319 320 private function trash_matching_posts(array $urls, bool $dry) { 321 global $wpdb; 322 323 // Extract slugs from URLs 324 $slugs = []; 325 foreach ($urls as $u) { 326 // Parse the URL to get the path 327 if (strpos($u, '://') !== false) { 328 $p = wp_parse_url($u); 329 $path = isset($p['path']) ? $p['path'] : ''; 330 } else { 331 $parts = explode('/', $u, 2); 332 $path = isset($parts[1]) ? '/'.$parts[1] : ''; 333 } 334 // Get the last segment of the path as the slug 335 $path = rtrim($path, '/'); 336 if ($path) { 337 $segments = explode('/', $path); 338 $slug = end($segments); 339 if ($slug && $slug !== '') { 340 $slugs[] = $slug; 341 } 342 } 343 } 344 345 if (empty($slugs)) { 346 return ['count' => 0, 'posts' => []]; 347 } 348 349 $slugs = array_unique($slugs); 350 351 // Find posts matching these slugs 352 $placeholders = implode(',', array_fill(0, count($slugs), '%s')); 353 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Bulk operation 354 $posts = $wpdb->get_results( 355 $wpdb->prepare( 356 "SELECT ID, post_title, post_name, post_type FROM {$wpdb->posts} WHERE post_name IN ($placeholders) AND post_status = 'publish'", 357 ...$slugs 358 ), 359 ARRAY_A 360 ); 361 362 $result = ['count' => 0, 'posts' => []]; 363 364 foreach ((array)$posts as $post) { 365 $result['count']++; 366 $result['posts'][] = $post['post_title'] . ' (/' . $post['post_name'] . '/) [' . $post['post_type'] . ']'; 367 368 if (!$dry) { 369 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Bulk operation 370 $wpdb->update( 371 $wpdb->posts, 372 ['post_status' => 'trash'], 373 ['ID' => (int)$post['ID']] 374 ); 375 clean_post_cache((int)$post['ID']); 376 } 377 } 378 379 return $result; 380 } 381 293 382 private function build_patterns(array $urls) { 294 383 $esc = static function($s){ return preg_quote($s, '/'); }; -
410-link-unlinker/trunk/readme.txt
r3422890 r3423068 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1.0. 67 Stable tag: 1.0.7 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 24 24 25 25 == Changelog == 26 = 1.0.7 = 27 * Add "Trash Matching Posts" feature to bulk-trash posts whose URLs match your 410 list (removes them from author pages, archives, etc.). 28 26 29 = 1.0.6 = 27 30 * Fix URL parsing to accept URLs without https:// prefix.
Note: See TracChangeset
for help on using the changeset viewer.