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 Compose: .env

Compose nos permite una gran personalización mediante el archivo de configuración pero hay algunas cosas que no están soportadas dentro del mismo. Para algunas de estas configuraciones existen parámetros que se pueden establecer en la línea de comando o mediante variables de entorno.

Estas formas son poco prácticas porque implican acordarse los parámetros o nombres de las variables y siempre están sujetas al riesgo de los errores de tipeo. Por suerte, Compose, automáticamente levanta el archivo .env y establece las variables durante el tiempo de ejecución.

Hay que tener en cuenta que estas variables no son visibles para los contenedores sino para Docker Compose.

Hay varias, pero las más interesantes son:

  • COMPOSE_PROJECT_NAME: Permite establecer el nombre del proyecto. Esta cadena es la que usa Docker Compose como prefijo al momento de crear los containers. Por defecto utiliza el nombre de la carpeta actual.
  • COMPOSE_FILE: Para poder indicar el nombre del archivo de configuración que utilizará Docker Compose. Sirve para poder tener configurados distintos entornos ,por ejemplo, para desarrollo y para testing. Por defecto, docker-compose.yml.
  • DOCKER_HOST: En caso de que queramos utilizar el servicio de docker de un host remoto. Por ejemplo, tcp://192.168.0.13:2375. El valor por defecto apunta al socket local.
  • DOCKER_TLS_VERIFY: Si es necesario utilizar TLS para conectarse al servicio de Docker, alcanza con establecer a cualquier valor aunque generalmente se establece a 1. Si no hace falta utilizar TLS no hay que setear la variable porque cualquier valor (incluidos vacio, cero, false, étc) van a habilitarlo.
  • DOCKER_CERT_PATH: Si vamos a utilizar TLS vamos a necesitar certificados. Por defecto busca los archivos ca.pem, cert.pem y key.pem en la carpeta ~/.docker pero podemos establecer la que más nos convenga.

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

Docker

Hace unos meses como consecuencia de haber tenido que cambiar de discos en mi PC me encontré con la necesidad de volver a montar un entorno local de desarrollo. Como buen programador, decidí que montar todo igual que antes era demasiado sencillo y decidí revisar desde cero mi stack.

El primer cambio importante fue el abandonar Windows 10 + WSL por Ubuntu 18.04. Hay dos razones principales para esta decisión: la velocidad de I/O y los contenedores. Ya sea por el manejo de la fragmentación o falta de DNLC, los problemas de NTFS con los directorios con gran cantidad de archivos son históricos y agregarle una capa más de abstracción como es el subsistema de Linux no ayuda. Si agregamos que WSL no soporta cgroups por lo que no nos permite ejecutar nativamente containers el salto de un SO a otro es un paso lógico.

Anteriormente tenía en Windows instalado XAMPP y mediante el uso de junctions, el archivo hosts y algunos truquillos más codeaba en VSCode, utilizaba Webpack, Gulp, étc en WSL y visualizaba en un navegador en Windows.

Este workflow funcionaba en general bastante bien pero presentaba una rigidez notable y era poco práctico para testear cosas nuevas. Por esto, con el paso a Linux, la instalación de un sistema de gestión de contenedores era un must. La opción inicial era LXD pero este resultó estar más orientado a contenedores de SO que de aplicaciones. Si bien da mucha más flexibilidad para la configuración implica también más intervención del usuario mientras que Docker agrega una capa de abstracción que automatiza mucho de estos procesos lo que se adaptaba mejor a mis necesidades.

Primeras impresiones

Docker Compose es, diría, indispensable para el uso diario de Docker. Un simple archivo yaml permite configurar en unas pocas líneas una aplicación de múltiples contenedores.

En un principio el sistema de imágenes parece poco personalizable hasta que se descubre la posibilidad de montar «volúmenes» que, a los efectos prácticos, no son más que los tradicionales soft links entre el host y el contenedor. Esta posibilidad de compartir archivos y directorios nos permite, por ejemplo, externalizar en un repositorio git los archivos de configuración para mantenerla fácilmente sincronizada. O hacer lo mismo con la carpeta de datos de MySQL para volver los mismos persistentes entre distintos contenedores o replicar fácilmente la BBDD en producción.

La suma de los dos puntos anteiores vuelve el montar sistemas de prueba algo eficiente y completamente automatizable lo que repercute mucho en las ganas de hacer y usar los tests y sus beneficios asociados.

Habiendo trabajado antes con VM las diferencias de uso de recursos y tiempo de warm up y mantenimiento con Docker son bastante grandes. La configuración de los network bridge son un problema que queda en manos de Docker liberándonos de una tarea, por lo menos en desarrollo, que suele mostrarse por demás conflictiva. Esto no significa que no podamos trabajar con redes pero si que no es necesario hacerlo con Docker out-of-the-box en entornos privados.

Los repositorios públicos de imágenes son uno de los fuertes de Docker. Hasta el momento no me pasó de encontrarme sin una aplicación que necesitara y no tuviera su respectiva imagen.

