Idioma

Contactar

Plugin de WooCommerce · desde 2014 · aka WOOF

HUSKY

Un filtro de productos facetado para WooCommerce: taxonomías, atributos, precio, stock, campos meta y texto, como widget o shortcode, con recarga de página o AJAX.

Primera versión
Instalaciones activas
80.000+
Valoración
4.6 / 5 (336)Leer reseñas
  • PHP
  • WordPress
  • WooCommerce
  • WP_Query
  • WP_Tax_Query
  • WP_Meta_Query
  • MySQL
  • Transients
  • Caché de objetos
  • jQuery
  • AlaSQL
  • PDO
  • WPML
  • Polylang
Ver en wordpress.org wordpress.org

Sobre mí

HUSKY filtra los productos de WooCommerce por categorías, atributos, etiquetas, precio, estado de stock, valoración, autor, SKU, campos meta personalizados y texto. Está en wordpress.org desde 2014. Las ediciones gratuita y premium se construyen a partir de una única base de código; la edición se elige por el número de versión mayor, y las partes exclusivas de la edición premium se sustituyen por stubs o se limitan en la versión gratuita.

Cada estado del filtro acaba convertido en una consulta de WordPress sobre wp_posts, wp_postmeta y las tablas de términos, y las tiendas manejan decenas de miles de productos y variaciones. Mostrar cuántos productos devolvería cada opción supone una consulta más por cada término visible en cada visualización de página. Los productos variables, la visibilidad del catálogo, los impuestos y los plugins de divisas cambian, cada uno, lo que cuenta como coincidencia.

El plugin también se ejecuta dentro de un procesamiento de peticiones que no le pertenece: el análisis de reglas de reescritura de WordPress, la consulta principal de productos y las plantillas de WooCommerce, los page builders, los plugins multilingües y las capas de caché. La mayor parte del código descrito a continuación existe porque alguna de esas capas hacía que el enfoque directo fuera incorrecto o demasiado lento.

HUSKY · filtro de productos

qué se resolvió

ACTIVOdesde 2014

URL amigables para SEO en los estados del filtro

en el producto

Qué aportaLas páginas filtradas obtienen direcciones limpias y estables que los buscadores pueden indexar. El propietario decide qué páginas filtradas aparecen en Google y con qué título.

Problema
Los estados del filtro son cadenas de consulta (query strings) que los buscadores ignoran o rastrean sin límite. Las URL con ruta como /swoof/color-red/size-m/ no las conocen las reglas de reescritura de WordPress y entran en conflicto con las redirecciones canónicas.
Solución

Interceptar el análisis de la petición, quitar el segmento del filtro, dejar que WordPress resuelva el resto y reconstruir el estado del filtro a partir del segmento con una codificación fija y reglas de índice por profundidad.

Stack
  • WordPress rewrite API
  • PHP
  • JavaScript
  • Yoast SEO
Tareas · 6
/shop/swoof/color-red/size-xl/ no coincide con ninguna regla de reescritura y devuelve 404Cortar el segmento del filtro, enrutar el resto con las reglas de reescritura del núcleo y reconstruir los filtros a partir de él

Un enlace de filtro es la URL del archivo más el slug del filtro (swoof por defecto) y un segmento por cada filtro: /product-category/shoes/swoof/color-red-and-blue/size-xl/. El plugin no registra reglas de reescritura. url_process() se ejecuta en do_parse_request en el front end y termina de inmediato salvo que get_search_request() encuentre /<slug>/ en la ruta. Toma el segmento del filtro hasta /page/, ? o #, lo elimina y compara el resto (/product-category/shoes/ o /shop/page/2) con $wp_rewrite->wp_rewrite_rules() usando código copiado de WP::parse_request(): reglas de página detalladas (verbose), variables de consulta públicas y privadas, tipos de contenido visibles, taxonomías no consultables, wp_resolve_numeric_slug_conflicts() y el filtro request. A continuación llama a query_posts(), handle_404() y register_globals() (la acción parse_request antes de WordPress 6.0) y devuelve false, de modo que el núcleo no vuelve a analizar la URL.

parse_url_query() divide el segmento por '/' y compara cada parte con el mapa de nombres de get_all_items(): slugs de atributos sin pa_, slugs de taxonomías traducidos por WPML, price y las palabras fijas instock, onsale, featured, backorder_not_in. -and- y -or- pasan a ser comas, -to- un separador de rango y + un espacio; price-10-to-50 pasa a ser min_price y max_price. add_request() combina el resultado mediante el filtro woof_get_request_data, de modo que get_request_data() devuelve el mismo array que para una cadena de consulta ?swoof=1&pa_color=red. En la consulta principal, parse_query() establece entonces post_type product, marca la consulta como archivo de tipo de contenido y añade las meta queries, y tax_query() en woocommerce_product_query_tax_query sustituye las entradas de WooCommerce para las mismas taxonomías por la tax query del plugin.

