<?php
// Standalone check for file fingerprints and the recover mode: php -d zend.assertions=1 tests/fingerprint.php
// No WordPress, no framework. It fails if a file that was only copied or moved (new inode, new mtime)
// blocks cleanup, if an edited file slips through, if a legacy hash stops validating, or if
// recovering from the trash depends on the fingerprint again (wp.org forum, 2026-08).

function __( $s, $d = null ) { return $s; }
function apply_filters( $tag, $value ) { return $value; }
function wp_json_encode( $v ) { return json_encode( $v ); }
class WP_Error {
	public $code;
	function __construct( $code = '', $message = '' ) { $this->code = $code; }
	function get_error_code() { return $this->code; }
}
function is_wp_error( $v ) { return $v instanceof WP_Error; }
class Meow_WPMC_Transient_Exception extends Exception {}
require __DIR__ . '/../classes/core.php';

class Probe extends Meow_WPMC_Core {
	public $dir;
	function log( $data = null, $force = false ) {}
	public function resolve_upload_path( $relative_path, $must_exist = false ) { return $this->dir . '/uploads/' . $relative_path; }
	public function resolve_trash_path( $relative_path, $must_exist = false ) { return $this->dir . '/trash/' . $relative_path; }
}

$p = ( new ReflectionClass( 'Probe' ) )->newInstanceWithoutConstructor();
$p->dir = sys_get_temp_dir() . '/wpmc-fp-' . getmypid();
@mkdir( $p->dir . '/uploads', 0777, true );
@mkdir( $p->dir . '/trash', 0777, true );
$file = $p->dir . '/uploads/a.jpg';
file_put_contents( $file, random_bytes( 200000 ) );

$v2 = $p->file_fingerprint( $file );
$legacy = $p->file_fingerprint( $file, true );
$issue = function ( $hash ) { return (object) array( 'manifest' => json_encode( array( 'a.jpg' => $hash ) ) ); };

assert( strpos( $v2, 'v2:' ) === 0, 'new fingerprints carry the v2 prefix' );
assert( $p->validate_issue_manifest( $issue( $legacy ) ) === true, 'a legacy hash still validates an untouched file' );

// A copy gives the file a new inode and mtime, which is what some hosts do on their own.
sleep( 1 );
copy( $file, $file . '.tmp' ); unlink( $file ); rename( $file . '.tmp', $file );
assert( $p->validate_issue_manifest( $issue( $v2 ) ) === true, 'a copied file still validates with v2' );

// An edit must still be refused for cleanup, but never block a recovery.
$h = fopen( $file, 'r+' ); fwrite( $h, 'X' ); fclose( $h );
$changed = $p->validate_issue_manifest( $issue( $v2 ) );
assert( is_wp_error( $changed ) && $changed->get_error_code() === 'wpmc_file_changed_since_scan', 'an edited file is refused for cleanup' );
rename( $file, $p->dir . '/trash/a.jpg' );
assert( $p->validate_issue_manifest( $issue( $v2 ), 'recover' ) === true, 'recovering from the trash ignores the fingerprint' );
assert( $p->validate_issue_manifest( $issue( $legacy ), 'recover' ) === true, 'recovering a legacy result ignores the fingerprint too' );

// A file that vanished is still reported, even in recover mode.
unlink( $p->dir . '/trash/a.jpg' );
$gone = $p->validate_issue_manifest( $issue( $v2 ), 'recover' );
assert( is_wp_error( $gone ), 'a missing file is still reported when recovering' );

@rmdir( $p->dir . '/uploads' ); @rmdir( $p->dir . '/trash' ); @rmdir( $p->dir );
echo "fingerprint: ok\n";
