WordPress CS en VSCode

WordPress tiene recomendaciones sobre cómo se debe escribir el código tanto si es para el core como para plugins y themes e incluso para la documentación.

Como hay una gran cantidad de aspectos tratados por estas reglas y, a veces, las mismas son contra intuitivas o por lo menos van en contra de los usos y costumbres es necesario incluir herramientas en nuestro workflow que nos obliguen a seguirlas y nos marquen cuando nos olvidemos.

Primero, para todo el código en JavaScript trabajaremos con ESLint y el plugin homónimo para VSCode. Primero instalamos el paquete de reglas:

npm install --save-dev eslint-config-wordpress

Si no tenemos instalado eslint globalmente también tendremos que instalarlo.

Lo siguiente es crear el archivo de configuración y agregar lo siguiente:

{
  "extends": "wordpress"
}

En caso de trabajar en Windows y, dependiendo del editor y nuestra configuración, puede ser útil desactivar la regla que nos marca los saltos de línea incorrectos. WordPress espera que utilicemos \n y Windows utiliza \r\n:

{
  "extends": "wordpress",
  "rules": {
    "linebreak-style": "off"
  }
}

Con esto estaría la configuración de ESLint, ahora pasamos a VSCode. Buscamos el plugin ESLint y lo instalamos. Si no tenemos ninguna configuración u organización partícular debería funcionar out-of-the-box.

En caso de necesitar tunear la configuración para indicar otros path o ajustes, en el Summary del plugin están detalladas todas las configuraciones necesarias.

En lo que respecta a PHP la idea es utilizar PHP Code Sniffer y el plugin phpcs. Nuevamente, arrancamos instalando todo con Composer:

composer require --dev squizlabs/php_codesniffer wp-coding-standards/wpcs

A continuación tenemos que indicarle a PHP Code Sniffer donde encontrar las reglas que instalamos:

./vendor/bin/phpcs --config-set installed_paths vendor/wp-coding-standards/wpcs

Ahora, en VSCode buscamos e instalamos el plugin phpcs y lo configuramos ya sea a través de la interfaz o editando el settings.json:

{
  "phpcs.enable": true,
  "phpcs.standard": "WordPress-Core",
}

Con respecto al standard tenemos 4:

  • WordPress-Core: Conjunto principal de reglas establecidos para codificar en PHP para WordPress.
  • WordPress-Docs: Reglas que abarcan todo lo relacionado con la documentación de código PHP.
  • WordPress-Extra: Un superconjunto que incluye a WordPress-Core y lo extiende agregando reglas que fomentan buenas prácticas que no están contempladas en los estándares de WordPress.
  • WordPress: El superconjunto que incluye todos los ruleset antes mencionados.

A menos que estemos contribuyendo directamente con WordPress me quedaría con el WordPress-Core o WordPress-Extra para evitarnos el rollo de la documentación que por momentos llega a ser barroca.

Existen, también, reglas para la correcta escritura del HTML y los stylesheet. En el primer caso la recomendación oficial va por el uso del W3C Validator y en el otro hay un conjunto de reglas para stylelint pero, por ahora, no lo utilicé nunca así que queda para otro momento esa parte.

Docker: WordPress

Siguiendo con el tema de Docker, uno de mis usos preferidos es utilizarlo para crear entornos locales para testear WordPress. Si bien hay una imagen oficial de WordPress para Docker su configuración out-of-the-box necesita algunos ajustes para sernos de utilidad.

Primero, el archivo docker-compose.yml:

version: "3"

services:
  mysql:
    image: mysql:5.7
    restart: "no"
    volumes:
      - ./wordpress/database:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: wordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress

  wordpress:
    depends_on:
      - mysql
    image: wordpress
    ports:
      - 8080:80
    restart: "no"
    volumes:
      - ./wordpress/content:/var/www/html/wp-content
      - ./wordpress/.htaccess:/var/www/html/.htaccess
      - ./wordpress/php.ini:/usr/local/etc/php/conf.d/wordpress.ini
      - <build dir>:/var/www/html/wp-content/themes/<theme name>
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DEBUG: 'true'

  adminer:
    depends_on:
      - mysql
    image: adminer
    restart: "no"
    ports:
      - 8000:8080
    environment:
      ADMINER_DEFAULT_SERVER: mysql

Como vemos, trabajo con 3 images: mysql, wordpress y adminer.