Limitaciones

Como siempre, no todo es un camino de rosas y Docker presenta algunas limitaciones y problemas que pueden hacer que nos agarremos la cabeza hasta que le encontremos la vuelta para evitarlos.

Si bien Docker, y los containers en general, son una gran mejora sobre las VM todavía presentan un overhead que para los costes de servidor en el tercer mundo son demasiado altos por lo que por ahora el deploy va a seguir siendo bare metal o VPS.

El manejo de permisos de los volumenes es complejo, a los efectos prácticos estamos compartiendo archivos y carpetas entre dos SO diferentes, con usuarios diferentes. Sumado a eso, por defecto, los comandos se ejecutan como root dentro de los containers y, por lo tanto, los archivos creados por esos comandos suelen pertenecer al mismo usuario y grupo mientras que los que creamos en el host suelen tener permisos para nuestro usuario.

Todo en Docker está pensado para ocultar las complejidades del manejo de contenedores y esto inevitablemente nos lleva a soslayar los problemas de seguridad. Por ejemplo, por defecto, los containers no tienen ningún límite a los recursos que pueden utilizar; como siempre que utilizamos recursos públicos, únicamente un análisis de seguridad o usando self baked images nos podemos asegurar que no estemos utilizando una rogue image con backdoors; étc.

Trucos y consejos

En Linux los permisos se manejan con el UID y el GID, no con el nombre de usuario y grupo. Si en nuestro container el id de www-data coincide con el de nuestro usuario en el host, el manejo de permisos se vuelve más sencillo. Este truco es conocido por los que usan Docker hace más tiempo e incluso algunas imágenes, como la de PHP, lo soportan por defecto.

Este es más un truco de Bash que de Docker pero igualmente útil. Muchas veces necesitamos trabajar con todos los containers activos lo que es sumamente engorroso porque implica ejecutar docker por cada container. Para evitar esto podemos utilizar la sustitución de comandos de Bash de la siguiente forma:

docker stop $(docker ps -q)

Este comando va a ejecutar stop en todos los containers activos.

Para poder ver en tiempo real los logs de un container de Docker (o de varios si usamos Docker Compose):

docker logs -f <contenedor>

Hacer y restaurar backups de nuestros container es muy sencillo:

# Hacer el backup del contenedor a una imagen
docker commit -p <container id> <image name>

# Exportar la imagen a un archivo
docker save -o <filename.tar> <image name>

# Restaurar una imagen a partir de un archivo
docker load -i <filename.tar>

# Restaurar (crear) un contenedor a partir de la imagen
docker run <image name>

Por último, el parámetro ports de Docker Compose sirve para exponer puertos del container en el host pero su formato en vez de ser origen:destino como resulta intuitivo es exactamente al revés. Nada que sea complicado pero recordarlo evitará darnos la cabeza contra la pared cuando no sepamos por qué no funciona nuestro container.

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/.

Arquitectura de un sitio web

La arquitectura de un sitio web o aplicación web es la descripción de la configuración, función y relación de los distintos componentes que la integran. En este artículo me voy a centrar en dar un vistazo general de la arquitectura del hardware subyacente sobre el que corre un sitio web.

En el principio…

Cuando comenzamos con nuestro sitio lo más probable que nuestra arquitectura se limite a un único servidor donde están los tres componentes más comunes: el servidor web, la base de datos y el almacenamiento. Además de esto nos apoyaremos en un servidor DNS, generalmente manejado por nuestro proveedor de hosting, y en un CDN para servir las librerías y/o el framework que utilicemos.

Servidor web

Es el encargado de recibir y responder las peticiones HTTP del navegador de los usuarios. En lo básico su funcionamiento se limita a recibir un pedido de una url, buscar el archivo correspondiente en el sistema de archivos y enviarlo. Este funcionamiento se puede ampliar mediante módulos o extensiones como PHP que le permite «ejecutar» los archivos escritos en ese lenguaje antes de enviar la respuesta al cliente. Apache httpd, NGINX y Microsoft IIS son los tres más usados.

Base de datos

Toda aquella información que no guardamos en archivos y no tomamos de otros servicios normalmente termina almacenado de una forma sistematizada en la base de datos. Las ventajas derivadas de tener la información en una BBDD viene dada por la especialización de las mismas que permiten la realización de consultas mucho más complejas y más rápido que con otros sistemas de almacenamiento. Oracle Database, MySQL y Microsoft SQL Server son las tres más usadas.

Almacenamiento

Imágenes, audio, código fuente, étc, todo tiene que estar guardado en algún lugar accesible para el servidor web. A los efectos prácticos es donde va a parar todo aquello que por su tamaño u otras razones no podemos almacenar en nuestra base de datos. Dependiendo de nuestras necesidades puede ser el mismo sistema de archivos del sistema operativo o algún sistema por bloques u objetos más especializado.

CDN