Como el resto se enruta con las reglas existentes, la base de la tienda, la base de categorías de producto y la paginación de archivos siguen funcionando sin reglas nuevas: /page/N/ se corta antes de localizar el segmento del filtro y permanece en la ruta enrutada. parse_query() desactiva is_tax en las páginas filtradas, por lo que la categoría actual se recupera del último segmento de la URL limpia (get_term_from_cleared_url()). Cuando la página de la tienda es también la página de inicio, la corrección de redirect_canonical que se describe más abajo evita que WooCommerce redirija la URL del filtro a /.

Para una URL que coincide con una regla SEO, meta_head() imprime la canónica: la URL del filtro sin la cadena de consulta si el estado es indexable, con las canónicas de Yoast y SEOPress desactivadas; en caso contrario, la URL cortada en el slug del filtro, que es el archivo simple. Con Yoast SEO, sitemap_index() en wpseo_sitemap_index añade las URL que un administrador enumera en un textarea de ajustes, una por línea; la versión premium las escapa con esc_url_raw() y esc_xml().

El orden en que se usan los filtros no debe cambiar la URLEscribir los filtros en orden de clave; replicar la gramática -and- / -to- / + en PHP y JS

woof_get_submit_link() en url_parser.js ordena los filtros activos por clave y escribe cada uno como <name>-<values>, con ',' como -and-, un separador de rango como -to- y los espacios como +. El precio pasa a ser price-<min>-to-<max>; en stock, en oferta, destacado y backorder pasan a ser palabras fijas. parse_url_query() invierte la correspondencia.

Gracias a la ordenación, elegir primero el color y después la talla da la misma ruta que hacerlo al revés. Los valores dentro de un mismo filtro se escriben en el orden en que se seleccionaron.

El atributo pa_niveau y el campo meta niveau piden el mismo nombre de URLDetectar colisiones de nombres: la taxonomía conserva el nombre corto y el otro recibe un nombre alternativo único

Los nombres de URL omiten el prefijo pa_, así que un atributo y una clave meta pueden producir el mismo nombre, y una sola entrada del mapa ocultaría a la otra. resolve_seo_key_conflict() asigna el nombre corto a la taxonomía registrada con independencia del orden de los filtros en los ajustes, de modo que las URL existentes siguen siendo válidas, y deriva un nombre para el otro filtro a partir de su clave real, añadiendo un sufijo numérico si hace falta. Los slugs de taxonomías traducidos por WPML se resuelven antes de este paso.

Cada consulta de recuento vuelve a leer el estado del filtro desde la ruta de la URLMemorizar parse_url_query() por URL, pero solo después de wp_loaded

get_request_data() se llama desde cada consulta de recuento, y con las URL SEO cada llamada pasa por woof_get_request_data hasta parse_url_query(). Con Dynamic Recount y Hide Empty (premium), una sola página puede hacer más de mil de esas llamadas, por lo que el resultado del análisis se guarda en caché por URL.

La caché se activa solo después de wp_loaded, de modo que una llamada hecha antes de que existan las taxonomías y los ajustes no pueda fijar un mapa de nombres incompleto durante el resto de la petición.

Qué páginas filtradas indexar, y con qué título y metaIndexar por profundidad y por reglas de expresiones regulares con meta propia por regla; omitir en silencio los patrones rotos

WOOF_SEO cuenta los filtros activos. Por encima de index_deep (2 por defecto), la página recibe el ajuste 'Disable page indexing' (sí por defecto, es decir, noindex,nofollow mediante wp_robots) y no se comprueban las reglas. En ese valor o por debajo, una regla coincidente hace que la página sea indexable. Las reglas son patrones de URL con marcadores {any} compilados a expresiones regulares, cada una con título, meta descripción, H1 y texto SEO que pueden incluir los nombres de los términos seleccionados, y se guardan por idioma. Las etiquetas canónicas solo se imprimen para las URL que coinciden con una regla.

En la versión premium, preg_match se ejecuta bajo un manejador de errores temporal, de modo que una regla cuya URL no es una expresión válida se omite y se lista en un aviso de administración, y una regla vacía también se omite. La edición gratuita usa solo las dos primeras reglas.

WooCommerce 11 redirigía las URL de filtro cuando la tienda era la página de inicioCancelar solo la redirección a home_url, y solo para las URL que llevan el slug del filtro

