/** * =========================== * 1. URL Rewrite для фильтров * =========================== */ class WF_Rewrite { public static function init() { add_action('init', [__CLASS__, 'add_rewrite_rules']); add_filter('query_vars', [__CLASS__, 'add_query_vars']); } // Добавляем правила перезаписи для кастомных URL фильтров publi c static function add_rewrite_rules() { $filters = get_posts([ 'post_type' => 'filters', 'numberposts' => -1, 'post_status' => 'publish', ]); foreach ($filters as $filter) { $custom_url = trim(get_post_meta($filter->ID, 'wf_custom_url', true), '/'); if ($custom_url) { add_rewrite_rule("^{$custom_url}/?$", 'index.php?wf_filter_id=' . $filter->ID, 'top'); add_rewrite_rule("^{$custom_url}/page/([0-9]+)/?$", "index.php?wf_filter_id={$filter->ID}&paged=\$matches[1]", 'top'); } } flush_rewrite_rules(false); } // Регистрируем переменную запроса public static function add_qu ery_vars($vars) { $vars[] = 'wf_filter_id'; return $vars; } } WF_Rewrite::init(); /** * ==================================== ======= * 2. Главный WooCommerce фильтр по фильтрам * =========================================== */ add_action('pre_get_posts', function ($query) { // Только фронт, основной запрос и есть наш фильтр if (!is_admin () && $query->is_main_query() && get_query_var('wf_filter_id')) { $filter_id = get_query_var('wf_filter_id'); $selected_categories = get_post_meta($filter_id, 'wf_selected_categories', true) ?: []; $selected_attributes = get_post_meta($filter_id, 'wf_selected_attributes', true) ?: []; $excluded_products = get_post_meta($filter_id, 'wf_excluded_products', true) ?: []; $additional_products = get_post_meta($filter_id, 'wf_additional_products', true) ?: []; $filter_logic = get_post_meta($filter_id, 'wf_filter_logic', true) ?: 'AND'; // Woo-магия: превращаем в архив товаров $query->set('post_typ e', 'product'); $query->is_archive = true; $query->is_post_type_archive = true; $query->is_shop = true; add_filter('woocommerce_is_shop', '__return_true'); // Получаем товары из выбранных категорий $category_product_id s = []; if (!empty($selected_categories)) { $category_product_ids = get_posts([ 'post_type' => 'product', 'posts_per_page' => -1, 'fields' => 'ids', 'tax_query' => [[ 'taxonomy' => 'product_cat', 'field' => 'term_id', 'terms' => $selected_categories, ]], ]); } // Итоговый массив товаров $product_ids = array_unique(array_m erge($category_product_ids, $additional_products)); // Не выбрано ничего? Блокируем выдачу. if (empty($selected_ca tegories) && empty($additional_products)) { $query->set('post__in', [0]); } else { // Если после фильтрации не осталось товаров — тоже не показываем $query->set('post__in', !empty($product_ids) ? $product_ids : [0]); } // Фильтрация по атрибутам (terms/attributes) $tax_query = []; if (!empty($selected_attributes)) { foreach ($selected_attributes as $attr_id) { $term = get_term($attr_id); if ($term && !is_wp_error($term)) { $tax_query[] = [ 'taxonomy' => $term->taxonomy, 'field' => 'term_id', 'terms' => [$attr_id], 'operator' => 'IN', ]; } } } // Дополнительные GET фильтры через ?taxonomy=slug (если разрешен о) if (get_post_meta($filter_id, 'wf_allow_external_filters', true) === '1') { foreach ($_GET as $key => $value) { if (in_array($key, ['wf_filter_id', 'paged'])) continue; $taxonomy = sanitize_key(urldecode($key)); if (taxonomy_exists($taxonomy)) { $terms = array_map('sanitize_text_field', (array)$value); $tax_query[] = [ 'taxonomy' => $taxonomy, 'field' => 'slug', 'terms' => $terms, 'operator' => 'IN', ]; } } } if (!empty($tax_query)) { $query->set('tax_query', array_merg e([ 'relation' => ($filter_logic === 'AND') ? 'AND' : 'OR' ], $tax_query)); } // Исключённые товары if (!empty($excluded_products)) { $q uery->set('post__not_in', $excluded_products); } } }, 9999); /** * ================================= * 3. Кастомизация Woo/ S EO вывода * ================================= */ // Кастомный H1 страницы add_filter('woocommerce_page_title', function ($title) { if ($filter_id = get_query_var('wf_filter_id')) { $custom_title = get_post_meta($filter_id, 'wf_h1_title', true); return $custom_title ?: $title; } return $title; }); // SEO-описание под списком товаров add_action('woocommerce_after_main_content', function () { if ($filter_id = get_query_var('wf_filter_id')) { $custom_description = get_post_meta($filter_id, 'wf_seo_description', true); if (!empty($custom_description)) { echo '
'; echo wpautop(wp_kses_post($custom_description)); echo '
'; } } }, 5); // Yoast SEO: подменяем title, description, canonical add_action('wp', function () { if ($filter_id = get_query_var('wf_filter_id')) { global $post; $post = get_post($filter_id); setup_postdata($post); add_filter('wpseo_title', fn($title) => get_post_meta($post->ID, '_yoast_wpseo_title', true) ?: $title, 99); add_filter('wpseo_metadesc', fn($desc) => get_post_meta($post->ID, '_yoast_wpseo_metadesc', true) ?: $desc); add_filter('wpseo_canonical', fn($can) => get_post_meta($post->ID, '_yoast_wpseo_canonical', true) ?: home_url($_SERVER['REQUEST_URI'])); wp_reset_postdata(); } }); /** * ================================== * 4. Кастомные хлебны е крошки (breadcrumbs) * ================================== */ function wf_custom_breadcrumbs($crumbs) { if (!get_query_var('wf_filter_id')) return $crumbs; $filter_id = get_query_var('wf_filter_id'); return [ [get_the_title(wc_get_page_id('shop')), get_permalink(wc_get_page_id('shop'))], [get_the_title($filter_id), home_url($_SERVER['REQUEST_URI'])] ]; } add_filter('wpseo_breadcrumb_links', 'wf_custom_breadcrumbs'); add _filter('woocommerce_get_breadcrumb', 'wf_custom_breadcrumbs'); /** * ================================== * 5. Слова-триггеры в заголовках товаров (the_title) * ================================== */ global $wf_title_processed_ids, $wf_product_title_index; $wf_title_processed_ids = []; $wf_product_title_index = 0; add_action('wp', function () { global $wf_global_filter_id; $wf _global_filter_id = get_query_var('wf_filter_id'); }); add_filter('the_title', function ($title, $post_id) { global $wf_ product_title_index, $wf_title_processed_ids, $wf_global_filter_id; if (!is_admin() && get_post_type($post_id) === 'product') { // Для уникальных товаров увеличиваем индекс if (!in_array($post_id, $wf_title_processed_ids)) { $wf_title_processed_ids[] = $post_id; $wf_product_title_index++; } $filter_id = $wf_global_filter_id; $trigger_word = get_post_meta($filter_id, 'wf_trigger_word', true); $trigger_action = get_post_meta($filter_id, 'wf_trigger_action', true); $trigger_text = get_post_meta($filter_id, 'wf_trigger_text', true); $trigger_frequency = max(1, intval(get_post_meta($filter_id, 'wf_trigger_frequency', true))); if ($wf_product_title_index % $trigger_frequency !== 0 || !$trigger_action || !$trigger_text) { return $title; } $words = explode(' ', $title); $trigger_found = false; foreach ($words as $i => $word) { if (mb_strtolower($word) === mb_strtolower($trigger_word)) { $trigger_found = true; switch ($trigger_action) { case 'before_trigger': array_splice($words, $i, 0, $trigger_text); break; case 'after_trigger': array_splice($words, $i + 1, 0, $trigger_text); break; case 'replace_trigger': $words[$i] = $trigger_text; break; } break; } } if (!$trigger_found && in_array($trigger_action, ['before_title', 'after_title'])) { if ($trigger_action === 'before_title') array_unshift($words, $trigger_text); else array_push($words, $trigger_text); } $title = implode(' ', $words); } return $title; }, 10, 2); /** * ======================================== * Редирект с зер кал на кастомный URL * ======================================== */ add_action('template_redirect', function () { if (is_admin()) return; $current_path = rtrim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_P ATH), '/'); $filters = get_posts([ 'post_type' => 'filters', 'numberpost s' => -1, 'post_status' => 'publish', ]); foreach ($filters as $filter) { $mirrors = get_post_meta($filte r->ID, 'wf_filter_mirrors', true); if (empty($mirrors) || !is_array($mirrors)) continue; // Получаем кастомный URL фильтра $custom_url = get_post_meta( $filter->ID, 'wf_custom_url', true); if (!$custom_url) continue; foreach ($mirrors as $mirror) { $mirror = trim($mirror); if (empty($mirror) || $mirror === '/') continue; // Получаем path + query из mirror $parsed = parse_url($mirr or); $mirror_path = rtrim($parsed['path'] ?? '', '/'); $mirror_query = $parsed['query'] ?? ''; // Текущий URL $current_path = rtrim(parse_url($_SERVER['REQ UEST_URI'], PHP_URL_PATH), '/'); $current_query = $_SERVER['QUERY_STRING'] ?? ''; // Сравниваем path и query if ($mirror_path === $current_pat h && $mirror_query === $current_query) { wp_redirect(home_url($custom_url), 301); exit; } } } }, 5); /** * ======================================== * Д обавляем коло нку и фильтрацию в списке в админке * ======================================== */ // 1. Добавляем колонки "URL" и "Перейти" add_filter('manage_filte rs_posts_columns', function($columns) { $columns['wf_custom_url'] = 'URL'; $columns['wf_custom_url_go'] = 'Перейти'; return $columns; }); // 2. Выводим данные в колонках add_action('manage_filters_posts_custom_column', function($column, $post_id) { if ($column === 'wf_custom_url') { $url = get_post_meta($post_id, 'wf_custom_url', true); echo $url ? esc_html($url) : '(нет)'; } if ($column === 'wf_custom_url_go') { $url = get_post_meta($post_id, 'wf_custom_url', true); if ($url) { echo 'Перейти'; } else { echo ''; } } }, 10, 2); // 3. Поиск по URL (и по названию фильтра) add_filter('posts_search', function($search, $query) { global $wpdb; if (!is_admin() || !$query->is_main_query() || $query->get('post_type') !== 'filters') { return $search; } $search_term = $query->get('s'); if ($search_term) { $search = ''; $search .= " AND ("; $search .= "{$wpdb->posts}.post_title LIKE '%" . esc_sql($search_term) . "%'"; $search .= " OR EXISTS (SELECT 1 FROM {$wpdb->postmeta} WHERE post_id = {$wpdb->posts}.ID AND meta_key = 'wf_custom_url' AND meta_value LIKE '%" . esc_sql($search_term) . "%')"; $search .= ")"; } return $search; }, 10, 2); // 4. Сортировка add_filter('manage_edit-filters_sortable_columns', function($columns) { $columns['wf_custom_url'] = 'wf_custom_url'; return $columns; }); /** * ======================================== * Запрет на пере ход по прямому URL * ======================================== */ add_action('template_redirect', function () { if (is_singular('filters')) { // Проверяем, не находимся ли мы на кастомном URL (например, твой обработчик уже вывел контент) // Если стандартная страница фильтра — выдаём 404 global $post; // Например, если мы не на своем кастомном URL (логика ниже подправляется под твой роутинг) // По умолчанию ВСЕ страницы CPT filters становятся 404! if (!defined('WF_CUSTOM_FILTER_PAGE')) { // Можешь определить константу при выводе кастомного урла global $wp_query; $wp_query->set_404(); status_header(404); nocache_headers(); include(get_query_template('404')); exit; } } // Запрет на архив фильтров (если вдруг кто-то туда попадет) if (is_post_type_archive('filters')) { global $wp_query; $wp_query->set_404(); status_header(404); nocache_headers(); include(get_query_template('404')); exit; } }); /** * ======================================== * 6. Yoast SEO s itemap: подмена URL на кастомные * ======================================== * Меняем ссылки в sitemap на значения из мета `wf_custom_url` * для любых постов/терминов, если мета задана. * Регистрируем хуки ПОСЛЕ загрузки Yoast. */ if (!function_exists('wf_register_yoast_sitemap_overrides')) { function wf_register_yoast_sitemap_overrides() { static $done = false; if ($done) { return; } $done = true; // Хелпер: абсолютный URL из wf_custom_url (post/term) $wf_get _custom_abs_url = function ($entity) { $raw = ''; if ($entity instanceof WP_Post) { $raw = get_post_meta($entity->ID, 'wf_custom_url', true); } elseif ($entity instanceof WP_Term) { $raw = get_term_meta($entity->term_id, 'wf_custom_url', true); } $raw = is_string($raw) ? trim($raw) : ''; if ($raw === '') return null; // Абсолютный? — вернём как есть (без дубля завершающего слэша) if (filter_var($raw, FILTER_VALIDATE_URL)) { return rtrim($raw, '/'); } // Относительный путь → соберём абсолютный $path = '/' . ltr im($raw, '/'); return rtrim(home_url($path), '/'); }; // Новый API Yoast: любая запись sitemap (post/term) add_filte r('wpseo_sitemap_entry', function ($url, $type, $obj) use ($wf_get_custom_abs_url) { $custom = $wf_get_custom_abs_url($obj); if ($custom) { // Не навязываем слэш к URL с query/fragment $url['loc'] = strpos($custom, '?') === false && strpos($custom, '#') === false ? trailingslashit($custom) : $custom; } return $url; }, 10, 3); // Старый API Yoast: постовые URL add_filter('wpseo_xml_sitema p_post_url', function ($permalink, $post) use ($wf_get_custom_abs_url) { $custom = $wf_get_custom_abs_url($post); if (!$custom) return $permalink; return strpos($custom, '?') === false && strpos($custom, '#') === false ? trailingslashit($custom) : $custom; }, 10, 2); // Сброс кэша sitemap при изменении wf_custom_url add_action(' save_post', function ($post_id) { if (wp_is_post_revision($post_id)) return; $val = get_post_meta($post_id, 'wf_custom_url', true); if ($val !== '' && $val !== null) { do_action('wpseo_invalidate_sitemap_cache'); } }); add_action('edited_term', function ($term_id) { $val = get_term_meta($term_id, 'wf_custom_url', true); if ($val !== '' && $val !== null) { do_action('wpseo_invalidate_sitemap_cache'); } }); } // 1) Идеально — когда Yoast сообщает, что загрузился add_action ('wpseo_loaded', 'wf_register_yoast_sitemap_overrides'); // 2) Подстраховка: если Yoast уже загружен к init — тоже регистри руем add_action('init', function () { if (defined('WPSEO_VERSION')) { wf_register_yoast_sitemap_overrides(); } }, 20); }

Fatal error: Uncaught Error: Class 'WF_Rewrite' not found
in /var/www/kompasgid.ru/wp-content/plugins/woocommerce-filters/woocommerce-filters.php on line 33

Call stack:

  1. {closure}()
    wp-includes/class-wp-hook.php:324
  2. WP_Hook::apply_filters()
    wp-includes/class-wp-hook.php:348
  3. WP_Hook::do_action()
    wp-includes/plugin.php:517
  4. do_action()
    wp-settings.php:550
  5. require_once()
    wp-config.php:38
  6. require_once()
    wp-load.php:50
  7. require_once()
    wp-blog-header.php:13
  8. require()
    index.php:17

Query Monitor