pluginTemplateDir = rtrim( $trimmed, '/\\' ); $this->stylesheetDirectoryProvider = $stylesheetDirectoryProvider ?? static function (): ?string { if ( function_exists( 'get_stylesheet_directory' ) ) { $value = get_stylesheet_directory(); return is_string( $value ) ? $value : null; } return null; }; $this->templateDirectoryProvider = $templateDirectoryProvider ?? static function (): ?string { if ( function_exists( 'get_template_directory' ) ) { $value = get_template_directory(); return is_string( $value ) ? $value : null; } return null; }; $this->fileExistsProbe = $fileExistsProbe ?? static fn ( string $path ): bool => is_file( $path ); } /** * Resolve `$key` to an absolute filesystem path. * * @param TemplateKey $key Validated §6.4 template key. * * @return string Absolute filesystem path (post-realpath). * * @throws TemplateResolutionException When no candidate file is * found in any of the §6.5 * search locations. */ public function resolve( TemplateKey $key ): string { $searched = array(); foreach ( $this->candidates( $key ) as $candidate ) { $searched[] = $candidate['path']; if ( ! ( $this->fileExistsProbe )( $candidate['path'] ) ) { continue; } $realCandidate = realpath( $candidate['path'] ); if ( $realCandidate === false ) { continue; } $realBase = realpath( $candidate['base'] ); if ( $realBase === false ) { continue; } if ( ! str_starts_with( $realCandidate, $realBase . DIRECTORY_SEPARATOR ) ) { continue; } return $realCandidate; } // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw TemplateResolutionException::notFound( $key->value(), $searched ); } /** * Resolve `$key` to an absolute filesystem path, returning null * instead of throwing when no candidate file is found. * * @param TemplateKey $key Validated §6.4 template key. */ public function tryResolve( TemplateKey $key ): ?string { try { return $this->resolve( $key ); } catch ( TemplateResolutionException $unused ) { return null; } } /** * Un-resolved plugin-default candidate path for `$key`. * * @param TemplateKey $key Validated §6.4 template key. */ public function pluginTemplatePath( TemplateKey $key ): string { return $this->pluginTemplateDir . DIRECTORY_SEPARATOR . $key->value() . '.php'; } /** * Ordered list of candidate paths {@see self::resolve()} would * probe for `$key`, including only steps whose provider * supplied a non-empty base directory. * * @param TemplateKey $key Validated §6.4 template key. * * @return list */ public function searchedPaths( TemplateKey $key ): array { $paths = array(); foreach ( $this->candidates( $key ) as $candidate ) { $paths[] = $candidate['path']; } return $paths; } /** * Build the ordered list of candidate `(base, path)` pairs the * §6.5 search order produces for `$key`, skipping steps whose * provider did not supply a usable base directory. * * @param TemplateKey $key Validated §6.4 template key. * * @return list */ private function candidates( TemplateKey $key ): array { $candidates = array(); $stylesheetDir = $this->normalizeProvidedDirectory( ( $this->stylesheetDirectoryProvider )() ); if ( $stylesheetDir !== null ) { $base = $stylesheetDir . DIRECTORY_SEPARATOR . 'affilun' . DIRECTORY_SEPARATOR . 'templates'; $candidates[] = array( 'base' => $base, 'path' => $base . DIRECTORY_SEPARATOR . $key->value() . '.php', ); } $templateDir = $this->normalizeProvidedDirectory( ( $this->templateDirectoryProvider )() ); if ( $templateDir !== null ) { $base = $templateDir . DIRECTORY_SEPARATOR . 'affilun' . DIRECTORY_SEPARATOR . 'templates'; $candidates[] = array( 'base' => $base, 'path' => $base . DIRECTORY_SEPARATOR . $key->value() . '.php', ); } $candidates[] = array( 'base' => $this->pluginTemplateDir, 'path' => $this->pluginTemplatePath( $key ), ); return $candidates; } /** * Normalize a provider-supplied directory string. Returns null * when the provider returned null, the empty string, or a * whitespace-only string; otherwise returns the trimmed value * with trailing separators removed. * * @param string|null $value Raw provider output. */ private function normalizeProvidedDirectory( ?string $value ): ?string { if ( $value === null ) { return null; } $trimmed = trim( $value ); if ( $trimmed === '' ) { return null; } return rtrim( $trimmed, '/\\' ); } }