Cuando la página de la tienda es también la página de inicio estática, WooCommerce 11.0.0 la establece como objeto consultado en los archivos de productos. redirect_canonical() trata entonces /swoof/... como una copia no canónica de la página de inicio y redirige a /, perdiendo todos los filtros. fix_front_page_shop_canonical() devuelve false solo cuando la página de inicio es la página de la tienda, la ruta contiene el slug del filtro y el destino de la redirección es la URL de inicio, de modo que la normalización de barras y slugs sigue funcionando en el resto de casos.

Recuento dinámico y caché de consultas

en el producto

Qué aportaCada opción del filtro muestra cuántos productos devolverá, así que los compradores evitan callejones sin salida. Los recuentos se guardan en caché, por lo que la tienda sigue siendo rápida en catálogos grandes.

Problema
Cada opción del filtro muestra cuántos productos devolvería con la selección actual. Eso supone una consulta de productos por cada término visible en cada visualización de página, y es el coste principal en los catálogos grandes.
Solución

Los recuentos salen de una consulta reducida que solo devuelve IDs y reproduce la semántica del filtro. Los resultados se guardan en caché por petición, en una tabla propia y en transients, y el formulario puede diferirse hasta que el visitante lo pida.

Stack
  • WP_Query
  • MySQL
  • Transients
  • Caché de objetos
Tareas · 6
El recuento de un término debe reflejar el resto de la selección actualReconstruir la consulta por término, incorporando el término según las reglas de selección simple o múltiple

dynamic_count() reconstruye la tax_query para cada término. Los términos ya seleccionados no reciben recuento. En radio y select (simple), el término sustituye al valor seleccionado de su taxonomía; en checkbox y multiselect (múltiple), se añade a él. Los términos de la página de categoría actual y de los de la taxonomía indicada en el atributo taxonomies del shortcode se combinan siempre con AND, de modo que los recuentos en una página de categoría se mantienen dentro de esa categoría.

La lógica por taxonomía (OR, AND, NOT IN) definida en los ajustes, los rangos de precio y valoración, las condiciones meta de las extensiones, el idioma de WPML y la visibilidad del catálogo se aplican igual que en la consulta de productos. Las extensiones declaran su comportamiento de recuento mediante html_type_dynamic_recount_behavior, de modo que los tipos color, imagen y etiqueta reutilizan este código.

Ocultar las opciones sin resultados se basa en estos recuentos: el término se cuenta y se omite cuando el recuento es 0, por lo que no puede ser más barato que mostrar los recuentos. Este interruptor es exclusivo de la edición premium; la edición gratuita lo fuerza a desactivado en las vistas.

Una consulta de recuento no debería cargar entradas ni ejecutar FOUND_ROWSHeredar de WP_Query con los métodos de found-rows y de bucle sin efecto, y obtener solo IDs

WP_QueryWOOFCounterIn extiende WP_Query y sobrescribe set_found_posts(), setup_postdata(), the_post() y have_posts(). La consulta de recuento se ejecuta con fields=ids y nopaging, de modo que MySQL devuelve una lista de IDs, WordPress no construye objetos de entrada y no lanza una segunda consulta FOUND_ROWS. El recuento es la longitud de la lista.

Los filtros posts_clauses de popularidad y valoración de WooCommerce se eliminan antes de la consulta de recuento, porque el orden no importa para un recuento y esos filtros añaden joins al SQL.

Los mismos recuentos se recalculan para cada visitante y cada páginaGuardar los recuentos en una tabla clave-valor indexada por el md5 de los argumentos completos de la consulta y vaciarla mediante cron

Con 'cache count data' activado, WP_QueryWoofCounter calcula el hash del JSON de los argumentos finales de la consulta, que ya incluyen las partes de idioma y visibilidad, y busca el hash en {prefix}woof_query_cache, una tabla de dos columnas con el hash como clave primaria. Las escrituras usan INSERT ... ON DUPLICATE KEY UPDATE, de modo que dos visualizaciones de página que escriben la misma clave a la vez no fallan. Un sitio con caché de objetos persistente puede mover los recuentos a wp_cache_* mediante el filtro woof_use_wp_cache.

No hay una forma barata de averiguar qué recuentos en caché afecta un producto nuevo o editado, así que un evento programado vacía (TRUNCATE) toda la tabla en el periodo configurado. Si el usuario de MySQL carece del privilegio CREATE, la página de ajustes imprime el SQL para ejecutarlo a mano y mantiene la opción desactivada.

La consulta de mín./máx. del slider de precio recorre postmeta en tiendas grandesMemorizar mín./máx. por hash del SQL en cada petición y en un transient, y borrarlo al guardar un producto