Aunque está representado únicamente por un servidor es, en realidad, una red de servidores que proveen almacenamiento distribuido en muchos puntos del planeta. La idea detrás de este servicio es reducir la distancia entre el usuario y los archivos proveyendo una copia de los mismos en un punto de la red más cercano físicamente. Esto permite liberar parte de la carga de nuestro servidor y mejorar los tiempos de acceso a la información. Algunos de los más conocidos son Akamai, MaxCDN, Amazon AWS y Cloudflare.

Más potencia…

A medida que la cantidad de usuarios que acceden a nuestro sitio web comienza a aumentar los recursos de nuestra aplicación se van a ver excedidos. Esta falta de recursos suele identificarse en alguno de estos cuatro puntos:

  • CPU
  • Memoria
  • E/S de red
  • E/S de disco

En líneas generales, dejando de lado los problemas de código, nuestros problemas se van a centrar en CPU y memoria. Tanto nuestro servidor web como nuestra base de datos compiten por estos recursos y cuando se agotan es cuando suelen comenzar a aparecer las desconexiones intermitentes, falsos 404 y caídas.

La solución obvia pasa por el escalamiento vertical, es decir, mover el sitio a un servidor más potente. Durante un tiempo esto nos va a permitir seguir trabajando pero en algún momento vamos a encontrarnos con un techo y vamos a necesitar una nueva solución.

Esta solución consisten en utilizar servidores específicos para cada componente del sitio web. Ahora ya no compiten entre si sino que tienen recursos dedicados.

Reverse Proxy

Un servidor proxy funciona como intermediario entre los dos extremos de una conexión permitiendo hacer cambios en el contenido como el destino de la comunicación de forma transparente para ambos terminales. En lo que a nuestro sitio respecta, su funcionalidad pasa por recibir todos los pedidos de los clientes y redirigirlos al servidor apropiado. En el gráfico se utiliza para separar el contenido estático de la aplicación propiamente dicha.

CDN

Si tenemos una gran cantidad de archivos estáticos o son de un gran tamaño también puede ser momento de empezar a utilizar la CDN también para nuestros contenidos. Cada servicio tiene sus particularidades pero en general esta distribución de contenido se dará mediante push o pull. El primer caso es la carga activa de nuestros contenidos en la CDN, utilizamos la red para aquellos contenidos que específicamente  subamos. El otro método implica que la CDN funcione como intermediaria con nuestro sitio web y la primera vez que un usuario solicite un contenido lo cargue de forma automática.

No hay dos sin tres…

Al igual que la primera solución, la anterior, también puede verse superada y, nuevamente, nos encontraremos en la necesidad de agregar más servidores pero ¿dónde? Cada parte de nuestro sitio está en su propio servidor y no podemos subdividirlo en componentes más pequeños por lo que ahora tendremos que empezar a duplicarlo. Si tenemos dos servidores web en vez de uno deberíamos duplicar nuestra capacidad, ídem con la BBDD. 

La duplicación de los distintos módulos trae, igualmente, nuevos problemas asociados: como distribuir los pedidos de los usuarios entre uno y otro y cómo mantenerlos sincronizados para que den la misma respuesta.

Load Balancer

Para solucionar el primer problema entra en el juego los balanceadores de carga cuya función específica es decidir a que nodo enviar la petición recibida. Dependiendo de la implementación que utilicemos además tendremos la posibilidad de desviar a el contenido a otro servidor si uno se cae, persistir las sesiones mandando todas las peticiones de un usuario al mismo servidor, étc.

Replicación de la BBDD

Mantener sincronizadas distintas base de datos es un problema bastante complejo pero, por suerte, en lo que respecta a nuestro sitio web, los distintos DBMS traen sistemas que se encargan de hacerlo con unas pocas líneas de configuración de nuestra parte.

En líneas generales la idea detrás de la replicación es la existencia una base de datos original que, cada vez que se modifica, envía a una BBDD copia o replica la información necesaria para reproducir el cambio. De esta forma cuando hace falta una consulta se puede realizar tanto a la BBDD original como a las replicas con la seguridad de obtener la misma información. 

Obviamente, esta solución va a requerir algunos cambios en nuestra aplicación porque únicamente se puede escribir en una sola de las BBDD y, en caso de que nuestra aplicación haga más escrituras que lecturas, puede que la performance empeore en vez de mejorar por la carga de mantener la sincronización.

En conclusión…

Las arquitecturas presentadas son aproximaciones incompletas y genéricas. Existen muchos otros sistemas que podemos incluir en nuestro sitio para mejorar el desempeño como la cache tanto para el servidor web como para la base de datos.

Además, dependiendo de nuestras necesidades puede ser que tengamos múltiples servidores web y una única base de datos o únicamente un servidor web y muchos servidores de almacenamiento. También el balanceo de carga puede hacerse después del proxy y no antes.

En resumen, cada caso necesita un análisis específico y los ejemplos dados no tienen porque ser aplicables a todos.