En la primera, mysql, creo un volumen que apunta a ./wordpress/database en el host y a /var/lib/mysql en el container lo que me permite persistir la información de la base de datos mediante el sencillo proceso de almacenar los archivos de datos de MySQL en el host. Es importante que especifiquemos la versión de MySQL en vez de usar latest, para evitar que los datos se pierdan al actualizarse el servidor.

La segunda, wordpress, tiene 4 volumenes creados:

  • ./wordpress/content, nos permite persistir plugins, themes, actualizaciones, étc.
  • ./wordpress/.htaccess, nos sirve para poder trabajar con el .htaccess de WordPress. En mi caso lo utilizo para redirigir las solicitudes a la carpeta uploads al servidor de producción para no tener que replicarla cada vez que creo un container para hacer pruebas.
  • ./wordpress/php.ini, nos da la posibilidad de configurar algunos parámetros de PHP. Son de particular interés el tamaño máximo de los uploads y el tiempo de ejecución ya que los por defecto son muy chicos.
  • <build dir>, este es el directorio donde se encuentra la build del theme (o plugin) con el que estemos trabajando.

Con respecto a las configuraciones, si no hicimos ningún cambio en las variables de la BBDD, no hace falta tocarlas. Si no necesitamos que esté activado el debug podemos borrar la susodicha configuración o ponerla en ‘false’.

adminer, necesario para poder trastear con la BBDD. La única configuración que pasamos es más un QOL que otra cosa y sirve para establecer el servidor por defecto.

Una cosa importante a tener en cuenta es que, como Docker crea las carpetas correspondientes a los volúmenes con el usuario root, tenemos que cambiar el owner por el usuario www-data. Para esto, una vez creado los container, alcanza con ejecutar el siguiente comando:

docker-compose exec wordpress chown www-data:www-data /var/www/html/wp-content /var/www/html/wp-content/themes

Por último dejo el archivo .htaccess y php.ini que utilizo con las características antes comentadas:

<IfModule mod_rewrite.c>
  RewriteEngine on
  RewriteBase /
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{HTTP_HOST} ^localhost:8080$
  RewriteRule ^wp-content/uploads/(.*)$ https://server/wp-content/uploads/$1 [NC,L]
</IfModule>
file_uploads = On
memory_limit = 64M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 600

Divagación: Gutenberg

El cambio de editor por defecto de WordPress en diciembre del año pasado generó mucha controversia. Por parte de sus detractores, lo poco intuitivo de su interfaz gráfica, la falta de retro compatibilidad, la escasa documentación y lo inestable de su API fueron caballitos de batalla.

Sus simpatizantes se decantaron por las mejoras mobile-friendly, el dejar de almacenar el contenido de los posts en formato HTML, la posibilidad de abandonar los shortcodes en favor de bloques más amigables con el usuario y, en general, una experiencia de edición de contenido más similar a la de Medium.

Ambos puntos de vista tienen razones válidas y entiendo por qué generaron posiciones enconadas y extensas discusiones pero la discusión de Gutenberg soslayó otro montón de puntos conflictivos sobre el CMS que son más importantes. Igualmente, antes de entrar de lleno sobre este tema, voy a analizar los cambios que trae aparejado Gutenberg.

Contenido como bloques

La principal diferencia entre el editor clásico y Gutenberg radica en como se conceptualiza el contenido. El anterior entendía cada post como un todo, el contenido era el conjunto de código HTML ingresado por el usuario a través de una interfaz más amigable, el editor.

Este enfoque presenta como ventaja que da mayor poder al usuario sobre el contenido y su presentación. Si el editor no soporta algo de forma nativa un copy paste de un código nos permite traer un tweet o video de YouTube a nuestro post.

Como contra podemos decir que da mayor poder al usuario sobre el contenido y su presentación y que permite hacer copy paste de un código extraño para incluir características no soportadas. La aplicación de parches para evitar el overflow de contenido e imágenes, limpiar el formato HTML de lo pegado del portapapeles y los hacks para adaptar el contenido embebido desde sitios de terceros son el ABC de cualquier theme que vayamos a distribuir.

Además, cualquier tipo de post procesamiento que querramos hacer sobre el contenido implica el parseo del código HTML en el mejor de los casos o el uso de expresiones regulares en el peor. El contenido propiamente dicho esta inevitablemente ligado a las etiquetas HTML.