get_filtered_price() construye un único SELECT MIN/MAX sobre _price unido a la tax query y la meta query actuales. El mismo rango se pedía varias veces por página (mín. y máx. para cada filtro de precio), así que el resultado se memoriza en un array estático indexado por el md5 del SQL. Con 'Optimize price filter' activado, los resultados también van a un transient durante un día, y cualquier woocommerce_update_product lo elimina. El SQL pasa por woof_get_filtered_price_query, de modo que un sitio puede añadir sugerencias de índice (index hints).

Los árboles de términos se reconstruyen desde cero para cada taxonomía en cada páginaGuardar en caché los árboles ya construidos en transients indexados por argumentos e idioma actual

WOOF_HELPER::get_terms() carga todos los términos de una taxonomía con get_categories() y arma el árbol padre-hijo en PHP. El resultado se memoriza por petición y, con 'Cache terms' activado, se guarda en un transient cuya clave incluye la taxonomía, hide_empty, los indicadores de profundidad y el idioma actual de WPML o Polylang, de modo que cada idioma tiene su propio árbol en caché.

La limpieza recorre wp_options en busca de _transient_woof_terms_cache_% y elimina cada transient, ya que WordPress no tiene borrado por prefijo para los transients.

El recuento se ejecuta aunque el visitante nunca abra el filtroMostrar un botón en lugar del formulario y cargar el formulario por AJAX en el primer clic

Con start_filtering_btn=1 el servidor solo devuelve un botón, de modo que no se ejecuta ninguna consulta de términos al cargar la página. Al hacer clic, front.js envía woof_draw_products con el shortcode de productos establecido en 'woof_nothing': el servidor renderiza el formulario del filtro y ningún producto, se sustituye la zona de redibujado y se reinicializan los widgets. El botón se omite cuando ya hay una búsqueda activa, de modo que una página filtrada sigue mostrando su selección.

Ocultar el formulario con CSS o reglas display no ahorra nada, porque los recuentos se calculan en el servidor antes de enviar el HTML. En los hilos sobre rendimiento del foro de soporte hubo que insistir con frecuencia en este punto.

Trabajar dentro de la consulta de productos de WooCommerce

en el producto

Qué aportaEl filtrado sigue funcionando con el tema propio de la tienda, las páginas de categoría, los productos variables, los impuestos y las divisas, de modo que los compradores nunca ven artículos en tallas agotadas.

Problema
El filtro modifica la consulta principal de la tienda y depende de plantillas de WooCommerce que no le pertenecen. Tiene que seguir funcionando a través de los cambios en el modelo de datos de WooCommerce, con productos variables, visibilidad del catálogo, impuestos y plugins de divisas.
Solución

Engancharse tarde a parse_query y a la consulta de productos de WooCommerce, restaurar el estado de archivo que esperan las plantillas y tratar de forma explícita cada diferencia del modelo de datos por versión o por tabla.

Stack
  • WP_Query
  • WooCommerce
  • MySQL
  • jQuery
Tareas · 6
Los resultados filtrados deben renderizarse con la plantilla de tienda del temaReescribir las banderas de la consulta principal en parse_query con prioridad 9999 para que se renderice como archivo de productos

Cuando el slug de búsqueda está en la petición, parse_query() establece post_type=product e is_post_type_archive, y desactiva is_tax, is_tag, is_home, is_single e is_search. WooCommerce y el tema renderizan entonces el bucle de la tienda para cualquier URL filtrada. Las extensiones añaden sus condiciones meta mediante assemble_query_params(), y los operadores por taxonomía proceden del filtro woof_main_query_tax_relations.

Los mismos hooks no deben tocar las peticiones REST ni de Store API. Los manejadores terminan pronto en las URL /wp-json/ y parse_query se desengancha en rest_api_init, de modo que los temas de bloques y los clientes headless obtienen los resultados propios de WooCommerce.

Al filtrar en una página de categoría se pierde la categoríaTransportar el término como really_curr_tax y guardarlo en un transient indexado por la IP del visitante

Una URL de filtro ya no es el archivo del término, así que get_queried_object() deja de devolver la categoría. Cuando se renderiza el archivo, set_really_current_term() guarda el término mediante WOOF_STORAGE, que el plugin ejecuta en modo transient: un transient por visitante, indexado por un hash de la IP del visitante y conservado durante un día. El JS también añade really_curr_tax=<term_id>-<taxonomy> al enlace del filtro, y get_really_current_term() recurre a él cuando no hay nada guardado. El término se combina entonces con AND en la consulta de productos, en las consultas de recuento y en la búsqueda de texto. Las peticiones AJAX leen el término guardado pero nunca lo sobrescriben.

