<?php

class MeowPro_WPMC_Core {

	private $item = 'Media Cleaner Pro';
	private $core = null;
	private $parsers = null;
	private $excluded_dirs = array();
	private $cache_meta = null;
	private $cache_mainfile = null;
	private $cache_croppedfiles = null;
	private $check_content = null;
	private $check_medialibrary = null;
	private $imagefiletypes = '|jpg|jpeg|jpe|gif|png|tiff|bmp';
	private $last_file_page = array( 'scanned' => 0, 'finished' => true, 'skipped' => array() );

	public function __construct( $core ) {
		$this->core = $core;

		// Common behaviors, license, update system, etc.
		new MeowKitPro_WPMC_Licenser( WPMC_PREFIX, WPMC_ENTRY, WPMC_DOMAIN, $this->item, WPMC_VERSION );

		//new MeowApps_Admin_Pro( $prefix, $mainfile, $domain, $this->item, $version );

		// Support for WP CLI
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			$wpcli = new MeowPro_WPMC_CLI();
			WP_CLI::add_command( 'media-cleaner', $wpcli );
		}

		// Overrides for the Pro
		add_filter( 'wpmc_plugin_title', array( $this, 'plugin_title' ), 10, 1 );
		add_filter( 'wpmc_list_uploaded_files', array( $this, 'list_uploaded_files' ), 10, 4 );
		add_filter( 'wpmc_file_page_info', array( $this, 'file_page_info' ), 10, 1 );
		add_filter( 'wpmc_check_file', array( $this, 'check_file' ), 10, 2 );
		add_filter( 'wpmc_requested_image_sizes', array( $this, 'requested_image_sizes' ), 10, 1 );

		// Initializers
		add_action( 'wpmc_initialize_parsers', array( $this, 'initialize_parsers' ), 10, 0 );
		add_action( 'wpmc_check_file_init', array( $this, 'check_file_init' ), 10, 0 );

		// Live Content
		// if ( $this->core->get_option( 'live_content' ) ) {
		// 	add_action( 'wpmc_scan_extra', array( $this, 'live_content' ), 10, 1 );
		// }

