Changeset 3345787
- Timestamp:
- 08/17/2025 06:22:32 AM (6 months ago)
- Location:
- media-tracker
- Files:
-
- 45 added
- 4 edited
-
tags/1.0.9 (added)
-
tags/1.0.9/assets (added)
-
tags/1.0.9/assets/dist (added)
-
tags/1.0.9/assets/dist/css (added)
-
tags/1.0.9/assets/dist/css/mt-admin.css (added)
-
tags/1.0.9/assets/dist/css/mt-admin.css.map (added)
-
tags/1.0.9/assets/dist/js (added)
-
tags/1.0.9/assets/dist/js/mt-admin.js (added)
-
tags/1.0.9/assets/src (added)
-
tags/1.0.9/assets/src/js (added)
-
tags/1.0.9/assets/src/js/mt-admin.js (added)
-
tags/1.0.9/assets/src/scss (added)
-
tags/1.0.9/assets/src/scss/mt-admin.scss (added)
-
tags/1.0.9/composer.json (added)
-
tags/1.0.9/gulp.config.js (added)
-
tags/1.0.9/gulpfile.babel.js (added)
-
tags/1.0.9/includes (added)
-
tags/1.0.9/includes/Admin (added)
-
tags/1.0.9/includes/Admin.php (added)
-
tags/1.0.9/includes/Admin/Duplicate_Images.php (added)
-
tags/1.0.9/includes/Admin/Media_Usage.php (added)
-
tags/1.0.9/includes/Admin/Menu.php (added)
-
tags/1.0.9/includes/Admin/Unused_Media_List.php (added)
-
tags/1.0.9/includes/Admin/views (added)
-
tags/1.0.9/includes/Admin/views/unused-media-list.php (added)
-
tags/1.0.9/includes/Installer.php (added)
-
tags/1.0.9/includes/Media_Tracker_i18n.php (added)
-
tags/1.0.9/languages (added)
-
tags/1.0.9/languages/media-tracker.pot (added)
-
tags/1.0.9/media-tracker.php (added)
-
tags/1.0.9/package.json (added)
-
tags/1.0.9/readme.txt (added)
-
tags/1.0.9/vendor (added)
-
tags/1.0.9/vendor/autoload.php (added)
-
tags/1.0.9/vendor/composer (added)
-
tags/1.0.9/vendor/composer/ClassLoader.php (added)
-
tags/1.0.9/vendor/composer/InstalledVersions.php (added)
-
tags/1.0.9/vendor/composer/LICENSE (added)
-
tags/1.0.9/vendor/composer/autoload_classmap.php (added)
-
tags/1.0.9/vendor/composer/autoload_namespaces.php (added)
-
tags/1.0.9/vendor/composer/autoload_psr4.php (added)
-
tags/1.0.9/vendor/composer/autoload_real.php (added)
-
tags/1.0.9/vendor/composer/autoload_static.php (added)
-
tags/1.0.9/vendor/composer/installed.json (added)
-
tags/1.0.9/vendor/composer/installed.php (added)
-
trunk/includes/Admin/Unused_Media_List.php (modified) (8 diffs)
-
trunk/includes/Admin/views/unused-media-list.php (modified) (1 diff)
-
trunk/media-tracker.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
media-tracker/trunk/includes/Admin/Unused_Media_List.php
r3244839 r3345787 84 84 85 85 /* translators: %s: post title */ 86 $aria_label = sprintf( __( ' “%s”(Edit)', 'media-tracker' ), $item->post_title );86 $aria_label = sprintf( __( '"%s" (Edit)', 'media-tracker' ), $item->post_title ); 87 87 88 88 $output = '<strong class="has-media-icon"> … … 150 150 } 151 151 152 /** 153 * Get all used media IDs with optimized queries to prevent timeouts 154 * 155 * @return array Array of used media IDs 156 */ 157 private function get_used_media_ids() { 158 global $wpdb; 159 160 $used_image_ids = []; 161 162 // Increase memory limit and execution time 163 if ( function_exists( 'ini_set' ) ) { 164 ini_set( 'memory_limit', '512M' ); 165 set_time_limit( 300 ); 166 } 167 168 // 1. Get featured images (post thumbnails) - Most common usage 169 $featured_images = $wpdb->get_col(" 170 SELECT DISTINCT meta_value 171 FROM {$wpdb->postmeta} 172 WHERE meta_key = '_thumbnail_id' 173 AND meta_value != '0' 174 AND meta_value != '' 175 "); 176 if ( $featured_images ) { 177 $used_image_ids = array_merge( $used_image_ids, array_map( 'intval', $featured_images ) ); 178 } 179 180 // 2. Get WooCommerce product gallery images 181 if ( class_exists( 'WooCommerce' ) ) { 182 $gallery_images = $wpdb->get_col(" 183 SELECT DISTINCT meta_value 184 FROM {$wpdb->postmeta} 185 WHERE meta_key = '_product_image_gallery' 186 AND meta_value != '' 187 AND meta_value IS NOT NULL 188 "); 189 190 foreach ( $gallery_images as $gallery_string ) { 191 if ( ! empty( $gallery_string ) ) { 192 $gallery_ids = explode( ',', $gallery_string ); 193 $used_image_ids = array_merge( $used_image_ids, array_map( 'intval', array_filter( $gallery_ids ) ) ); 194 } 195 } 196 197 // Get WooCommerce variation images 198 $variation_images = $wpdb->get_col(" 199 SELECT DISTINCT meta_value 200 FROM {$wpdb->postmeta} 201 WHERE meta_key = '_thumbnail_id' 202 AND post_id IN ( 203 SELECT ID FROM {$wpdb->posts} 204 WHERE post_type = 'product_variation' 205 ) 206 AND meta_value != '0' 207 AND meta_value != '' 208 "); 209 if ( $variation_images ) { 210 $used_image_ids = array_merge( $used_image_ids, array_map( 'intval', $variation_images ) ); 211 } 212 } 213 214 // 3. Get images used in post content (process in batches to avoid memory issues) 215 $batch_size = 100; 216 $offset = 0; 217 218 while ( true ) { 219 $posts_with_content = $wpdb->get_results( $wpdb->prepare(" 220 SELECT ID, post_content 221 FROM {$wpdb->posts} 222 WHERE post_content LIKE %s 223 AND post_status = 'publish' 224 LIMIT %d OFFSET %d 225 ", '%wp-image-%', $batch_size, $offset ) ); 226 227 if ( empty( $posts_with_content ) ) { 228 break; 229 } 230 231 foreach ( $posts_with_content as $post ) { 232 preg_match_all( '/wp-image-(\d+)/', $post->post_content, $matches ); 233 if ( ! empty( $matches[1] ) ) { 234 $used_image_ids = array_merge( $used_image_ids, array_map( 'intval', $matches[1] ) ); 235 } 236 } 237 238 $offset += $batch_size; 239 } 240 241 // 4. Get Elementor images (process in smaller batches) 242 $elementor_posts = $wpdb->get_col(" 243 SELECT post_id 244 FROM {$wpdb->postmeta} 245 WHERE meta_key = '_elementor_data' 246 LIMIT 50 247 "); 248 249 foreach ( $elementor_posts as $post_id ) { 250 $elementor_data = get_post_meta( $post_id, '_elementor_data', true ); 251 252 if ( $elementor_data ) { 253 $elementor_data = json_decode( $elementor_data, true ); 254 255 if ( is_array( $elementor_data ) ) { 256 array_walk_recursive( $elementor_data, function( $item, $key ) use ( &$used_image_ids ) { 257 if ( $key === 'id' && is_numeric( $item ) ) { 258 $used_image_ids[] = intval( $item ); 259 } 260 } ); 261 } 262 } 263 } 264 265 // 5. Get Site Icon ID 266 $site_icon_id = get_option( 'site_icon' ); 267 if ( $site_icon_id ) { 268 $used_image_ids[] = intval( $site_icon_id ); 269 } 270 271 // 6. Get custom logo 272 $custom_logo_id = get_theme_mod( 'custom_logo' ); 273 if ( $custom_logo_id ) { 274 $used_image_ids[] = intval( $custom_logo_id ); 275 } 276 277 // 7. Get header image 278 $header_image_id = get_theme_mod( 'header_image_data' ); 279 if ( is_array( $header_image_id ) && isset( $header_image_id['attachment_id'] ) ) { 280 $used_image_ids[] = intval( $header_image_id['attachment_id'] ); 281 } 282 283 // 8. Get background image 284 $background_image_id = get_theme_mod( 'background_image_thumb' ); 285 if ( $background_image_id ) { 286 $used_image_ids[] = intval( $background_image_id ); 287 } 288 289 // Remove duplicates and invalid IDs 290 $used_image_ids = array_unique( array_filter( array_map( 'intval', $used_image_ids ) ) ); 291 292 return $used_image_ids; 293 } 294 152 295 public function prepare_items() { 153 296 global $wpdb; … … 164 307 $offset = ( $current_page - 1 ) * $per_page; 165 308 166 // Check if the results are cached. 167 $cache_key = 'unused_media_list_' . md5( serialize( array( $search, $this->author_id, $current_page, $per_page ) ) ); 309 // Check for force refresh parameter 310 $force_refresh = isset( $_GET['refresh_cache'] ) && $_GET['refresh_cache'] === '1'; 311 312 // Check if the results are cached 313 $cache_key = 'unused_media_list_v2_' . md5( serialize( array( $search, $this->author_id, $current_page, $per_page ) ) ); 314 315 // Clear cache if forced or if there's recent activity 316 if ( $force_refresh || $this->should_invalidate_cache() ) { 317 delete_transient( $cache_key ); 318 } 319 168 320 $cached_results = get_transient( $cache_key ); 169 321 170 if ( false === $cached_results ) {171 // Get all post IDs that use Elementor.172 $ elementor_posts = $wpdb->get_col("173 SELECT post_id 174 FROM {$wpdb->postmeta}175 WHERE meta_key = '_elementor_data'176 ");177 178 // Collect image IDs used by Elementor.179 $used_image_ids = []; 180 181 foreach ( $elementor_posts as $post_id) {182 $ elementor_data = get_post_meta( $post_id, '_elementor_data', true);183 184 if ( $elementor_data ) {185 $elementor_data = json_decode( $elementor_data, true ); 186 187 if ( is_array( $elementor_data )) {188 array_walk_recursive( $elementor_data, function( $item, $key ) use ( &$used_image_ids ) {189 if ( $key === 'id' && is_numeric( $item ) ) {190 $used_image_ids[] = $item; 191 }192 } );193 }194 }195 } 196 197 // Get Site Icon ID 198 $site_icon_id = get_option( 'site_icon' );199 if ($site_icon_id) {200 $used_image_ids[] = $site_icon_id;201 }202 203 // Ensure unique IDs.204 $ used_image_ids = array_unique( $used_image_ids);205 206 // Build the base query to retrieve unused attachments.322 if ( false === $cached_results || $force_refresh ) { 323 // Get used media IDs with optimized method 324 $used_image_ids = $this->get_used_media_ids(); 325 326 // Build optimized query for unused attachments 327 $where_conditions = [ 328 "p.post_type = 'attachment'", 329 "p.post_status = 'inherit'" 330 ]; 331 332 // Exclude used image IDs with better performance 333 if ( ! empty( $used_image_ids ) ) { 334 $used_image_ids_placeholder = implode( ',', array_map( 'intval', $used_image_ids ) ); 335 $where_conditions[] = "p.ID NOT IN ($used_image_ids_placeholder)"; 336 } 337 338 // Filter by author ID if provided 339 if ( $this->author_id ) { 340 $where_conditions[] = $wpdb->prepare( 'p.post_author = %d', $this->author_id ); 341 } 342 343 // If there is a search query, add the search condition 344 if ( $search ) { 345 $where_conditions[] = $wpdb->prepare( 'p.post_title LIKE %s', '%' . $wpdb->esc_like( $search ) . '%' ); 346 } 347 348 $where_clause = 'WHERE ' . implode( ' AND ', $where_conditions ); 349 350 // Count query first 351 $count_query = " 352 SELECT COUNT(*) 353 FROM {$wpdb->posts} p 354 $where_clause 355 "; 356 $total_items = $wpdb->get_var( $count_query ); 357 358 // Main query with pagination 207 359 $query = " 208 360 SELECT p.ID, p.post_title, p.guid, p.post_author, p.post_date 209 361 FROM {$wpdb->posts} p 210 LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.meta_value AND pm.meta_key = '_thumbnail_id' 211 LEFT JOIN {$wpdb->posts} pp ON pp.post_content LIKE CONCAT('%wp-image-', p.ID, '%') 212 LEFT JOIN {$wpdb->postmeta} site_icon ON p.ID = site_icon.meta_value 213 AND site_icon.meta_key = 'site_icon' 214 WHERE p.post_type = 'attachment' 215 AND pm.meta_value IS NULL 216 AND pp.ID IS NULL 217 AND site_icon.meta_value IS NULL 362 $where_clause 363 ORDER BY p.post_date DESC 364 LIMIT $offset, $per_page 218 365 "; 219 366 220 // Exclude used image IDs. 221 if ( ! empty( $used_image_ids ) ) { 222 $used_image_ids_placeholder = implode( ',', array_fill( 0, count( $used_image_ids ), '%d' ) ); 223 $query .= $wpdb->prepare( 224 " AND p.ID NOT IN ($used_image_ids_placeholder)", 225 $used_image_ids 226 ); 227 } 228 229 // Filter by author ID if provided. 230 if ( $this->author_id ) { 231 $query .= $wpdb->prepare( ' AND p.post_author = %d', $this->author_id ); 232 } 233 234 // If there is a search query, add the search condition. 235 if ( $search ) { 236 $query .= $wpdb->prepare( ' AND p.post_title LIKE %s', '%' . $wpdb->esc_like( $search ) . '%' ); 237 } 238 239 // Order by post_date in descending order. 240 $query .= ' ORDER BY p.post_date DESC'; 241 242 // Calculate total items count. 243 $total_items_query = $wpdb->prepare( "SELECT COUNT(*) FROM ($query) as total_query" ); 244 $total_items = $wpdb->get_var( $total_items_query ); 245 246 // Add pagination to the main query. 247 $query .= " LIMIT $offset, $per_page"; 248 249 // Retrieve items based on the constructed query. 367 // Retrieve items based on the constructed query 250 368 $this->items = $wpdb->get_results( $query ); 251 369 252 // Cache the results for 1 hour. 253 set_transient( $cache_key, array( 'items' => $this->items, 'total_items' => $total_items ), HOUR_IN_SECONDS ); 370 // Cache the results for shorter time (10 seconds) with smart invalidation 371 set_transient( $cache_key, array( 'items' => $this->items, 'total_items' => $total_items ), 10 ); // 10 seconds 372 373 // Update last cache time 374 update_option( 'unused_media_last_cache_time', time() ); 254 375 } else { 255 // Use cached results .376 // Use cached results 256 377 $this->items = $cached_results['items']; 257 378 $total_items = $cached_results['total_items']; 258 379 } 259 380 260 // Define column headers, hidden columns, and sortable columns .381 // Define column headers, hidden columns, and sortable columns 261 382 $columns = $this->get_columns(); 262 383 $hidden = []; … … 264 385 $this->_column_headers = [ $columns, $hidden, $sortable ]; 265 386 266 // Set pagination arguments for display .387 // Set pagination arguments for display 267 388 $this->set_pagination_args( array( 268 389 'total_items' => $total_items, … … 273 394 274 395 /** 396 * Check if cache should be invalidated based on recent media activity 397 * 398 * @return bool True if cache should be cleared 399 */ 400 private function should_invalidate_cache() { 401 global $wpdb; 402 403 // Get the last time cache was created 404 $last_cache_time = get_option( 'unused_media_last_cache_time', 0 ); 405 406 // Check if any media was added/modified since last cache 407 $recent_media_activity = $wpdb->get_var( $wpdb->prepare(" 408 SELECT COUNT(*) 409 FROM {$wpdb->posts} 410 WHERE post_type = 'attachment' 411 AND post_modified_gmt > %s 412 ", date( 'Y-m-d H:i:s', $last_cache_time ) ) ); 413 414 // Check if any posts were modified (which might affect image usage) 415 $recent_post_activity = $wpdb->get_var( $wpdb->prepare(" 416 SELECT COUNT(*) 417 FROM {$wpdb->posts} 418 WHERE post_type IN ('post', 'page', 'product') 419 AND post_modified_gmt > %s 420 ", date( 'Y-m-d H:i:s', $last_cache_time ) ) ); 421 422 return ( $recent_media_activity > 0 || $recent_post_activity > 0 ); 423 } 424 425 /** 426 * Force clear all cache manually 427 */ 428 public function force_clear_cache() { 429 $this->clear_cache(); 430 delete_option( 'unused_media_last_cache_time' ); 431 } 432 433 /** 275 434 * Get total number of items, optionally filtered by search term. 435 * This method now supports real-time counting for display purposes. 276 436 * 277 437 * @param string $search Optional. Search term to filter items. 438 * @param bool $force_fresh Optional. Force fresh calculation without cache. 278 439 * @return int Total number of items. 279 440 */ 280 public function get_total_items( $search = '' ) {441 public function get_total_items( $search = '', $force_fresh = false ) { 281 442 global $wpdb; 282 443 283 // Get all post IDs that use Elementor. 284 $elementor_posts = $wpdb->get_col(" 285 SELECT post_id 286 FROM {$wpdb->postmeta} 287 WHERE meta_key = '_elementor_data' 288 "); 289 290 // Collect image IDs used by Elementor. 291 $used_image_ids = []; 292 293 foreach ( $elementor_posts as $post_id ) { 294 $elementor_data = get_post_meta( $post_id, '_elementor_data', true ); 295 296 if ( $elementor_data ) { 297 $elementor_data = json_decode( $elementor_data, true ); 298 299 if ( is_array( $elementor_data ) ) { 300 array_walk_recursive( $elementor_data, function( $item, $key ) use ( &$used_image_ids ) { 301 if ( $key === 'id' && is_numeric( $item ) ) { 302 $used_image_ids[] = $item; 303 } 304 } ); 305 } 306 } 307 } 308 309 // Get Site Icon ID 310 $site_icon_id = get_option( 'site_icon' ); 311 if ($site_icon_id) { 312 $used_image_ids[] = $site_icon_id; 313 } 314 315 // Ensure unique IDs. 316 $used_image_ids = array_unique( $used_image_ids ); 317 318 // Build the base query to count unused attachments. 444 // Check for force refresh parameter or force_fresh flag 445 $force_refresh = ( isset( $_GET['refresh_cache'] ) && $_GET['refresh_cache'] === '1' ) || $force_fresh; 446 447 $cache_key = 'unused_media_total_' . md5( serialize( array( $search, $this->author_id ) ) ); 448 449 // Clear cache if forced or if there's recent activity 450 if ( $force_refresh || $this->should_invalidate_cache() ) { 451 delete_transient( $cache_key ); 452 } 453 454 $cached_total = get_transient( $cache_key ); 455 456 if ( false !== $cached_total && ! $force_refresh ) { 457 return $cached_total; 458 } 459 460 // Get used media IDs 461 $used_image_ids = $this->get_used_media_ids(); 462 463 // Build the optimized count query 464 $where_conditions = [ 465 "p.post_type = 'attachment'", 466 "p.post_status = 'inherit'" 467 ]; 468 469 // Exclude used image IDs 470 if ( ! empty( $used_image_ids ) ) { 471 $used_image_ids_placeholder = implode( ',', array_map( 'intval', $used_image_ids ) ); 472 $where_conditions[] = "p.ID NOT IN ($used_image_ids_placeholder)"; 473 } 474 475 // Filter by author ID if provided 476 if ( $this->author_id ) { 477 $where_conditions[] = $wpdb->prepare( 'p.post_author = %d', $this->author_id ); 478 } 479 480 // If there is a search query, add the search condition 481 if ( $search ) { 482 $where_conditions[] = $wpdb->prepare( 'p.post_title LIKE %s', '%' . $wpdb->esc_like( $search ) . '%' ); 483 } 484 485 $where_clause = 'WHERE ' . implode( ' AND ', $where_conditions ); 486 319 487 $query = " 320 488 SELECT COUNT(*) 321 489 FROM {$wpdb->posts} p 322 LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.meta_value AND pm.meta_key = '_thumbnail_id' 323 LEFT JOIN {$wpdb->posts} pp ON pp.post_content LIKE CONCAT('%wp-image-', p.ID, '%') 324 LEFT JOIN {$wpdb->postmeta} site_icon ON p.ID = site_icon.meta_value 325 AND site_icon.meta_key = 'site_icon' 326 WHERE p.post_type = 'attachment' 327 AND pm.meta_value IS NULL 328 AND pp.ID IS NULL 329 AND site_icon.meta_value IS NULL 490 $where_clause 330 491 "; 331 492 332 // Exclude used image IDs.333 if ( ! empty( $used_image_ids ) ) {334 $used_image_ids_placeholder = implode( ',', array_fill( 0, count( $used_image_ids ), '%d' ) );335 $query .= $wpdb->prepare(336 " AND p.ID NOT IN ($used_image_ids_placeholder)",337 $used_image_ids338 );339 }340 341 // Filter by author ID if provided.342 if ( $this->author_id ) {343 $query .= $wpdb->prepare( ' AND p.post_author = %d', $this->author_id );344 }345 346 // If there is a search query, add the search condition.347 if ( $search ) {348 $query .= $wpdb->prepare( ' AND p.post_title LIKE %s', '%' . $wpdb->esc_like( $search ) . '%' );349 }350 351 // Get total items count.352 493 $total_items = $wpdb->get_var( $query ); 353 494 495 // Cache for shorter time (10 seconds as you wanted) but with smart invalidation 496 set_transient( $cache_key, $total_items, 10 ); // 10 seconds 497 498 // Update last cache time 499 update_option( 'unused_media_last_cache_time', time() ); 500 354 501 return $total_items; 502 } 503 504 /** 505 * Get fresh total count without any caching - for display purposes 506 * 507 * @param string $search Optional. Search term to filter items. 508 * @return int Total number of items. 509 */ 510 public function get_fresh_total_items( $search = '' ) { 511 return $this->get_total_items( $search, true ); 355 512 } 356 513 … … 396 553 } 397 554 555 // Clear cache after deletion 556 $this->clear_cache(); 557 398 558 // Set a transient to display a success message 399 559 $deleted_count = count( $media_ids ); … … 407 567 408 568 /** 569 * Clear all plugin-related cache 570 */ 571 private function clear_cache() { 572 global $wpdb; 573 574 // Delete all transients related to this plugin 575 $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_unused_media_%'" ); 576 $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_unused_media_%'" ); 577 578 // Also clear the last cache time option 579 delete_option( 'unused_media_last_cache_time' ); 580 } 581 582 /** 409 583 * Display the success message if a media file was deleted. 410 584 */ … … 412 586 if ( $message = get_transient( 'unused_media_delete_message' ) ) { 413 587 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html( $message ) . '</p></div>'; 414 delete_transient( 'unused_media_delete_message' ); // Remove the message after it's displayed588 delete_transient( 'unused_media_delete_message' ); 415 589 } 416 590 } -
media-tracker/trunk/includes/Admin/views/unused-media-list.php
r3148751 r3345787 14 14 <div class="media-toolbar-wrap wp-filter"> 15 15 <div class="unused-image-found"> 16 <h2><?php echo '<span>'.esc_html( $unused_media_list->get_ total_items() ).'</span>' . ' ' . esc_html__( 'unused media found!', 'media-tracker' ); ?></h2>16 <h2><?php echo '<span>'.esc_html( $unused_media_list->get_fresh_total_items() ).'</span>' . ' ' . esc_html__( 'unused media found!', 'media-tracker' ); ?></h2> 17 17 </div> 18 18 -
media-tracker/trunk/media-tracker.php
r3244839 r3345787 5 5 * Author: TheBitCraft 6 6 * Author URI: https://thebitcraft.com/ 7 * Version: 1.0. 87 * Version: 1.0.9 8 8 * Requires PHP: 7.4 9 9 * Requires at least: 5.9 10 * Tested up to: 6. 6.110 * Tested up to: 6.8.1 11 11 * Text Domain: media-tracker 12 12 * License: GPL v2 or later … … 28 28 * @var string 29 29 */ 30 const version = '1.0. 8';30 const version = '1.0.9'; 31 31 32 32 /** -
media-tracker/trunk/readme.txt
r3244839 r3345787 2 2 3 3 Contributors: thebitcraft, rejuancse 4 Tags: tracker, unused, media cleaner, duplicate 4 Tags: tracker, unused, media cleaner, duplicate, optimizer 5 5 Requires at least: 5.9 6 Tested up to: 6. 6.16 Tested up to: 6.8.1 7 7 Requires PHP: 7.4 8 Stable tag: 1.0. 88 Stable tag: 1.0.9 9 9 License: GPLv2 or later 10 10 License URI: http://www.gnu.org/licenses/gpl-2.0.html … … 20 20 **🔥 Media Usage: Track and analyze media usage across your WordPress site. This feature allows you to see where each media file is being used, helping you manage and optimize your media library more effectively. 21 21 22 **🔥 Cleaner: Find media files that are not in use on any posts, pages, or other content. This feature helps you keep your site clutter-free by highlighting files that can be safely removed.22 **🔥 Unused Media List: Find media files that are not in use on any posts, pages, or other content. This feature helps you keep your site clutter-free by highlighting files that can be safely removed. 23 23 24 24 **🔥 Duplicate Images: Detect and manage duplicate images within your media library. Consolidate or remove redundant files to save storage space and maintain an organized media library. 25 26 27 Media Tracker is compatible with WordPress Classic Editor, Gutenberg, and Elementor. 25 28 26 29 == Installation == … … 59 62 == Changelog == 60 63 64 = 1.0.9 [17/08/2025] = 65 * 502 network error issue fixed 66 * Images Markled as Unused When Used issue fixed 67 61 68 = 1.0.8 [22/02/2025] = 62 69 * Broken link featured removed
Note: See TracChangeset
for help on using the changeset viewer.