La visibilidad del catálogo se almacena de forma distinta según la versión de WooCommerceRamificar por versión: meta _visibility antes de la 3.0, product_visibility NOT IN después

WooCommerce 3.0 movió las banderas de visibilidad, destacado y sin stock de postmeta a la taxonomía product_visibility. product_visibility_not_in() convierte las banderas necesarias en cláusulas term_taxonomy_id NOT IN. generate_visibility_keys() añade outofstock cuando la tienda oculta los artículos sin stock; cuando la opción 'listen catalog visibility' está activada, añade además exclude-from-search siempre y exclude-from-catalog fuera de una búsqueda. Las tiendas más antiguas reciben en su lugar una condición meta _visibility.

Las mismas reglas se aplican a la consulta principal, la consulta del shortcode, las consultas de recuento, la búsqueda de texto y los datos del modo turbo. Si una de ellas difiere, los recuentos y los resultados no coinciden.

Un producto variable se muestra aunque la talla elegida esté agotadaBuscar en la tabla de búsqueda de atributos los padres de variaciones coincidentes agotadas y excluirlos

Un producto variable cuenta como en stock si alguna de sus variaciones lo está, así que una consulta simple de 'en stock + talla M' devuelve una camisa cuya variación M está agotada. Cuando el filtro de en stock se combina con filtros de atributos, posts_where() reúne los términos pa_* seleccionados, incluido el término que se está contando durante el recuento, y ejecuta dos consultas agrupadas sobre wc_product_attributes_lookup: padres con una variación coincidente sin stock y padres con una variación coincidente en stock. La diferencia se añade como ID NOT IN.

Un modo anterior, seleccionable en los ajustes del filtro de en stock, consulta las entradas product_variation por meta de atributo y _stock_status en lugar de la tabla de búsqueda. Es más lento y, por defecto, excluye a todos los padres que tienen una variación coincidente agotada; los padres que también tienen una coincidencia en stock se conservan solo cuando el filtro woof_exclude_existing_variations devuelve true.

Los límites de precio difieren de los precios almacenados con impuestos y divisasConvertir los límites de nuevo a la divisa base y quitar el impuesto incluido antes de la meta query

_price se almacena en la divisa base de la tienda y, en muchas tiendas, sin impuestos. Si el visitante usa una divisa de WPML (WCML), assemble_price_params() convierte los límites de nuevo a la divisa base. Si los precios se muestran con impuestos pero se almacenan sin ellos, el impuesto incluido de la tarifa estándar se resta de ambos límites. Para el extremo superior del slider, get_max_price() busca el producto con el precio máximo y pide a WooCommerce su precio con impuestos incluidos.

Con FOX (WOOCS), los valores del slider que se muestran se convierten mediante woocs_exchange_value mientras que la consulta mantiene cifras en la divisa base.

Modo AJAX: redibujar resultados, recuentos y partes de la página sin recargarlaUn único endpoint AJAX renderiza productos y formulario mediante shortcodes; el JS intercambia zonas y actualiza la URL

woof_draw_products() reconstruye $_GET a partir del enlace que habría abierto el visitante, renderiza [woof_products] y [woof] con ese estado y devuelve como JSON los productos, el formulario y fragmentos adicionales (número de resultados, ordenación, paginación). front.js sustituye cada fragmento, restituye los fragmentos que la respuesta no incluía, reinicializa los widgets y llama a history.pushState para que la URL pueda compartirse. onpopstate recarga la página cuando la cadena de consulta ha cambiado. Una bandera de bloqueo descarta los clics mientras hay una petición en curso.

El bucle de la tienda se envuelve enganchándose a woocommerce_before_shop_loop y woocommerce_after_shop_loop. Los temas que no disparan ambos hooks rompen el modo AJAX, lo que explica toda una familia de hilos de soporte con síntomas distintos y una sola causa.

Modo turbo: filtrar en el navegador

en el producto

Qué aportaEn los catálogos grandes, el filtrado se ejecuta en el navegador del comprador en lugar de en el servidor. Los resultados aparecen rápido y el servidor soporta menos carga en las horas de más tráfico.

Problema
En los catálogos grandes, cada clic en un filtro cuesta en el servidor un conjunto de joins de taxonomías y meta, además de una consulta de recuento por cada término visible.
Solución

Precalcular un índice JSON plano de productos y variaciones, cargarlo en el navegador y ejecutar el filtro como SQL sobre él con AlaSQL. El servidor solo renderiza los IDs coincidentes. Incluido en ambas ediciones.

Stack
  • PHP
  • JSON
  • AlaSQL
  • JavaScript
  • Cron
Tareas · 5
Los datos de un producto están repartidos entre posts, postmeta, términos y variacionesAplanar cada producto y variación en un único registro JSON: precios, términos, meta, stock

