No te repitas: Automatización de tareas repetitivas con WP-CLI

Publicado: 2022-03-11

¿Alguna vez te has encontrado yendo al área de administración de WordPress para actualizar temas, complementos y el núcleo de WP? Por supuesto que tienes. ¿Se le ha preguntado, "¿Puede crear/actualizar/eliminar todos los usuarios en este archivo CSV?" Estoy seguro de que te has topado con eso también. ¿Ha intentado migrar un sitio y deseaba que hubiera un complemento o una herramienta de terceros que pudiera utilizar para hacer el trabajo? ¡Sé que tengo!

Automatización de tareas repetitivas con WP-CLI

Hay una herramienta muy poderosa disponible para ayudarlo con estas tareas y más. Antes de contarles sobre esto, me gustaría establecer una anécdota rápida.

El problema: en un proyecto reciente, había varias tareas programáticas que necesitaba repetir con regularidad. Una tarea en particular involucró la actualización de permisos a nivel de usuario en función de la evidencia de compra o suscripción a nivel de membresía. Si la empresa no podía encontrar un pago del usuario para un nivel de membresía en particular, quería que se eliminara el nivel de membresía del usuario. ¿Por qué se necesitaba esto? Tal vez un miembro detuvo una suscripción, pero un evento no se activó, por lo que el miembro todavía tiene acceso aunque no esté pagando por él (¡ay!). O tal vez alguien tenía una oferta de prueba, pero esa oferta expiró y el cliente todavía tiene una suscripción (¡también!).

La solución: en lugar de ingresar al panel de administración y eliminar manualmente cientos (quizás miles) de suscripciones, opté por una de mis herramientas favoritas de WordPress, WP-CLI, que solucionó el problema con solo presionar unas pocas teclas.

En esta publicación, quiero presentarles WP-CLI (asumiendo que aún no son amigos cercanos), guiarlos a través de un comando personalizado simple que escribí para esta situación particular y brindarles algunas ideas y recursos para usar WP-CLI en tu propio desarrollo.

¿Qué es WP-CLI?

Si nunca antes ha oído hablar de WP-CLI, no está solo. El proyecto, aunque tenía varios años, pareció pasar desapercibido por WordPress durante un tiempo. Aquí hay una breve descripción de lo que WP-CLI es y hace desde el sitio web oficial:

WP-CLI es un conjunto de herramientas de línea de comandos para administrar las instalaciones de WordPress. Puede actualizar complementos, configurar instalaciones multisitio y mucho más, sin usar un navegador web.

Los siguientes comandos le muestran el poder de WP-CLI listo para usar:

  • wp plugin update --all todas las actualizaciones de todos los complementos actualizables.
  • wp db export exporta un volcado SQL de su base de datos.
  • wp media regenerate regenera miniaturas para archivos adjuntos (por ejemplo, después de cambiar el tamaño en su tema).
  • wp checksum core verifica que los archivos principales de WordPress no hayan sido manipulados.
  • wp search-replace busca y reemplaza cadenas en la base de datos.

Si explora más comandos aquí, verá que hay muchos comandos disponibles para tareas repetitivas que todos los desarrolladores de WordPress o mantenedores de sitios realizan a diario o semanalmente. Estos comandos me han ahorrado incontables horas de apuntar, hacer clic y esperar recargas de página en el transcurso del año.

¿Estás convencido? ¿Listo para comenzar? ¡Genial!

Deberá tener instalado WP-CLI con su WordPress (o globalmente en su máquina local). Si aún no ha instalado WP-CLI en su entorno de desarrollo local, puede encontrar las instrucciones de instalación en el sitio web aquí. Si está utilizando Vagrant Vagrants Variing (VVV2), WP-CLI está incluido. Muchos proveedores de alojamiento también tienen WP-CLI incluido en su plataforma. Asumiré que tiene esto instalado con éxito en el futuro.

Usando WP-CLI para resolver el problema

