HEX
Server: nginx
System: Linux pool195-106-36.bur.atomicsites.net 6.12.57+deb12-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.57-1~bpo12+1 (2025-11-17) x86_64
User: (0)
PHP: 8.3.32
Disabled: pcntl_fork
Upload Files
File: //wordpress/mu-plugins/photon-subsizes/photon-subsizes-cli.php
<?php

class Photon_Subsizes_Cli_Recover_Disk_Space {
	private $is_dry_run = false;
	private $is_verbose = false;
	private $images_removed = 0;
	private $bytes_recovered = 0;

	function __construct( $cli_assoc_args ) {
		$this->is_dry_run = WP_CLI\Utils\get_flag_value( $cli_assoc_args, 'dry-run', false /* default */ );
		$this->is_verbose = WP_CLI\Utils\get_flag_value( $cli_assoc_args, 'verbose', false /* default */ );
	}

	function run() {
		global $wpdb;

		if ( ! photon_subsizes_site_has_standard_uploads_location() ) {
			WP_CLI::error( 'Cannot run command. Site has a non-standard uploads location which is not supported by photon-subsizes.' );
		}

		$this->images_removed = 0;
		$this->bytes_recovered = 0;

		$upload_dir = wp_get_upload_dir();
		if ( ! file_exists( $upload_dir['basedir'] ) ) {
			WP_CLI::error( "Cannot find uploads directory: {$upload_dir['path']}" );
			return;
		}

		if ( $this->is_dry_run ) {
			WP_CLI::log( "Beginning dry run..." );
		}

		$disk_usage_before_removal = $this->get_site_disk_usage();
		if ( false === $disk_usage_before_removal ) {
			WP_CLI::warning( "Could not read disk usage info" );
		}

		$sql_is_photon_supported_mime_type = 'post_mime_type IN ( "image/jpeg", "image/png", "image/gif" )';
		$count = $wpdb->get_var( $wpdb->prepare( "
			SELECT COUNT(*)
			FROM $wpdb->posts
			WHERE
				post_type = 'attachment' AND
				$sql_is_photon_supported_mime_type
		" ) );

		$this->log_verbose( "Removing intermediate images for $count image attachments" );

		$progress = WP_CLI\Utils\make_progress_bar( "Removing intermediate image files", $count );

		$batch_size = 10;
		$max_post_id = 0;

		// Though this loop should break when it gets to the end of attachments,
		// limit with max iterations as a safety against infinite looping 
		$max_iterations = ceil( $count / $batch_size );
		for ( $i = 0; $i < $max_iterations; ++$i ) {
			$attachments = $wpdb->get_results( $wpdb->prepare(
				"
					SELECT ID, file.meta_value as original, meta.meta_value AS meta, backup.meta_value as backup_sizes
					FROM $wpdb->posts posts
						INNER JOIN $wpdb->postmeta file ON
							posts.ID=file.post_id AND
							file.meta_key='_wp_attached_file'
						INNER JOIN $wpdb->postmeta meta ON
							posts.ID=meta.post_id AND
							meta.meta_key='_wp_attachment_metadata'
						LEFT OUTER JOIN $wpdb->postmeta backup ON
							posts.ID=backup.post_id AND
							backup.meta_key='_wp_attachment_backup_sizes'
					WHERE
						posts.ID > %d AND
						posts.post_type = 'attachment' AND
						$sql_is_photon_supported_mime_type
					ORDER BY posts.ID
					LIMIT %d
				",
				$max_post_id,
				$batch_size
			) );

			if ( count( $attachments ) === 0 ) {
				break;
			}

			foreach ( $attachments as $attachment ) {
				$progress->tick();

				$max_post_id = $attachment->ID;

				$original_path = "{$upload_dir['basedir']}/{$attachment->original}";
				if ( ! file_exists( $original_path ) ) {
					WP_CLI::warning( "Skipping attachment. Original image '$original_path' does not exist." );
					continue;
				}

				$attachment_meta = maybe_unserialize( $attachment->meta );
				if ( ! isset( $attachment_meta['sizes'] ) ) {
					WP_CLI::warning( "Skipping image. Cannot find sizes metadata for '$original_path'." );
					continue;
				}

				$this->log_verbose( "Cleaning up intermediate images for $original_path" );
				$base_path = dirname( $original_path );
				$this->remove_subsizes( $base_path, $attachment_meta['sizes'] );

				if ( null !== $attachment->backup_sizes ) {
					$this->log_verbose( "Cleaning up backup intermediate images for $original_path" );
					$backup_sizes = maybe_unserialize( $attachment->backup_sizes );
					$this->remove_subsizes( $base_path, $backup_sizes );
				}
			}
		}

		$progress->finish();

		$formatted_images_removed = number_format( $this->images_removed );
		$formatted_size = size_format( $this->bytes_recovered, 1 );
		$savings_percent = round( $this->bytes_recovered / $disk_usage_before_removal * 100, 1 );
		
		WP_CLI::log(
			"Removed {$formatted_images_removed} intermediate images for a total of {$formatted_size} " .
			"which was {$savings_percent}% of the total disk space used by this site"
		);

		if ( ! $this->is_dry_run ) {
			$this->log_verbose( "Bumping stats..." );

			$stat_result = wp_safe_remote_get( "http://pixel.wp.com/g.gif?v=wpcom-no-pv&x_photon_subsizes/recovered_bytes={$this->bytes_recovered}" );
			if ( is_wp_error( $stat_result ) ) {
				WP_CLI::error( $stat_result, false /* do not exit */ );
			}
			$stat_result = wp_safe_remote_get( "http://pixel.wp.com/g.gif?v=wpcom-no-pv&x_photon_subsizes/removed_images={$this->images_removed}" );
			if ( is_wp_error( $stat_result ) ) {
				WP_CLI::error( $stat_result, false /* do not exit */ );
			}

			$this->log_verbose( "Done bumping stats." );
		}

		if ( $this->is_dry_run ) {
			WP_CLI::log( "Dry run complete." );
		}
	}

	private function remove_subsizes( $base_path, $sizes ) {
		$removable_subsize_files = $this->get_removable_subsize_files( $base_path, $sizes );

		foreach( $removable_subsize_files as $file_path => $size_in_bytes ) {
			$this->images_removed++;
			$this->bytes_recovered += $size_in_bytes;

			$formatted_size = size_format( $size_in_bytes, 1 );
			$this->log_verbose( "Deleting $formatted_size intermediate image $file_path" );

			if ( ! $this->is_dry_run ) {
				@unlink( $file_path );
			}
		}
	}

	private function get_removable_subsize_files( $base_path, $sizes ) {
		// Collect files referenced by full image sizes so we can avoid removing
		// full images that are also referenced by non-full size names
		$full_size_files = array();
		foreach ( $sizes as $name => $size ) {
			if ( 1 === preg_match( "#^full(?:-|$)#", $name ) ) {
				$this->log_verbose( "Found full image size: '$name' => '{$size['file']}'" );
				$full_size_files[ $size['file'] ] = true;
                        }
		}

		$removable_files = array();
		foreach ( $sizes as $name => $size ) {
			if ( isset( $full_size_files[ $size['file'] ] ) ) {
                                $this->log_verbose( "Skipping subsize. File '{$size['file']}' is associated with a full image size." );
				continue;
			}

			if ( 0 === preg_match( "#-\d+x\d+\.(?:png|jpg|jpeg|gif)$#i", $size['file'] ) ) {
				$this->log_verbose( "Skipping subsize. File name does not match the intermediate image naming convention: {$size['file']}" );
				continue;
			}

			$size_path = realpath( "{$base_path}/{$size['file']}" );
			if ( false === $size_path ) {
				$this->log_verbose( "Skipping subsize. File does not exist {$size['file']}." );
				continue;
			}

			if ( 0 !== strpos( $size_path, $base_path ) ) { 
				WP_CLI::warning( "Skipping subsize. File '{$size_path}' is not under uploads dir." );
				continue;
			}

			if ( isset( $removable_files[ $size_path ] ) ) {
				$this->log_verbose( "Already found file '{$size_path}' referenced by another subsize" );
				continue;
			}

			$stat = stat( $size_path );
			if ( false === $stat ) {
				WP_CLI::warning( "Skipping subsize. Could not stat() file {$size_path}." );
				continue;
			}

			$this->log_verbose( "Found removable file {$size_path}" );
			$removable_files[ $size_path ] = $stat['size'];
		}

		return $removable_files;
	}

	private function get_site_disk_usage() {
		$site_info_json = file_get_contents( '/tmp/.at-site-info' );
		if ( false === $site_info_json ) {
			return false;
		}

		$site_info = json_decode( $site_info_json );
		if ( null === $site_info ) {
			return false;
		}

		if ( ! isset( $site_info->space_used ) || ! is_numeric( $site_info->space_used ) ) {
			return false;
		}

		return $site_info->space_used;
	}

	private function log_verbose( $message ) {
		if ( $this->is_verbose ) {
			WP_CLI::log( $message );
		}
	}
}

/**
 * Removes intermediate image files to recover disk space.
 *
 * ## Options
 *
 * [--verbose]
 * : Enable verbose output.
 *
 * [--dry-run]
 * : Simulate removal.
 */
function photon_subsizes_cli_recover_disk_space( $args = array(), $assoc_args = array() ) {
	$runner = new Photon_Subsizes_Cli_Recover_Disk_Space( $assoc_args );
	$runner->run();
}

if ( defined( 'WP_CLI' ) && WP_CLI ) {
	WP_CLI::add_command( 'atomic photon-subsizes-recover-space', 'photon_subsizes_cli_recover_disk_space' );
}