		$this->excluded_dirs = $this->core->get_excluded_upload_dirs();
	}

	public function __destruct() {
		remove_filter( 'wpmc_plugin_title', array( $this, 'plugin_title' ), 10 );
		remove_filter( 'wpmc_list_uploaded_files', array( $this, 'list_uploaded_files' ), 10 );
		remove_filter( 'wpmc_file_page_info', array( $this, 'file_page_info' ), 10 );
		remove_filter( 'wpmc_check_file', array( $this, 'check_file' ), 10, 2 );
		remove_action( 'wpmc_initialize_parsers', array( $this, 'initialize_parsers' ), 10 );
		remove_action( 'wpmc_check_file_init', array( $this, 'check_file_init' ), 10 ); // Without this Apache child crashed
	}

	function initialize_parsers() {
		if ( $this->parsers )
			return;
		$this->parsers = new MeowPro_WPMC_Parsers();
	}

	// Unregistered sizes the active theme or plugins still ask for, as found by Storage.
	function requested_image_sizes( $sizes ) {
		$stats = ( new MeowPro_WPMC_Storage( $this->core ) )->get();
		foreach ( $stats['sizes'] as $size ) {
			if ( !$size['registered'] && !empty( $size['requested_by'] ) ) $sizes[] = $size['name'];
		}
		return array_values( array_unique( $sizes ) );
	}

	function plugin_title( $string ) {
		return $string . " (Pro)";
	}

	function check_file_init() {
		$this->check_content = $this->core->get_option( 'filesystem_content' );
		$this->check_medialibrary = $this->core->get_option( 'media_library' );
		$this->buildCroppedFileCache();
	}

	function live_content( $post ) {
		// ! We block Live Content for the moment.
		return;

		// The file_get_contents() render operation runs on the server (as a not logged on user)
		// It can only render published public pages (Not future. Not Drafts. etc.)
		
		// 2022/12/26: Don't restrict to pages and posts. It's a good idea to scan all public content.
		// $type = get_post_field( 'post_type', $post );
		// ( $type == 'page' || $type == 'post')

		if ( get_post_field( 'post_status', $post ) == 'publish' ) {
			$html = @file_get_contents( $this->core->site_url . '?p=' . get_post_field( 'ID', $post ) );
			if ( $html === false ) {
				$err = "Unable to render Post ID {get_post_field('ID', $post)}";
				error_log( $err );
				$this->core->log( '🚫 ' . $err );
			} else {
				$this->core->safe_do_action( 'wpmc_scan_post', $html, $post ); // Uses same scan process as post_content. Some duplication of effort...
			}
		}
	}

	function list_uploaded_files( $result, $path, $offset = 0, $limit = -1 ) {
		$real_path = $this->core->resolve_upload_path( $path ?: '', true );
		if ( is_wp_error( $real_path ) || !is_dir( $real_path ) ) {
			throw new RuntimeException( is_wp_error( $real_path ) ? $real_path->get_error_message() : __( 'The requested uploads path is not a directory.', 'media-cleaner' ) );
		}
		$files = $this->scan_list_uploaded_files( $real_path, $offset, $limit );
		return $files;
	}

	function file_page_info( $info ) {
		return $this->last_file_page;
	}

	function scan_list_uploaded_files( $dir, $offset = 0, $limit = -1 ) {
		$result = array();
		// Optimize Thumbnails is about thumbnails by definition, so it does not depend on the
		// Thumbnails Only setting, which belongs to the Uploads Folder scan.
		$thumbnails_only = $this->core->get_option( 'thumbnails_only' ) || $this->core->current_method === 'optimize_thumbnails';
		$dirs_filter = $this->core->get_option( 'dirs_filter' );
		$files_filter = $this->core->get_option( 'files_filter' );
		$scanned = 0;
		$position = 0;
		$finished = true;
		$skipped = array( 'excluded' => 0, 'filtered' => 0, 'symlink' => 0, 'unreadable' => 0 );

		try {
			$iterator = new FilesystemIterator( $dir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::CURRENT_AS_FILEINFO );
		}
		catch ( UnexpectedValueException $e ) {
			throw new RuntimeException( sprintf( __( 'The uploads directory is not readable: %s', 'media-cleaner' ), $e->getMessage() ) );
		}

		foreach ( $iterator as $file_info ) {
			if ( $position < $offset ) {
				$position++;
				continue;
			}
			if ( $limit > 0 && $scanned >= $limit ) {
				$finished = false;
				break;
			}
			$position++;
			$scanned++;
			$file = $file_info->getFilename();
			if ( in_array( $file, $this->excluded_dirs, true ) ) {
				$skipped['excluded']++;
				continue;
			}
			if ( $file_info->isLink() ) {
				$skipped['symlink']++;
				continue;
			}
			$fullpath = $file_info->getPathname();
			$relativepath = $this->core->clean_uploaded_filename( $fullpath );
			if ( $relativepath === '' ) {
				$skipped['unreadable']++;
				continue;
			}
			$relativepath_dir = dirname( $relativepath );
			$relativepath_dir = preg_replace( '{^\.}', '', $relativepath_dir, 1) ; // Remove the dot at the beginning
			$type=''; // file or dir
			if ( $file_info->isDir() ) {
				$type = 'dir';
			}
			else {
				if ( $dirs_filter && preg_match( $dirs_filter, $relativepath_dir ) !== 1 ) {
					$skipped['filtered']++;
					continue;
				}
				if ( $thumbnails_only && !preg_match( '/-\d+x\d+\.(?:jpe?g|png|gif|webp|avif)$/i', $file ) ) {
					$skipped['filtered']++;
					continue;
				}
				if ( $files_filter ) {
					if ( @preg_match( $files_filter, $file ) === 0 ) {
						$skipped['filtered']++;
						continue;
					}
					$this->core->log( "Filter matches: " . $file . " " . $files_filter );
				}
				$type = 'file';
			}
			array_push( $result, array (
				'path' => $relativepath,
				'type' => $type,
				'cursor' => $position,
			) );
		}
		$this->last_file_page = array( 'scanned' => $scanned, 'next_cursor' => $position, 'finished' => $finished, 'skipped' => $skipped );
		return $result;
	}

	function getCachedMeta( &$id ) {
		if ( empty($this->cache_meta) || $this->cache_meta[0] != $id ) {
			$meta = wp_get_attachment_metadata( $id );
			$this->cache_meta = array($id, $meta);
			return $meta;
		} else {
			return $this->cache_meta [1];
		}
	}

	function buildCroppedFileCache() {
		global $wpdb;
		$this->cache_croppedfiles = array();
		$run_id = $this->core->get_run_id( true );
		$phase = 'croppedaliases';
		$target = 'cropped-attachment-aliases';
		$marker = $this->core->runs->get_work( $run_id, $phase, $target );
		if ( $marker && $marker->status === 'complete' ) return;
		if ( !$marker ) {
			if ( !$this->core->runs->enqueue_work( $run_id, $phase, 'initializer', $target ) ) {
				throw new RuntimeException( __( 'Media Cleaner could not initialize cropped attachment aliases.', 'media-cleaner' ) );
			}
			$marker = $this->core->runs->get_work( $run_id, $phase, $target );
		}
		// Get all rows with a -e9999999999999 timestamp in the filename.
		// Read them in batches: a site can have any number of cropped aliases, and nothing here
		// needs the whole set at once since each row only produces a reference. The ORDER BY also
		// keeps the paging stable from one batch to the next.
		$batch_size = 1000;
		$offset = 0;
		$total = 0;
		do {
			$sql = $wpdb->prepare( "SELECT meta_value,post_id FROM {$wpdb->postmeta} WHERE meta_key='_wp_attached_file' and meta_value REGEXP '-e[0-9]{13}.[A-Za-z]{3,4}' order by meta_value LIMIT %d OFFSET %d", $batch_size, $offset );
			$results = $wpdb->get_results( $sql, ARRAY_N );
			if ( $wpdb->last_error ) throw new RuntimeException( sprintf( __( 'Media Cleaner could not read cropped attachment aliases: %s', 'media-cleaner' ), $wpdb->last_error ) );
			foreach ($results as $result) {
				$matches=array();
				// Validate -e9999999999999 timestamp is present and compute offset (matches[1][1]) to the timestamp
				if (preg_match('/[A-Za-z0-9-_,\)\(\\\\\/.\s](-e\d{13}).[a-z]{3,4}/', $result[0], $matches, PREG_OFFSET_CAPTURE ) ) {
					// Remove the -e9999999999999 timestamp to get a mainfile name from before the image was cropped
					$precroppedfilename = substr( $result[0], 0, $matches[1][1] ) . substr( $result[0],  $matches[1][1] + strlen( $matches[1][0] ) );
					$this->core->add_reference_url( $precroppedfilename, 'MEDIA LIBRARY', (int) $result[1] );
				}
			}
			$this->core->write_references();
			$total += count( $results );
			$offset += $batch_size;
		} while ( count( $results ) === $batch_size );
		if ( !$marker || !$this->core->runs->update_work( $marker->id, 'complete', $total ) ) {
			throw new RuntimeException( __( 'Media Cleaner could not finalize cropped attachment aliases.', 'media-cleaner' ) );
		}
		//error_log("CACHE COUNT=" . count($this->cache_croppedfiles) );
	}

	function getCachedMainfile( &$path, $mainfile ) {
		global $wpdb;
		if ( !empty( $this->cache_mainfile ) && 
			 ( $this->cache_mainfile[0] == $mainfile || $this->cache_mainfile[2] == $mainfile || $this->cache_mainfile[3] == $mainfile ) ) {
				// Found $mainfile in the cache. 
				$id = $this->cache_mainfile[1];
				//error_log("CACHE HIT: " . $mainfile . " [" . $this->cache_mainfile[1] . "] [" . $this->cache_mainfile[0]  . "] [" . $this->cache_mainfile[2]  . "] [" . $this->cache_mainfile[3] . "] PATH=" . $path);
				// Check for .jpg within meta of .pdf 
				if ( $this->cache_mainfile[0] !== null && $this->cache_mainfile[2] !== null && 
					 substr ($this->cache_mainfile[0], -3) !== substr ( $this->cache_mainfile[2], -3 ) ) {
					// Must be .jpg resolution image file for a .pdf
					// Return Id of .pdf to let check_meta_value_sizes() check for .jpg in meta_value
					if ( empty( $id ) ) {
						$this->core->log( "🚫 File {$path} not found as Media {$id} (PDF)");
						return null;
					} else {
						return -$id; // Id of mainfile (.pdf)						
					}
				} else {
					if ( empty( $id ) ) {
						$this->core->log( "🚫 File {$path} not found as _wp_attached_file (Library)");
						return null;
					} else {
						if ( $path == $mainfile) {
							$this->core->log( "🚫 File {$path} found as Media {$id}");	
						} 
					}
					return $id; // Id of mainfile or null
				}
		} else {
			// $mainfile does not match the cache
			//error_log("CACHE MISS: " . $mainfile . " " . $this->cache_mainfile[1] . " " . $this->cache_mainfile[0]  . " " . $this->cache_mainfile[2]  . " " . $this->cache_mainfile[3]);
			$id = $this->core->find_media_id_from_file( $mainfile, false ); 
			if ( empty ( $id ) ) {
				// $mainfile not found in Media Library - Try three different methods to find this disk file in the Media Library rows
				//
				$pathinfo = pathinfo( $mainfile );
				$ext = isset( $pathinfo['extension'] ) ? $pathinfo['extension'] : "";
				// These three methods only apply to image files
				if ( strpos( $this->imagefiletypes, $ext ) !== false ) {
					// Did not find the mainfile in the Media Library. 
					// 1. See if there is a cropped main file.
					//$ret = $this->Get_Cropped_File( $mainfile ); // ret [1]=ID, [0]=YYYY/MM/filename-e9999999999999.ext
					$ret = $this->getCroppedFileFromCache ( $mainfile ); // ret [1]=ID, [0]=YYYY/MM/filename-e9999999999999.ext
					if (!empty( $ret ) ) {
						$id = $ret[1];
						// Found cropped filename in Media Library. Make a cache entry to handle cropped mainfile names.
						$this->cache_mainfile = array( $mainfile, $ret[1], $ret[0], null, true );	
						//error_log("CACHE SET 1: " . $this->cache_mainfile[0] . " " . $this->cache_mainfile[1]  . " " . $this->cache_mainfile[2]  . " " . $this->cache_mainfile[3]);
						if ( $path == $mainfile ) {
							$this->core->log("✅ File {$mainfile} found as Media {$id}");	
						}
						return $id;  // Return Id to see if the $path is in the meta_value
					} 
					// 2. It might be an image that is in a pdf's meta
					$skiptablescan = false;
					if ( substr( $mainfile, -4 ) == '.jpg' ) {
						$pdfmainfileDash = '';
						// Create a PDF filename and see if it exists
						if ( substr( $mainfile, -8) == '-pdf.jpg' ) { 
							// -pdf.jpg
							$pdfmainfile = substr( $mainfile, 0, strlen( $mainfile) - 8 ) . ".pdf";
							$pdfmainfileDash = substr( $mainfile, 0, strlen( $mainfile) - 7 ) . ".pdf"; // trailing - (see below)
							$skiptablescan = true; // We know this jpg is associated with a pdf so skip table scan for images below
						} else {
							// .jpg
							$pdfmainfile = substr( $mainfile, 0, strlen( $mainfile) - 4 ) . ".pdf";
						}
						$id = $this->core->find_media_id_from_file( $pdfmainfile, false ); // No need to make log entry	
						if ( !empty( $id ) ) {
							// Found the PDF - Make cache entries so variations will match the cache
							$pdfmainfile1 = substr( $pdfmainfile, 0, strlen($pdfmainfile) - 4 ) . '.jpg';
							$pdfmainfile2 = substr( $pdfmainfile, 0, strlen($pdfmainfile) - 4 ) . '-pdf.jpg';
							$this->cache_mainfile = array( $pdfmainfile, $id,  $pdfmainfile1, $pdfmainfile2, false );	
							//error_log("CACHE SET 2A: " . $this->cache_mainfile[0] . " " . $this->cache_mainfile[1]  . " " . $this->cache_mainfile[2]  . " " . $this->cache_mainfile[3]);
							return -$id; // Return Id to let check_meta_value_sizes() check it
						}
						// Found five filenames like 'Sewer-Rates-Adopted-October-25-2016-.pdf' (trailing dash) in __wp_attached_file. This code handles that.
						$id = $this->core->find_media_id_from_file( $pdfmainfileDash, false ); // No need to make log entry	
						if ( !empty( $id ) ) {
							// Found the PDF - Make cache entries so variations will match the cache
							$pdfmainfile1 = substr( $pdfmainfile, 0, strlen($pdfmainfile) - 4 ) . '.jpg';
							$pdfmainfile2 = substr( $pdfmainfile, 0, strlen($pdfmainfile) - 4 ) . '-pdf.jpg';  
							$this->cache_mainfile = array( $pdfmainfile, $id,  $pdfmainfile1, $pdfmainfile2 , false);	
							//error_log("CACHE SET 2B: " . $this->cache_mainfile[0] . " " . $this->cache_mainfile[1]  . " " . $this->cache_mainfile[2]  . " " . $this->cache_mainfile[3]);
							return -$id; // Return Id to let check_meta_value_sizes() check it
						}
					}
					// 3. See if the image is in the meta_value of another image. Not elegant but it works!
					if ( !$skiptablescan ) {
						$table = $wpdb->prefix . 'postmeta';
						$sql = $wpdb->prepare("SELECT post_id FROM {$table} WHERE meta_key = '_wp_attachment_metadata' AND meta_value LIKE %s;" ,'%'. $mainfile . '%');
						$id = $wpdb->get_var( $sql );
						if ( !empty( $id ) ) {
							$this->cache_mainfile = array( $mainfile, $id, null, null, false );	
							//error_log("CACHE SET 3A: " . $this->cache_mainfile[0] . " " . $this->cache_mainfile[1]  . " " . $this->cache_mainfile[2]  . " " . $this->cache_mainfile[3]);
							if ( $path == $mainfile ) {
								$this->core->log("✅ File {$mainfile} found in metadata of Media {$id} (_wp_attachment_metadata)");	
							}
							return $id; 
						}
						// Look to see if a resolution image file is within a meta_value and then let check_meta_value_sizes() check it
						if ( $path != $mainfile ) {
							$pathinfo = pathinfo( $path ); 
							$basename = $pathinfo['basename'];
							// Searching for just basename (without directories) may lead to finding the wrong file. 
							// The call to check_meta_value_sizes() will display found or not found comparing the entire $path
							$sql = $wpdb->prepare("SELECT post_id FROM {$table} WHERE meta_key = '_wp_attachment_metadata' AND meta_value LIKE %s;" ,'%'. $basename . '%');
							$id = $wpdb->get_var( $sql );
							if ( !empty( $id ) ) {
								$this->cache_mainfile = array( $mainfile, $id, null, null, false );	
								//error_log("CACHE SET 3B: " . $this->cache_mainfile[0] . " " . $this->cache_mainfile[1]  . " " . $this->cache_mainfile[2]  . " " . $this->cache_mainfile[3]);
								if ( $path == $mainfile ) {
									$this->core->log("✅ File {$mainfile} found in metadata of Media {$id} (_wp_attachment_metadata)");	
								}
								return $id; 
							}
						}
					}
				
				} 
				// An on disk file not in the Media Library
				$this->core->log( "👻 File {$path} not found as Media");
				$this->cache_mainfile = array( $mainfile, null , null, null, false );	
				//error_log("CACHE SET 4: " . $this->cache_mainfile[0] . " " . $this->cache_mainfile[1]  . " " . $this->cache_mainfile[2]  . " " . $this->cache_mainfile[3]) . " path=" . $path;
				return null; 
			} else {
				// Found in Media Library. If a PDF, make a cache entry to handle .jpg filenames used in PDF's meta
				if ( substr($mainfile, -4 ) == '.pdf' ) {
					// Just in case there will be image files that are in the PDF's meta
					$pdfmainfile1 = substr( $mainfile, 0, strlen($mainfile) - 4 ) . '.jpg';
					$pdfmainfile2 = substr( $mainfile, 0, strlen($mainfile) - 4 ) . '-pdf.jpg';
					$this->cache_mainfile = array( $mainfile, $id,  $pdfmainfile1, $pdfmainfile2, false );	
					//error_log("CACHE SET 5: " . $this->cache_mainfile[0] . " " . $this->cache_mainfile[1]  . " " . $this->cache_mainfile[2]  . " " . $this->cache_mainfile[3] . " path=" . $path);
					$this->core->log( "✅ File {$path} found as Media {$id} (PDF)");
					return $id; // Return Id to let check_meta_value_sizes()
				} else {
					// Found in Media Library
					$this->cache_mainfile = array( $mainfile, $id, null , null, false );
					//error_log("CACHE SET 6: " . $this->cache_mainfile[0] . " " . $this->cache_mainfile[1]  . " " . $this->cache_mainfile[2]  . " " . $this->cache_mainfile[3] . " path=" . $path);
					if ( $path == $mainfile) {
						$this->core->log( "✅ File {$path} found as Media {$id}");	
					} 
				}
				return $id; // Media ID or null
			}
		}
	}

	function getCroppedFileFromCache( &$file ) {
		// When an image is cropped, a filename like '2019/01/image-e1547821274436.jpg' is created, the original file is removed from 
		// Media Library and resolution filenames in the meta_value array may or may not contain the -e1547821274436 timestamp.
		// Here we have a potential main filename assumed to be without a timestamp for which we could find no meta_value in wp_postmeta
		// That may mean that the main filename was cropped so the main filename was changed to include a timestamp.
		// Here we try to find the main filename by looking for a filename with a '-e' followed by a 13-digit timestamp.
		global $wpdb;
		$file = $this->core->clean_uploaded_filename( $file ); // YYYY/MM/filename.ext
		$pathinfo = pathinfo( $file );
		$ext = $pathinfo['extension'];
		if ( strpos( $this->imagefiletypes, $ext ) === false ) {
			// Only images can be cropped
			return null;
		}
		$itm = $this->searchCache( $file ); // itm: [0]=YYYY/MM/filename.ext [1]=media ID [2]=YYYY/MM/filename-e9999999999999.ext (from meta_value)
		if ( empty( $itm ) ) return null;
		//error_log( "ID=" . $itm[1] . " " . $itm[0] . " " . $itm[2] );
		return array($itm[2], $itm[1], $itm[0] );
	}

	function searchCache( &$file ) { 
		if ( empty( $this->cache_croppedfiles ) || count( $this->cache_croppedfiles ) == 0 ) return false; 
		$low = 0; 
		$high = count( $this->cache_croppedfiles ) - 1; 
		while ($low <= $high) { 
			$mid = floor(($low + $high) / 2); 
			if ( $file == $this->cache_croppedfiles[$mid][0] ) {
				return $this->cache_croppedfiles[$mid];
			}	
			if ( $file < $this->cache_croppedfiles[$mid][0] ) { 
				$high = $mid - 1; 
			} 
			else { 
				$low = $mid + 1; 
			} 
		} 
		return null; 
	}

	// Return true if the files is referenced, false if it is not.
	function check_file( $in_use, $path ) {
		if ( $in_use ) {
			return true;
		}

		if( is_array( $path ) ) {
			if( array_key_exists( 'type', $path ) ) {
				if ( $path['type'] == 'dir' ) {
					// If the path is a directory, we don't check it.
					return true;
				} elseif ( $path['type'] == 'file' ) {
					// If the path is a file, we check it.
					$path = $path['path'];
				} else {
					// Unknown type, nothing to check.
					return true;
				}
			} else {
				// No path provided, nothing to check.
				return true;
			}
		}	

		$filepath = trailingslashit( $this->core->upload_path ) . stripslashes( $path );

		// Ignored path
		if ( $this->core->check_is_ignore( $path ) ) {
			return true;
		}

		// Does reference exist
		$clean_path = $this->core->clean_uploaded_filename( $path );
		if ( $this->core->reference_exists( $clean_path, null ) ) {
			return true;
		}
		if ( $this->core->current_method === 'files' ) {
			$main_path = $this->core->clean_url_from_resolution( $clean_path );
			if ( $main_path !== $clean_path && $this->core->reference_exists( $main_path, null ) ) return true;
		}

		// Retina support
		if ( strpos( $path, '@2x.' ) !== false ) {
			$originalfile = str_replace( '@2x.', '.', $filepath );
			return file_exists( $originalfile ) ? true : 'ORPHAN_RETINA';
		}

		// WebP support
		if ( substr( $path, -5) == '.webp' ) {
			// for files converted from filename.jpg to filename.jpg.webp
			$original_file = str_replace( '.webp', '', $filepath );
			// for files converted from filename.jpg to filename.webp
			$original_file_alt = str_replace( '.webp', '.jpg', $filepath );
			return file_exists( $original_file ) || file_exists( $original_file_alt ) ? true : 'ORPHAN_WEBP';
		}

		// If there is a check against the Media Library
		if ( $this->check_medialibrary ) {
			$mainfile = $this->core->clean_url_from_resolution( $path );
			// TODO: Somehow, the getCachedMainfile seems to fail for PDF thumbnails but only when using WP-CLI
			$attachment_Id = $this->getCachedMainfile( $path, $mainfile ); 
			if ( !empty( $attachment_Id ) && ( $attachment_Id < 0 || $path != $mainfile ) ) {
				$attachment_Id = abs( $attachment_Id );
				if ( $this->check_medialibrary )
					// Check for a resolution image file
					if ( !$this->check_meta_value_sizes ( $mainfile, $path, $attachment_Id ) ) {
						$attachment_Id = null; // Wasn't found in meta_value
					}
			}
			return empty( $attachment_Id ) ? 'ORPHAN_FILE' : true;
		}

		$issue_tag = 'NO_CONTENT';

		switch( $this->core->current_method ) {
			case 'optimize_thumbnails':
				$issue_tag = 'NOT_NEEDED_THUMB';
				break;
			case 'duplicates':
				$issue_tag = 'DUPLICATE';
				break;
			
			default:
				$issue_tag = 'NO_CONTENT';
				break;
		}

		return $issue_tag;
	}

	function check_meta_value_sizes ( &$mainfile, &$path, &$attachment_Id ) {
		$meta = $this->getCachedMeta( $attachment_Id );
		if ( $meta ) {
			if ( array_key_exists( 'file', $meta ) ) {
				$pathinfo = pathinfo( $meta['file'] );
				$dirname = $pathinfo['dirname'];
				$dirname = trailingslashit( $dirname );
				if ( $dirname == "./" ) $dirname = '';
				//error_log($meta['file'] . " == " . $path . "   " . $mainfile);
				if ( $this->check_medialibrary && $meta['file'] == $path ) {
					$this->core->log( "✅ File {$path} found in 'file' metadata of Media {$attachment_Id}." );
					//error_log("FOUND main meta: " . $meta['file'] . "==" . $path  . " " . $attachment_Id );
					return true;
				} 
			}
			else {
				// No 'file' key for PDF thumbnails so use mainfile's pathinfo
				$pathinfo = pathinfo( $mainfile );
				$dirname = $pathinfo['dirname'];
				$dirname = trailingslashit( $dirname );
				if ( $dirname == "./" ) $dirname = '';
			}
			if ( array_key_exists( 'sizes', $meta) ) {	
				foreach ( $meta['sizes'] as $size ) {
					if ( is_array( $size ) && array_key_exists( 'file', $size ) ) {
						//error_log("COMPARE: " . $dirname . $size['file'] . "==" . $path );
						if ( $dirname . $size['file'] == $path ) {
							if ( $this->check_medialibrary ) {
								$this->core->log( "✅ File {$path} found in metadata of Media {$attachment_Id}." );
								//error_log("FOUND: " . $dirname . $size['file'] . "==" . $path . " " . $attachment_Id );
								return true;
							} 
						}
					} else {
						// Soft Fail - unexpected meta_value format
						error_log( $mainfile . ' Unable to parse resolutions metadata. No file key.' );
						error_log( "meta size=" . print_r( $size, true ) );
						$this->core->log( "🚫 File {$path} unrecognized resolution meta for Media {$attachment_Id}." );
						return true; // To be safe!
					}	
				}
				$this->core->log( "🚫 File {$path} not found in metadata of Media {$attachment_Id}." );
				//error_log("NOT FOUND: " . $path . " " . $attachment_Id);
				return false;
			} else {
				// Soft Fail - unexpected meta_value format
				error_log( $mainfile . ' Unable to parse resolutions metadata. No sizes key.' );
				error_log("meta=" . print_r( $meta, true ) );
				$this->core->log( "🚫 File {$path} unrecognized resolution meta for Media {$attachment_Id}." );
				return true; // To be safe!
			}	
		}
		$this->core->log( "👻 File {$path} not found in Media Library." );
		return false;
	}
}