Este tipo de vínculo estrecho no era un problema hace 16 años cuando WordPress salió a mundo. Los diseños web eran más sencillos, la interactividad esperada por el usuario era menor y la navegación estaba casi completamente abocada al escritorio.

Con todos los cambios tecnológicos y la ampliación de la variedad de plataformas donde nuestro contenido va a ser consumido que hubo durante la última década este enfoque comenzó a mostrar serias limitaciones. Si bien el contenido a presentar se mantiene, el formato y el layout del mismo cambian según desde dónde se consume.

La respuesta de Automattic a este problema es la introducción de los bloques de contenido. Cada parte del contenido: texto, imágenes, videos, étc, es ahora un bloque separado con atributos propios. Ya no es un todo indivisible sino que son unidades independientes.

Este enfoque permite separar en mayor medida el código HTML del contenido. El Rich Text proveído por TinyMCE se limita a los bloques de texto y las imágenes y demás embebidos pasan a recibir un tratamiento diferenciado. Se abstrae aún más el contenido de su presentación.

React

Como todo este cambio afecta directamente al editor su implementación fue necesaria hacerla del lado del navegador donde JavaScript reina. Entre desarrollar desde 0 o utilizar uno de los frameworks existentes, los desarrolladores de WP se inclinaron por utilizar React.

Y aquí es donde se presenta uno de los puntos de fricción sobre Gutenberg, el desarrollo para WordPress siempre tuvo una barrera de acceso muy baja: con algunos conocimientos sobre HTML, CSS y PHP se podía armar un theme o plugin. A partir de ahora, es requisito tener un buen nivel de JavaScript y React para poder llevar adelante la implementación de algunas características.

Si bien esto aumenta la curva de aprendizaje necesaria para trabajar en un sitio con WordPress también amplia las capacidades del editor en varias magnitudes. Casi cualquier componente presentacional de React puede ser adaptado para ser usado en Gutenberg.

Siendo de uso diario muchas aplicaciones desarrolladas en React no creo que sea necesario explayarse en detalle sobre el potencial de esto.

Shortcodes

Meta etiquetas que se procesan en el servidor cuando se solicita un post y que, a nivel editor, el usuario únicamente ve un fragmento de texto o una imagen sin relación directa con el contenido que se va a presentar. Además de romper con WYSIWYG los shortcodes presentaban otros problemas:

  • El contenido mostrado en el post no es parte real del mismo por lo que se vuelve invisible al buscador de WordPress
  • Al ser el shortcode indistinguible, en principio, del resto del contenido no hay forma sencilla de eliminarlo cuando, por ejemplo, se desinstala el plugin del que depende
  • Como adenda al punto anterior, la identificación de los shortcodes en un post implica el parseo del contenido utilizando una regexp que se las trae
  • En el caso de los shortcodes que no tienen una representación visual propia en el editor (como las galerías) existe la posibilidad de que el usuario los modifique inadvertidamente dejándolos no funcionales

Los bloques, por su implementación, suplen sobradamente todo el contenido servido a través de shortcodes sin ninguna de estas falencias y vuelven esta característica obsoleta.

Meta fields

Una situación similar a la de los shortcodes se presenta con muchos de los meta campos que implementan themes y plugins: son contenido pero se manejan y almacenan por separado del demás contenido del post.

Aunque extremadamente útiles para poder agregar, de forma simple de usar de cara al usuario, opciones para personalizar en los posts la implementación por parte de WordPress no podía ser más engorrosa de implementar y costosa de usar por la gran cantidad de querys extras que necesita.

En este contexto surgió uno de los plugins más usados hoy en día al momento de extender la funcionalidad de los posts, ACF. El que durante años sería el referente indiscutido en el área se encontró con que en Gutenberg no se iban a mostrar más las meta boxes y meta fields quedándose así fuera del ruedo.

Con mucho acierto, el equipo detrás de ACF decidió adaptarse a los tiempos que corren y creó un generador propio de bloques que se integra perfectamente con Gutenberg.

Este cambio fue un alivio para todas las personas que tienen desarrollos que dependen fuertemente de dicho del plugin pero que sigue soslayando el tema principal: extender la funcionalidad de los posts es todavía un tema extremadamente engorroso aunque se esté yendo en la dirección correcta.

UI/UX

Con respecto al tema de la interfaz del nuevo editor está claro que se orienta a dispositivos táctiles y no muy anchos, vamos que se han preocupado más del móvil que del escritorio.

