false, 'message' => '', 'count' => 0, 'details' => array(), ); try { $posts = get_posts( array( 'post_type' => array( 'post', 'page' ), 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids, post_content', 'no_found_rows' => true, ) ); $fixed_count = 0; foreach ( $posts as $post ) { $content = $post->post_content; $updated = false; // Convert HTTP → HTTPS. if ( is_ssl() ) { $new_content = str_replace( 'http://', 'https://', $content ); if ( $new_content !== $content ) { $content = $new_content; $updated = true; } } // Detect and restore broken images. $site_url = get_site_url(); $content = preg_replace_callback( '/]+src=["\']([^"\']+)["\'][^>]*>/i', function ( $matches ) use ( $site_url, &$updated ) { $src = $matches[1]; if ( strpos( $src, $site_url ) !== false ) { $file_path = str_replace( $site_url, ABSPATH, $src ); if ( ! file_exists( $file_path ) ) { $updated = true; $filename = basename( $src ); $upload = wp_upload_dir(); $foundfile = self::find_file_recursive( $upload['basedir'], $filename ); if ( $foundfile ) { return str_replace( $src, $upload['baseurl'] . '/' . $foundfile, $matches[0] ); } } } return $matches[0]; }, $content ); if ( $updated ) { $wpdb->update( $wpdb->posts, array( 'post_content' => $content ), array( 'ID' => $post->ID ) ); ++$fixed_count; $results['details'][] = get_the_title( $post->ID ); } } $results['success'] = true; $results['count'] = $fixed_count; $results['message'] = sprintf( /* translators: %d: Number of posts fixed. */ esc_html__( 'Fixed %d posts with broken images', 'aims-quick-cleanup-tools' ), $fixed_count ); } catch ( Exception $e ) { $results['message'] = $e->getMessage(); } return $results; } /** * Remove orphaned post meta. * * @return array */ public static function clean_orphaned_postmeta() { global $wpdb; $results = array( 'success' => false, 'message' => '', 'count' => 0, ); try { $count = $wpdb->query( "DELETE pm FROM {$wpdb->postmeta} pm LEFT JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE p.ID IS NULL" ); $results['success'] = true; $results['count'] = $count; $results['message'] = sprintf( /* translators: %d: Number of orphan postmeta deleted. */ esc_html__( 'Removed %d orphaned post meta entries', 'aims-quick-cleanup-tools' ), $count ); } catch ( Exception $e ) { $results['message'] = $e->getMessage(); } return $results; } /** * Fix duplicate slugs for posts and pages. * * @return array */ public static function fix_duplicate_slugs() { global $wpdb; $results = array( 'success' => false, 'message' => '', 'count' => 0, 'details' => array(), ); try { $duplicates = $wpdb->get_results( " SELECT post_name, COUNT(*) AS count FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type IN ('post', 'page') GROUP BY post_name HAVING count > 1 " ); $fixed_count = 0; foreach ( $duplicates as $dup ) { $posts = $wpdb->get_results( $wpdb->prepare( " SELECT ID, post_title, post_name FROM {$wpdb->posts} WHERE post_name = %s AND post_status = 'publish' ORDER BY ID ASC ", $dup->post_name ) ); array_shift( $posts ); // Keep first slug. foreach ( $posts as $post ) { $new_slug = $post->post_name . '-' . $post->ID; $wpdb->update( $wpdb->posts, array( 'post_name' => $new_slug ), array( 'ID' => $post->ID ) ); ++$fixed_count; $results['details'][] = sprintf( '%s: %s → %s', $post->post_title, $post->post_name, $new_slug ); } } $results['success'] = true; $results['count'] = $fixed_count; $results['message'] = sprintf( /* translators: %d: Number of duplicate slugs fixed. */ esc_html__( 'Fixed %d duplicate slugs', 'aims-quick-cleanup-tools' ), $fixed_count ); } catch ( Exception $e ) { $results['message'] = $e->getMessage(); } return $results; } /** * Delete orphaned comments. */ public static function clean_orphaned_comments() { global $wpdb; $results = array( 'success' => false, 'message' => '', 'count' => 0, ); try { $count = $wpdb->query( " DELETE c FROM {$wpdb->comments} c LEFT JOIN {$wpdb->posts} p ON p.ID = c.comment_post_ID WHERE p.ID IS NULL " ); $results['success'] = true; $results['count'] = $count; $results['message'] = sprintf( /* translators: %d: Number of orphan comments deleted. */ esc_html__( 'Removed %d orphaned comments', 'aims-quick-cleanup-tools' ), $count ); } catch ( Exception $e ) { $results['message'] = $e->getMessage(); } return $results; } /** * Clean expired WordPress transients. */ public static function clean_transients() { global $wpdb; $results = array( 'success' => false, 'message' => '', 'count' => 0, ); try { $time = time(); $count = $wpdb->query( $wpdb->prepare( " DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b WHERE a.option_name LIKE %s AND a.option_name NOT LIKE %s AND b.option_name = CONCAT('_transient_timeout_', SUBSTRING(a.option_name, 12)) AND b.option_value < %d ", $wpdb->esc_like( '_transient_' ) . '%', $wpdb->esc_like( '_transient_timeout_' ) . '%', $time ) ); // Multisite support. if ( is_multisite() ) { $count += $wpdb->query( $wpdb->prepare( " DELETE a, b FROM {$wpdb->sitemeta} a, {$wpdb->sitemeta} b WHERE a.meta_key LIKE %s AND a.meta_key NOT LIKE %s AND b.meta_key = CONCAT('_site_transient_timeout_', SUBSTRING(a.meta_key, 17)) AND b.meta_value < %d ", $wpdb->esc_like( '_site_transient_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_' ) . '%', $time ) ); } $results['success'] = true; $results['count'] = $count; $results['message'] = sprintf( /* translators: %d: Number of expired transients deleted. */ esc_html__( 'Cleaned %d expired transients', 'aims-quick-cleanup-tools' ), $count ); } catch ( Exception $e ) { $results['message'] = $e->getMessage(); } return $results; } /** * Delete old post revisions. * * @param int $keep Number of latest revisions to keep. */ public static function clean_revisions( $keep = 5 ) { global $wpdb; $results = array( 'success' => false, 'message' => '', 'count' => 0, ); try { $posts = $wpdb->get_col( " SELECT DISTINCT post_parent FROM {$wpdb->posts} WHERE post_type = 'revision' " ); $deleted = 0; foreach ( $posts as $post_id ) { $revisions = $wpdb->get_results( $wpdb->prepare( " SELECT ID FROM {$wpdb->posts} WHERE post_parent = %d AND post_type = 'revision' ORDER BY post_date DESC ", $post_id ) ); $to_delete = array_slice( $revisions, $keep ); foreach ( $to_delete as $rev ) { wp_delete_post_revision( $rev->ID ); ++$deleted; } } $results['success'] = true; $results['count'] = $deleted; $results['message'] = sprintf( /* translators: 1: Number of old revisions deleted. 2: Number of revisions kept per post. */ esc_html__( 'Deleted %1$d old revisions (kept %2$d per post)', 'aims-quick-cleanup-tools' ), $deleted, $keep ); } catch ( Exception $e ) { $results['message'] = $e->getMessage(); } return $results; } /** * Delete unused (count = 0) tags. */ public static function clean_unused_tags() { global $wpdb; $results = array( 'success' => false, 'message' => '', 'count' => 0, ); try { $unused = $wpdb->get_col( " SELECT t.term_id FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id WHERE tt.taxonomy = 'post_tag' AND tt.count = 0 " ); $count = 0; foreach ( $unused as $term_id ) { if ( wp_delete_term( $term_id, 'post_tag' ) ) { ++$count; } } $results['success'] = true; $results['count'] = $count; $results['message'] = sprintf( /* translators: %d: Number of unused tags deleted. */ esc_html__( 'Deleted %d unused tags', 'aims-quick-cleanup-tools' ), $count ); } catch ( Exception $e ) { $results['message'] = $e->getMessage(); } return $results; } /** * Remove auto-draft posts older than 7 days. */ public static function clean_auto_drafts() { global $wpdb; $results = array( 'success' => false, 'message' => '', 'count' => 0, ); try { $count = $wpdb->query( " DELETE FROM {$wpdb->posts} WHERE post_status = 'auto-draft' AND DATE_SUB(NOW(), INTERVAL 7 DAY) > post_date " ); $results['success'] = true; $results['count'] = $count; $results['message'] = sprintf( /* translators: %d: Number of auto-draft posts deleted. */ esc_html__( 'Deleted %d auto-draft posts', 'aims-quick-cleanup-tools' ), $count ); } catch ( Exception $e ) { $results['message'] = $e->getMessage(); } return $results; } /** * Optimize all WordPress database tables. */ public static function optimize_database() { global $wpdb; $results = array( 'success' => false, 'message' => '', 'count' => 0, 'details' => array(), ); try { $tables = $wpdb->get_col( 'SHOW TABLES' ); $optimized = 0; foreach ( $tables as $table ) { if ( $wpdb->query( $wpdb->prepare( 'OPTIMIZE TABLE `%s`', $table ) ) ) { ++$optimized; $results['details'][] = $table; } } $results['success'] = true; $results['count'] = $optimized; $results['message'] = sprintf( /* translators: %d: Number of database tables optimized. */ esc_html__( 'Optimized %d database tables', 'aims-quick-cleanup-tools' ), $optimized ); } catch ( Exception $e ) { $results['message'] = $e->getMessage(); } return $results; } /** * Regenerate the .htaccess file with default WordPress rewrite rules. * * @return array { * @type bool $success Operation status. * @type string $message Result message. * } */ public static function regenerate_htaccess() { global $wp_filesystem; require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); $home_path = get_home_path(); $htaccess_file = trailingslashit( $home_path ) . '.htaccess'; // Default WordPress htaccess rules. $default_rules = "# BEGIN WordPress\n" . "\n" . "RewriteEngine On\n" . "RewriteBase /\n" . "RewriteRule ^index\\.php$ - [L]\n" . "RewriteCond %{REQUEST_FILENAME} !-f\n" . "RewriteCond %{REQUEST_FILENAME} !-d\n" . "RewriteRule . /index.php [L]\n" . "\n" . "# END WordPress\n"; try { // Ensure WP_Filesystem is ready. if ( ! $wp_filesystem || ! is_object( $wp_filesystem ) ) { return array( 'success' => false, 'message' => esc_html__( 'File system initialization failed.', 'aims-quick-cleanup-tools' ), ); } // Create file if missing. if ( ! $wp_filesystem->exists( $htaccess_file ) ) { $wp_filesystem->put_contents( $htaccess_file, $default_rules, FS_CHMOD_FILE ); } // Ensure file is writable. if ( ! $wp_filesystem->is_writable( $htaccess_file ) ) { return array( 'success' => false, 'message' => esc_html__( '.htaccess is not writable. Change permissions to 644.', 'aims-quick-cleanup-tools' ), ); } // Write default rules. $wp_filesystem->put_contents( $htaccess_file, $default_rules, FS_CHMOD_FILE ); // Flush rewrite rules. global $wp_rewrite; if ( isset( $wp_rewrite ) ) { $wp_rewrite->flush_rules( true ); } return array( 'success' => true, 'message' => esc_html__( '.htaccess successfully regenerated.', 'aims-quick-cleanup-tools' ), ); } catch ( Exception $e ) { $error_message = sprintf( /* translators: %s: Error message */ esc_html__( 'Error regenerating .htaccess: %s', 'aims-quick-cleanup-tools' ), $e->getMessage() ); return array( 'success' => false, 'message' => $error_message, ); } } /** * Fix file and directory permissions. */ public static function fix_file_permissions() { $results = array( 'success' => false, 'message' => '', 'count' => 0, 'details' => array(), ); try { $upload = wp_upload_dir(); $dirs_fixed = 0; $files_fixed = 0; if ( file_exists( $upload['basedir'] ) ) { self::fix_permissions_recursive( $upload['basedir'], $dirs_fixed, $files_fixed ); } $paths = array( WP_CONTENT_DIR . '/themes', WP_CONTENT_DIR . '/plugins', WP_CONTENT_DIR . '/uploads', ); foreach ( $paths as $path ) { if ( file_exists( $path ) ) { self::fix_permissions_recursive( $path, $dirs_fixed, $files_fixed ); } } $results['success'] = true; $results['count'] = $dirs_fixed + $files_fixed; $results['message'] = sprintf( /* translators: %1$d: Number of directories fixed. %2$d: Number of files fixed. */ esc_html__( 'Fixed permissions: %1$d directories, %2$d files', 'aims-quick-cleanup-tools' ), $dirs_fixed, $files_fixed ); } catch ( Exception $e ) { $results['message'] = $e->getMessage(); } return $results; } /** * Delete spam comments and orphaned commentmeta. */ public static function clean_spam_comments() { global $wpdb; $results = array( 'success' => false, 'message' => '', 'count' => 0, ); try { $count = $wpdb->query( " DELETE FROM {$wpdb->comments} WHERE comment_approved = 'spam' " ); $wpdb->query( " DELETE cm FROM {$wpdb->commentmeta} cm LEFT JOIN {$wpdb->comments} c ON c.comment_ID = cm.comment_id WHERE c.comment_ID IS NULL " ); $results['success'] = true; $results['count'] = $count; $results['message'] = sprintf( /* translators: %d: Number of unused tags deleted. */ esc_html__( 'Deleted %d spam comments', 'aims-quick-cleanup-tools' ), $count ); } catch ( Exception $e ) { $results['message'] = $e->getMessage(); } return $results; } /** * Recursively search for a file. * * @param string $dir Directory to search. * @param string $filename File name to locate. * @return string|false */ private static function find_file_recursive( $dir, $filename ) { if ( ! is_dir( $dir ) ) { return false; } $files = scandir( $dir ); foreach ( $files as $file ) { if ( '.' === $file || '..' === $file ) { continue; } $path = $dir . '/' . $file; if ( is_dir( $path ) ) { $found = self::find_file_recursive( $path, $filename ); if ( $found ) { return $found; } } elseif ( $file === $filename ) { return str_replace( wp_upload_dir()['basedir'] . '/', '', $path ); } } return false; } /** * Recursively fix directory and file permissions. * * @param string $path Full path of the directory or file to process. * @param int $dirs Counter for directories updated (passed by reference). * @param int $files Counter for files updated (passed by reference). * * @return void */ private static function fix_permissions_recursive( $path, &$dirs, &$files ) { global $wp_filesystem; // Initialize WP_Filesystem if not loaded. if ( ! $wp_filesystem ) { require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); } if ( ! file_exists( $path ) ) { return; } if ( is_dir( $path ) ) { $current = substr( sprintf( '%o', fileperms( $path ) ), -4 ); if ( '0755' !== $current ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod $wp_filesystem->chmod( $path, 0755 ); ++$dirs; } foreach ( scandir( $path ) as $item ) { if ( '.' === $item || '..' === $item ) { continue; } self::fix_permissions_recursive( $path . '/' . $item, $dirs, $files ); } } else { $current = substr( sprintf( '%o', fileperms( $path ) ), -4 ); if ( '0644' !== $current ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod $wp_filesystem->chmod( $path, 0644 ); ++$files; } } } }