Files
f1den 29d10ce4cf Тема магазина по макету Figma
Каталог, карточка товара, корзина, оформление заказа и блог на WooCommerce.

Стили разложены по слоям: токены из Figma, база, компоненты, страницы,
брейкпоинты. Цвета, типографика, радиусы и отступы взяты из Variables и
текстовых стилей макета, ниже по слоям хардкода нет.

Иконки — Phosphor, вставляются инлайном как SVG, символами ничего не рисуется.
Стили WooCommerce отключены, вёрстка своя, !important не используется.
2026-07-30 22:38:49 +03:00

187 lines
5.7 KiB
PHP

<?php
/**
* URL handling.
*
* @package VyatkaShop
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Build links against the host the visitor actually asked for.
*
* WordPress stores one canonical home URL, so a site reachable under more than
* one name (a bare host and a proxied domain, say) would otherwise send people
* back to the stored one mid-session. Only the request host is trusted here,
* and only when it matches a host WordPress already knows about, so this cannot
* be driven by a forged Host header.
*
* @param string $url Complete URL generated by WordPress.
* @return string
*/
function vyatka_shop_request_host_url( string $url ): string {
$request_host = vyatka_shop_request_host();
if ( '' === $request_host || ! vyatka_shop_host_is_known( $request_host ) ) {
return $url;
}
$parts = wp_parse_url( $url );
if ( ! is_array( $parts ) ) {
return $url;
}
$forwarded_proto = isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] )
? strtolower( trim( explode( ',', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ) )[0] ) )
: '';
$scheme = 'https' === $forwarded_proto || is_ssl() ? 'https' : 'http';
$rewritten = $scheme . '://' . $request_host;
$rewritten .= $parts['path'] ?? '';
$rewritten .= isset( $parts['query'] ) ? '?' . $parts['query'] : '';
$rewritten .= isset( $parts['fragment'] ) ? '#' . $parts['fragment'] : '';
return $rewritten;
}
add_filter( 'home_url', 'vyatka_shop_request_host_url' );
add_filter( 'site_url', 'vyatka_shop_request_host_url' );
/**
* The host from the current request, without its port.
*/
function vyatka_shop_request_host(): string {
if ( ! isset( $_SERVER['HTTP_HOST'] ) ) {
return '';
}
return strtolower( preg_replace( '/:\d+$/', '', sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) ) );
}
/**
* Hosts this installation answers to: whatever is stored in the options, plus
* anything a site owner adds through the filter.
*
* @return array<int,string>
*/
function vyatka_shop_known_hosts(): array {
$hosts = array();
foreach ( array( 'home', 'siteurl' ) as $option_name ) {
$host = wp_parse_url( (string) get_option( $option_name ), PHP_URL_HOST );
if ( is_string( $host ) && '' !== $host ) {
$hosts[] = strtolower( $host );
}
}
// Sites behind a proxy answer to a name the stored options do not know.
// Declare those in wp-config.php:
// define( 'VYATKA_SHOP_HOSTS', 'shop.example.com, www.example.com' );
if ( defined( 'VYATKA_SHOP_HOSTS' ) ) {
foreach ( explode( ',', (string) VYATKA_SHOP_HOSTS ) as $host ) {
$host = strtolower( trim( $host ) );
if ( '' !== $host ) {
$hosts[] = $host;
}
}
}
/**
* Additional hosts the site is served under.
*
* @param array<int,string> $hosts Known hostnames.
*/
return array_values( array_unique( (array) apply_filters( 'vyatka_shop_known_hosts', $hosts ) ) );
}
/**
* @param string $host Hostname to test.
*/
function vyatka_shop_host_is_known( string $host ): bool {
return in_array( strtolower( $host ), vyatka_shop_known_hosts(), true );
}
/**
* Strip the origin from internal links in the finished HTML.
*
* Rewriting only the rendered document leaves redirects, REST responses and
* WP-CLI output fully qualified, while the markup a browser receives stays
* usable under whichever host served it.
*
* @param string $html Completed response body.
* @return string
*/
function vyatka_shop_relative_frontend_urls( string $html ): string {
foreach ( headers_list() as $header ) {
if ( 0 === stripos( $header, 'Content-Type:' ) && false === stripos( $header, 'text/html' ) ) {
return $html;
}
}
$hosts = vyatka_shop_known_hosts();
$request_host = vyatka_shop_request_host();
if ( '' !== $request_host ) {
$hosts[] = $request_host;
}
$absolute_bases = array();
foreach ( array_unique( $hosts ) as $host ) {
$absolute_bases[] = 'https://' . $host;
$absolute_bases[] = 'http://' . $host;
$absolute_bases[] = '//' . $host;
}
// Longest first, so "//host" never truncates "https://host".
usort( $absolute_bases, static fn( string $left, string $right ): int => strlen( $right ) <=> strlen( $left ) );
return str_replace( $absolute_bases, '', $html );
}
/**
* Buffer ordinary frontend documents so their internal links can be made
* relative without changing the site's canonical configuration.
*/
function vyatka_shop_start_relative_url_buffer(): void {
if ( is_admin() || wp_doing_ajax() || wp_is_json_request() || is_feed() ) {
return;
}
ob_start( 'vyatka_shop_relative_frontend_urls' );
}
add_action( 'template_redirect', 'vyatka_shop_start_relative_url_buffer', 0 );
/**
* Recover stale filtered category URLs when the selected page no longer exists.
*
* Filter forms always target page one, but bookmarked or cached /page/N/ URLs
* can still reach WordPress directly and become a 404 after the result set is
* narrowed. Preserve their query string and retry the category from page one.
*/
function vyatka_shop_redirect_stale_filtered_category_page(): void {
if ( ! is_404() || empty( $_GET ) ) {
return;
}
$filter_keys = array(
'filter_min_price',
'filter_max_price',
'feature',
'collection',
'per_page',
'orderby',
);
if ( ! array_intersect( $filter_keys, array_keys( $_GET ) ) ) {
return;
}
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
$request_path = wp_parse_url( $request_uri, PHP_URL_PATH );
if ( ! is_string( $request_path ) || ! preg_match( '#^(.*/product-category/[^/]+)/page/[1-9][0-9]*/?$#u', $request_path, $matches ) ) {
return;
}
$target = add_query_arg( wp_unslash( $_GET ), trailingslashit( $matches[1] ) );
wp_safe_redirect( $target, 302, 'Vyatka Shop' );
exit;
}
add_action( 'template_redirect', 'vyatka_shop_redirect_stale_filtered_category_page', -10 );