WOLF
Un editor con formato de hoja de cálculo para editar de forma masiva, filtrar, exportar y revertir entradas, páginas y cualquier tipo de contenido personalizado.
- Primera versión
- Instalaciones activas
- 4000+
- Valoración
- Leer reseñas
Sobre mí
WOLF (antes WPBE) es el equivalente para entradas del editor masivo de WooCommerce BEAR. Muestra las entradas de cualquier tipo de contenido registrado en una tabla editable, permite filtrarlas por campos básicos, taxonomías y meta, y después cambiar, eliminar o exportar todo el conjunto filtrado en una sola operación.
Lo difícil es que nada es fijo. Cada tipo de contenido trae sus propias taxonomías y claves meta, los valores meta pueden ser arrays serializados, listas de ID de imágenes o indicadores con valores personalizados, y el conjunto de destino pueden ser miles de entradas en un alojamiento compartido con límites de tiempo de PHP cortos.
El plugin responde a esto con un registro de campos reconstruido por tipo de contenido, un filtro que se guarda en el servidor y se compila en un único WP_Query, ejecuciones masivas dirigidas por el navegador en lotes de tamaño fijo, y un registro de historial que guarda el valor anterior de cada campo antes de escribirlo, de modo que una ejecución masiva también puede revertirse por lotes.
La versión premium y la versión gratuita de wordpress.org comparten una base de código; un único indicador activa los límites de la versión gratuita.
WOLF · editor masivo de entradas
qué se resolvió
Un editor para cualquier tipo de contenido
shipped · 2019Qué aportaEl personal edita cualquier tipo de contenido en una tabla que sigue siendo rápida en sitios grandes, con columnas configuradas por persona y por idioma.
- Problema
- Las entradas, las páginas y los tipos de contenido personalizados tienen taxonomías, claves meta y campos básicos aplicables distintos. Una tabla debe mostrar cualquiera de ellos y seguir siendo rápida en sitios con muchos registros.
- Solución
Un registro de campos se reconstruye para el tipo de contenido activo y la tabla funciona en el modo del lado del servidor de DataTables sobre WP_Query, de modo que cada petición lee una página de entradas.
- Stack
Cada tipo de contenido trae su propio conjunto de taxonomíasLas columnas de taxonomía se construyen con get_object_taxonomies() para el tipo de contenido activo
WPBE_SETTINGS::get_fields() combina la lista estática de campos de data/fields.php con una columna por cada taxonomía registrada para el tipo de contenido actual, y luego aplica el orden de columnas y los títulos guardados del usuario. Los campos nuevos que aparecen más tarde (una taxonomía nueva, una clave meta nueva) se añaden al final en lugar de perderse.
Las definiciones de campo llevan allow_post_types y prohibit_post_types. wrap_field_val() devuelve una celda 'restricted' cuando un campo no se aplica, por ejemplo los campos de estado y de comentarios para los adjuntos, el tipo MIME solo para los adjuntos, sticky solo para las entradas. Los adjuntos se consultan con el estado adicional 'inherit'.
Los sitios grandes no pueden cargar todas las entradas en el navegadorDataTables en el lado del servidor: cada página, ordenación y búsqueda es un WP_Query paginado
La tabla se configura con serverSide: true y llama a wpbe_get_posts con los parámetros de paginación de DataTables. WPBE_POSTS::gets() convierte start/length en paged/posts_per_page y cambia a la ordenación meta_value_num cuando la columna de ordenación es un campo meta numérico o de fecha.
Las filas por página están limitadas a 100 con el comentario del código 'to avoid 500 error on weak servers'. La versión gratuita lo limita a 10.
El código del editor no debe ralentizar el resto de wp-adminLas extensiones y los modelos se cargan solo en la página del editor y en las acciones AJAX wpbe_*
init() ejecuta primero una comprobación de rol y después is_should_init(), que devuelve true solo para page=wpbe o una acción AJAX cuyo nombre contenga 'wpbe_'. En otras pantallas de administración solo se registran la entrada de menú, el botón de la barra de administración y los avisos.
Las extensiones siguen una convención de nombres (ext/<slug>/<slug>.php, clase WPBE_<SLUG>) y reciben el almacenamiento, los ajustes y el modelo de entradas mediante init_vars(). El mismo cargador recoge también extensiones de terceros desde wp-content/wpbe_ext.
Cada usuario necesita sus propias columnas, por tipo de contenido si hace faltaOpciones guardadas por ID de usuario, opcionalmente divididas por tipo de contenido (ajuste premium)
Los ajustes residen en la opción wpbe_options_<user_id>. Cuando el interruptor 'separate settings' está activado, el tipo de contenido se añade a la clave, y la lista de campos meta registrados se divide por tipo de contenido de la misma manera. El tipo de contenido activo se recuerda por usuario en el almacenamiento del plugin.
El interruptor está desactivado en la versión gratuita.
En sitios con WPML el editor debe funcionar en el idioma actual de administraciónEl idioma actual de WPML se pasa a cada consulta; lista de sticky guardada por idioma
Cuando SitePress está presente, el filtro wpbe_current_language devuelve ICL_LANGUAGE_CODE, gets() llama a $sitepress->switch_lang() antes de construir WP_Query, y la opción de entradas sticky se lee y se escribe bajo sticky_posts_<lang>.
Edición masiva, eliminación y exportación por lotes
shipped · 2019Qué aportaLos cambios en miles de entradas terminan de forma fiable en alojamientos corrientes, con progreso visible y la posibilidad de detener la ejecución a mitad.
- Problema
- Editar miles de entradas en una sola petición PHP choca con los límites de tiempo y de memoria, y el usuario necesita ver el progreso y poder detener la ejecución.
- Solución
El navegador dirige la ejecución en lotes de ID de entradas de tamaño fijo; la propia operación se guarda una vez en el servidor bajo una clave masiva aleatoria.
- Stack
Una petición PHP no puede editar miles de entradasID resueltos una vez y luego enviados en lotes de 50 a un endpoint de trabajador sin estado
wpbe_bulk_posts_count guarda el formulario masivo bajo 'wpbe_bulk_<key>' (16 caracteres aleatorios generados en el navegador) y devuelve los ID que coinciden con el filtro actual, usando fields=ids y no_found_rows. El navegador divide esa lista en lotes de 50 y los envía uno tras otro a wpbe_bulk_posts, actualizando una barra de progreso y un indicador en la barra de administración. Cancelar aborta el XHR pendiente; wpbe_bulk_finish devuelve el total contado.
La eliminación masiva usa el mismo patrón con lotes de 10 y wp_trash_post(), de modo que las entradas eliminadas siguen siendo recuperables desde la papelera.
Tras una ejecución, el navegador vuelve a dibujar solo las filas afectadas cuando se cambiaron cuatro entradas o menos, y vuelve a dibujar la página actual en caso contrario.
Los campos de texto necesitan ediciones relativas al valor actualAñadir al final, añadir al principio, reemplazar (con o sin distinción de mayúsculas), establecer nuevo; macros por entrada
__process_text_data() lee el valor actual de cada entrada y aplica el comportamiento elegido. Los valores pueden contener {TITLE}, {ID}, {MENU_ORDER} y {meta:key}, resueltos por entrada, y macros de mayúsculas y minúsculas como {DO_STRING_UP} o {DO_STRING_TITLE} que usan las funciones mb_*, de modo que los títulos no latinos se tratan correctamente.
El meta numérico necesita cambios relativos y un redondeo propio de los preciosPor valor o porcentaje, fórmula con otra clave meta, rango aleatorio, redondeo a .05, .10 o .x9
__process_number_data() admite aumentar o disminuir por valor o porcentaje y, opcionalmente, combina el resultado con otro campo meta de la misma entrada (+, -, *, /, con la división por cero sustituida por 1) y con un número aleatorio de un rango escalado por un factor decimal.
El redondeo de la fracción a pasos de .05 o .10, o a una terminación como .99, ocurre en update_page_field() con el número de decimales configurado; los céntimos se convierten mediante un paso entero porque el módulo en coma flotante da resultados erróneos.
Las taxonomías jerárquicas y las planas necesitan una entrada de términos distintaID de término para las taxonomías jerárquicas, slugs de término uno por uno para las planas
WordPress espera ID para las taxonomías jerárquicas y nombres o slugs para las de tipo etiqueta. El trabajador masivo comprueba is_taxonomy_hierarchical(); para las taxonomías planas convierte cada ID en un slug, y para 'replace' escribe el primer término con append=false y el resto con append=true. 'Remove' usa wp_remove_object_terms().
El hook del historial se dispara manualmente antes de los cambios de términos, porque esta vía evita update_page_field().
Las exportaciones de selecciones grandes chocan con los mismos límitesCSV añadido por lotes de 10 mediante fputcsv; XML escrito en formato WXR de WordPress
El paso de recuento escribe la fila de cabecera; cada lote añade filas con fputcsv() sobre un archivo abierto en modo a+. El código señala que WP_Filesystem no tiene escritura línea a línea, y por eso aquí se usan funciones de archivo sin procesar.
La exportación XML construye un documento RSS con los espacios de nombres WXR 1.2 (wp:, content:, excerpt:), los términos como <category domain nicename> y el meta como wp:postmeta con CDATA, de modo que el archivo sigue el formato del exportador de WordPress. Los nombres de archivo llevan la fecha y el minuto de la exportación, y los archivos de más de dos días se eliminan mediante una comprobación limitada por un transient.
Filtrado por campos, términos y meta
shipped · 2019Qué aportaEl personal selecciona exactamente las entradas que quiere por campos, categorías, valores personalizados o listas de ID pegadas, y las ediciones y exportaciones se aplican a esa misma selección.
- Problema
- La edición masiva y la exportación se aplican a lo que coincida con el filtro, así que el filtro debe convertir muchas condiciones de campos en un único WP_Query correcto y mantenerse idéntico entre peticiones AJAX separadas y pestañas del navegador.
- Solución
El estado del filtro se guarda en el servidor bajo una clave aleatoria por pestaña y se compila en cláusulas tax_query, meta_query y posts_where en un hook que usan todas las consultas.
- Stack
Las peticiones de la tabla, de edición masiva y de exportación deben ver el mismo filtroFiltro guardado en el servidor bajo una clave de 16 caracteres generada por pestaña del navegador
El navegador genera wpbe_filter_current_key cuando se aplica un filtro y la envía con cada petición; el comentario del código fuente dice que esto permite la edición masiva por filtro en distintas pestañas. wpbe_apply_query_filter_data() lee el filtro almacenado y se aplica dentro de WPBE_POSTS::gets(), de modo que la lista de ID de la edición masiva la construye el mismo código que la tabla visible.
Coincidir con todos los términos de varios exige una cláusula tax_query por términoAND dividido en una cláusula tax_query por término; se añaden EXISTS y NOT EXISTS
Para el operador AND, cada término seleccionado se convierte en su propia cláusula unida por la relación externa AND, que es la solución alternativa a la que alude el comentario del código. Los demás operadores (IN, NOT IN) pasan la lista de términos tal cual. include_children lo controla un filtro en lugar del valor por defecto de WordPress.
Los rangos de meta numérico necesitan una conversión a DECIMAL con escala explícitaBETWEEN con conversión DECIMAL(30,20); límites unilaterales e iguales tratados
El comentario del código expone el caso: con DECIMAL sin precisión, una búsqueda de 100 a 150 devolvía también 99.xx. El filtro usa DECIMAL(30,20), cambia a >= cuando solo se establece 'from' y a = cuando ambos límites son iguales. El meta de fecha pasa primero por normalize_calendar_date().
Un valor meta vacío puede ser '' o una fila inexistente'Empty' se compila como value = '' OR NOT EXISTS; 'not empty' como value != ''
Las entradas creadas antes de que se introdujera una clave meta no tienen ninguna fila, así que una comprobación de igualdad por sí sola no las encuentra. El comportamiento 'empty' emite un meta_query anidado con relación OR.
La búsqueda de texto necesita OR, AND y NOT sin SQL sin procesarSintaxis breve (^ para OR, ~ para AND, ! para NOT) compilada a REGEXP preparado
posts_txt_where() pasa la entrada a minúsculas, la divide por ^ y construye condiciones LOWER(column) REGEXP / NOT REGEXP con los modos begin, end, exact y not. Cada valor se enlaza mediante $wpdb->prepare() y la columna debe estar en una lista de permitidos de cinco columnas de wp_posts.
Los usuarios pegan listas y rangos de IDEntrada como '12,40-55' expandida a post__in
Los elementos separados por comas se toman como ID; un elemento con un guion se expande al rango completo sin importar el orden. Los rangos de fechas y de números en columnas de wp_posts (menu_order, post_parent, fechas de la entrada) pasan por un filtro posts_where separado con una lista de permitidos de columnas.
Historial y reversión
shipped · 2019Qué aportaUna edición masiva errónea puede deshacerse en un solo paso en lugar de corregir miles de entradas a mano.
- Problema
- Una edición masiva errónea de miles de entradas es difícil de deshacer: las revisiones de WordPress no cubren el meta, los términos ni la mayoría de los campos básicos, y la propia reversión puede ser tan grande como la edición.
- Solución
Cada escritura de un campo va precedida de una fila de registro con el valor anterior; las filas de una misma ejecución masiva comparten su clave y se revierten por lotes.
- Stack
Capturar el valor antiguo de cualquier tipo de campo antes de que cambieUn hook antes de cada escritura guarda el valor anterior en wp_wpbe_history
write() está enganchada a wpbe_before_update_page_field, que update_page_field() dispara antes de cada escritura; la vía masiva de taxonomías, que escribe los términos directamente, la dispara a mano. Los valores de taxonomía se guardan como una lista JSON de ID de término, el meta de arrays como JSON (con JSON_HEX_QUOT y JSON_HEX_TAG para el editor de meta serializado) y los escalares tal cual. Cada fila tiene user_id y post_type, y la clave masiva cuando forma parte de una ejecución.
Miles de cambios deben formar una operación que se pueda deshacerTabla de cabecera wpbe_history_bulk por ejecución: claves, número de entradas y estado
start_bulk() inserta la fila de cabecera con la lista de campos editados como JSON; cada lote suma su tamaño mediante UPDATE posts_count = posts_count + %d; finish_bulk() marca la ejecución como completada. Una ejecución cancelada por el usuario conserva el estado por defecto 'terminated', de modo que el listado la muestra como parcial. Ambas tablas se crean con dbDelta y el conjunto de caracteres del sitio, se indexan por bulk_key y user_id y se eliminan al desinstalar.
Revertir una ejecución grande choca con los mismos límites de tiempoReversión en porciones de 10 filas por petición; fila de cabecera eliminada al final
El navegador pide el número de filas de la clave masiva y luego llama repetidamente a wpbe_history_revert_bulk_portion con límite 10 y una barra de progreso. Cada fila revertida se elimina, así que la siguiente porción recoge las restantes; la fila de cabecera se elimina cuando el contador alcanza el total.
Una reversión no debe registrarse a sí misma como un cambio nuevoremove_all_actions() sobre el hook de registro antes de escribir los valores antiguos
revert() desengancha el escritor del historial, decodifica el valor almacenado según el tipo de campo (listas de ID de término, arrays meta JSON) y lo escribe de vuelta mediante el mismo update_page_field() usado para las ediciones, de modo que save_post y edit_post se disparan en una reversión igual que en una edición.
Las ediciones individuales y las ejecuciones masivas deben aparecer en una misma línea de tiempoFilas individuales y cabeceras masivas combinadas y ordenadas por tiempo, por usuario y tipo de contenido
get_history() lee las filas individuales y las cabeceras masivas del usuario y el tipo de contenido actuales, las combina y las ordena por tiempo, las más recientes primero, de modo que una lista muestra ambos tipos de cambio.
La versión gratuita muestra las dos entradas más recientes y poda las filas individuales y cabeceras masivas más antiguas cuando se abre la lista.
Campos meta y contenido estructurado
shipped · 2019Qué aportaLos datos personalizados, como listas, indicadores y galerías de imágenes, pueden editarse de forma segura sin romperlos, incluso cuando el personal no sabe cómo se almacenan.
- Problema
- Los tipos de contenido personalizados guardan la mayor parte de sus datos en postmeta, a menudo como arrays serializados, listas de ID de imágenes o indicadores con valores propios de cada sitio, que una celda de texto simple corrompería.
- Solución
Las claves meta se registran con un tipo y una vista de edición, y cada vista tiene su propio lector, escritor, filtro y manejador de edición masiva.
- Stack
Los administradores no saben qué claves meta usa un tipo de contenidoClaves leídas de una entrada de muestra; las claves registradas se convierten en columnas, filtros y campos de edición masiva
wpbe_meta_get_keys devuelve las claves meta de un ID de entrada introducido por el administrador. Una clave registrada se inyecta mediante los hooks wpbe_extend_fields, wpbe_filter_* y wpbe_bulk_*, de modo que aparece a la vez en la tabla, en el panel de filtros y en el panel de edición masiva. Las claves se recortan de espacios pero no se pasan a minúsculas, porque claves como _woocs_sale_price_USD distinguen entre mayúsculas y minúsculas.
La versión gratuita usa solo los dos primeros campos meta registrados.
Los arrays serializados deben editarse sin romper la estructuraEl constructor emergente envía arrays clave/valor; el servidor reconstruye arrays anidados
El elemento emergente envía keys[]/values[] para arrays planos y anidados, y keys2[][]/values2[][] para listas de objetos. __process_jsoned_meta_data() reconstruye el array PHP, decodifica los valores que son JSON válido y recurre al valor sin procesar para las cadenas simples. Luego update_post_meta() lo almacena serializado como espera WordPress. La misma vía la usa el trabajador masivo.
Los indicadores se guardan como 'yes'/'no' u otros pares propios de cada sitioReglas de asignación 'key:on^off' que traducen entre el interruptor 1/0 y los valores almacenados
over_switcher_swicher_to_val() y over_switcher_val_to_swicher() leen una cadena de ajustes como 'dist:yes,stest:true' y convierten en ambos sentidos, de modo que la interfaz del interruptor funciona con valores escritos por otros plugins.
Galerías de imágenes guardadas en el meta como listas de ID de adjuntosTipo de meta galería guardado como una lista de ID de adjuntos separada por comas
El elemento emergente de la galería envía un formulario codificado en URL; el manejador detecta la codificación, lo analiza con parse_str(), lo sanea y guarda los ID como una cadena separada por comas. El trabajador masivo acepta el mismo formato.
El contenido del editor de bloques no se puede editar en un textarea simpleEntrada abierta en el editor de bloques dentro de un iframe; contenido leído mediante wp.data
Opción 'Use gutenberg'. Para los tipos de contenido que usan el editor de bloques, el elemento emergente carga post.php con wpbe_popup_editor=1; wp_editor_compatibility() oculta los controles de publicación y de la barra lateral en ese marco y expone una función que devuelve wp.data.select('core/editor').getEditedPostContent(). La página padre lo lee y lo guarda mediante la actualización normal de campos.
Control de acceso y endurecimiento de peticiones
shipped · 2023Qué aportaSolo los roles de confianza pueden usar el editor, los autores trabajan únicamente con sus propias entradas y el personal ve solo los campos que se le permite cambiar.
- Problema
- Una herramienta que puede reescribir cualquier campo de cualquier entrada de forma masiva es un objetivo de alto valor: cada endpoint AJAX debe comprobar a quien llama, y la entrada de los filtros acaba cerca de SQL.
- Solución
Una puerta de rol antes de registrar cualquier endpoint, nonces en las acciones de campos, edición masiva, herramientas y ajustes, derechos por campo para los roles que no son administrador, y SQL preparado con listas de permitidos de columnas.
- Stack
Solo los roles de confianza pueden acceder a cualquier endpoint del editorLa comprobación de rol en init() se ejecuta antes de añadir cualquier manejador wp_ajax_wpbe_*
can_manage_data() permite a los administradores, a los editores y a los roles añadidos mediante el filtro wpbe_author_area_roles. Si falla, init() retorna antes de que se carguen las extensiones, de modo que las acciones AJAX no existen para ese usuario. Las actualizaciones de campos, la edición y eliminación masivas, el panel de herramientas (crear, duplicar, eliminar, términos), los ajustes meta, el formulario de opciones, la lista del historial y las reversiones individuales comprueban además un nonce.
Los editores no deberían ver ni cambiar todos los campos y tipos de contenidoEl administrador define la visibilidad de campos y los tipos de contenido permitidos para los roles que no son administrador (premium)
Para los roles que no son administrador, get_fields() descarta las columnas ocultas, de modo que no se ofrecen en la tabla ni en el panel de edición masiva; update_page_field() comprueba is_current_user_can_edit_field() y devuelve 'forbidden' para un campo oculto; filter_post_types() acota el selector de tipos de contenido, y el constructor de ajustes traslada al usuario a un tipo de contenido permitido. En la versión gratuita las casillas de visibilidad se muestran desactivadas.
Los autores deberían trabajar solo con sus propias entradasLos roles del área de autores reciben author=self en las consultas del editor y no pueden cambiar post_author
Para los roles añadidos mediante el filtro wpbe_author_area_roles, WPBE_AUTHOR_AREA añade 'author' => ID del usuario actual mediante el filtro de consulta que usan la tabla, la lista de ID de la edición masiva y la exportación, de modo que estas listas muestran solo las entradas del propio usuario. Su callback wpbe_user_can_edit bloquea post_author para esos roles.
La entrada de los filtros y los datos copiados de entradas acaban en SQLNombres de columna de listas de permitidos, cada valor enlazado mediante $wpdb->prepare()
Los filtros de texto y de rango aceptan solo columnas de wp_posts de la lista y enlazan los valores mediante prepare(). Las consultas del historial enlazan usuario, tipo de contenido y clave masiva, y construyen listas NOT IN a partir de marcadores de posición de array_fill().
La duplicación copia los términos por slug y todo postmeta excepto _wp_old_slug en una sola sentencia INSERT ... SELECT %d, %s, %s UNION ALL, con cada fila construida con prepare(), y después deja la copia como borrador.
Los campos de contenido deben conservar los embeds pero descartar los scriptswp_kses con la lista de permitidos de post más una lista estrecha de atributos de iframe
wpbe_update_page_field() sanea los arrays de forma recursiva con wp_kses y la lista de permitidos de post. Para los campos de contenido, extracto y texto enriquecido añade iframe con un conjunto fijo de atributos para que los embeds de vídeo sobrevivan. Un filtro puede cambiar a sanitize_text_field() para sitios más estrictos.
Cifras
wordpress.org- Instalaciones activas 4000+ contador de wordpress.org.
- Reseñas 27 Media de 4.7 sobre 5.
- Años de soporte 7 Desde 3 de septiembre de 2019, última actualización 12 de octubre de 2026.