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;
}
}
}
}