get_data_by_id() escribe el ID, el título, el SKU, el autor, el padre, el estado de stock, los slugs de términos de cada taxonomía filtrable (los atributos de variación para las variaciones), los campos meta configurados y una lista de pares de precio normal/precio de oferta. Un producto variable guarda un par por cada hijo, de modo que el navegador puede calcular su rango de precios con la misma regla que usa WooCommerce: el precio de oferta solo cuenta cuando es menor que el precio normal.

Los precios se guardan con impuestos cuando la tienda muestra precios con impuestos incluidos, y FOX se cambia a la divisa por defecto antes de leerlos, de modo que el índice queda en una sola divisa.

Indexar miles de productos choca con los límites de tiempo de PHPConstruir en pasos AJAX de 10 productos, con un archivo por idioma de WPML y por blog de multisitio

create_data_search_files() recibe un desplazamiento (offset), procesa 10 IDs de productos y variaciones con las reglas de visibilidad aplicadas, los fusiona en el archivo JSON y devuelve el siguiente desplazamiento; el script de administración itera hasta recibir -1. WPML se cambia al idioma por defecto y cada producto se escribe en el archivo de cada idioma mediante su ID de traducción. check_id_file() añade el ID del blog en multisitio para que los sitios no compartan archivo.

El límite por defecto es de 15,000 registros, ajustable con el filtro woof_turbo_mode_products_limit.

Una reconstrucción programada no debe servir un índice a medio escribirUn ejecutor de cron propio procesa porciones de IDs por petición e intercambia un archivo _temp al final

PN_WP_CRON_WOOF_TURBO_MODE guarda el estado del evento (lista completa de IDs, posición, próxima ejecución) en una opción y se ejecuta en init. Cada llamada a process() gestiona una porción de 10 IDs, y una limitación de 5 segundos almacenada en woof_cron_limit_do evita que una ráfaga de visualizaciones de página ejecute porciones una tras otra. Las porciones se escriben en data_<id>_temp_<lang>.json; cuando la lista se agota, do_recreate_file() mueve el archivo temporal sobre el activo, y los visitantes conservan el índice anterior hasta que el nuevo está completo.

El coste de este diseño es la obsolescencia: los productos añadidos después de la última reconstrucción son invisibles para el filtro, y la reconstrucción depende del tráfico del sitio igual que WP-Cron.

El estado del filtro tiene que convertirse en una consulta que el navegador pueda ejecutarGenerar cláusulas WHERE de AlaSQL con funciones propias para rangos de precio y visibilidad

turbo_mode.js convierte los valores actuales en una cadena WHERE: pertenencia a taxonomías mediante indexOf sobre los arrays de términos, comparaciones meta, LIKE para SKU y título, BETWEEN para sliders y fechas. Funciones personalizadas de AlaSQL calculan el precio mínimo y máximo de un producto a partir de su lista de variaciones y aplican la visibilidad del catálogo. Para los productos variables, el filtro de en stock selecciona primero los padres de variaciones sin stock que coinciden con los demás filtros. El recuento se ejecuta sobre la misma tabla en memoria.

Los IDs resultantes se envían a woof_draw_products como turbo_mode_ids, y el servidor renderiza una consulta post__in en lugar de los joins del filtro.

El navegador necesita los ajustes, pero contienen secretos del servidorQuitar las credenciales de la base de datos de estadísticas y las claves secretas de cron de los ajustes localizados

El JS reconstruye la lógica del filtro a partir de woof_settings, así que necesita en la página la mayor parte de esa opción. get_public_settings() elimina el bloque de estadísticas, que contiene las credenciales de la base de datos MySQL externa, y todas las cron_secret_key, y pasa el resto por un filtro para eliminar más datos. En la versión premium, el endpoint de reconstrucción exige un nonce y manage_woocommerce.

Estadísticas de búsqueda

en el producto

Qué aportaEl propietario ve qué filtran y buscan realmente los compradores, sin ralentizar la tienda ni llenar su base de datos con un registro que crece sin parar.

Problema
Registrar por qué filtran los compradores supone escribir en la ruta crítica de cada visualización de página filtrada, desde visitantes anónimos y bots, a menudo en hosting compartido.
Solución

Escribir una pequeña fila en bruto por petición en una tabla de preparación (staging) y mantener las estadísticas a largo plazo en un servidor MySQL independiente que la extensión requiere.

Stack
  • MySQL
  • PDO
Tareas · 2
El registro no debe multiplicar las escrituras ni verse inundado por un solo clienteRegistrar una vez por petición, eliminar duplicados por hash de petición+IP+día y limitar las filas en staging por IP

