# custom-404-pro/trunk/tests/AdminClassTest.php

Custom 404 Pro, version trunk. 297 lines.

- Page: https://pluginprobe.com/plugins/custom-404-pro/trunk/code/tests/AdminClassTest.php
- Raw: https://pluginprobe.com/plugins/custom-404-pro/trunk/raw/tests/AdminClassTest.php
- Modified: 2026-08-30T02:41:10+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/custom-404-pro/trunk/code/tests/AdminClassTest.php#L10-L20`.

```php
<?php
/**
 * Unit tests for the AdminClass class.
 *
 * @package Custom_404_Pro
 */

use PHPUnit\Framework\TestCase;

/**
 * AdminClass test case.
 */
class AdminClassTest extends TestCase {

	/**
	 * Asserts that helpers is an explicitly declared property.
	 */
	public function test_helpers_is_declared_property() {
		$ref = new ReflectionClass( AdminClass::class );
		$this->assertTrue(
			$ref->hasProperty( 'helpers' ),
			'helpers must be explicitly declared to avoid PHP 8.2+ deprecation'
		);
	}

	/**
	 * Asserts that helpers is an instance of Helpers after construction.
	 */
	public function test_helpers_is_instance_of_helpers_after_construction() {
		$admin = new AdminClass();
		$ref   = new ReflectionClass( $admin );
		$prop  = $ref->getProperty( 'helpers' );
		$prop->setAccessible( true );
		$this->assertInstanceOf( Helpers::class, $prop->getValue( $admin ) );
	}

	/**
	 * Resets multilingual stubs, filter registry, redirect captures, and transient store before each test.
	 */
	protected function setUp(): void {
		parent::setUp();
		$GLOBALS['_test_filters']        = array();
		$GLOBALS['_test_transients']     = array();
		$GLOBALS['_test_options']        = array();
		$GLOBALS['_test_is_404']         = false;
		$GLOBALS['_test_permalinks']     = array();
		$GLOBALS['_test_redirect_url']   = null;
		$GLOBALS['_test_redirect_status'] = null;
		$GLOBALS['_pll_get_post_return'] = false;
		unset( $GLOBALS['_pll_current_language'] );
		unset( $GLOBALS['_pll_default_language'] );
	}

	/**
	 * Asserts that the original page ID is returned when Polylang finds no translation.
	 */
	public function test_resolve_multilingual_page_id_returns_original_when_polylang_finds_no_translation() {
		$GLOBALS['_pll_get_post_return'] = false; // pll_get_post returns false → no translation.
		$admin                           = new AdminClass();
		$this->assertSame( 42, $admin->resolve_multilingual_page_id( 42 ) );
	}

	/**
	 * Asserts that the translated page ID is returned when Polylang finds a translation.
	 */
	public function test_resolve_multilingual_page_id_returns_translated_id_from_polylang() {
		$GLOBALS['_pll_get_post_return']   = 99;
		$GLOBALS['_pll_current_language']  = 'fr';
		$admin                             = new AdminClass();
		$this->assertSame( 99, $admin->resolve_multilingual_page_id( 42 ) );
	}

	/**
	 * Asserts that the translated page ID is returned when a WPML filter is registered.
	 */
	public function test_resolve_multilingual_page_id_returns_translated_id_from_wpml_filter() {
		add_filter(
			'wpml_object_id',
			function ( $page_id ) {
				return 77; // Simulate WPML returning the translated page ID.
			}
		);
		$admin = new AdminClass();
		$this->assertSame( 77, $admin->resolve_multilingual_page_id( 42 ) );
	}

	/**
	 * Asserts that the original page ID is returned when no WPML filter is registered.
	 */
	public function test_resolve_multilingual_page_id_returns_original_when_no_wpml_filter_registered() {
		$admin = new AdminClass();
		$this->assertSame( 42, $admin->resolve_multilingual_page_id( 42 ) );
	}

	// ------------------------------------------------------------------
	// normalize_page_id_to_default_language
	// ------------------------------------------------------------------

	/**
	 * Calls the private normalize_page_id_to_default_language method via reflection.
	 *
	 * @param AdminClass $admin   Instance to invoke on.
	 * @param int        $page_id Page ID to normalize.
	 * @return int
	 */
	private function normalize( AdminClass $admin, int $page_id ): int {
		$ref    = new ReflectionClass( $admin );
		$method = $ref->getMethod( 'normalize_page_id_to_default_language' );
		$method->setAccessible( true );
		return $method->invoke( $admin, $page_id );
	}

	/**
	 * Asserts that the original ID is returned when no multilingual plugin is active.
	 */
	public function test_normalize_returns_original_id_when_no_multilingual_plugin_active() {
		$admin = new AdminClass();
		$this->assertSame( 42, $this->normalize( $admin, 42 ) );
	}

	/**
	 * Asserts that the WPML default-language ID is returned when a WPML filter is registered.
	 */
	public function test_normalize_returns_default_language_id_via_wpml() {
		// Register wpml_default_language filter.
		add_filter( 'wpml_default_language', function () { return 'en'; } );
		// Register wpml_object_id filter that returns the default-language page ID.
		add_filter(
			'wpml_object_id',
			function ( $page_id ) {
				return 5; // Simulate WPML returning the English (default) page for any input.
			}
		);
		$admin = new AdminClass();
		$this->assertSame( 5, $this->normalize( $admin, 20 ) );
	}

	/**
	 * Asserts that the Polylang default-language ID is returned when pll_get_post finds a translation.
	 */
	public function test_normalize_returns_default_language_id_via_polylang() {
		$GLOBALS['_pll_default_language'] = 'en';
		$GLOBALS['_pll_get_post_return']  = 10; // pll_get_post returns the default-language page.
		$admin = new AdminClass();
		$this->assertSame( 10, $this->normalize( $admin, 20 ) );
	}

	/**
	 * Asserts that the original ID is returned when Polylang finds no default-language translation.
	 */
	public function test_normalize_returns_original_id_when_polylang_finds_no_default_translation() {
		$GLOBALS['_pll_default_language'] = 'en';
		$GLOBALS['_pll_get_post_return']  = false; // pll_get_post returns false → no translation.
		$admin = new AdminClass();
		$this->assertSame( 20, $this->normalize( $admin, 20 ) );
	}

	// ------------------------------------------------------------------
	// custom_404_pro_redirect — page mode
	// ------------------------------------------------------------------

	/**
	 * Helper: seed settings into the test options store.
	 *
	 * @param array $settings Settings to merge into defaults.
	 */
	private function seed_settings( array $settings ): void {
		$defaults = array(
			'mode'               => '',
			'mode_page'          => '0',
			'mode_url'           => '',
			'redirect_error_code' => 302,
			'logging_enabled'    => false,
			'send_email'         => false,
			'log_ip'             => true,
			'email_cooldown'     => HOUR_IN_SECONDS,
			'max_log_count'      => 0,
			'max_log_age'        => 0,
		);
		update_option( Helpers::OPTION_KEY, array_merge( $defaults, $settings ) );
	}

	/**
	 * Asserts that page mode redirects to the URL returned by get_permalink(), not any other value.
	 */
	public function test_redirect_page_mode_uses_get_permalink() {
		$GLOBALS['_test_is_404']          = true;
		$GLOBALS['_test_permalinks'][42]  = 'https://example.com/custom-404/';
		$this->seed_settings( array( 'mode' => 'page', 'mode_page' => '42' ) );

		$admin = new AdminClass();
		$admin->custom_404_pro_redirect();

		$this->assertSame(
			'https://example.com/custom-404/',
			$GLOBALS['_test_redirect_url'],
			'Redirect URL must come from get_permalink(), not post->guid or any other property.'
		);
	}

	/**
	 * Asserts that no redirect fires in page mode when get_permalink() returns false
	 * (e.g. the configured page ID no longer exists).
	 */
	public function test_redirect_page_mode_does_not_redirect_when_permalink_is_false() {
		$GLOBALS['_test_is_404']         = true;
		$GLOBALS['_test_permalinks'][99] = false; // page deleted / unpublished
		$this->seed_settings( array( 'mode' => 'page', 'mode_page' => '99' ) );

		$admin = new AdminClass();
		$admin->custom_404_pro_redirect();

		$this->assertNull(
			$GLOBALS['_test_redirect_url'],
			'No redirect should fire when get_permalink() returns false.'
		);
	}

	/**
	 * Asserts that no redirect fires when the current request is not a 404.
	 */
	public function test_redirect_page_mode_does_not_fire_when_not_404() {
		$GLOBALS['_test_is_404']         = false;
		$GLOBALS['_test_permalinks'][42] = 'https://example.com/custom-404/';
		$this->seed_settings( array( 'mode' => 'page', 'mode_page' => '42' ) );

		$admin = new AdminClass();
		$admin->custom_404_pro_redirect();

		$this->assertNull(
			$GLOBALS['_test_redirect_url'],
			'No redirect should fire for non-404 requests.'
		);
	}

	// ------------------------------------------------------------------
	// is_email_on_cooldown
	// ------------------------------------------------------------------

	/**
	 * Asserts that is_email_on_cooldown() returns false when no transient has been set.
	 */
	public function test_is_email_on_cooldown_returns_false_when_no_transient_set() {
		$admin = new AdminClass();
		$this->assertFalse( $admin->is_email_on_cooldown() );
	}

	/**
	 * Asserts that is_email_on_cooldown() returns true when the cooldown transient is set.
	 */
	public function test_is_email_on_cooldown_returns_true_when_transient_is_set() {
		set_transient( 'custom_404_pro_email_cooldown', true, HOUR_IN_SECONDS );
		$admin = new AdminClass();
		$this->assertTrue( $admin->is_email_on_cooldown() );
	}

	/**
	 * Asserts that is_email_on_cooldown() returns false after the cooldown transient is deleted.
	 */
	public function test_is_email_on_cooldown_returns_false_after_transient_deleted() {
		set_transient( 'custom_404_pro_email_cooldown', true, HOUR_IN_SECONDS );
		delete_transient( 'custom_404_pro_email_cooldown' );
		$admin = new AdminClass();
		$this->assertFalse( $admin->is_email_on_cooldown() );
	}

	// ------------------------------------------------------------------
	// Asset cache busting
	// ------------------------------------------------------------------

	/**
	 * Admin assets must be versioned with the plugin version so that each
	 * release invalidates the browser cache. This was pinned to a hardcoded
	 * '3.2.0' for many releases, leaving users on stale CSS and JS.
	 */
	public function test_asset_version_tracks_the_plugin_version() {
		$this->assertSame( CUSTOM_404_PRO_VERSION, AdminClass::asset_version() );
	}

	/**
	 * The asset version must no longer be the stale hardcoded value.
	 */
	public function test_asset_version_is_not_the_stale_hardcoded_value() {
		$this->assertNotSame( '3.2.0', AdminClass::asset_version() );
	}

	/**
	 * asset_version() must be callable statically from the enqueue callbacks.
	 */
	public function test_asset_version_is_public_static() {
		$ref    = new ReflectionClass( AdminClass::class );
		$method = $ref->getMethod( 'asset_version' );
		$this->assertTrue( $method->isPublic(), 'asset_version must be public' );
		$this->assertTrue( $method->isStatic(), 'asset_version must be static' );
	}
}

```