Para resolver el problema de las tareas repetitivas, debemos hacer que un comando WP-CLI personalizado esté disponible para nuestra instalación de WordPress. Una de las formas más fáciles de agregar funcionalidad a cualquier sitio es crear un complemento. Usaremos un complemento en este caso por tres razones principales:

  1. Podremos desactivar el comando personalizado si no lo necesitamos
  2. Podemos extender fácilmente nuestros comandos y subcomandos mientras mantenemos las cosas modulares.
  3. Podemos mantener la funcionalidad entre temas e incluso otras instalaciones de WordPress.

Crear el complemento

Para crear un complemento, debemos agregar un directorio a nuestro directorio /plugins en nuestro directorio wp-content . Podemos llamar a este directorio toptal-wpcli . Luego crea dos archivos en ese directorio:

  • index.php , que solo debe tener una línea de código: <?php // Silence is golden
  • plugin.php , que es donde irá nuestro código (puedes nombrar este archivo como quieras).

Abra el archivo plugin.php y agregue el siguiente código:

 <?php /** * Plugin Name: TOPTAL WP-CLI Commands * Version: 0.1 * Plugin URI: https://n8finch.com/ * Description: Some rando wp-cli commands to make life easier... * Author: Nate Finch * Author URI: https://n8finch.com/ * Text Domain: toptal-wpcli * Domain Path: /languages/ * License: GPL v3 */ /** * NOTE: THIS PLUGIN FILE WILL NOT WORK IN PRODUCTION AS IS AND IS ONLY FOR DEMONSTRATION PURPOSES! * You can of course take the code and repurpose it:-). */ if ( !defined( 'WP_CLI' ) && WP_CLI ) { //Then we don't want to load the plugin return; }

Hay dos partes en estas primeras líneas.

Primero, tenemos el encabezado del complemento. Esta información se introduce en la página de administración de complementos de WordPress y nos permite registrar nuestro complemento y activarlo. Solo se requiere el nombre del complemento, pero debemos incluir el resto para cualquiera que quiera usar este código (¡así como para nosotros mismos en el futuro!).

En segundo lugar, queremos verificar que WP-CLI esté definido. Es decir, estamos comprobando si la constante WP-CLI está presente. Si no es así, queremos rescatar y no ejecutar el complemento. Si está presente, podemos ejecutar el resto de nuestro código.

Entre estas dos secciones, he agregado una nota de que este código no debe usarse "tal cual" en producción, ya que algunas de las funciones son marcadores de posición para funciones reales. Si cambia estas funciones de marcador de posición a funciones reales y activas, no dude en eliminar esta nota.

Agregar el comando personalizado

A continuación, queremos incluir el siguiente código:

 class TOPTAL_WP_CLI_COMMANDS extends WP_CLI_Command { function remove_user() { echo "\n\n hello world \n\n"; } } WP_CLI::add_command( 'toptal', 'TOPTAL_WP_CLI_COMMANDS' );

Este bloque de código hace dos cosas por nosotros:

  1. Define la clase TOPTAL_WP_CLI_COMMANDS , a la que podemos pasar argumentos.
  2. Asigna el comando toptal a la clase, por lo que podemos ejecutarlo desde la línea de comando.

Ahora, si ejecutamos wp toptal remove_user , vemos:

 $ wp toptal hello hello world

Esto significa que nuestro comando toptal está registrado y nuestro subcomando remove_user está funcionando.

Configuración de variables

Dado que estamos procesando de forma masiva la eliminación de usuarios, queremos configurar las siguientes variables:

 // Keep a tally of warnings and loops $total_warnings = 0; $total_users_removed = 0; // If it's a dry run, add this to the end of the success message $dry_suffix = ''; // Keep a list of emails for users we may want to double check $emails_not_existing = array(); $emails_without_level = array(); // Get the args $dry_run = $assoc_args['dry-run']; $level = $assoc_args['level']; $emails = explode( ',', $assoc_args['email'] );

La intención de cada una de las variables es la siguiente:

  • total_warnings : Contaremos con una advertencia si el correo electrónico no existe o si el correo electrónico no está asociado con el nivel de membresía que estamos eliminando.
  • $total_users_removed : queremos contar la cantidad de usuarios eliminados en el proceso (consulte la advertencia a continuación).
  • $dry_suffix : si se trata de una prueba, queremos agregar texto al aviso de éxito final.
  • $emails_not_existing : almacena una lista de correos electrónicos que no existen.
  • $emails_without_level : Almacena una lista de correos electrónicos que no tienen el nivel especificado.
  • $dry_run : un valor booleano que almacena si el script está realizando una ejecución en seco (verdadero) o no (falso).
  • $level : un número entero que representa el nivel para verificar y posiblemente eliminar.
  • $email : una matriz de correos electrónicos para verificar con el nivel dado. Recorreremos esta matriz

Con nuestras variables configuradas, estamos listos para ejecutar la función. En la verdadera moda de WordPress, ejecutaremos un bucle.

Escribir la función en sí

Comenzamos creando un bucle foreach para recorrer todos los correos electrónicos en nuestra matriz $emails :

 // Loop through emails foreach ( $emails as $email ) { // code coming soon } // end foreach

Luego, agregamos una verificación condicional:

 // Loop through emails foreach ( $emails as $email ) { //Get User ID $user_id = email_exists($email); if( !$user_id ) { WP_CLI::warning( "The user {$email} does not seem to exist." ); array_push( $emails_not_existing, $email ); $total_warnings++; continue; } } // end foreach

Esta verificación asegura que tenemos un usuario registrado con el correo electrónico que estamos revisando. Utiliza la función email_exists() para verificar si hay un usuario con ese correo electrónico. Si no encuentra un usuario con ese correo, lanza un aviso para que sepamos en la pantalla de nuestro terminal que no se encontró el correo:

 $ wp toptal remove_user [email protected] --dry-run Warning: The user [email protected] does not seem to exist.

Luego, el correo electrónico se almacena en la matriz $emails_not_existing para mostrarlo más tarde. Luego incrementamos la advertencia total en uno y continuamos a través del bucle hasta el siguiente correo electrónico.

Si el correo electrónico existe, usaremos las variables $user_id y $level para verificar si el usuario tiene acceso al nivel. Almacenamos el valor booleano resultante en la variable $has_level :

 // Loop through emails foreach ( $emails as $email ) { //Get User ID $user_id = email_exists($email); if( !$user_id ) { WP_CLI::warning( "The user {$email} does not seem to exist." ); array_push( $emails_not_existing, $email ); $total_warnings++; continue; } // Check membership level. This is a made up function, but you could write one or your membership plugin probably has one. $has_level = function_to_check_membership_level( $level, $user_id ); } // end foreach

Como la mayoría de las funciones en este ejemplo, esta function_to_check_membership_level() está fabricada, pero la mayoría de los complementos de membresía deberían tener funciones de ayuda para obtener esta información.

Ahora, pasaremos a la acción principal: eliminar el nivel del usuario. Usaremos una estructura if/else , que se ve así:

 foreach ( $emails as $email ) { // Previous code here... // Check membership level. This is a made up function, but you could write one or your membership plugin probably has one. $has_level = function_to_check_membership_level( $level, $user_id ); if ( $has_level ) { if ( !$dry_run ) { // Deactivate membership level. This is a made up function, but you could write one or your membership plugin probably has one. function_to_deactivate_membership_level( $level, $user_id, 'inactive' ); } WP_CLI::success( "Membership canceled for {$email}, Level {$level} removed" . PHP_EOL ); $total_users_removed++; } else { WP_CLI::warning( "The user {$email} does not have Level = {$level} membership." ); array_push( $emails_without_level, $email ); $total_warnings++; } // We could echo something here to show that things are processing... } // end foreach

Si el valor de $has_level es "veraz", lo que significa que el usuario tiene acceso al nivel de membresía, queremos ejecutar una función para eliminar ese nivel. En este ejemplo, usaremos la function_to_deactivate_membership_level() para realizar esta acción.

Sin embargo, antes de que eliminemos el nivel del usuario, queremos encerrar esa función en una verificación condicional para ver si esto es realmente un dry-run . Si es así, no queremos eliminar nada, solo informar que lo hicimos. Si no es una dry-run , continuaremos y eliminaremos el nivel del usuario, registraremos nuestro mensaje de éxito en la terminal y continuaremos revisando los correos electrónicos.

Si, por otro lado, el valor de $has_level es "falso", lo que significa que el usuario no tiene acceso al nivel de membresía, queremos registrar una advertencia en el terminal, enviar el correo electrónico a la matriz $emails_without_level y continuar. recorriendo los correos electrónicos.

Terminando y Reportando

Una vez que el ciclo ha terminado, queremos registrar nuestros resultados en la consola. Si esto fue un ensayo, queremos registrar un mensaje adicional en la consola:

 if ( $dry_run ) { $dry_suffix = 'BUT, nothing really changed because this was a dry run:-).'; }

Este $dry-suffix se agregará a las advertencias y notificaciones de éxito que registramos a continuación.

Para finalizar, queremos registrar nuestros resultados como un mensaje de éxito y nuestras advertencias como mensajes de advertencia. Lo haremos así:

 WP_CLI::success( "{$total_users_removed} User/s been removed, with {$total_warnings} warnings. {$dry_suffix}" ); if ( $total_warnings ) { $emails_not_existing = implode(',', $emails_not_existing); $emails_without_level = implode(',', $emails_without_level); WP_CLI::warning( "These are the emails to double check and make sure things are on the up and up:" . PHP_EOL . "Non-existent emails: " . $emails_not_existing . PHP_EOL . "Emails without the associated level: " . $emails_without_level . PHP_EOL ); }

Tenga en cuenta que estamos utilizando los métodos auxiliares WP_CLI::success y WP_CLI::warning . Estos son proporcionados por WP-CLI para registrar información en la consola. Puede registrar fácilmente cadenas, que es lo que hacemos aquí, incluidas nuestras $total_users_removed , $total_warnings y $dry_suffix .

Finalmente, si acumulamos advertencias durante el tiempo de ejecución del script, queremos imprimir esa información en la consola. Después de ejecutar una verificación condicional, convertimos las variables de matriz $emails_not_existing y $emails_without_level en variables de cadena. Hacemos esto para poder imprimirlos en la consola usando el método auxiliar WP_CLI::warning .

Agregar una descripción

Todos sabemos que los comentarios son útiles para los demás y para nosotros mismos en el futuro, volviendo a nuestro código semanas, meses o incluso años después. WP-CLI proporciona una interfaz de descripciones cortas (shortdesc) y descripciones largas (longdesc) que nos permite anotar nuestro comando. Pondremos en la parte superior de nuestro comando, después de definir la clase TOPTAL_WP_CLI_COMMANDS :

 /** * Remove a membership level from a user * * ## OPTIONS * --level=<number> * : Membership level to check for and remove * * --email=<email> * : Email of user to check against * * [--dry-run] * : Run the entire search/replace operation and show report, but don't save changes to the database. * * ## EXAMPLES * * wp toptal remove_user --level=5 [email protected],[email protected], [email protected] --dry-run * * @when after_wp_load */

En el longdesc, definimos lo que esperamos que reciba nuestro comando personalizado. La sintaxis para shortdesc y longdesc es Markdown Extra. En la sección ## OPTIONS , definimos los argumentos que esperamos recibir. Si se requiere un argumento, lo envolvemos en < > , y si es opcional, lo envolvemos en [ ] .

Estas opciones se validan cuando se ejecuta el comando; por ejemplo, si omitimos el parámetro de correo electrónico requerido, obtenemos el siguiente error:

 $ wp toptal remove_user --level=5 --dry-run Error: Parameter errors: missing --email parameter (Email of user to check against)

La sección ## EXAMPLES incluye un ejemplo de cómo se vería el comando cuando se llama.

Nuestro comando personalizado ahora está completo. Puedes ver la esencia final aquí.

Una advertencia y margen de mejora

Es importante revisar el trabajo que hemos realizado aquí para ver cómo se podría mejorar, ampliar y refactorizar el código. Hay muchas áreas de mejora para este script. Aquí hay algunas observaciones sobre las mejoras que se podrían hacer.

Ocasionalmente, descubrí que este script no eliminará a todos los usuarios que registra como "eliminados". Lo más probable es que esto se deba a que el script se ejecuta más rápido de lo que pueden ejecutar las consultas. Su experiencia puede variar según el entorno y la configuración en la que se ejecuta el script. La forma rápida de evitar esto es ejecutar repetidamente con las mismas entradas; eventualmente se pondrá a cero e informará que no se ha eliminado ningún usuario.

La secuencia de comandos podría mejorarse para esperar y validar que un usuario ha sido eliminado antes de registrar al usuario como realmente eliminado. Esto ralentizaría la ejecución del script, pero sería más preciso y solo tendría que ejecutarlo una vez.

De manera similar, si se encontraran errores como este, la secuencia de comandos podría arrojar errores para alertar de que un nivel no se ha eliminado de un usuario.

Otra área para mejorar la secuencia de comandos es permitir que se eliminen varios niveles a la vez de una dirección de correo electrónico. El script podría detectar automáticamente si había uno o más niveles y uno o más correos electrónicos para eliminar. Me dieron archivos CSV por nivel, por lo que solo necesitaba ejecutar un nivel a la vez.

También podríamos refactorizar parte del código para usar operadores ternarios en lugar de las verificaciones condicionales más detalladas que tenemos actualmente. He optado por hacer que esto sea más fácil de leer por el bien de la demostración, pero siéntete libre de hacer que el código sea tuyo.

En el paso final, en lugar de imprimir correos electrónicos en la consola en el paso final, también podríamos exportarlos automáticamente a un archivo CSV o de texto sin formato.

Finalmente, no hay comprobaciones para asegurarse de que estamos obteniendo un número entero para la variable $level o un correo electrónico o una lista de correos electrónicos separados por comas en la variable $emails . Actualmente, si alguien incluyera cadenas en lugar de números enteros, o nombres de inicio de sesión de usuario en lugar de correos electrónicos, el script no funcionaría (y no generaría errores). Se pueden agregar comprobaciones de números enteros y correos electrónicos.

Ideas para una mayor automatización y lecturas adicionales

Como puede ver, incluso en este caso de uso específico, WP-CLI es bastante flexible y lo suficientemente potente como para ayudarlo a realizar su trabajo de manera rápida y eficiente. Quizás se esté preguntando: "¿Cómo puedo comenzar a implementar WP-CLI en mi flujo de desarrollo diario y semanal?"

Hay varias formas de usar WP-CLI. Aquí están algunos de mis favoritos:

  • Actualice temas, complementos y el núcleo de WP sin tener que ingresar al panel de administración.
  • Exportar bases de datos para realizar copias de seguridad o realizar un volcado SQL rápido si quiero probar una consulta SQL.
  • Migrar sitios de WordPress.
  • Instale nuevos sitios de WordPress con datos ficticios o configuraciones de conjuntos de complementos personalizados.
  • Ejecute sumas de verificación en los archivos principales para asegurarse de que no se hayan visto comprometidos. (En realidad, hay un proyecto en marcha para expandir esto a temas y complementos en el repositorio de WP).
  • Escriba su propio script para verificar, actualizar y mantener los hosts del sitio (sobre lo que escribí aquí).

Las posibilidades con WP-CLI son casi ilimitadas. Aquí hay algunos recursos para seguir avanzando:

  • El sitio principal de WP-CLI: http://wp-cli.org
  • Comandos WP-CLI: https://developer.wordpress.org/cli/commands/
  • Blog oficial de WP-CLI: https://make.wordpress.org/cli/
  • Manual de WP-CLI: https://make.wordpress.org/cli/handbook/
  • ¿En WooCommerce? Consulte el WC-CLI: https://github.com/woocommerce/woocommerce/wiki/WC-CLI-Overview#woocommerce-commands
  • Podcast Entrevista con Daniel Bachhuber, mantenedor del proyecto: https://howibuilt.it/episode-28-daniel-bachhuber-wp-cli/