En una pantalla ancha el recorrido que hay que hacer con el mouse desde el botón para agregar un bloque hasta la sidebar con los atributos del mismo se hace tedioso. Asimismo el alto grado de anidamiento de las distintas opciones tendiente a reducir la cantidad que se muestra en pantalla se puede presentar tedioso cuando vemos más del 50% de la misma en blanco y desaprovechado.

Nada que nos impida usar la interfaz pero que siendo tan sencillo de solucionar con una sidebar al estilo Elementor resulta inexcusable aún sabiendo que el editor se lanzó antes de estar terminado.

REST API

Uno del puntos que menos sufrió cambios con el lanzamiento de la versión 5.0 de WordPress, que debería haberse tenido más en cuenta y que podría haberse aprovechado el cambio de editor para mejorar es la REST API. Uno de los puntos más problemáticos de la misma es lo fuertemente ligada que está al CMS. La clase controladora de los end points, WP_REST_Controller, es un bodoque monolítico que carga casi la misma cantidad de módulos de WordPress que cuando estamos mostrando el post de forma directa.

Otro punto es la gran cantidad de datos que envía como respuesta sin posibilidad más que de aplicarle unos pocos filtros. Conseguir desde nuestro frontend el url de la imagen destacada, el título y fecha de publicación implica traer también el resumen, todo el contenido, la fecha de modificación, el endpoint de los comentarios y un largo étc.

Sin llegar al punto de implementar GraphQL, que sería un objetivo a apuntar, podrían haberse hecho muchas mejoras a nivel filtrado de campos y separación de la REST API del resto del CMS.

Documentación

La documentación de WordPress es algo que siempre te deja sin palabras y ni la nueva versión del CMS, ni el nuevo editor cambiaron eso. Una organización por demás enrevesada y ecléctica que sería el orgullo de Escher, no están todas las características documentadas y a veces la única información que hay es el código fuente sin ningún contexto. Y esas son sus puntos a favor.

Disclaimer: Este artículo no intenta tener ningún tipo de coherencia interna ni fin, como indica el título es una divagación. Los subtítulos fueron agregados a posteriori para cortar lo que es una pared de texto pero no van completamente acorde con lo que los sigue.

TDD WordPress Theme

Tanto al comenzar a desarrollar un tema desde cero como cuando ya adquirió mayor complejidad tenemos que hacer una serie de chequeos de forma manual para asegurarnos que no nos olvidamos de incluir ninguna funcionalidad o no quedó código muerto.

Obviamente una buena checklist y una organización bien planificada de nuestro código simplifica mucho el tema pero siempre es menester automatizar este tipo de tareas.

WordPress provee una Test Suit pero está orientada principalmente al testeo del funcionamiento de WordPress más que a las cuestiones atinentes al desarrollo de themes.

Si bien puede ser utilizada como base para testear nuestros temas, a mi entender, agrega demasiado overhead para lo que nos aporta. Partiendo de este presupuesto decidí comenzar a desarrollar algunos tests propios con PHPUnit.

Primeramente, basado en el checklist que antes mencioné, definí una serie de chequeos que me interesa automatizar:

  • Existe los archivos functions.php, style.css y el screenshot
  • No hay archivos sin usar
  • Existe el template correspondiente a los template tags usados. Por ej.: get_header y header.php
  • Todas las uris locales son relativas
  • Todos los scripts/styles registrados son usados y existen
  • Hay sidebars y todas son usadas en algún lado
  • Todos los espacios de menu son usados en algún momento
  • Las páginas/artículos y el sitio tienen metadatos
  • El favicon está definido y la ruta existe
  • El theme no tiene que tomar funciones de plugin:
    • Dashboard widgets
    • Custom Post Types
    • Custom Taxonomies
    • Shortcodes
    • Metaboxes
    • Social integrations
    • Bloques de Gutenberg
  • La paginación respeta la configuración de Ajustes > Lectura
  • Customizer sanitization
  • Scripts/styles externos cargados de forma agnóstica
  • No se usa @import en los stylesheet
  • Soporte para title tag
  • Uso de $content_width
  • CSS básicos definidos:
    • alignleft, aligncenter, alignright,
    • wp-caption,
    • size-full, size-large, size-medium, size-thumbnail
  • Uso de WP_Filesystem en vez de las PHP File Functions: mkdir, fopen, fread, fwrite, fputs
  • Sin estilos o scripts hardcodeados
  • Scripts cargados únicamente en footer
  • i18n