La extensión de estadísticas se engancha a woof_get_request_data, que se ejecuta muchas veces por página; una bandera estática limita el registro a la primera llamada, y las llamadas AJAX distintas del renderizado de productos se ignoran. Solo se conservan las claves de filtro elegidas en los ajustes, la petición se recorta a request_max_deep claves (5 por defecto) conservando el rango de precio, y se calcula su hash junto con la IP y la fecha.

Solo se inserta una fila si ese hash es nuevo y la IP tiene menos de user_max_requests filas (10 por defecto) en la tabla de staging.

Las estadísticas crecen sin límite dentro de la base de datos de la tiendaGuardar las estadísticas en un servidor MySQL independiente mediante PDO, con una tabla por mes

La extensión necesita su propio servidor MySQL, definido en las opciones. init_pdo() se conecta a él con PDO, ya que $wpdb no puede mantener una segunda conexión con otras credenciales. place_statdata_into_db() crea una tabla por mes con el nombre <year>_<month> e inserta las filas con sentencias preparadas.

Los informes consultan las tablas mensuales por rango de fechas, así que la base de datos de la tienda no almacena las estadísticas a largo plazo.

Refuerzo de seguridad e integridad de los ajustes

en el producto

Qué aportaLos visitantes anónimos no pueden usar el filtro para ejecutar código ni acceder a datos ocultos, y los ajustes guardados no se pierden ni se sobrescriben al hacer cambios.

Problema
El filtro recibe cadenas de consulta y envíos AJAX de visitantes anónimos, renderiza shortcodes y plantillas nombrados en esas peticiones, y guarda ajustes que contienen HTML, JavaScript y SQL. El changelog atribuye a investigadores externos una serie de informes de seguridad entre 2022 y 2026.
Solución

Tratar todo valor de la petición como datos: permitir solo shortcodes y plantillas conocidos, sanear los ajustes de forma recursiva según la ruta de la clave, exigir capacidades para todo lo que almacene código y combinar los ajustes en lugar de sobrescribirlos.

Stack
  • PHP
  • API de saneamiento de WordPress
  • WPML
  • Polylang
Tareas · 5
Las peticiones AJAX llevan el nombre de una plantilla PHP que hay que renderizarAceptar solo nombres de plantilla vistos antes en el contenido de la página; AJAX no puede añadir nuevos

[woof_products custom_tpl=...] se devuelve legítimamente en las llamadas AJAX, así que el valor no puede ni aceptarse sin más ni descartarse. sanitize_custom_tpl() conserva solo letras, dígitos, '_', '-' y '/', lo que elimina los puntos y las secuencias de recorrido de directorios. Un nombre renderizado a partir de contenido real de la página se añade a una opción de lista de permitidos (se conservan los últimos 20); durante AJAX solo se aceptan los nombres que ya están en esa lista.

Las rutas de inclusión construidas a partir de nombres de tipos de vista pasan por safe_view_slug(), que rechaza todo lo que no sea un slug simple.

Las peticiones AJAX llevan texto de shortcode que no debe ejecutar shortcodes arbitrariosExtraer del texto enviado solo las etiquetas propias del plugin con get_shortcode_regex

woof_draw_products() recibe de la página el texto de los shortcodes de productos y de formulario para que la salida AJAX coincida con la llamada original. check_shortcode() construye get_shortcode_regex() solo para las etiquetas del plugin ([woof_products] o [woof], más los alias del shortcode de productos del plugin) y conserva la primera coincidencia; todo lo demás de la cadena se descarta antes de do_shortcode().

Los ajustes mezclan HTML, JavaScript, slugs y texto plano a cualquier profundidadSanear de forma recursiva; clasificar cada campo por su clave o por su ruta en el árbol de ajustes

sanitize_array() recorre el árbol de ajustes, guarda cada valor bajo su clave saneada y clasifica cada campo por nombre o por su ruta en el árbol: el texto de las reglas SEO como HTML mediante wp_kses_post, las descripciones como textarea, las claves usadas en rutas de inclusión como slugs estrictos y todo lo demás como texto plano. Las reglas por ruta son necesarias porque las claves situadas en lo profundo del árbol, como text o description, son demasiado genéricas para enumerarlas globalmente.

El ajuste 'JavaScript after AJAX' solo pueden cambiarlo los usuarios con unfiltered_html.

La respuesta AJAX inyecta fragmentos de HTML, y la petición se reconstruye a partir de la URL del visitanteDescartar woof_redraw_elements del enlace analizado (premium); pasar cada fragmento por wp_kses

