WOOT
Plugin de WooCommerce que muestra el catálogo de la tienda como tablas ordenables y filtrables con añadir al carrito en cada fila.
- Primera versión
- Instalaciones activas
- 1000+
- Valoración
- Leer reseñas
Sobre mí
WOOT muestra un catálogo de WooCommerce como una tabla: una fila por producto, una columna para cualquier campo de producto, atributo, taxonomía o clave meta, y un control de añadir al carrito en cada fila. Las tablas se configuran en el panel de administración o se describen en línea en un shortcode [woot]. El mismo motor dibuja también el carrito, las listas de variaciones, los upsells, los cross-sells, los productos relacionados y las tablas de un solo producto.
Lo difícil es que una columna tiene que hacer tres trabajos: mostrar una celda, ordenar la consulta del servidor y actuar como filtro. Los productos son de varios tipos (simple, variable, agrupado, externo, variación) que se comportan de forma distinta en una celda y en el carrito. Todos los datos de las tablas pasan por un endpoint AJAX público, que debe seguir siendo utilizable en catálogos grandes y no debe exponer borradores, tipos de contenido no públicos ni archivos del servidor.
El motor de tablas en el navegador (data-table-23.js) es JavaScript puro con clases ES6 y fetch. Guarda en caché las páginas por hash de la petición, cancela las peticiones obsoletas, puede pasar una tabla al modo en el navegador y consultarla con SQL sobre un array JSON, y cambia la disposición para pantallas pequeñas. jQuery se usa solo para disparar los propios eventos de carrito de WooCommerce.
WOOT · tablas de productos
qué se resolvió
Consultas de tablas en el servidor
shipped · 2020Qué aportaLos clientes paginan, ordenan y filtran las tablas de productos por precio, SKU o stock en su propio idioma, y las listas preajustadas, como los más vendidos, muestran el número exacto de artículos.
- Problema
- Cada cambio de página, clic de ordenación y cambio de filtro llega como una petición AJAX. Tiene que convertirse en un único WP_Query que respete la visibilidad del catálogo, los preajustes de la tabla, los filtros, el idioma del visitante y las claves de ordenación que WP_Query no admite.
- Solución
get_table_data() lee un conjunto de claves de petición en lista blanca, aplica los preajustes y los filtros como transformaciones de los argumentos de la consulta, modifica el SQL solo donde WP_Query se queda corto y devuelve las filas con un recuento total para la paginación.
- Stack
Un endpoint público atiende todas las tablas y a todos los visitantesClaves de petición en lista blanca, cada una saneada según su tipo, construir los argumentos de WP_Query, devolver filas y recuento
get_table_data() está registrada para usuarios con sesión iniciada y anónimos. Recorre $_REQUEST con un switch y conserva solo las claves conocidas: post_type, per_page, orderby, order, current_page, table_id, wp_columns_actions, filter_data, filter_provider, fields, lang, predefinition y shortcode_args_set. Los números pasan por intval, las claves por sanitize_key y las cargas JSON por json_decode y un sanitizador recursivo.
Dos de estas claves son nombres de hooks. wp_columns_actions se pasa a apply_filters() para cargar el perfil de columnas, y filter_provider selecciona el filtro woot_filter_provider_{name} que añade las condiciones del filtro. Ambas se sanean como texto, pero no se comprueban frente a una lista de nombres registrados.
La misma función se llama desde PHP con un array para las tablas renderizadas al cargar la página, de modo que la ruta del servidor y la ruta AJAX comparten una implementación. Cada fila se produce llamando al closure de renderizado de cada columna solicitada, con el ID del producto conservado como pid para las acciones de celda.
Ordenar por precio, SKU o stock significa ordenar por postmetaLas columnas declaran meta_key y orderby; la consulta asigna las claves de ordenación a meta_value(_num)
Una entrada de columna como price lleva 'orderby' => 'meta_value_num' y 'meta_key' => '_price'. get_table_data() busca la clave de ordenación solicitada en el perfil de columnas y reescribe a partir de ella los argumentos de la consulta.
El desplegable de ordenación usa sus propias claves (price-desc, sku, stock-desc, sales y así sucesivamente), traducidas en un filtro woot_wp_query_args. Las ordenaciones por stock y por ventas totales excluyen los productos agrupados y externos con una cláusula product_type NOT IN, ya que esos tipos no tienen valor propio de stock ni de ventas. Para las ordenaciones del desplegable, el plugin llama además a WC()->query->get_catalog_ordering_args(), que añade las propias cláusulas de ordenación de WooCommerce para popularidad, valoración y precio, de modo que estas ordenaciones coinciden con las de la página de la tienda.
WP_Query no puede ordenar por post_statusEnganchar un filtro posts_orderby solo para esa consulta y quitarlo justo después de ejecutarla
Para orderby=post_status se añade un closure a posts_orderby antes de WP_Query y se quita después, de modo que otras consultas de la misma petición conservan su propia ordenación.
Los preajustes Top-N (más vendidos, más recientes) deben mostrar exactamente N productosLimitar posts_per_page a N y fijar found_posts en N para que el paginador cuente solo N artículos
El preajuste de más vendidos ordena por total_sales y el de más recientes por post_date, ambos con posts_per_page fijado en N. WP_Query informa de found_posts para todos los productos coincidentes, así que tras la consulta found_posts se fija en el límite del preajuste y el paginador muestra solo los N primeros.
Los preajustes necesitan incluir y excluir por varias taxonomíasAnalizar una cadena compacta de reglas a un tax_query anidado; invertir la relación para la exclusión
Una regla de preajuste se guarda como una cadena como product_cat:25,26|pa_color:19|rel:AND. get_by_taxonomy() la convierte en un grupo tax_query anidado y omite las taxonomías que ya no existen.
Para la variante de exclusión cada cláusula usa NOT IN y la relación se invierte (AND pasa a OR y viceversa), que es la ley de De Morgan: excluir los productos que coinciden con A y B significa conservar los que están fuera de A o fuera de B. Los preajustes de SKU asignan el SKU de una variación al ID de su producto padre, porque la tabla lista productos padre.
Las peticiones admin-ajax pierden el idioma del visitanteEnviar la configuración regional con cada petición y cambiar WPML a ella antes de consultar y renderizar
admin-ajax.php se ejecuta fuera del contexto de idioma del front-end. draw_table_data() escribe get_locale() en el JSON de la tabla, el navegador lo devuelve como lang, y get_table_data() lo pasa por el filtro woot_current_lang.
La clase de vocabulario engancha ese filtro y llama a $sitepress->switch_lang(), de modo que WP_Query devuelve los productos en el idioma correcto. Los títulos y las etiquetas de las columnas los traduce WOOT_Vocabulary::get() desde la tabla propia del plugin, cargada una vez por petición e indexada por configuración regional.
Modelo de columnas
shipped · 2020Qué aportaEl propietario elige qué detalles del producto aparecen como columnas de la tabla, incluidos los campos personalizados de su propia tienda, y puede copiar o mover configuraciones de tablas.
- Problema
- Una columna es más que una celda. El mismo campo debe mostrarse para cada tipo de producto, gobernar la ordenación y aparecer como un widget de filtro, y los propietarios de tiendas añaden sus propias taxonomías y claves meta.
- Solución
Cada tabla funciona con un perfil: un array de descriptores de campo cuyos closures renderizan la celda, cambian los argumentos de WP_Query para filtrar y describen el widget de filtro. Las taxonomías y las claves meta se añaden al perfil en tiempo de ejecución.
- Stack
Una definición de campo debe gobernar la celda, la ordenación y el filtroPerfil de descriptores con closures de renderizado, de argumentos de filtro y de interfaz de filtro para cada campo
WOOT_WooCommerce::action() devuelve el perfil. Cada clave es un campo con un título, un closure action que renderiza la celda para un ID de producto, datos de ordenación (order, orderby, meta_key) y, para los campos filtrables, los closures get_filter_query_args y get_filter_draw_data. El índice 0 contiene los ajustes de la tabla: tipo de contenido, estados permitidos, proveedor de filtro.
Los perfiles se registran por nombre y se resuelven con apply_filters sobre la acción de la tabla. El carrito, las variaciones, la vista de un solo producto e incluso las propias pantallas de administración del plugin (lista de tablas, claves meta, vocabulario) funcionan sobre el mismo motor con sus propios perfiles.
Cada taxonomía y atributo de producto debería ser una columnaColumnas a partir de get_object_taxonomies('product') menos las internas, mostradas como árboles de términos
product_type, product_visibility, product_shipping_class y translation_priority de WPML se quitan de la lista. La celda muestra los términos propios del producto mediante build_taxonomy_tree() limitado a los ID de término asignados, como enlaces o texto plano según la opción de la columna.
El lado del filtro añade una cláusula tax_query cuyo operador (IN, NOT IN, AND) procede de los ajustes de la tabla y se comprueba frente a esa lista blanca.
Los propietarios de tiendas necesitan columnas para claves meta arbitrariasUn registro meta tipado (texto, número, calendario) proporciona el modo de ordenación y el filtro de cada clave
Las claves meta se guardan por tabla en woot_tables_meta con un tipo. assemble_meta_for_system() convierte cada una en un campo del perfil: el texto se ordena por meta_value y se filtra con LIKE; el número se ordena por meta_value_num y se filtra con un BETWEEN numérico sobre un control deslizante de rango; el calendario se filtra como DATETIME o como marca de tiempo unix numérica, según el campo, porque plugins como ACF guardan las fechas en formatos distintos.
Los campos se combinan en el perfil mediante el filtro ext_{action}, y cada clave aparece también en el desplegable de ordenación con entradas ascendente y descendente.
Filas por columnas repiten las mismas búsquedas de productos y opcionesCachés estáticas a nivel de petición para productos, opciones de tabla, filas de columnas y filas meta
Cada closure de columna necesita el objeto WC_Product, así que get_product() mantiene un mapa estático de resultados de wc_get_product(). Las opciones de tabla se decodifican de JSON una vez por tabla, las filas meta se guardan en caché por el md5 de la cadena SQL, y el constructor de filtros resuelve el perfil una vez en lugar de por campo.
Las configuraciones de tablas necesitan su propio almacenamiento, clonación y transferenciaCuatro tablas propias mediante dbDelta; clonar copia columnas y meta; importar rellena desde JSON
install.php crea woot_tables, woot_tables_columns (indexada por table_id e is_active, ordenada por pos_num), woot_tables_meta y woot_vocabulary. El orden de columnas por arrastrar y soltar llega como una lista de ID y se escribe de vuelta como pos_num.
clone() copia la fila de la tabla, luego sus columnas y claves meta bajo el nuevo ID de tabla. La exportación vuelca las cuatro tablas y las opciones como JSON; la importación trunca cada tabla e inserta las filas, tras una comprobación de capacidad y nonce.
Filtrado
shipped · 2020Qué aportaLos clientes acotan la tabla por rangos, fechas, categorías y texto, y las tablas siguen al plugin de filtros existente de la tienda en la misma página.
- Problema
- Los filtros llegan del navegador como un mapa plano clave/valor y deben convertirse en argumentos de WP_Query: rangos meta, cláusulas de taxonomía, búsqueda de texto y rangos de fechas. Las tiendas que ya usan un plugin de filtros esperan que la tabla lo siga.
- Solución
Un proveedor de filtros entrega cada clave al closure de consulta de la propia columna. Los plugins de filtros externos se conectan como proveedores alternativos que devuelven una lista de ID de producto.
- Stack
Un filtro de rango de fechas necesita dos entradas para una columnaDividir un campo de calendario en key y key---to; el proveedor lo quita e invierte compare
Cuando se construye el formulario de filtros, un campo de calendario se duplica bajo una clave ---to con las etiquetas From y To. A la vuelta, provider() ve una clave desconocida que termina en ---to, quita el sufijo y llama al mismo closure de la columna con un indicador, de modo que el closure compara con <= en lugar de >=.
Los controles deslizantes de rango necesitan límites antes de que haya ningún filtroCalcular MIN y MAX de la clave meta en SQL cuando el administrador dejó los límites sin definir
Si un campo de rango no tiene mínimo o máximo configurado (-1), get_field_drawing_data() ejecuta SELECT MIN o MAX(cast(meta_value as UNSIGNED)) sobre postmeta para la clave meta del campo mediante $wpdb->prepare. El nombre de la función procede de una lista blanca interna, no de la petición. El control deslizante de número de comentarios lee sus límites de la tabla posts.
La búsqueda de texto debe cubrir los campos elegidos sin inyección SQLCláusula posts_where con esc_like y prepare; lista de campos limitada a tres columnas de post
Los campos de búsqueda proceden del shortcode, pero se filtran a post_title, post_content y post_excerpt. Cada uno se convierte en LOWER(field) LIKE %s con el valor escapado por esc_like y enlazado por prepare.
En el navegador, una búsqueda se dispara con Enter solo cuando la entrada alcanza su minlength, y un Enter vacío o Escape quita el término, lo que evita que palabras parciales cortas lleguen al servidor.
Las tablas deben seguir el filtro de HUSKY (WOOF) en la misma páginaReproducir la petición de WOOF, ejecutar su predicción de ID y pasar los ID a post__in
Con filter_provider=woof, el shortcode captura los datos de la petición de WOOF en el JSON de la tabla. En cada llamada AJAX el proveedor restaura $_GET a partir de él, pide todos los resultados, llama a woof_products_ids_prediction() y lee woof_wp_query_ids, aceptando un array o una cadena separada por comas y conservando solo enteros positivos. Sin ID, post__in = [0].
El estado del filtro de MDTF debe llegar a la petición AJAXDecodificar el estado de MDTF, pedir solo los argumentos de consulta, quitar sus propios indicadores, precargar los ID de producto
El parámetro page_mdf se decodifica y MDTF se ejecuta con mdf_get_query_args_only para que devuelva argumentos de WP_Query en lugar de HTML. La cláusula meta mdf_hide_post y la cláusula product_visibility se quitan (WOOT añade las suyas), se descarta la ordenación y la consulta se ejecuta con fields=ids. La lista de ID viaja como datos de filtro y se convierte en post__in.
Añadir al carrito desde la tabla
shipped · 2020Qué aportaLos clientes compran cualquier tipo de producto directamente desde la tabla, añaden muchas filas a la vez, y el carrito permanece sincronizado con el resto de la tienda.
- Problema
- Cada tipo de producto necesita un control de compra distinto, las cantidades están limitadas por el stock, y el carrito de la tabla debe mantenerse sincronizado con el minicarrito del tema y con la página de carrito de WooCommerce.
- Solución
El servidor emite un descriptor compacto por tipo de producto, el navegador construye los controles a partir de él y envía al endpoint AJAX propio de WooCommerce. El contenido del carrito lo dibuja el mismo motor de tablas.
- Stack
Simple, variable, agrupado y externo necesitan controles distintosEl servidor emite un descriptor data-about tipado; JS construye un input, una casilla o un enlace emergente
add_to_cart.php comprueba is_purchasable() e is_in_stock() y, cuando el stock está gestionado, exige una cantidad distinta de cero. Para los productos simples el descriptor lleva el ID, el stock y el SKU; el cliente construye un input numérico con max igual al stock, de solo lectura para los productos que se venden individualmente, más una casilla para añadir en bloque.
Los productos variables y agrupados reciben un botón que abre una tabla emergente con sus hijos. Los productos externos enlazan a la URL externa. Los tipos desconocidos pasan por el filtro woot_woocommerce_add_to_cart para que otros plugins puedan proporcionar el marcado.
Añadir al carrito debe comportarse como el botón propio de WooCommercePOST a wc-ajax=add_to_cart, luego disparar wc_fragment_refresh para los minicarritos del tema
La URL del endpoint se toma de wc_add_to_cart_params cuando WooCommerce la imprime, de modo que los endpoints AJAX personalizados siguen funcionando. La cantidad se comprueba frente a max antes de enviar. Tras el éxito, el plugin dispara wc_fragment_refresh, actualiza su propia información del carrito y, según una opción de la tabla, redirige al carrito o al pago.
Los clientes quieren añadir muchas filas en una sola acciónFilas marcadas y cantidades en una sola petición; las variaciones reciben el ID del padre y los atributos
Una casilla en la cabecera de la columna del carrito selecciona todas las filas. add_to_cart_some() recorre los artículos enviados. Para una variación reconstruye el mapa attribute_* a partir de get_attributes('edit') y llama a WC()->cart->add_to_cart() con el ID del padre, la cantidad, el ID de la variación y ese mapa, que es la forma que espera el carrito de WooCommerce.
Los productos variables necesitan una tabla de sus variacionesEl proveedor de filtros cambia post_type a product_variation y limita post__in a los hijos
WOOT_WooCommerceVariations::filter_provider() fija post_type en product_variation y post__in en los hijos del padre tras comprobar que el padre es visible para el visitante. Las tablas de upsells, cross-sells, agrupados y relacionados comparten la clase base WOOT_WooCommerceUniversal y difieren solo en el origen de los ID.
En las filas de variaciones, al título se le añaden los nombres de los términos de atributo, y las columnas de taxonomía leen el slug de atributo propio de la variación en lugar de los términos asignados al post.
El propio carrito debería ser una tabla ordenableLos closures del perfil del carrito toman el ID de producto y la cantidad; el motor lo dibuja en modo JSON
El perfil woot_cart combina las columnas de producto configuradas con la cantidad, el subtotal (WC_Cart::get_product_subtotal) y eliminar. Cada cambio en el carrito responde con una tabla de carrito nueva, el número de artículos y el subtotal. Eliminar y actualizar encuentran la línea del carrito por product_id o variation_id.
El carrito emergente toma el aspecto de la tabla que lo abrió: el bloque de estilo del padre se copia y su ID de tabla se sustituye por el ID de la tabla emergente. Una opción de la tabla también puede ocultar los productos que ya están en el carrito mediante post__not_in.
Motor de tablas en el navegador
shipped · 2020Qué aportaLas tablas responden con rapidez al paginar y ordenar, varias tablas pueden compartir una página, y las tablas anchas siguen siendo utilizables en teléfonos.
- Problema
- La paginación, la ordenación y la búsqueda tienen que sentirse inmediatas, varias tablas con distintos aspectos pueden compartir una página, y las tablas anchas tienen que funcionar en teléfonos.
- Solución
data-table-23.js es una clase sin dependencias por tabla con una caché por hash de petición, cancelación de peticiones, un modo SQL opcional en el navegador y un cambio a disposición compacta.
- Stack
Una página o un orden ya vistos no deberían volver a consultar al servidorGuardar las respuestas en caché bajo un hash de todo el objeto de petición; vaciarla con eventos
__hash_object() serializa request_data a JSON y lo reduce a un hash de 32 bits que se usa como clave de caché. La caché está desactivada en los modos JSON y load-more, donde los datos ya son locales. Extensiones como compare y favourites disparan table23-reset-cache cuando sus datos cambian.
Los catálogos pequeños deberían ordenarse y buscarse sin viajes al servidorEl modo to_json carga todas las filas una vez y luego las ordena, pagina y busca con AlaSQL
Con el modo to_json el servidor devuelve todas las filas (posts_per_page -1). Tras la primera respuesta la tabla pasa sola al modo json, y draw_data() construye SELECT ... WHERE ... ORDER BY ... LIMIT ... OFFSET y lo ejecuta con AlaSQL sobre el array. Una tabla de una sola página encontrada en la caché se busca y ordena de la misma manera.
Una respuesta anterior lenta no debe sobrescribir una página más recienteAbortar el fetch anterior con AbortController antes de enviar el siguiente
Cada tabla mantiene su AbortController actual. Una nueva petición de página, ordenación o filtro aborta la pendiente, de modo que una respuesta anterior lenta ya no puede sobrescribir la página más reciente.
Las tablas anchas deben cambiar a una disposición compacta en teléfonosVolver a renderizar solo cuando el ancho cruza el punto de ruptura; manejador de resize bloqueado durante 999 ms
Cada tabla tiene su propio compact_view_width. El listener de resize compara el nuevo estado con el anterior y vuelve a dibujar solo ante un cambio; un indicador de bloqueo impide que se dispare de nuevo durante 999 ms. Las columnas marcadas como hide-on-mobile se omiten al construir las cabeceras, la cabecera flotante se desactiva en el modo compacto y las reglas media-hide por columna se generan como CSS para el ID HTML de la tabla.
Varias tablas con distintos aspectos en una páginaEl CSS del aspecto usa un marcador tid sustituido por el ID HTML de cada tabla en el momento del renderizado
get_theme_css() lee el aspecto primero de la carpeta woot-skins del tema y en segundo lugar de la carpeta skins del plugin, sustituye el marcador tid por el ID HTML de la tabla y elimina comentarios y espacios sobrantes. Cada regla está acotada a una sola tabla, por lo que los aspectos no se filtran entre tablas de la misma página.
Las celdas pueden contener tablas propiasLa configuración de la tabla anidada viaja como JSON en un div oculto y se instancia tras cada dibujado
draw_table_data() codifica la configuración con JSON_HEX_QUOT y JSON_HEX_TAG para que pueda situarse dentro de HTML sin escapar del elemento. Una vez dibujadas las filas, do_after_draw() encuentra las tablas dentro de las celdas y crea una instancia de WOOT_GeneratedTables para cada una, sin la barra de desplazamiento horizontal.
Seguridad de los endpoints públicos
en el productoQué aportaLas tablas públicas muestran solo los productos que deben ser visibles, y los visitantes no pueden usar peticiones de tabla para acceder a contenido oculto ni ejecutar código.
- Problema
- Los datos de las tablas, los elementos emergentes y las acciones del carrito se sirven a visitantes anónimos mediante admin-ajax, y las peticiones llevan ajustes de tabla, argumentos de shortcode, nombres de aspectos y nombres de hooks.
- Solución
La visibilidad se aplica en el momento de la consulta y por post, los nombres que llegan a rutas, CSS o SQL se reducen a conjuntos de caracteres seguros en un único punto, y cada escritura de administración comprueba capacidad y nonce.
- Stack
Las peticiones públicas de tablas llevan un tipo de contenido y un estado elegidos por el clienteForzar el estado publish para los visitantes; tipos no públicos solo para las tablas creadas sobre ellos
Justo antes de ejecutar WP_Query, post_status se restablece a publish para cualquiera sin read_private_posts, sea lo que sea que haya pedido la petición, el shortcode o los ajustes de la tabla. Un estado de los argumentos del shortcode se respeta solo para esos usuarios.
Los tipos de contenido no visualizables (cupones, pedidos, plantillas) se rechazan para usuarios sin edit_posts, salvo que la tabla guardada la haya creado un administrador para ese tipo de contenido.
Los elementos emergentes y los shortcodes toman un ID de producto del clientewoot_can_read_post() comprueba el estado, la visualizabilidad y, para las variaciones, el padre
Una función decide si un visitante puede ver los datos de un post: los posts publicados de tipos públicos están abiertos, los tipos no públicos necesitan derechos de edición, los demás estados necesitan read_post, y una variación es visible solo cuando lo es su padre. Las tablas emergentes de producto único, variaciones, upsells y otras, el shortcode de galería y las extensiones la llaman antes de renderizar.
Los nombres de aspectos llegan de varias fuentes y acaban en una ruta de archivobasename() más un filtro [A-Za-z0-9_-] en un único punto de estrangulamiento; ID de tabla filtrado antes del CSS
El aspecto puede venir de ?woot_skin=, de shortcodes, de extensiones o de los ajustes de la tabla, y todos pasan por get_theme_css(). Allí el nombre se reduce con basename() y un filtro de caracteres que elimina puntos y barras, de modo que no puede apuntar fuera de las carpetas de aspectos. El ID HTML de la tabla, que se escribe en el CSS impreso, se filtra de la misma manera para que las llaves o los corchetes angulares no puedan escapar del bloque de estilo.
El endpoint de los elementos emergentes despacha hooks y shortcodes por nombreSolo se pueden invocar hooks woot_, call_id se limpia, solo se renderizan los shortcodes del plugin
get_smth() es pública. Su rama genérica llama a apply_filters con un nombre de la petición solo cuando el nombre empieza por woot_, y el call_id que lo acompaña se reduce a letras, dígitos, guion bajo y guion antes de que cualquier consulta posterior lo vea. La rama shortcodes_set elimina todos los shortcodes que no están en la lista blanca del plugin antes de do_shortcode(). Las ramas de exportación e importación exigen manage_woocommerce.
Escrituras de administración y SQL construido a partir de nombres de columnasCapacidad y nonce en cada escritura; identificadores SQL reducidos a [A-Za-z0-9_]
Los manejadores de tablas, columnas, meta y vocabulario comprueban manage_woocommerce y el woot_settings_nonce antes de escribir. La extensión de adjuntos añade una comprobación de inicio de sesión y comprobaciones edit_post a nivel de objeto.
Los nombres de columnas, los campos de ORDER BY y las claves de WHERE no se pueden enlazar con prepare(), así que get_rows() los reduce a un conjunto de caracteres seguro, limita el orden a ASC o DESC y la lógica a AND u OR, y enlaza solo los valores.
Cifras
wordpress.org- Instalaciones activas 1000+ contador de wordpress.org.
- Reseñas 18 Media de 4.8 sobre 5.
- Años de soporte 6 Desde 4 de septiembre de 2020, última actualización 12 de octubre de 2026.