A primera vista, algunos son sencillos como la verificación de la existencia de archivos, otros van a requerir análisis del código y algunos que podrían ser verificados utilizando la API que trae WordPress contra un servidor de desarrollo o staging.

Por ahora voy a ir haciendo el desarrollo y pruebas contra la copia de desarrollo del theme de un sitio que está en producción para tener un objetivo más real aunque la idea sería, a largo plazo, constituir un repositorio agnóstico que pueda ser rápidamente clonado en nuestro proyecto.

La primera aproximación quedaría con la siguiente estructura dentro de mi workflow habitual:

  • tests/bootstrap.php
  • tests/FaviconTest.php
  • tests/MissingFilesTest.php
  • tests/StylesheetTest.php
  • composer.json
  • phpunit.xml

Y el código propiamente de cada archivo sería:

Próximamente estaré publicando más novedades.

WordPress Customizer JS API

Esta entrada es la parte 1 de 1 en la serie Wordpress Customizer JS API

A partir de la versión 4.1 de WordPress implementó una API en Javascript que permite la creación y el control de todos los elementos del Customizer además del renderizado de controles a partir de plantillas de Underscore.

Estas dos novedades permiten el manejo de los paneles, las secciones y los controles únicamente desde Javascript, abriendo un mundo de posibilidades.

La organización de la API viene dada por el uso de colecciones para agrupar las instancias de los elementos que ya existen y modelos para la creación de nuevos. Las colecciones son:

  • wp.customize.control
  • wp.customize.panel
  • wp.customize.section

En tanto, los modelos son:

  • wp.customize.Control
  • wp.customize.Panel
  • wp.customize.Section

En el caso de los controles existen modelos específicos que extienden a Control y agregan características específicas para cada tipo:

  • BackgroundControl
  • BackgroundPositionControl
  • CodeEditorControl
  • CroppedImageControl
  • DateTimeControl
  • HeaderControl
  • ImageControl
  • MediaControl
  • SiteIconControl
  • UploadControl

La utilización de las colecciones y los métodos es bastante intuitiva ya que sigue esquemas conocidos para los que tienen una base de Javascript y similares a los que se utiliza mediante la API PHP de WordPress.Por ejemplo, 

Pero, antes de meternos de lleno, necesitamos que nuestro código se cargue cuando abramos el Customizer, para ello vamos a usar el hook customize_controls_enqueue_scripts:

function wp_enqueue_customize_script() {
    wp_enqueue_script( 'wp-customizer', get_template_directory_uri() . '/scripts/customizer/main.js', [], null, true );
}

add_action( 'customize_controls_enqueue_scripts', 'wp_enqueue_customize_script' );

Paneles

Para agregar paneles invocamos el método add de la colección panel que recibe como parámetro un objeto creado a partir del modelo Panel que, a su vez, recibe los mismos parámetros que $wp_customize->add_panel():

wp.customize.panel.add(
    new api.Panel( 'wp_nuevo_panel', {
        title: 'Nuevo panel',
        priority: 25
    })
);

Lo que agregaría un panel con id wp_nuevo_panel, título Nuevo panel y prioridad 25.

A partir de que lo agreguemos podremos acceder al mismo mediante la colección de la siguiente manera:

wp.customize.panel('wp_nuevo_panel').sections();

Lo que devuelve todas las secciones anidadas en este panel.

wp.customize.panel('wp_nuevo_panel').params.title;

Que nos permite obtener el título del panel en cuestión.

Secciones

Las secciones siguen el mismo formato que los paneles:

wp.customize.section.add(
    new api.Section( 'wp_nueva_seccion', {
        title: 'Nueva seccion',
        panel: 'wp_nuevo_panel',
        priority: 25,
        customizeAction: 'Personalización'
    })
);

Lo único particular que vemos es la opción customizeAction que representa el texto que aparece en la parte de arriba del título de la sección y que es necesario definirlo, por ahora, porque no tiene valor predeterminado.

Al igual que con los paneles, una vez agregada la sección, podemos acceder a la misma a través de la correspondiente colección:

wp.customize.sections('wp_nueva_seccion').priority(30);

Lo que cambiaría la prioridad asignada al panel cambiando la posición del mismo. O:

wp.customize.sections('wp_nueva_seccion').controls();

Para obtener la colección de controles que hay en la sección.

Controles