El renderizado AJAX reconstruye $_GET a partir del enlace de página enviado por el navegador, y la respuesta inyecta woof_redraw_elements en la página como HTML, por lo que un enlace manipulado no debe poder aportar esa clave. En la versión premium, la clave se elimina del enlace analizado. Los fragmentos legítimos (título SEO y H1, número de resultados, ordenación) los añaden filtros del lado del servidor, y cada fragmento pasa por wp_kses antes de devolverse.

Los colores e imágenes de los términos deberían definirse una sola vez para todos los idiomasCombinar los colores por término enviados sobre los almacenados; recurrir al término del idioma por defecto

La página de ajustes lista solo los términos del idioma actual de WPML o Polylang, así que un guardado envía colores solo para esos términos. woof_save_options() combina las entradas almacenadas de color, color_img e images_term_* por debajo de los valores enviados, de modo que los demás idiomas conservan los suyos.

Un término traducido sin color propio usa el de su original en el idioma por defecto, que encuentra get_original_language_term(). Lee el slug directamente de wp_terms, porque con el ajuste de ID de WPML get_term() podría devolver de nuevo el término traducido.

Depuración en producción

support forum

Qué aportaLos problemas de una tienda en producción se encuentran en su causa real, incluso cuando es el tema, el servidor, una caché u otro plugin.

Problema
El informe de una tienda en producción nombra un síntoma, no una causa. El mismo síntoma puede venir de una plantilla del tema, de una capa de caché u optimización, del servidor, de otro plugin o del propio filtro.
Solución

Reproducir en una instalación limpia o en una copia del sitio, descartar una capa cada vez y corregir donde está la causa: en el plugin para todos, o con un cambio específico en ese sitio.

Stack
  • Browser DevTools
  • Nginx
  • LiteSpeed Cache
  • jQuery
  • ACF
  • MySQL
Tareas · 5
Los conmutadores de subcategorías desaparecen y el filtrado falla siempre que la URL tiene alguna cadena de consultaSe aisló el fallo en el servidor con una prueba de parámetro GET arbitrario y después se corrigió la reescritura de Nginx

Incluso un parámetro sin sentido como ?dwdadd=1adw rompía la página, lo que demostró que el fallo estaba en el tratamiento de la cadena de consulta y no en el filtro. La configuración de Nginx insertaba un ? suelto después de php y descartaba todos los parámetros, así que el filtro nunca recibía sus argumentos.

La tienda deja de actualizarse tras filtrar una vez que LiteSpeed Cache está instaladoSe registró la respuesta AJAX para dejar a la vista un error de análisis JSON y se fueron descartando ajustes de LiteSpeed hasta llegar a CDN Mapping

La respuesta llegaba, pero JSON.parse fallaba por un carácter de escape incorrecto, lo que significaba que el CDN Mapping de LiteSpeed estaba reescribiendo URL dentro de ella. Desactivar CDN Mapping restableció el filtrado.

Los botones Filter y Reset actualizan la URL pero la lista de productos nunca se recargaSe rastreó hasta la versión slim de jQuery, se restauró jQuery completo y se sustituyó el núcleo de WordPress modificado del hosting por una instalación limpia

El sitio cargaba jQuery slim, que no tiene módulo AJAX, de modo que la petición de resultados filtrados nunca se enviaba mientras la URL sí cambiaba en el cliente; desactivar todos los demás plugins no cambió nada. La versión slim venía con un núcleo de WordPress modificado por el instalador automático del hosting; restaurar jQuery completo y reinstalar el núcleo lo solucionó.

Los sliders meta sobre valores decimales pequeños de ACF (alrededor de .005) no filtran nada o lo filtran todoSe añadió el filtro woof_slider_meta_query_type para poder fijar el cast SQL a DECIMAL por clave meta

Quien lo reportó reconstruyó el campo como un campo personalizado normal y el fallo persistió, así que ACF no era la causa; soporte lo reprodujo. La meta query comparaba los valores sin cast a DECIMAL, y el plugin recibió el hook woof_slider_meta_query_type, que recibe $type y $key.

El filtro se congela tras usar el botón Atrás del navegador; Search no hace nadaSe añadió un listener de pageshow que restablece el bloqueo de envío y el popup al restaurar desde bfcache, publicado en actualizaciones

Las páginas restauradas desde la caché de atrás/adelante mantenían establecido woof_submit_link_locked y el popup de carga abierto. El listener comprueba event.persisted o el tipo de navegación 2 y restablece ambos; el cambio pasó al plugin.

Cifras

wordpress.org
  • Instalaciones activas 80.000+ contador de wordpress.org.
  • Reseñas 336 Media de 4.6 sobre 5.
  • Años de soporte 12 Desde 1 de diciembre de 2014, última actualización 9 de octubre de 2026.