<?php
// Standalone check for the translation phase: php -d zend.assertions=1 tests/translated-refs.php
// No WordPress, no framework. It pins the references add_translated_references() produces against
// what the old inline code in add_reference_url() produced, because a reference this loses is a
// used file offered up for deletion.

function __( $s, $d = null ) { return $s; }
function is_wp_error( $t ) { return false; }
function sanitize_text_field( $s ) { return trim( (string) $s ); }
function wp_json_encode( $v ) { return json_encode( $v ); }

// WPML's filter, stubbed: every attachment has a sibling per language.
$GLOBALS['wpml_map'] = array();
function apply_filters( $hook, $value, ...$args ) {
	if ( $hook !== 'wpml_object_id' ) return $value;
	$language = isset( $args[2] ) ? $args[2] : null;
	return isset( $GLOBALS['wpml_map'][ $value ][ $language ] ) ? $GLOBALS['wpml_map'][ $value ][ $language ] : 0;
}

class FakeWpdb {
	public $last_error = '';
	public $num_queries = 0;
	public $postmeta = 'wp_postmeta';
	public $posts = 'wp_posts';
	public $rows = array(); // stored _wp_attached_file => attachment id
	public function esc_like( $t ) { return addcslashes( $t, '_%\\' ); }
	public function prepare( $sql, ...$args ) {
		if ( count( $args ) === 1 && is_array( $args[0] ) ) $args = $args[0];
		$out = ''; $i = 0;
		foreach ( preg_split( '/(%[sd])/', $sql, -1, PREG_SPLIT_DELIM_CAPTURE ) as $part ) {
			if ( $part === '%s' ) $out .= "'" . str_replace( "'", "''", (string) $args[ $i++ ] ) . "'";
			else if ( $part === '%d' ) $out .= (int) $args[ $i++ ];
			else $out .= $part;
		}
		return $out;
	}
	public function get_results( $sql ) {
		$this->num_queries++;
		$out = array();
		$limit = preg_match( '/ LIMIT (\d+)$/', $sql, $m ) ? (int) $m[1] : PHP_INT_MAX;
		if ( preg_match( '/ IN \( (.*?) \)/', $sql, $m ) ) {
			$wanted = array_map( function ( $v ) { return trim( $v, " '" ); }, explode( ',', $m[1] ) );
			foreach ( $this->rows as $candidate => $id ) {
				if ( in_array( $candidate, $wanted, true ) ) $out[] = (object) array( 'id' => $id, 'candidate' => $candidate );
				if ( count( $out ) >= $limit ) break;
			}
			return $out;
		}
		preg_match_all( "/LIKE '%([^']*)'/", $sql, $m );
		foreach ( $this->rows as $candidate => $id ) {
			foreach ( $m[1] as $suffix ) {
				if ( str_ends_with( $candidate, stripslashes( $suffix ) ) ) { $out[] = (object) array( 'id' => $id, 'candidate' => $candidate ); break; }
			}
			if ( count( $out ) >= $limit ) break;
		}
		return $out;
	}
}

$wpdb = new FakeWpdb();
require __DIR__ . '/../classes/core.php';

// write_references() talks to the database; the references themselves are what this checks, so the
// probe keeps them in the buffer instead and reads them back.
class Probe extends Meow_WPMC_Core {
	function write_references() {}
	public function take() {
		$r = new ReflectionProperty( 'Meow_WPMC_Core', 'refcache' );
		$r->setAccessible( true );
		$out = $r->getValue( $this );
		$r->setValue( $this, array() );
		return $out;
	}
	public function arm( $languages ) {
		foreach ( array( 'multilingual' => true, 'languages' => $languages, 'url_id_cache' => array(),
			'translated_ids_cache' => array(), 'refcache' => array(), 'current_method' => 'media' ) as $k => $v ) {
			$p = new ReflectionProperty( 'Meow_WPMC_Core', $k );
			$p->setAccessible( true );
			$p->setValue( $this, $v );
		}
	}
}

function probe( $library, $wpml, $languages = array( 'en', 'de' ) ) {
	global $wpdb;
	$wpdb->rows = $library;
	$GLOBALS['wpml_map'] = $wpml;
	$p = ( new ReflectionClass( 'Probe' ) )->newInstanceWithoutConstructor();
	$p->arm( $languages );
	return $p;
}

$library = array( '2020/10/photo.jpg' => 100 );
$wpml = array( 100 => array( 'en' => 100, 'de' => 200 ) );

// The core case: a referenced URL resolves to its attachment, and the German duplicate -- which a
// path match alone would never reach if WPML stored it under its own file -- is recorded too.
$p = probe( $library, $wpml );
$p->add_translated_references( array(
	(object) array( 'mediaUrl' => '2020/10/photo.jpg', 'originType' => 'Common Content', 'origin' => '42' ),
) );
$refs = $p->take();
$ids = array_column( $refs, 'id' );
assert( $ids === array( 100, 100, 200 ), 'the media and every translation are referenced: ' . implode( ',', $ids ) );
assert( $refs[0]['type'] === 'Common Content', 'the reference keeps the type it was found under' );
assert( $refs[0]['origin'] === '42', 'and its origin' );

// A sized thumbnail URL resolves through its full-size path, as it did inline.
$p = probe( $library, $wpml );
$p->add_translated_references( array(
	(object) array( 'mediaUrl' => '2020/10/photo-300x300.jpg', 'originType' => 'T', 'origin' => null ),
) );
assert( array_column( $p->take(), 'id' ) === array( 100, 100, 200 ), 'a thumbnail URL resolves to its media' );

// The expensive half is per distinct URL. The same media referenced from twelve posts must still
// produce a reference per row -- each carries its own origin -- but must not be looked up twelve
// times. This is the whole reason the phase exists.
$p = probe( $library, $wpml );
$rows = array();
for ( $i = 0; $i < 12; $i++ ) {
	$rows[] = (object) array( 'mediaUrl' => '2020/10/photo.jpg', 'originType' => 'Common Content', 'origin' => (string) $i );
}
$before = $wpdb->num_queries;
$p->add_translated_references( $rows );
$refs = $p->take();
assert( $wpdb->num_queries - $before === 1, 'one lookup for twelve rows of the same URL: ' . ( $wpdb->num_queries - $before ) );
assert( count( $refs ) === 36, 'and still a reference per row, per language: ' . count( $refs ) );
assert( array_column( $refs, 'origin' ) === array_merge( ...array_map( function ( $i ) {
	return array( (string) $i, (string) $i, (string) $i );
}, range( 0, 11 ) ) ), 'each row keeps its own origin' );

// A URL in no library resolves to nothing, and records nothing -- it must not attach itself to
// whichever attachment the database happened to return.
$p = probe( $library, $wpml );
$p->add_translated_references( array(
	(object) array( 'mediaUrl' => '1999/01/ghost.jpg', 'originType' => 'T', 'origin' => null ),
) );
assert( $p->take() === array(), 'an unresolvable URL records nothing' );

// An empty URL is a question with no meaning and must not match anything either.
$p = probe( $library, $wpml );
$p->add_translated_references( array( (object) array( 'mediaUrl' => '', 'originType' => 'T', 'origin' => null ) ) );
assert( $p->take() === array(), 'an empty URL records nothing' );

echo "ok\n";