Llegamos a la parte más interesante del Customize, los controles. Por ahora vamos a ver la interacción desde la API y más adelante, en otro artículo, las potencialidades de las templates de Underscore.

En este caso nos encontraremos con más particularidades que con los otros dos apartados porque los controles, para que sean finalmente almacenados en nuestra BBDD, tienen estar relacionados con una setting. Si bien se pueden agregar settings de forma dinámica, por ahora, sigue siendo más sencillo agregarlas desde PHP y utilizarlas desde Javascript.

wp.customize.control.add(
    new api.Control( 'wp_nueva_control', {
        label: 'Texto',
        section: 'wp_nueva_seccion',
        setting: wp.customize( 'mi_setting_de_texto' )
    })
);

Como vemos, un sencillo control de texto, no nos presenta ninguna dificultad. Lo único nuevo es la forma de pasar la setting y se debe a que wp.customize es, a su vez, la colección donde se almacenan las settings.

Pero, por ejemplo, cuando queremos agregar un MediaControl necesitamos especificar además el texto de las etiquetas:

wp.customize.control.add(
    new api.MediaControl( 'anred_placemark_image_control', {
        section: 'wp_nueva_seccion',
        label: 'Logo',
        setting: api( 'wp_logo_image' ),
        button_labels: {
            change: 'Cambiar logo',
            default: 'Sin logo',
            frame_button: 'Elegir logo',
            frame_title: 'Elegir logo',
            placeholder: 'Logo no elegido',
            remove: 'Quitar logo',
            select: 'Elegir logo'
        }
    }
)

Hasta el momento no encontré ninguna referencia sobre qué parámetros tienen valor por defecto y cuales no para cada control. La fuente última para saberlo es el código de la API que se encuentra en wp-admin/js/customize-controls.js o el código PHP de los controles en la carpeta wp-includes/customize/.

WordPress y PHPUnit

PHPUnit es uno de los frameworks para test unitarios automáticos para PHP más usados y el elegido por los desarrolladores de WordPress junto con QUnit para probar la plataforma. Para facilitar las pruebas, los desarrolladores han creado ya todo un conjunto de test y clases auxiliares que nos resultarán muy útiles en nuestro workflow.

Instalación

Primero, necesitamos instalar PHPUnit en su versión 6 ya que la 7 no es soportada por WordPress. Hay distintas formas documentadas en el sitio del framework y podemos elegir la que más se adapte a nuestro entorno.

Segundo, creamos una nueva base de datos separada para los tests porque que la suite va a borrar todos los datos de las tablas de la instalación donde se ejecute.

Tercero, no es necesario pero definitivamente es la mejor forma de manejar el entorno de pruebas vamos a instalar wp-cli. Nuevamente, hay varías formas de llevar adelante la tarea y va a depender de nuestro entorno el método que elijamos.

Por mi parte, me ha dado mejores resultados la instalación recomendada: descargar el .phar, darle permisos de ejecución y ponerlo en una carpeta que esté en PATH.

Cuarto, nos situamos en la carpeta de la instalación de wordpress para utilizar las opciones del paquete scaffold de wp-cli para generar los archivos necesarios para instalar los tests según sea un plugin o un theme.

wp scaffold plugin-tests <nombre plugin>
wp scaffold theme-tests <nombre theme>

Quinto, ejecutamos el archivo generado dentro de la carpeta del plugin/theme en el paso anterior para instalar el entorno de pruebas. Vamos a necesitar las credenciales y datos de acceso a la base de datos que creamos antes.

bin/install-wp-tests.sh <nombre bbdd> <usuario> <contraseña> <host>

Con esto queda todo listo para que empecemos a desarrollar nuestros tests y ponerlos en la carpeta homónima para que PHPUnit los encuentre.

Para más información sobre los unit test en WordPress estos artículos van a dar una idea de las posibilidades:

Workflow en WordPress

La estructura de directorios que uso en mi workflow se aprovecha de una feature de WordPress que permite distribuir grupos de themes. La idea es que en vez de almacenar un theme por directorio se puedan poner varios usando subdirectorios.

El funcionamiento de esta feature es bastante sencillo, cuando WP lista las carpetas en wp-content/themes para ver que themes hay instalados, revisa si en la raíz  de las mismas está el archivo style.css; si no lo encuentra busca en los subdirectorios de primer nivel de la misma por si es un grupo de themes.

La idea entonces es que en nuestro entorno de desarrollo ese subdirectorio, que WP va a identificar como el que contiene el theme, sea el de las builds y que cualquier otro subdirectorio de primer nivel no tenga un archivo style.css.

  • build
  • dist/theme
  • src
    • images
    • languages
    • scripts
      • admin
      • customize
      • modules
    • styles
    • templates
      • admin
      • customize
      • modules
    • vendor
  • tasks

Build y Dist

Como se deduce de sus nombre, build, contiene las builds con todo el código sin comprimir y preparado para debug mientras que en dist se guarda el theme listo para producción.

Para evitar confundir a WP, es importante que en la raiz del directorio dist no se encuentre el style.css sino en un subdirectorio de este. Esto, además, nos simplifica el proceso de crear el zip / tar.gz para distribuir porque ya nos queda todo en un directorio listo para comprimir.

Src

Los «fuentes» del theme en el que estamos trabajando: php, imágenes y scss sin procesar, archivos de traducción, étc. Casi todo lo que está en este directorio va a ser procesado, optimizado, comprimido y demás operaciones que van a dar como resultado nuestro theme.

La idea original del directorio images era, justamente, guardar imágenes que se usaran en el diseño del theme pero, hoy en día, con los avances de CSS únicamente almacena el screenshot.

La carpeta templates contiene todo el código php, el ordenamiento interno tiene que cumplir con las limitaciones y recomendaciones de cualquier theme.

Me gusta mucho trabajar con el customizer así que suelo generar bastante código para este y, por lo tanto, tanto en la carpeta de scripts como de templates lo guardo en un directorio separado.

Además, a lo largo del tiempo, he creado algunos snipets de código que utilizo tan habitualmente como preload y lazyload, google fonts, étc que los mantego de forma separada y que cuando los incluyo en un proyecto los guardo en la carpeta modules.

Por una cuestión de separación también guardo en otro directorio, admin, todo el código que está pensado únicamente para ejecutarse en el dashboard de WP.

Por último, como no siempre hay paquetes con la librería que necesito, la versión que busco o no quiero usar un CDN, o por lo que sea, tengo el directorio vendor que se copia íntegramente tanto a las build como a la dist.

Tasks

Las tasks de Gulp.js: build, clean, clean-vendor, images, scripts, styles, templates, translation, vendor, watch.

Mantengo separadas las partes de código propio del código de vendor: build y clean para uno y vendor y clean-vendor para el otro. Ídem con las traducciones: translation.

Normalmente, build se encarga tanto de generar tanto la versión de pruebas como la versión para distribución. Antes las generaba con tareas específicas pero siempre se terminaban dando dos escenarios: dos tareas muy parecidas que había que mantener sincronizadas manualmente o una tarea con un montón de código duplicado y un enredo de condicionales.

La más importante de las tareas es watch que monitorea los cambios y actualiza la build. Para esto utilizo el modulo que trae Gulp por defecto en conjunto con BrowserSync para la inyección del nuevo CSS pero sin actualización automática. Pero, cuando trabajo sobre el customizer, desactivo completamente BS porque no se llevan bien y se suele quedar con pantallas en blanco u errores.

Gists

WP_Deduplicator: Clase estática que permite llevar el control de los post mostrados para evitar duplicar el contenido.

WP_Keepalive: Función que agrega o modifica la cabecera Connection para informar al cliente que es posible reutilizar la conexión abierta ahorrándonos establecer una conexión nueva para cada archivo.

WP_Clipboard: Función que usa una lista blanca para filtrar las etiquetas HTML del texto que se pega en el editor.

Crear un child theme en WordPress

La creación de un child theme nos da la posibilidad de adaptar un theme pre existente a nuestro gusto. Cambiar algunas fuentes, espaciado, colores e incluso funciones de un theme que nos gusta para no tener que escribirlo de cero.

La principal ventaja de hacerlo a través de un child theme, en vez de modificar el theme original, radica en que cuando se actualice el theme original no perderemos nuestras modificaciones obteniendo así lo mejor de dos mundos, actualizaciones y personalización.

Como desventaja, agregamos una capa más que tiene que ser ejecutada sobre el theme original lo que podría, aunque no debería, hacer más lenta la carga del sitio. Además, dependiendo de la profundidad de las modificaciones, puede ocurrir que una actualización del theme original entre en conflicto con el child theme pero, nuevamente, es raro que ocurra.

El principio de funcionamiento de los child themes es que cualquier template que tengamos en nuestro theme, será ejecutado en vez del homónimo en el theme original. Es decir, si en nuestro child theme creamos un comments.php, este será ejecutado en vez del archivo incluido en el parent theme.

La única excepción a esto son los archivos functions.php que ámbos serán ejecutados, primero el del child theme y luego el del parent.

Para poder crear un child theme es necesario que el tema «padre» o parent esté instalado y tener acceso al directorio themes de tu sitio con permisos de escritura.

Los pasos serían:

  1. Accede a la carpeta donde tienes alojado los themes.
  2. Crea una nueva carpeta donde se alojará tu child-theme. Si no se te ocurre ningun nombre, una buena opción es <nombre del theme original>-child
  3. Crea los archivos functions.php y style.css
  4. Agrega el contenido mínimo a cada archivo
/*
 Theme Name:   <nombre de nuestro theme>
 Theme URI:    <url>
 Description:  <descripción>
 Author:       <autor>
 Author URI:   <url-autor>
 Template:     <nombre de la carpeta del theme padre>
 Version:      <versión de nuestro theme>
 License:      <licencia>
 License URI:  <url de la licencia>
 Text Domain:  <text domain>
*/

El nombre del theme tiene que ser único, no puede ser el mismo del parent.

En template, va únicamente el nombre de la carpeta del theme para el que estamos creando el child.

Al momento de elegir licencia es importante tener en cuenta que sea compatible con la licencia del tema original.

Si no vamos a cambiar los strings del theme original, no hace falta especificar el text-domain.

<?php
add_action( 'wp_enqueue_scripts', 'my_theme_enqueue_styles' );
function my_theme_enqueue_styles() {
    wp_enqueue_style( '<parent-theme>', get_template_directory_uri() . '/style.css' );
 
    wp_enqueue_style( 'child-style',
        get_stylesheet_directory_uri() . '/style.css',
        array( '<parent-theme>' ),
        wp_get_theme()->get('Version')
    );
}

En los child themes get_template_directory_uri() nos devuelve el path al directorio del theme original y get_stylesheet_directory_uri() nos devuelve el path al directorio del child theme.

¿Por qué cargamos el stylesheet del parent theme si ya lo hace este automáticamente? Porque los hooks, a menos que les indiquemos específicamente lo contrario, se ejecutan en el orden en que fueron cargados. Entonces, si no agregamos el stylesheet del parent theme en nuestro functions.php, este se va a cargar después del nuevo stylesheet haciendo que los cambios no se vean.

Es importante que hagamos el enqueue del parent theme con el mismo handle que en el theme original para evitar que se cargue dos veces. Si estuviéramos extendiendo o modificando el theme Divi, habría que reemplazar parent-theme por divi-style. Este dato lo podemos encontrar en el propio functions.php del parent theme.

Además, como se muestra en el ejemplo, es importante utilizar en el nombre de las funciones que creemos un prefijo único para evitar que colisionen con otras funciones del parent theme u otros plugins.

Una vez cargados los archivos, y las modificaciones del caso, el theme aparecerá junto a los demás en la sección Apariencia. Es importante recordar que no podemos eliminar el parent theme mientras esté activo el child.

Evitar los post duplicados al utilizar múltiples loops en WordPress

Durante el desarrollo de un theme de WordPress es habitual que lleguemos a una instancia en la que el loop por defecto se queda corto y necesitamos implementar nuevas querys para poder mostrar la información de la forma deseada.

Al ser independientes las distintas querys entre sí puede ocurrir que traigan más de una vez el mismo post a colación provocando que se vean repetidos en nuestro sitio. Para poder tener mayor control sobre qué post se incluyen en los resultados del query WP_Query pone a nuestra disposición la opción post__not_in que nos permite especificar los ID de los post que queremos dejar fuera del loop.

Ahora el problema que nos surge es cómo llevar el control de los posts mostrados en las distintas partes del template. Para este fin existen distintos enfoques, por ejemplo, tener una variable global en donde vayamos almacenando los distintos posts que se ya se hayan mostrado.

En mi caso, utilizo una sencilla clase que incluyo en el functions.php y que utiliza una variable y un par de funciones estáticas para llevar el control de los post ya mostrados:

Como se puede observar alcanza con llamar a WP_Deduplicator::add() después de haber cargado los datos del post o pasandole directamente el ID a excluir y luego, al hacer nuestro próximo query pasar a la opción post__not_in el retorno de WP_Deduplicator::get().